自定義日歷(四)-區間選擇控件


原文鏈接:自定義日歷(四)-區間選擇控件

一、概述

很早很早以前,寫過幾篇關於日歷的文章,不同於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. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM