警告

本部分包含从C++自动翻译到Python的代码片段,可能包含错误。

日历小部件示例#

日历小部件示例展示了如何使用 QCalendarWidget .

../_images/calendarwidgetexample.png

QCalendarWidget 可以显示一个月的日历,并允许用户选择一个日期。日历由四个组件组成:一个允许用户更改显示月份的导航栏、一个每个单元格代表一个月中一天的网格,以及两个显示星期名称和星期数的标题。

日历小部件示例显示了一个 QCalendarWidget ,并允许用户使用 QComboBoxQCheckBoxQDateEdit 配置其外观和行为。此外,用户还可以影响单个日期和标题的格式。

下表总结了 QCalendarWidget 的属性。

属性

描述

selectedDate

当前选择的日期。

minimumDate

可以选择的最早日期。

maximumDate

可以选择的最新日期。

firstDayOfWeek

显示为一周第一天的日子(通常是星期日或星期一)。

gridVisible

是否应显示网格。

selectionMode

是否允许用户选择日期。

horizontalHeaderFormat

水平标题中日期名称的格式(例如,“M”、“Mon”或“Monday”)。

verticalHeaderFormat

垂直标题的格式。

navigationBarVisible

是否显示日历小部件顶部的导航栏。

示例由一个类组成,即 Window 类,该类创建并布局 QCalendarWidget 及其他允许用户配置 QCalendarWidget 的控件。

Window类定义#

以下是对 Window 类的定义

class Window(QWidget):

    Q_OBJECT
# public
    Window(QWidget parent = None)
# private slots
    def localeChanged(index):
    def firstDayChanged(index):
    def selectionModeChanged(index):
    def horizontalHeaderChanged(index):
    def verticalHeaderChanged(index):
    def selectedDateChanged():
    def minimumDateChanged(date):
    def maximumDateChanged(date):
    def weekdayFormatChanged():
    def weekendFormatChanged():
    def reformatHeaders():
    def reformatCalendarPage():
# private
    def createPreviewGroupBox():
    def createGeneralOptionsGroupBox():
    def createDatesGroupBox():
    def createTextFormatsGroupBox():
    createColorComboBox = QComboBox()
    previewGroupBox = QGroupBox()
    previewLayout = QGridLayout()
    calendar = QCalendarWidget()
    generalOptionsGroupBox = QGroupBox()
    localeLabel = QLabel()
    firstDayLabel = QLabel()            ...

mayFirstCheckBox = QCheckBox()

通常,表示独立窗口的类大部分API都是私有的。我们将随着实现过程中的遇到,复习这些私有成员。

窗口类实现#

现在,我们来审查类实现,从构造函数开始

def __init__(self, parent):
    super().__init__(parent)

    createPreviewGroupBox()
    createGeneralOptionsGroupBox()
    createDatesGroupBox()
    createTextFormatsGroupBox()
    layout = QGridLayout()
    layout.addWidget(previewGroupBox, 0, 0)
    layout.addWidget(generalOptionsGroupBox, 0, 1)
    layout.addWidget(datesGroupBox, 1, 0)
    layout.addWidget(textFormatsGroupBox, 1, 1)
    layout.setSizeConstraint(QLayout.SetFixedSize)
    setLayout(layout)
    previewLayout.setRowMinimumHeight(0, calendar.sizeHint().height())
    previewLayout.setColumnMinimumWidth(0, calendar.sizeHint().width())
    setWindowTitle(tr("Calendar Widget"))

我们首先使用四个私有的 create...GroupBox() 函数创建了四个 QGroupBox 以及它们的子控件(包括 QCalendarWidget ),然后我们将这些分组盒排列在一个 QGridLayout 中。

我们将网格布局的调整大小策略设置为 SetFixedSize 以防止用户调整窗口的大小。在该模式下,窗口的大小将根据其内容控件的尺寸提示自动设置。

