目前已实施:
布局中的日期与日历内容不匹配:
如何在 QCalendar 中对用户选择的日期进行四舍五入?我正在使用 PyQt6。
CalendarWidget.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)
根据布局,圆角应为 45px。
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
属性,则调色板可能会由样式重新初始化,有时会不一致且不可预测。将样式表与样式项委托一起使用
::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
。最后,圆角边框的半径是硬编码的,不能基于实际项目大小。
实现自定义委托
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()
的函数根据索引坐标正确访问日期。