日历部件示例

日历部件示例展示了QCalendarWidget的使用。

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

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

下表总结了QCalendarWidget的特性。

属性描述
selectedDate当前选定的日期。
minimumDate可以选定的最早日期。
maximumDate可以选定的最晚日期。
firstDayOfWeek显示为一周第一天的那一天(通常是星期日或星期一)。
gridVisible是否应显示网格。
selectionMode用户是否可以选中日期。
horizontalHeaderFormat水平首部中星期名称的格式(例如,“M”、“Mon”或“Monday”)。
verticalHeaderFormat垂直首部的格式。
navigationBarVisible是否显示日历部件顶部的导航栏。

示例包含一个类,Window,该类创建并布局QCalendarWidget及允许用户配置QCalendarWidget的其他部件。

窗口类定义

以下是Window类的定义:

class Window : public QWidget
{
    Q_OBJECT

public:
    Window(QWidget *parent = nullptr);

private slots:
    void localeChanged(int index);
    void firstDayChanged(int index);
    void selectionModeChanged(int index);
    void horizontalHeaderChanged(int index);
    void verticalHeaderChanged(int index);
    void selectedDateChanged();
    void minimumDateChanged(QDate date);
    void maximumDateChanged(QDate date);
    void weekdayFormatChanged();
    void weekendFormatChanged();
    void reformatHeaders();
    void reformatCalendarPage();

private:
    void createPreviewGroupBox();
    void createGeneralOptionsGroupBox();
    void createDatesGroupBox();
    void createTextFormatsGroupBox();
    QComboBox *createColorComboBox();

    QGroupBox *previewGroupBox;
    QGridLayout *previewLayout;
    QCalendarWidget *calendar;

    QGroupBox *generalOptionsGroupBox;
    QLabel *localeLabel;
    QLabel *firstDayLabel;
    ...
    QCheckBox *mayFirstCheckBox;
};

与表示自包含窗口的类一样,大多数API都是私有的。随着我们在实现中遇到它们,我们将审视这些私有成员。

窗口类实现

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