为确保在改变 QCalendarWidget 的某个属性时(例如,隐藏导航栏、垂直标题或网格),我们设置第0行的最小高度和第0列的最小宽度为 QCalendarWidget 的初始大小。

接下来,让我们来审查 createPreviewGroupBox() 函数

def createPreviewGroupBox(self):

    previewGroupBox = QGroupBox(tr("Preview"))
    calendar = QCalendarWidget()
    calendar.setMinimumDate(QDate(1900, 1, 1))
    calendar.setMaximumDate(QDate(3000, 1, 1))
    calendar.setGridVisible(True)
    calendar.currentPageChanged.connect(
            self.reformatCalendarPage)
    previewLayout = QGridLayout()
    previewLayout.addWidget(calendar, 0, 0, Qt.AlignCenter)
    previewGroupBox.setLayout(previewLayout)

预览分组盒中只包含一个控件:一个 QCalendarWidget 。我们对其进行设置,将它的 currentPageChanged() 信号连接到我们的 reformatCalendarPage() 信号槽,以确保每个新页面都获得用户指定的格式。

createGeneralOptionsGroupBox() 函数相当大,并且几个控件都是以相同的方式设置的。在这里,我们将查看它的实现的一部分,跳过其余部分

def createGeneralOptionsGroupBox(self):

    generalOptionsGroupBox = QGroupBox(tr("General Options"))
    localeCombo = QComboBox()
    curLocaleIndex = -1
    index = 0
    for _lang in range(QLocale.C, QLocale.LastLanguage + 1):
        QLocale.Language lang = QLocale.Language(_lang)
        locales =
            QLocale.matchingLocales(lang, QLocale.AnyScript, QLocale.AnyTerritory)
        for loc in locales:
            label = QLocale.languageToString(lang)
            territory = loc.territory()
            label += '/'
            label += QLocale.territoryToString(territory)
            if locale().language() == lang and locale().territory() == territory:
                curLocaleIndex = index
            localeCombo.addItem(label, loc)
            index = index + 1


    if curLocaleIndex != -1:
        localeCombo.setCurrentIndex(curLocaleIndex)
    localeLabel = QLabel(tr("Locale"))
    localeLabel.setBuddy(localeCombo)
    firstDayCombo = QComboBox()
    firstDayCombo.addItem(tr("Sunday"), Qt.Sunday)
    firstDayCombo.addItem(tr("Monday"), Qt.Monday)
    firstDayCombo.addItem(tr("Tuesday"), Qt.Tuesday)
    firstDayCombo.addItem(tr("Wednesday"), Qt.Wednesday)
    firstDayCombo.addItem(tr("Thursday"), Qt.Thursday)
    firstDayCombo.addItem(tr("Friday"), Qt.Friday)
    firstDayCombo.addItem(tr("Saturday"), Qt.Saturday)
    firstDayLabel = QLabel(tr("Week starts on:"))
    firstDayLabel.setBuddy(firstDayCombo)            ...

首先,我们从设置“周开始于”组合框开始。此组合框控制哪一天应显示为每周的第一天。

QComboBox 类让我们可以为每个条目附加二维码作为 QVariant 的用户数据。随后可以通过 QComboBox 的 itemData() 函数检索数据。QVariant 不直接支持 Qt::DayOfWeek 数据类型,但它支持 int,并且 C++ 会将任何枚举值愉快地转换为 int

    ...

localeCombo.currentIndexChanged.connect(
        self.localeChanged)
firstDayCombo.currentIndexChanged.connect(
        self.firstDayChanged)
selectionModeCombo.currentIndexChanged.connect(
        self.selectionModeChanged)
gridCheckBox.toggled.connect(
        calendar.setGridVisible)
navigationCheckBox.toggled.connect(
        calendar.setNavigationBarVisible)
horizontalHeaderCombo.currentIndexChanged.connect(
        self.horizontalHeaderChanged)
verticalHeaderCombo.currentIndexChanged.connect(
        self.verticalHeaderChanged)            ...

