欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

自定义日历(四)-区间选择控件

程序员文章站 2023-02-04 15:12:37
[TOC] 原文链接: "自定义日历(四) 区间选择控件" 一、概述 很早很早以前,写过几篇关于日历的文章,不同于Qt原生的控件,这些控件都是博主使用自绘的方式进行完成,因此可定制性更强一些,感兴趣的可以参考 "自定义日历(一)" 、 "自定义日历(二)" 和 "自定义日历(三))" 。 本篇文章还 ......

原文链接:

一、概述

很早很早以前,写过几篇关于日历的文章,不同于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. qt定制控件列表
  2. 牛逼哄哄的qt库





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








自定义日历(四)-区间选择控件 自定义日历(四)-区间选择控件






很重要--转载声明

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

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