原文連接:自定義日曆(四)-區間選擇控件函數
很早很早之前,寫過幾篇關於日曆的文章,不一樣於Qt原生的控件,這些控件都是博主使用自繪的方式進行完成,所以可定製性更強一些,感興趣的能夠參考自定義日曆(一)、自定義日曆(二)和自定義日曆(三))。ui
本篇文章仍是繼續來寫咱們的日曆控件,仍然採用自繪的方式,帶來更加炫酷的效果。看本文的標題就應該就能明白,此次實現的是一個能夠區間選擇的日曆控件。this
效果圖以下,一個簡單的效果展現。spa
日曆控件與Qt原生的QDateEdit同樣,是由一個按鈕進行觸發,彈出如期選擇面板。不一樣的是這個日曆選擇面板由2個小的日期面板組成,分別是開始和結束日期,規則以下:.net
開始講解具體內容以前,先來看下總體的結構劃分,實現這個日期段選擇控件,總共須要如下4個類,下圖是工程結構rest
如下是4個類的說明code
其中QPickDate類就是對外使用的類,使用也很簡單,可能像下面這樣htm
QPickDate * pickDate = new QPickDate; pickDate->SetQuickValue(QDatePanel::DAY_ONE);
意思是構造一個日期段選擇空間,而後初始時爲選擇當天。有了一個大體的瞭解後,下面開始詳細的講解每一個類的實現過程blog
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中獲取。
以下是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: ... };
快速選擇:能夠快速選擇一日、一週、一月和一年時間段
日期選擇:分爲左右結構,左側時其實日期,右側時結束日期
操做按鈕:選擇好日期後,能夠經過點擊肯定或者取消來關閉面板
上邊說了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個:
因爲繪製篇幅緣由,仍是隻保留主要邏輯
如下代碼是精簡事後的繪製選中背景色,看起來仍是很長,不過大致上是分下面這幾步
下面是主要的繪製流程,代碼就不細講了,你們能夠自行閱讀
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(); }
最後就是繪製的順序,這裏必定要注意,必定得線繪製背景色,若是是最後繪製的話會擋住當前繪製的文字和選中狀態。
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); }
值得一看的優秀文章:
很重要--轉載聲明