Как показать выбранную дату с закругленными углами в QCalendarWidget?

На данный момент реализовано:

Как показать выбранную дату с закругленными углами в QCalendarWidget?

Дата в макете не соответствует содержимому календаря:

Как показать выбранную дату с закругленными углами в QCalendarWidget?

Как сделать округление выбранной пользователем даты в QCalendar? Я использую PyQt6.

КалендарьВиджет.py:

from __init__ import *

class CalendarWidget(QCalendarWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setGridVisible(False)
        self.setVerticalHeaderFormat(QCalendarWidget.VerticalHeaderFormat.NoVerticalHeader)
        self.setHorizontalHeaderFormat(QCalendarWidget.HorizontalHeaderFormat.NoHorizontalHeader)
        self.setSelectedDate(QDate.currentDate())
        self.setNavigationBarVisible(False)

        for dayOff in (Qt.DayOfWeek.Saturday, Qt.DayOfWeek.Sunday):
            format = self.weekdayTextFormat(dayOff)
            format.setForeground(QColor("#CAD3F5"))
            self.setWeekdayTextFormat(dayOff, format)

    def paintCell(self, painter, rect, date):
        if not date.month() == self.selectedDate().month():
            painter.save()
            painter.setPen(QPen(QColor("#939ab7")))
            painter.drawText(rect, int(Qt.AlignmentFlag.AlignCenter), str(date.day()))
            painter.restore()
        else:
            QCalendarWidget.paintCell(self, painter, rect, date)

Согласно макету, размер скругления должен составлять 45 пикселей.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

QCalendarWidget внутренне использует QTableView для отображения сетки дат.

Существуют различные способы добиться желаемого внешнего вида, каждый из которых имеет свои плюсы и минусы.

Переопределение палитры таблицы

Выбор фактически рисуется внутри делегата элемента с использованием палитры представления, а именно роли цвета Highlight.

Если мы переопределим палитру представления, установив цвет для этой роли прозрачным, выделение не будет отображаться, и мы сможем нарисовать его самостоятельно, прежде чем вызывать реализацию по умолчанию:

class CalendarWidget(QCalendarWidget):
    def __init__(self, parent=None):
        ...
        table = self.findChild(QTableView)
        palette = table.palette()
        palette.setColor(
            QPalette.ColorRole.Highlight, Qt.GlobalColor.transparent)
        table.setPalette(palette)

    def paintCell(self, painter, rect, date):
        if not date.month() == self.selectedDate().month():
            painter.save()
            painter.setPen(QPen(QColor("#939ab7")))
            painter.drawText(rect, int(Qt.AlignmentFlag.AlignCenter), str(date.day()))
            painter.restore()
        else:
            if date == self.selectedDate():
                painter.save()
                painter.setBrush(self.palette().highlight())
                painter.setPen(Qt.PenStyle.NoPen)
                radius = min(rect.width(), rect.height()) / 2
                painter.drawRoundedRect(rect, radius, radius)
                painter.restore()
            QCalendarWidget.paintCell(self, painter, rect, date)

Этот вариант достаточно прост, но имеет некоторые ограничения. Самое главное: если используются таблицы стилей и для представлений элементов установлено свойство selection-background-color, палитра может быть повторно инициализирована стилем, иногда непоследовательно и непредсказуемо.

Использование таблиц стилей с делегатом стилизованного элемента

Табличное представление виджета календаря использует базовый QItemDelegate, который наследует только цвета, установленные в таблице стилей для таблицы (свойства подэлемента управления ::item неэффективны).

Одна из возможностей заключается в том, чтобы вместо этого использовать QStyledItemDelegate и соответствующим образом установить таблицу стилей:

class CalendarWidget(QCalendarWidget):
    def __init__(self, parent=None):
        ...
        table = self.findChild(QTableView)
        table.setItemDelegate(QStyledItemDelegate(table))
        table.setStyleSheet('''
            QTableView::item:selected {
                border: none;
                border-radius: 5px;
        ''')

К сожалению, это создает косвенные проблемы.

Самое главное, что переопределение paintCell() не будет вызвано, поскольку ответственность за его вызов лежит на делегате по умолчанию (подробнее об этом в следующем решении).

Кроме того, будут затронуты все элементы (включая горизонтальные и вертикальные заголовки), что приведет к более «стилизованному» отображению элементов; поскольку QSS не позволяет выбирать отдельные элементы, попытка переопределить фон элементов будет полностью игнорировать любой цветовой формат, установленный для определенных дат до setDateTextFormat(), поскольку цвета QSS полностью игнорируют BackgroundRole и ForegroundRole модели при установке настроек background и color соответственно.

Наконец, радиус закругленных углов жестко запрограммирован и не может зависеть от фактического размера элемента.

Реализация пользовательского делегата

Другая возможность — использовать простой QItemDelegate и переопределить его функцию paint(). Если индекс не выбран, вызывается реализация по умолчанию, в противном случае мы рисуем фон, затем меняем палитру параметров аналогично тому, что сделано выше, и, наконец, вызываем реализацию по умолчанию.

class CalendarDelegate(QItemDelegate)
    def paint(self, qp, opt, index):
        if not opt.state & QStyle.State_Selected:
            super().paint(qp, opt, index)
            return

        qp.save()
        if opt.state & QStyle.State.State_Active:
            cg = QPalette.ColorGroup.Normal
        else:
            cg = QPalette.ColorGroup.Inactive
        qp.setBrush(opt.palette.brush(cg, QPalette.ColorRoleHighlight))
        qp.setPen(Qt.NoPen)
        radius = min(rect.width(), rect.height()) / 2
        qp.drawRoundedRect(opt.rect, radius, radius)
        qp.restore()

        opt.palette.setColor(QPalette.Highlight, Qt.transparent)
        super().paint(qp, opt, index)


class CalendarWidget(QCalendarWidget):
    def __init__(self, parent=None):
        ...
        table = self.findChild(QTableView)
        table.setItemDelegate(CalendarDelegate(table))

Обычно этот подход является хорошим компромиссом, но он не лишен недостатков: аналогично подходу со стилизацией делегата, описанному выше, использование другого делегата полностью предотвращает возможность переопределения paintCell() календаря.

Внутренний делегат QCalendarWidget по умолчанию переопределяет свой метод paint() и оттуда вызывает метод paintCell() календаря, используя частную функцию модели, которая извлекает дату из QModelIndex.

Пользовательский делегат, очевидно, не имеет такой функциональности, и его необходимо реализовать вручную, учитывая видимые заголовки и, следовательно, предполагая дату на основе строки и столбца данного индекса.

Хотя это и осуществимо, это не происходит немедленно, и делегат также должен быть правильно «подключен» к календарю (прямо сейчас, в приведенном выше примере, он связан только с табличным представлением), чтобы можно было вызвать функцию, подобную paintCell(), для правильного доступа дата на основе индексных координат.

Другие вопросы по теме