自定義日曆(四)-區間選擇控制元件
目錄
- 一、概述
- 二、效果展示
- 三、整體結構
- 四、分析實現
- 1、QPickDate
- 2、QDatePanel
- 3、QDateWidget、QDateContent
- 4、 排程繪製
- 五、相關文章
原文連結:自定義日曆(四)-區間選擇控制元件
一、概述
很早很早以前,寫過幾篇關於日曆的文章,不同於Qt原生的控制元件,這些控制元件都是博主使用自繪的方式進行完成,因此可定製性更強一些,感興趣的可以參考自定義日曆(一)、自定義日曆(二)和自定義日曆(三))。
本篇文章還是繼續來寫我們的日曆控制元件,仍然採用自繪的方式,帶來更加炫酷的效果。看本文的標題就應該就能明白,這次實現的是一個可以區間選擇的日曆控制元件。
二、效果展示
效果圖如下,一個簡單的效果展示。
日曆控制元件與Qt原生的QDateEdit一樣,是由一個按鈕進行觸發,彈出如期選擇面板。不同的是這個日曆選擇面板由2個小的日期面板組成,分別是開始和結束日期,規則如下:
- 開始日期必須小於結束日期
- 頂部有快速返回按鈕
- 選中的日期段上有高亮背景色
- 選中的日期點上有藍色圓形標識
- 點選確定按鈕以後日期選擇面板關閉
三、整體結構
開始講解具體內容之前,先來看下整體的結構劃分,實現這個日期段選擇控制元件,總共需要以下4個類,下圖是工程結構
以下是4個類的說明
- QDateContent:單個日曆視窗
- QDateWidget:包含了年月選擇的單個日曆視窗
- QDatePanel:日期段選擇面板
- 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個:
- 計算當前日期是否在選擇日期段當中,返回status
- 修正第一步返回的status
由於繪製篇幅原因,還是隻保留主要邏輯
以下程式碼是精簡過後的繪製選中背景色,看起來還是很長,不過大體上是分下面這幾步
- 根據當前年月日返回status,表示當前day是否在選擇的開始和選擇的結束日期之間
- 根據所處列和day修改第一步返回的status
- 根據status調整要繪製的形狀
- 繪製背景色
下面是主要的繪製流程,程式碼就不細講了,大家可以自行閱讀
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之模擬視窗失去焦點隱藏
值得一看的優秀文章:
- 財聯社-產品展示
- 廣聯達-產品展示
- Qt定製控制元件列表
- 牛逼哄哄的Qt庫
如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支援。您的支援是我最大的動力,謝謝!!!
很重要--轉載宣告
本站文章無特別說明,皆為原創,版權所有,轉載時請用連結的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords
如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。