zoukankan      html  css  js  c++  java
  • 自定义日历(四)-区间选择控件

    原文链接:自定义日历(四)-区间选择控件

    一、概述

    很早很早以前,写过几篇关于日历的文章,不同于Qt原生的控件,这些控件都是博主使用自绘的方式进行完成,因此可定制性更强一些,感兴趣的可以参考自定义日历(一)自定义日历(二)自定义日历(三))

    本篇文章还是继续来写我们的日历控件,仍然采用自绘的方式,带来更加炫酷的效果。看本文的标题就应该就能明白,这次实现的是一个可以区间选择的日历控件。

    二、效果展示

    效果图如下,一个简单的效果展示。

    日历控件与Qt原生的QDateEdit一样,是由一个按钮进行触发,弹出如期选择面板。不同的是这个日历选择面板由2个小的日期面板组成,分别是开始和结束日期,规则如下:

    1. 开始日期必须小于结束日期
    2. 顶部有快速返回按钮
    3. 选中的日期段上有高亮背景色
    4. 选中的日期点上有蓝色圆形标识
    5. 点击确定按钮以后日期选择面板关闭

    三、整体结构

    开始讲解具体内容之前,先来看下整体的结构划分,实现这个日期段选择控件,总共需要以下4个类,下图是工程结构

    以下是4个类的说明

    1. QDateContent:单个日历窗口
    2. QDateWidget:包含了年月选择的单个日历窗口
    3. QDatePanel:日期段选择面板
    4. QPickDate:日期选择按钮,用于呼出日期选择面板

    其中QPickDate类就是对外使用的类,使用也很简单,可能像下面这样

    QPickDate * pickDate = new QPickDate;
    pickDate->SetQuickValue(QDatePanel::DAY_ONE);
    

    意思是构造一个日期段选择空间,然后初始时为选择当天。有了一个大致的了解后,下面开始详细的讲解每个类的实现过程

    四、分析实现

    1、QPickDate

    QPickDate类是对外导出类,也是我们使用的时候需要了解的类,他的头文件实现如下

    class QPickDate : public QPushButton
    {
    	Q_OBJECT
    
    public:
    	QPickDate(QWidget * parent = nullptr);
    	~QPickDate();
    
    signals:
    	void PickSuccess();//选择日期成功时调用
    
    public:
    	void SetQuickValue(QDatePanel::QuickPick pick);
    	void GetStartDate(unsigned short year, unsigned short month, unsigned short day);
    	void GetEndDate(unsigned short year, unsigned short month, unsigned short day);
    
    private slots:
    	void OnClicked();
    
    private:
    	void InitializeUI();
    
    private:
    	QDatePanel::QuickPick m_ePick = QDatePanel::DAY_CUSTOM;
    	QDatePanel * m_pPanel = nullptr;
    };
    

    接口看起来也比较简单,SetQuickValue接口上一小节使用过,主要是用来初始化日期控件状态。GetStartDate和GetEndDate接口主要就是获取日期段的开始时间和结束时间。其中的实现具体的日期数据是从成员变量QDatePanel中获取。

    2、QDatePanel

    如下是QDatePanel的头文件声明,由于代码量的问题,其中精简了一部分,QDatePanel这个类就是日期选择面板,垂直方向由三部分组成,分别是快速选择日期选择操作按钮

    class QDatePanel : public QFrame
    {
    	...
    public:
    	enum QuickPick
    	{
    		DAY_ONE,//今天
    		DAY_WEEK,//近一周
    		DAY_MONTH,//近一月
    		DAY_YEAR,//近一年
    		DAY_CUSTOM,//自定义
    	};
    
    signals:
    	void PickSuccess(QuickPick, const QString &);
        ...
    public:
    	QString GetQuickName(QuickPick pick);
    	void SetQuickValue(QuickPick pick);
    	void GetStartDate(unsigned short year, unsigned short month, unsigned short day);
    	void GetEndDate(unsigned short year, unsigned short month, unsigned short day);
    
    private:
        ...
    };
    

    快速选择:可以快速选择一日、一周、一月和一年时间段

    日期选择:分为左右结构,左侧时其实日期,右侧时结束日期

    操作按钮:选择好日期后,可以通过点击确定或者取消来关闭面板

    3、QDateWidget、QDateContent

    上边说了QDatePanel是日期选择面板,其中日期选择部分就是由左右两部分组成,其实分别就是一个QDateWidget,如下图所示

    QDateContent类是主要的日期计算和绘制类,被QDateWidget包裹了一层,并添加上了年和月的操作按钮。

    下面主要介绍下QDateContent类的实现,首先来看下声明文件

    由于篇幅原因还是注释了一大部分代码

    class QDateContent : public QWidget
    {
        ...
    signals:
    	void DateClicked(unsigned short year, unsigned short month, unsigned short day);
    
    public:
    	void SetSelectDate(unsigned short year, unsigned short month, unsigned short day);
    	void GetSelectDate(unsigned short & year, unsigned short & month, unsigned short & day);
    	void SetDate(unsigned short year, unsigned short month, unsigned short day);
    	void GetDate(unsigned short & year, unsigned short & month, unsigned short & day);
    
    	//设置关联日期
    	void SetRelationDate(QDateContent * content);
    
    public slots :
    	void PreviousMonth();//上一月
    	void NextMonth();//下一月
    	void PreviousYear();//上一年
    	void NextYear();//下一年
        ...
    private:
    	struct QDateContentPrivate;
    	QDateContentPrivate * d_ptr;
    };
    

    切换月份和年份的接口代码中已经包含了注释,其他Set和Get接口看名称基本也能明白,QDateContent类的代码量还是比较大的,下面我们主要来看绘制部分,其中有3个比较重要的点绘制头绘制数字绘制选中

    绘制头

    该绘制模块主要是绘制表头,也就是周日、周一这样的字段,绘制的位置时通过私有函数GetColumnLeft和GetColumnRight获取。

    void QDateContent::DrawWeek(QPainter & painter)
    {
    	//	QString aText[7] = { STR("周日"), STR("周一"), STR("周二"), STR("周三"), STR("周四"), STR("周五"), STR("周六") };
    	QString aText[7] = { STR("日"), STR("一"), STR("二"), STR("三"), STR("四"), STR("五"), STR("六") };
    
    	painter.save();
    	painter.setFont(d_ptr->weekFont);
    	QFontMetrics fm(d_ptr->weekFont);
    	int height = fm.height();
    
    	//painter.fillRect(d_ptr->GetColumnLeft(0), d_ptr->topBorder, d_ptr->GetColumnRight(6) - 3, d_ptr->topBorder + height, QColor(20, 22, 23));
    
    	for (int i = 0; i < 7; ++i)
    	{
    		int left = d_ptr->GetColumnLeft(i);
    		int right = d_ptr->GetColumnRight(i);
    		QRect rect(left, d_ptr->topBorder, right - left, height);
    		painter.setPen(QColor("#838D9E"));
    
    		painter.drawText(rect, Qt::AlignCenter, aText[i]);
    	}
    
    	painter.restore();
    }
    

    绘制数字

    绘制数字和绘制标题原理基本一致,位置信息都是使用GetColumnLeft和GetColumnRight获取,不同的是,绘制数字时还需要绘制额外的选中状态、悬浮状态

    由于是绘制函数,因此有一些数据计算是通过整理好的,比如说需要绘制的数字当前行数当月第一天周几等等

    由于绘制篇幅原因,还是只保留主要逻辑

    void QDateContent::DrawDay(QPainter & painter)
    {
    	painter.save();
    
    	for (int column = 0; column < d_ptr->m_column_count; ++column)
    	{
    		int column_left = d_ptr->GetColumnLeft(column);
    		int column_right = d_ptr->GetColumnRight(column);
    		for (int row = 0; row < d_ptr->m_row_count; ++row)
    		{
    			int index = row * d_ptr->m_column_count + column;
    			QRect & rcTmp = d_ptr->m_aRect[index];
    			tDayFlag & flag = d_ptr->m_aDayFlag[index];
    			flag.m_chEnable = (column != 0 && column != 6) ? true : false;
    
    			bool selected = d_ptr->MatchRealDate(flag);
    			if (selected)
    			{
    				QPainterPath path;
    				path.addEllipse(QRectF(rcTmp).center(), 12, 12);
    				painter.fillPath(path, QColor("#218CF2"));
    			}
    
    			painter.drawText(rcTmp, Qt::AlignCenter, QString::number(flag.m_chFlagD));
    
    			painter.restore();
    		}
    	}
    
    	painter.restore();
    }
    

    绘制选中

    以下代码是绘制选中时的水平背景色,绘制代码比较简单,复杂的地方主要有2个:

    1. 计算当前日期是否在选择日期段当中,返回status
    2. 修正第一步返回的status

    由于绘制篇幅原因,还是只保留主要逻辑

    以下代码是精简过后的绘制选中背景色,看起来还是很长,不过大体上是分下面这几步

    1. 根据当前年月日返回status,表示当前day是否在选择的开始和选择的结束日期之间
    2. 根据所处列和day修改第一步返回的status
    3. 根据status调整要绘制的形状
    4. 绘制背景色

    下面是主要的绘制流程,代码就不细讲了,大家可以自行阅读

    void QDateContent::DrawSelectedBackground(QPainter & painter)
    {
    	painter.save();
    
    	for (int column = 0; column < d_ptr->m_column_count; ++column)
    	{
    		for (int row = 0; row < d_ptr->m_row_count; ++row)
    		{
    			tDayFlag & flag = d_ptr->m_aDayFlag[index];
    
    			if (little)
    			{
    				status = d_ptr->GetSelectedStatus(d_ptr->m_wYear, d_ptr->m_wMonth, d_ptr->m_wDay, d_ptr->m_sYear
    					, d_ptr->m_sMonth, flag.m_chFlagD, year, month, day);
    			}
    			else
    			{
    				status = d_ptr->GetSelectedStatus(year, month, day, d_ptr->m_sYear
    					, d_ptr->m_sMonth, flag.m_chFlagD, d_ptr->m_wYear, d_ptr->m_wMonth, d_ptr->m_wDay);
    			}
    
    			//修正数据
    			CorrentStatus(status, column, flag.m_chFlagD);
    
    			if (status == 0)
    			{
    				continue;
    			}
    			QRect rect = rcTmp.adjusted(0, 3, 0, -3);
    			if (rect.height() < 15)
    			{
    				rect.setHeight(15);
    				rect.moveCenter(rcTmp.center());
    			}
    			if (status == 2)
    			{
    				rect.adjust(-4, 0, 4, 0);
    				painter.drawRect(rect);
    			}
    			else if (status == 1)//只有左半边
    			{
    				rect.adjust(-4, 0, -4, 0);
    				painter.drawRoundedRect(rect, rect.height() / 2, rect.height() / 2);
    				painter.drawRect(rect.adjusted(0, 0, -rect.height() / 2, 0));
    			}
    			else if (status == 3)
    			{
    				rect.adjust(4, 0, 4, 0);
    				painter.drawRoundedRect(rect, rect.height() / 2, rect.height() / 2);
    				painter.drawRect(rect.adjusted(rect.height() / 2, 0, 0, 0));
    			}
    			else if (status == 5)
    			{
    				rect.adjust(4, 0, -4, 0);
    				painter.drawRoundedRect(rect, rect.height() / 2, rect.height() / 2);
    			}
    		}
    	}
    
    	painter.restore();
    }
    

    4、 调度绘制

    最后就是绘制的顺序,这里一定要注意,一定得线绘制背景色,如果是最后绘制的话会挡住当前绘制的文字和选中状态。

    void QDateContent::paintEvent(QPaintEvent * event)
    {
    	QDate date = QDate::currentDate();
    	d_ptr->m_tYear = date.year();
    	d_ptr->m_tMonth = date.month();
    	d_ptr->m_tDay = date.day();
    
    	QPainter painter(this);
    	painter.setRenderHint(QPainter::Antialiasing, true);
    
    	//painter.drawRect(rect());
    
    	d_ptr->ResetDayFlag();
    
    	DrawSelectedBackground(painter);
    
    	DrawWeek(painter);
    
    	DrawDay(painter);
    }
    

    五、相关文章

    自定义日历(一)

    自定义日历(二)

    自定义日历(三))

    Qt之模拟窗口失去焦点隐藏


    值得一看的优秀文章:

    1. 财联社-产品展示
    2. 广联达-产品展示
    3. Qt定制控件列表
    4. 牛逼哄哄的Qt库

    如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!




    很重要--转载声明

    1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords

    2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。


  • 相关阅读:
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    cookie相关内容:用法,特点,常用功能以及与session的异同
    JSP (一)
    Node.js npm 环境配置
    新老版本vue-cli的安装及创建项目等方式的比较
    npm 代理设置及更换为国内下载源
    for...of的使用
    打印机使用方法
    给OC项目添加icon
  • 原文地址:https://www.cnblogs.com/swarmbees/p/11783045.html
Copyright © 2011-2022 走看看