创建小部件后,我们连接信号和槽。我们将组合框连接到 Window 的私有槽,或者连接到 QComboBox 提供的公共槽。

    ...

firstDayChanged(firstDayCombo.currentIndex())
selectionModeChanged(selectionModeCombo.currentIndex())
horizontalHeaderChanged(horizontalHeaderCombo.currentIndex())
verticalHeaderChanged(verticalHeaderCombo.currentIndex())

函数结束时,我们调用更新日历的槽以确保在启动时 QCalendarWidget 与其他小部件同步。

现在我们来查看 createDatesGroupBox() 私有函数

def createDatesGroupBox(self):

    datesGroupBox = QGroupBox(tr("Dates"))
    minimumDateEdit = QDateEdit()
    minimumDateEdit.setDisplayFormat("MMM d yyyy")
    minimumDateEdit.setDateRange(calendar.minimumDate(),
                                  calendar.maximumDate())
    minimumDateEdit.setDate(calendar.minimumDate())
    minimumDateLabel = QLabel(tr("Minimum Date:"))
    minimumDateLabel.setBuddy(minimumDateEdit)
    currentDateEdit = QDateEdit()
    currentDateEdit.setDisplayFormat("MMM d yyyy")
    currentDateEdit.setDate(calendar.selectedDate())
    currentDateEdit.setDateRange(calendar.minimumDate(),
                                  calendar.maximumDate())
    currentDateLabel = QLabel(tr("Current Date:"))
    currentDateLabel.setBuddy(currentDateEdit)
    maximumDateEdit = QDateEdit()
    maximumDateEdit.setDisplayFormat("MMM d yyyy")
    maximumDateEdit.setDateRange(calendar.minimumDate(),
                                  calendar.maximumDate())
    maximumDateEdit.setDate(calendar.maximumDate())
    maximumDateLabel = QLabel(tr("Maximum Date:"))
    maximumDateLabel.setBuddy(maximumDateEdit)

在这个函数中,我们创建了最小日期、最大日期和当前日期编辑器小部件,它们控制日历的最小、最大和选定日期。日历的最小和最大日期已经在 createPrivewGroupBox() 中设置,然后我们可以将小部件的默认值设置为日历的值。

currentDateEdit.dateChanged.connect(
        calendar.setSelectedDate)
calendar.selectionChanged.connect(
        self.selectedDateChanged)
minimumDateEdit.dateChanged.connect(
        self.minimumDateChanged)
maximumDateEdit.dateChanged.connect(
        self.maximumDateChanged)            ...

我们将 currentDateEdit 的 dateChanged() 信号直接连接到日历的 setSelectedDate() 槽。当日历的选定日期发生变化时,无论是由于用户操作还是程序操作,我们的 selectedDateChanged() 槽会更新当前日期编辑器。我们还需要对用户更改最小日期和最大日期编辑器做出反应。

下面是 createTextFormatsGroup() 函数

def createTextFormatsGroupBox(self):

    textFormatsGroupBox = QGroupBox(tr("Text Formats"))
    weekdayColorCombo = createColorComboBox()
    weekdayColorCombo.setCurrentIndex(
            weekdayColorCombo.findText(tr("Black")))
    weekdayColorLabel = QLabel(tr("Weekday color:"))
    weekdayColorLabel.setBuddy(weekdayColorCombo)
    weekendColorCombo = createColorComboBox()
    weekendColorCombo.setCurrentIndex(
            weekendColorCombo.findText(tr("Red")))
    weekendColorLabel = QLabel(tr("Weekend color:"))
    weekendColorLabel.setBuddy(weekendColorCombo)

我们使用 createColorCombo() 设置了工作日颜色和周末颜色组合框,该函数实例化一个 QComboBox 并用颜色(“红色”、“蓝色”等)填充它。

