首先觉得Qt自带的日历控件不好看,跟现在的设计风格有些不符了;其次自带的控件没法满足目前的功能需求。所以只能参考网上的大神们的帖子,结合自己的项目需求,基于QCalendarWidget开发自定义日历控件。最终效果就是如下图所示了。

jquery 手机日历插件 安卓日历插件 widget_QCalendarWidget

代码实现

BaseCalendarWidget类就是我们的自定义日历控件,主要的定义如下,继承于QCalendarWidget。下面分析每个主要成员函数的作用。

class BaseCalendarWidget : public QCalendarWidget
{
    Q_OBJECT
public:
    explicit BaseCalendarWidget(QWidget *parent = nullptr);
    ~BaseCalendarWidget();

protected:
    void paintCell(QPainter *painter, const QRect &rect, const QDate &date) const;

private:
    void initUI();
    void initSetting();
};
1. 日历导航栏的修改

导航栏的修改也是参考了众多帖子,发现主要是两种方法:

  1. 隐藏原生的导航栏,完全自己实现导航栏,方法不难,但是比较繁琐,需要绑定各种信号槽才能实现原生导航栏的功能。适用于大量关于日历功能的开发。
  2. 获取原生的导航栏中的各个控件,直接修改原生控件的样式。开发量比较小,不会影响原生导航栏的功能逻辑。当然我们也不能灵活的新增导航栏的功能。

我比较懒,所以采用了方法2。当然方法2也满足了我的需求。
initUI函数主要是改变Qt原生日历中导航栏的样式,即年份、月份按钮所在的那一行。去掉了月份按钮自带的下拉菜单,修改了下一个月份上一个月份的按钮图标,最后修改了导航栏的背景颜色。

void BaseCalendarWidget::initUI()
{
    QMenu *monthMenu = QCalendarWidget::findChild<QMenu*>();
    Q_ASSERT( nullptr != monthMenu);
    QToolButton *monthBtn = qobject_cast<QToolButton*>(monthMenu->parentWidget());
    Q_ASSERT( nullptr != monthBtn);
    //monthBtn->setMenu(nullptr);
    monthBtn->setStyleSheet("QToolButton::menu-indicator {image: none;}");

    QToolButton *prevBtn = QCalendarWidget::findChild<QToolButton*>(QLatin1String("qt_calendar_prevmonth"));
    Q_ASSERT( nullptr != prevBtn);
    QSize prevSize = prevBtn->iconSize();
    prevSize.rwidth() -= 2;
    prevSize.rheight() -= 2;
    prevBtn->setIconSize(prevSize);
    prevBtn->setIcon(QIcon(":/BackDisplay/Resource/BackDisplay/arrow_left.png"));

    QToolButton *nextBtn = QCalendarWidget::findChild<QToolButton*>(QLatin1String("qt_calendar_nextmonth"));
    Q_ASSERT( nullptr != nextBtn);
    QSize nextSize = nextBtn->iconSize();
    nextSize.rwidth() -= 2;
    nextSize.rheight() -= 2;
    nextBtn->setIconSize(nextSize);
    nextBtn->setIcon(QIcon(":/BackDisplay/Resource/BackDisplay/arrow_right.png"));

    setStyleSheet("QCalendarWidget QWidget#qt_calendar_navigationbar {background-color: #696969;}");
}
2. 整体风格的设置

initSetting函数主要是设置日历的显示风格,这个根据个人的喜好和需求自由设置,没啥好说的。重点说下setWeekdayTextFormat函数的功能,设置日历表头中星期几的显示风格。

void BaseCalendarWidget::initSetting()
{
    setLocale(QLocale(QLocale::Chinese));
    setSelectionMode(QCalendarWidget::SingleSelection);
    setVerticalHeaderFormat(QCalendarWidget::NoVerticalHeader);
    setHorizontalHeaderFormat(QCalendarWidget::SingleLetterDayNames);

    QTextCharFormat format;
    format.setForeground(QColor(160, 160, 160));
    format.setBackground(QColor(255, 255, 255));
    setHeaderTextFormat(format);
    setWeekdayTextFormat(Qt::Saturday, format);
    setWeekdayTextFormat(Qt::Sunday,   format);
    setWeekdayTextFormat(Qt::Monday,   format);
    setWeekdayTextFormat(Qt::Tuesday,  format);
    setWeekdayTextFormat(Qt::Wednesday,format);
    setWeekdayTextFormat(Qt::Thursday, format);
    setWeekdayTextFormat(Qt::Friday,   format);
}
3. 日期的绘制

最重要的就是paintCell函数了,但是官方文档说明却很简单。

[virtual protected] void QCalendarWidget::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const
 Paints the cell specified by the given date, using the given painter and rect.

说明下每个参数的作用

  1. QPainter *painter : 控件的画笔,提供给我们自绘用的。
  2. const QRect &rect :当前绘制的区域,此区域限定第三个参数data显示的区域。
  3. const QDate &date :当前绘制时的日期,也就是我们要自定义绘制的日期。

主要的工作,就是设置当前需要绘制的日期背景颜色,显示的日期数字的颜色、大小、是否加粗等等。

/* 需要根据自己的业务逻辑处理当前日期绘制的逻辑 */
void BaseCalendarWidget::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const
{
    painter->save();
    painter->setRenderHint(QPainter::Antialiasing);
    
    /* 绘制背景 */
	painter->setPen(Qt::NoPen);
    painter->setBrush(QColor(0, 145, 255));
    painter->drawRoundedRect(rect.x()+1, rect.y()+1, rect.width()-2, rect.height()-2, 3, 3);

    /* 绘制前景 */
    QFont dateFont = painter->font();
    dateFont.setPixelSize(rect.height()-10);
	painter->setFont(dateFont);
	painter->setPen(QColor(255, 255, 255));
    QRect dateRect = QRect(rect.x()+3, rect.y()+3, rect.width()-6, rect.height()-6);
    painter->drawText(dateRect, Qt::AlignCenter, QString::number(date.day()));

    painter->restore();
}

PS: 由于是项目中的代码,所以不能贴上完整代码,但是关键的地方都已经贴出来了,只要根据自己的实际需求修改。