Warning

This section contains snippets that were automatically translated from C++ to Python and may contain errors.

Calendar Widget Example#

The Calendar Widget example shows use of QCalendarWidget .

../_images/calendarwidgetexample.png

QCalendarWidget displays one calendar month at a time and lets the user select a date. The calendar consists of four components: a navigation bar that lets the user change the month that is displayed, a grid where each cell represents one day in the month, and two headers that display weekday names and week numbers.

The Calendar Widget example displays a QCalendarWidget and lets the user configure its appearance and behavior using QComboBox es, QCheckBox es, and QDateEdit s. In addition, the user can influence the formatting of individual dates and headers.

The properties of the QCalendarWidget are summarized in the table below.

Property

Description

selectedDate

The currently selected date.

minimumDate

The earliest date that can be selected.

maximumDate

The latest date that can be selected.

firstDayOfWeek

The day that is displayed as the first day of the week (usually Sunday or Monday).

gridVisible

Whether the grid should be shown.

selectionMode

Whether the user can select a date or not.

horizontalHeaderFormat

The format of the day names in the horizontal header (e.g., “M”, “Mon”, or “Monday”).

verticalHeaderFormat

The format of the vertical header.

navigationBarVisible

Whether the navigation bar at the top of the calendar widget is shown.

The example consists of one class, Window, which creates and lays out the QCalendarWidget and the other widgets that let the user configure the QCalendarWidget .

Window Class Definition#

Here is the definition of the Window class:

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()

As is often the case with classes that represent self-contained windows, most of the API is private. We will review the private members as we stumble upon them in the implementation.

Window Class Implementation#

Let’s now review the class implementation, starting with the constructor:

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"))

We start by creating the four QGroupBox es and their child widgets (including the QCalendarWidget ) using four private create...GroupBox() functions, described below. Then we arrange the group boxes in a QGridLayout .

We set the grid layout’s resize policy to SetFixedSize to prevent the user from resizing the window. In that mode, the window’s size is set automatically by QGridLayout based on the size hints of its contents widgets.

To ensure that the window isn’t automatically resized every time we change a property of the QCalendarWidget (for example, hiding the navigation bar, the vertical header, or the grid), we set the minimum height of row 0 and the minimum width of column 0 to the initial size of the QCalendarWidget .

Let’s move on to the createPreviewGroupBox() function:

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)

The Preview group box contains only one widget: the QCalendarWidget . We set it up, connect its currentPageChanged() signal to our reformatCalendarPage() slot to make sure that every new page gets the formatting specified by the user.

The createGeneralOptionsGroupBox() function is somewhat large and several widgets are set up in the same way. We will look at parts of its implementation here and skip the rest:

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)            ...

We start with the setup of the Week starts on combobox. This combobox controls which day should be displayed as the first day of the week.

The QComboBox class lets us attach user data as a QVariant to each item. The data can later be retrieved with QComboBox ‘s itemData() function. QVariant doesn’t directly support the DayOfWeek data type, but it supports int, and C++ will happily convert any enum value to 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)            ...

After having created the widgets, we connect the signals and slots. We connect the comboboxes to private slots of Window or to public slots provided by QComboBox .

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

At the end of the function, we call the slots that update the calendar to ensure that the QCalendarWidget is synchronized with the other widgets on startup.

Let’s now take a look at the createDatesGroupBox() private function:

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)

In this function, we create the Minimum Date, Maximum Date, and Current Date editor widgets, which control the calendar’s minimum, maximum, and selected dates. The calendar’s minimum and maximum dates have already been set in createPrivewGroupBox(); we can then set the widgets default values to the calendars values.

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

We connect the currentDateEdit's dateChanged() signal directly to the calendar’s setSelectedDate() slot. When the calendar’s selected date changes, either as a result of a user action or programmatically, our selectedDateChanged() slot updates the Current Date editor. We also need to react when the user changes the Minimum Date and Maximum Date editors.

Here is the createTextFormatsGroup() function:

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)

We set up the Weekday Color and Weekend Color comboboxes using createColorCombo(), which instantiates a QComboBox and populates it with colors (“Red”, “Blue”, etc.).

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"))

The Header Text Format combobox lets the user change the text format (bold, italic, or plain) used for horizontal and vertical headers. The First Friday in blue and May 1 in red check box affect the rendering of specific dates.

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)

We connect the check boxes and comboboxes to various private slots. The First Friday in blue and May 1 in red check boxes are both connected to reformatCalendarPage(), which is also called when the calendar switches month.

    ...
reformatHeaders()
reformatCalendarPage()

At the end of createTextFormatsGroupBox(), we call private slots to synchronize the QCalendarWidget with the other widgets.

We’re now done reviewing the four create...GroupBox() functions. Let’s now take a look at the other private functions and slots.

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

In createColorCombo(), we create a combobox and populate it with standard colors. The second argument to addItem() is a QVariant storing user data (in this case, QColor objects).

This function was used to set up the Weekday Color and Weekend Color comboboxes.

def firstDayChanged(self, index):

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

When the user changes the Week starts on combobox’s value, firstDayChanged() is invoked with the index of the combobox’s new value. We retrieve the custom data item associated with the new current item using itemData() and cast it to a DayOfWeek .

selectionModeChanged(), horizontalHeaderChanged(), and verticalHeaderChanged() are very similar to firstDayChanged(), so they are omitted.

def selectedDateChanged(self):

    currentDateEdit.setDate(calendar.selectedDate())

The selectedDateChanged() updates the Current Date editor to reflect the current state of the QCalendarWidget .

def minimumDateChanged(self, date):

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

When the user changes the minimum date, we tell the QCalenderWidget. We also update the Maximum Date editor, because if the new minimum date is later than the current maximum date, QCalendarWidget will automatically adapt its maximum date to avoid a contradicting state.

def maximumDateChanged(self, date):

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

maximumDateChanged() is implemented similarly to minimumDateChanged().

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)

Each combobox item has a QColor object as user data corresponding to the item’s text. After fetching the colors from the comboboxes, we set the text format of each day of the week.

The text format of a column in the calendar is given as a QTextCharFormat , which besides the foreground color lets us specify various character formatting information. In this example, we only show a subset of the possibilities.

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() is the same as weekdayFormatChanged(), except that it affects Saturday and Sunday instead of Monday to Friday.

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)

The reformatHeaders() slot is called when the user changes the text format of the headers. We compare the current text of the Header Text Format combobox to determine which format to apply. (An alternative would have been to store QTextCharFormat values alongside the combobox items.)

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)

In reformatCalendarPage(), we set the text format of the first Friday in the month and May 1 in the current year. The text formats that are actually used depend on which check boxes are checked and what the weekday/weekend formats are.

QCalendarWidget lets us set the text format of individual dates with the setDateTextFormat() . We chose to set the date formats when the calendar page changes - i.e. a new month is displayed - and when the weekday/weekend format is changed. We check which of the mayFirstCheckBox and firstDayCheckBox, if any, are checked and set the text formats accordingly.

Example project @ code.qt.io