headerTextFormatCombo = QComboBox()
headerTextFormatCombo.addItem(tr("Bold"))
headerTextFormatCombo.addItem(tr("Italic"))
headerTextFormatCombo.addItem(tr("Plain"))
headerTextFormatLabel = QLabel(tr("Header text:"))
headerTextFormatLabel.setBuddy(headerTextFormatCombo)
firstFridayCheckBox = QCheckBox(tr("First Friday in blue"))
mayFirstCheckBox = QCheckBox(tr("May 1 in red"))

Header Text Format 组合框允许用户更改用于水平和垂直标题的文本格式(加粗、斜体或普通)。第一个周五蓝色和五一红色复选框会影响特定日期的渲染。

weekdayColorCombo.currentIndexChanged.connect(
        self.weekdayFormatChanged)
weekdayColorCombo.currentIndexChanged.connect(
        self.reformatCalendarPage)
weekendColorCombo.currentIndexChanged.connect(
        self.weekendFormatChanged)
weekendColorCombo.currentIndexChanged.connect(
        self.reformatCalendarPage)
headerTextFormatCombo.currentIndexChanged.connect(
        self.reformatHeaders)
firstFridayCheckBox.toggled.connect(
        self.reformatCalendarPage)
mayFirstCheckBox.toggled.connect(
        self.reformatCalendarPage)

我们连接了复选框和组合框到各种私有槽。第一个周五蓝色和五一红色复选框都连接到 reformatCalendarPage() 槽,该槽还在日历切换月份时调用。

    ...

reformatHeaders()
reformatCalendarPage()

在 createTextFormatsGroupBox() 结束时,我们调用私有槽以使 QCalendarWidget 与其他小部件同步。

我们已经完成了对四个 create...GroupBox() 函数的审查。现在,让我们看看其他的私有函数和槽函数。

QComboBox Window.createColorComboBox()

    comboBox = QComboBox()
    comboBox.addItem(tr("Red"), QColor(Qt.red))
    comboBox.addItem(tr("Blue"), QColor(Qt.blue))
    comboBox.addItem(tr("Black"), QColor(Qt.black))
    comboBox.addItem(tr("Magenta"), QColor(Qt.magenta))
    return comboBox

createColorCombo() 中,我们创建了一个组合框,并用标准颜色填充它。传递给 addItem() 的第二个参数是一个存储用户数据(在这个例子中,是 QColor 对象)的 QVariant 对象。

这个函数被用来设置星期几颜色和周末颜色的组合框。

def firstDayChanged(self, index):

    calendar.setFirstDayOfWeek(Qt.DayOfWeek(
                                firstDayCombo.itemData(index).toInt()))

当用户更改“周第一天”组合框的值时,将调用 firstDayChanged() 并传回组合框新值的索引。我们使用 itemData() 获取与新当前项关联的自定义数据项,并将其转换为 Qt::DayOfWeek。

selectionModeChanged()horizontalHeaderChanged()verticalHeaderChanged()firstDayChanged() 很相似,因此被省略。

def selectedDateChanged(self):

    currentDateEdit.setDate(calendar.selectedDate())

selectedDateChanged() 更新当前日期编辑器,以反映 QCalendarWidget 的当前状态。

def minimumDateChanged(self, date):

    calendar.setMinimumDate(date)
    maximumDateEdit.setDate(calendar.maximumDate())

当用户更改最小日期时,我们通知 QCalenderWidget。我们也更新最大日期编辑器,因为如果新的最小日期晚于当前的最新日期,QCalendarWidget 将自动调整其最大日期,以防止出现矛盾的状态。

def maximumDateChanged(self, date):

    calendar.setMaximumDate(date)
    minimumDateEdit.setDate(calendar.minimumDate())

minimumDateChanged() 类似的实现方式 maximumDateChanged()

