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

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

Как сделать округление выбранной пользователем даты в 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 пикселей.






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(), для правильного доступа дата на основе индексных координат.