Window::Window(QWidget *parent)
    : QWidget(parent)
{
    createPreviewGroupBox();
    createGeneralOptionsGroupBox();
    createDatesGroupBox();
    createTextFormatsGroupBox();

    QGridLayout *layout = new 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的形式排列这些分组框。

我们将网格布局的调整大小策略设置为QLayout::SetFixedSize,以阻止用户调整窗口大小。在此模式下,根据其内容部件的大小提示,QGridLayout会自动设置窗口的大小。

为了确保每次改变QCalendarWidget(例如,隐藏导航栏、垂直标题或网格)的属性时,窗口不会被自动调整大小,我们将第0行的最小高度和第0列的最小宽度设置为QCalendarWidget的初始大小。

接下来让我们来看一下createPreviewGroupBox()函数

void Window::createPreviewGroupBox()
{
    previewGroupBox = new QGroupBox(tr("Preview"));

    calendar = new QCalendarWidget;
    calendar->setMinimumDate(QDate(1900, 1, 1));
    calendar->setMaximumDate(QDate(3000, 1, 1));
    calendar->setGridVisible(true);

    connect(calendar, &QCalendarWidget::currentPageChanged,
            this, &Window::reformatCalendarPage);

    previewLayout = new QGridLayout;
    previewLayout->addWidget(calendar, 0, 0, Qt::AlignCenter);
    previewGroupBox->setLayout(previewLayout);
}

预览组框中只包含一个控件:QCalendarWidget。我们进行了设置,连接了它的currentPageChanged()信号到我们的reformatCalendarPage()槽,以确保每一页都有一个用户指定的格式。

createGeneralOptionsGroupBox()函数相对较大,其中设置了多个控件。在这里我们将查看其实现的部分内容,并跳过其余部分。

void Window::createGeneralOptionsGroupBox()
{
    generalOptionsGroupBox = new QGroupBox(tr("General Options"));

    localeCombo = new QComboBox;
    int curLocaleIndex = -1;
    int index = 0;
    for (int _lang = QLocale::C; _lang <= QLocale::LastLanguage; ++_lang) {
        QLocale::Language lang = static_cast<QLocale::Language>(_lang);
        const auto locales =
            QLocale::matchingLocales(lang, QLocale::AnyScript, QLocale::AnyTerritory);
        for (auto loc : locales) {
            QString label = QLocale::languageToString(lang);
            auto territory = loc.territory();
            label += QLatin1Char('/');
            label += QLocale::territoryToString(territory);
            if (locale().language() == lang && locale().territory() == territory)
                curLocaleIndex = index;
            localeCombo->addItem(label, loc);
            ++index;
        }
    }
    if (curLocaleIndex != -1)
        localeCombo->setCurrentIndex(curLocaleIndex);
    localeLabel = new QLabel(tr("&Locale"));
    localeLabel->setBuddy(localeCombo);

    firstDayCombo = new 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 = new QLabel(tr("Wee&k starts on:"));
    firstDayLabel->setBuddy(firstDayCombo);
    ...

我们从周开始于下拉列表的设置开始。这个下拉列表控制应该显示为每周第一天的哪一天。

QComboBox类允许我们将用户数据作为QVariant附加到每个项上。这些数据以后可以用QComboBoxitemData()函数检索。QVariant不支持Qt::DayOfWeek数据类型,但它支持int,C++会把枚举值愉快地转换成int

    ...
    connect(localeCombo, &QComboBox::currentIndexChanged,
            this, &Window::localeChanged);
    connect(firstDayCombo, &QComboBox::currentIndexChanged,
            this, &Window::firstDayChanged);
    connect(selectionModeCombo, &QComboBox::currentIndexChanged,
            this, &Window::selectionModeChanged);
    connect(gridCheckBox, &QCheckBox::toggled,
            calendar, &QCalendarWidget::setGridVisible);
    connect(navigationCheckBox, &QCheckBox::toggled,
            calendar, &QCalendarWidget::setNavigationBarVisible);
    connect(horizontalHeaderCombo, &QComboBox::currentIndexChanged,
            this, &Window::horizontalHeaderChanged);
    connect(verticalHeaderCombo, &QComboBox::currentIndexChanged,
            this, &Window::verticalHeaderChanged);
    ...

创建完控件后,我们连接信号和槽。将下拉列表连接到Window的私有槽或者QComboBox提供的公共槽。

    ...
    firstDayChanged(firstDayCombo->currentIndex());
    selectionModeChanged(selectionModeCombo->currentIndex());
    horizontalHeaderChanged(horizontalHeaderCombo->currentIndex());
    verticalHeaderChanged(verticalHeaderCombo->currentIndex());
}

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

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

void Window::createDatesGroupBox()
{
    datesGroupBox = new QGroupBox(tr("Dates"));

    minimumDateEdit = new QDateEdit;
    minimumDateEdit->setDisplayFormat("MMM d yyyy");
    minimumDateEdit->setDateRange(calendar->minimumDate(),
                                  calendar->maximumDate());
    minimumDateEdit->setDate(calendar->minimumDate());

    minimumDateLabel = new QLabel(tr("&Minimum Date:"));
    minimumDateLabel->setBuddy(minimumDateEdit);

    currentDateEdit = new QDateEdit;
    currentDateEdit->setDisplayFormat("MMM d yyyy");
    currentDateEdit->setDate(calendar->selectedDate());
    currentDateEdit->setDateRange(calendar->minimumDate(),
                                  calendar->maximumDate());

    currentDateLabel = new QLabel(tr("&Current Date:"));
    currentDateLabel->setBuddy(currentDateEdit);

    maximumDateEdit = new QDateEdit;
    maximumDateEdit->setDisplayFormat("MMM d yyyy");
    maximumDateEdit->setDateRange(calendar->minimumDate(),
                                  calendar->maximumDate());
    maximumDateEdit->setDate(calendar->maximumDate());

    maximumDateLabel = new QLabel(tr("Ma&ximum Date:"));
    maximumDateLabel->setBuddy(maximumDateEdit);

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

    connect(currentDateEdit, &QDateEdit::dateChanged,
            calendar, &QCalendarWidget::setSelectedDate);
    connect(calendar, &QCalendarWidget::selectionChanged,
            this, &Window::selectedDateChanged);
    connect(minimumDateEdit, &QDateEdit::dateChanged,
            this, &Window::minimumDateChanged);
    connect(maximumDateEdit, &QDateEdit::dateChanged,
            this, &Window::maximumDateChanged);
    ...
}

我们将currentDateEditdateChanged()信号直接连接到日历的setSelectedDate()槽。当日历的选定日期发生更改(无论是用户行为还是程序更改)时,我们的selectedDateChanged()槽会更新当前日期编辑器。我们还需要在用户更改最小日期最大日期编辑器时做出反应。

下面是createTextFormatsGroup()函数

void Window::createTextFormatsGroupBox()
{
    textFormatsGroupBox = new QGroupBox(tr("Text Formats"));

    weekdayColorCombo = createColorComboBox();
    weekdayColorCombo->setCurrentIndex(
            weekdayColorCombo->findText(tr("Black")));

    weekdayColorLabel = new QLabel(tr("&Weekday color:"));
    weekdayColorLabel->setBuddy(weekdayColorCombo);

    weekendColorCombo = createColorComboBox();
    weekendColorCombo->setCurrentIndex(
            weekendColorCombo->findText(tr("Red")));

    weekendColorLabel = new QLabel(tr("Week&end color:"));
    weekendColorLabel->setBuddy(weekendColorCombo);

我们使用createColorCombo()设置了工作日颜色周末颜色下拉列表,它创建了一个QComboBox并填充了颜色(“红色”,“蓝色”等)。

    headerTextFormatCombo = new QComboBox;
    headerTextFormatCombo->addItem(tr("Bold"));
    headerTextFormatCombo->addItem(tr("Italic"));
    headerTextFormatCombo->addItem(tr("Plain"));

    headerTextFormatLabel = new QLabel(tr("&Header text:"));
    headerTextFormatLabel->setBuddy(headerTextFormatCombo);

    firstFridayCheckBox = new QCheckBox(tr("&First Friday in blue"));

    mayFirstCheckBox = new QCheckBox(tr("May &1 in red"));

标题文本格式下拉列表允许用户更改用于水平和垂直标题的文本格式(加粗、斜体或普通)。第一周五使用蓝色5月1日使用红色复选框影响特定日期的渲染。

    connect(weekdayColorCombo, &QComboBox::currentIndexChanged,
            this, &Window::weekdayFormatChanged);
    connect(weekdayColorCombo, &QComboBox::currentIndexChanged,
            this, &Window::reformatCalendarPage);
    connect(weekendColorCombo, &QComboBox::currentIndexChanged,
            this, &Window::weekendFormatChanged);
    connect(weekendColorCombo, &QComboBox::currentIndexChanged,
            this, &Window::reformatCalendarPage);
    connect(headerTextFormatCombo, &QComboBox::currentIndexChanged,
            this, &Window::reformatHeaders);
    connect(firstFridayCheckBox, &QCheckBox::toggled,
            this, &Window::reformatCalendarPage);
    connect(mayFirstCheckBox, &QCheckBox::toggled,
            this, &Window::reformatCalendarPage);

我们将复选框和下拉列表连接到各种私有槽。第一周五使用蓝色5月1日使用红色复选框都连接到reformatCalendarPage(),当日历切换月份时也会调用这个函数。

    ...
    reformatHeaders();
    reformatCalendarPage();
}

createTextFormatsGroupBox() 的末尾,我们调用私有槽以将 QCalendarWidget 与其他小部件同步。

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

QComboBox *Window::createColorComboBox()
{
    QComboBox *comboBox = new 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() 中,我们创建一个组合框并用标准颜色填充它。函数 QComboBox::addItem() 的第二个参数是一个 QVariant,它存储用户数据(在这种情况下,是 QColor 对象)。

此函数用于设置 工作日颜色周末颜色 组合框。

void Window::firstDayChanged(int index)
{
    calendar->setFirstDayOfWeek(Qt::DayOfWeek(
                                firstDayCombo->itemData(index).toInt()));
}

当用户更改 周开始于 组合框的值时,将调用 firstDayChanged() 以获取组合框新值的索引。我们使用 itemData() 获取与新的当前项目相关联的自定义数据项,并将其强制转换为 Qt::DayOfWeek

selectionModeChanged()horizontalHeaderChanged()verticalHeaderChanged()firstDayChanged() 非常相似,所以这里省略了。

void Window::selectedDateChanged()
{
    currentDateEdit->setDate(calendar->selectedDate());
}

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

void Window::minimumDateChanged(QDate date)
{
    calendar->setMinimumDate(date);
    maximumDateEdit->setDate(calendar->maximumDate());
}

当用户更改最小日期时,我们通知 QCalenderWidget。我们还更新 最大日期 编辑器,因为如果新的最小日期晚于当前的最大日期,QCalendarWidget 将自动将其最大日期调整为避免冲突状态。

void Window::maximumDateChanged(QDate date)
{
    calendar->setMaximumDate(date);
    minimumDateEdit->setDate(calendar->minimumDate());
}

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

void Window::weekdayFormatChanged()
{
    QTextCharFormat format;

    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 给出的,除了前景色外,它还允许指定各种字符格式信息。在这个例子中,我们只显示了可能的子集。

void Window::weekendFormatChanged()
{
    QTextCharFormat format;

    format.setForeground(qvariant_cast<QColor>(
        weekendColorCombo->itemData(weekendColorCombo->currentIndex())));
    calendar->setWeekdayTextFormat(Qt::Saturday, format);
    calendar->setWeekdayTextFormat(Qt::Sunday, format);
}

weekendFormatChanged()weekdayFormatChanged() 同样,但它影响星期六和星期日而不是星期一到星期五。

void Window::reformatHeaders()
{
    QString text = headerTextFormatCombo->currentText();
    QTextCharFormat format;

    if (text == tr("Bold"))
        format.setFontWeight(QFont::Bold);
    else if (text == tr("Italic"))
        format.setFontItalic(true);
    else if (text == tr("Green"))
        format.setForeground(Qt::green);
    calendar->setHeaderTextFormat(format);
}

reformatHeaders() 槽在用户更改标题的文本格式时被调用。我们比较 标题文本格式 组合框的当前文本以确定要应用哪种格式。(另一种方法是存储与组合框项一起的 QTextCharFormat 值。)

void Window::reformatCalendarPage()
{
    QTextCharFormat mayFirstFormat;
    const QDate mayFirst(calendar->yearShown(), 5, 1);

    QTextCharFormat firstFridayFormat;
    QDate firstFriday(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 this day of the week.
        Qt::DayOfWeek dayOfWeek(static_cast<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);
    } else if (!firstFridayCheckBox->isChecked() || firstFriday != 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(static_cast<Qt::DayOfWeek>(mayFirst.dayOfWeek()));
        calendar->setDateTextFormat(mayFirst, calendar->weekdayTextFormat(dayOfWeek));
    }

    calendar->setDateTextFormat(mayFirst, mayFirstFormat);
}

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

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

示例项目 @ code.qt.io

© 2024 The Qt Company Ltd. 文档的贡献为各自所有者的版权。本提供的文档受GNU自由文档许可证版本1.3的许可,由自由软件基金会发布。Qt以及相应的商标是芬兰和其他国家/地区的 The Qt Company Ltd. 的商标。所有其他商标均为其各自所有者的财产。