def weekdayFormatChanged(self):

    format = QTextCharFormat()
    format.setForeground(qvariant_cast<QColor>(
        weekdayColorCombo.itemData(weekdayColorCombo.currentIndex())))
    calendar.setWeekdayTextFormat(Qt.Monday, format)
    calendar.setWeekdayTextFormat(Qt.Tuesday, format)
    calendar.setWeekdayTextFormat(Qt.Wednesday, format)
    calendar.setWeekdayTextFormat(Qt.Thursday, format)
    calendar.setWeekdayTextFormat(Qt.Friday, format)

每个组合框项都有一个与项文本对应的 QColor 对象作为用户数据。在从组合框中获取颜色后,我们设置每周每一天的文本格式。

日历中列的文本格式以 QTextCharFormat 的形式给出,除了前景色,还允许我们指定各种字符格式信息。在本例中,我们只展示了可能性的一个子集。

def weekendFormatChanged(self):

    format = QTextCharFormat()
    format.setForeground(qvariant_cast<QColor>(
        weekendColorCombo.itemData(weekendColorCombo.currentIndex())))
    calendar.setWeekdayTextFormat(Qt.Saturday, format)
    calendar.setWeekdayTextFormat(Qt.Sunday, format)

weekendFormatChanged()weekdayFormatChanged() 相同,但它影响周六和周日而不是周一至周五。

def reformatHeaders(self):

    text = headerTextFormatCombo.currentText()
    format = QTextCharFormat()
    if text == tr("Bold"):
        format.setFontWeight(QFont.Bold)
    elif text == tr("Italic"):
        format.setFontItalic(True)
    elif text == tr("Green"):
        format.setForeground(Qt.green)
    calendar.setHeaderTextFormat(format)

当用户更改头部文本格式时,将调用 reformatHeaders() 槽函数。我们比较当前头部文本格式组合框的文本,以确定应用哪种格式。(另一种选择是将 QTextCharFormat 值与组合框项一起存储。)

def reformatCalendarPage(self):

    mayFirstFormat = QTextCharFormat()
    mayFirst = QDate(calendar.yearShown(), 5, 1)
    firstFridayFormat = QTextCharFormat()
    firstFriday = QDate(calendar.yearShown(), calendar.monthShown(), 1)
    while firstFriday.dayOfWeek() != Qt.Friday:
        firstFriday = firstFriday.addDays(1)
    if firstFridayCheckBox.isChecked():
        firstFridayFormat.setForeground(Qt.blue)
    else: // Revert to regular colour for self day of the week.
        Qt.DayOfWeek dayOfWeek(Qt.DayOfWeek(firstFriday.dayOfWeek()))
        firstFridayFormat.setForeground(calendar.weekdayTextFormat(dayOfWeek).foreground())

    calendar.setDateTextFormat(firstFriday, firstFridayFormat)
    # When it is checked, "May First in Red" always takes precedence over "First Friday in Blue".
    if mayFirstCheckBox.isChecked():
        mayFirstFormat.setForeground(Qt.red)
     elif not firstFridayCheckBox.isChecked() or firstFriday not = mayFirst:
        # We can now be certain we won't be resetting "May First in Red" when we restore
        # may 1st's regular colour for this day of the week.
        Qt.DayOfWeek dayOfWeek(Qt.DayOfWeek(mayFirst.dayOfWeek()))
        calendar.setDateTextFormat(mayFirst, calendar.weekdayTextFormat(dayOfWeek))

    calendar.setDateTextFormat(mayFirst, mayFirstFormat)

reformatCalendarPage() 中,我们设置月份的第一个周五和当年5月1日的文本格式。实际使用的文本格式取决于哪些复选框被勾选,以及工作日/周末格式是什么。

QCalendarWidget 允许我们使用 setDateTextFormat() 方法设置单个日期的文本格式。我们选择在日历页面更改时设置日期格式 - 即显示新月份时,以及在工作日/周末格式更改时进行设置。我们检查是否有任何 mayFirstCheckBoxfirstDayCheckBox 被选中,并根据这些设置相应的文本格式。

示例项目 @ code.qt.io