1. 程式人生 > >element.ui-Qt實現之時間控制元件

element.ui-Qt實現之時間控制元件

時分秒滾動控制元件

廢話少說,直入主題,今天我們來實現一個時分秒滾動控制元件,類似前端元件
element時間控制元件

Qt實現的時間控制元件效果,因為不會傳動態效果,所以沒有滾動效果。

注意本文只介紹了時分秒滾動區域的實現,只是當前日期元件的一部分,整個日期控制元件在後面的部落格中介紹
在這裡插入圖片描述

  • QScrollTime有三個listview組成,分別是可滾動的時、分、秒區域
QScrollTime::QScrollTime(QWidget *parent, Qt::WindowFlags enumFlags)
	: QFrame(parent, enumFlags)
{
	ui = new Ui::QScrollTime();
	ui->setupUi(this);
	ui->listViewHour->setModel(new QHourModel(this));
	ui->listViewHour->setItemDelegate(new QScrollTimeDelegate(this));
	ui->listViewHour->viewport()->installEventFilter(this);
	ui->listViewHour->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
	ui->listViewMinute->setModel(new QMinuteModel(this));
	ui->listViewMinute->setItemDelegate(new QScrollTimeDelegate(this));
	ui->listViewMinute->viewport()->installEventFilter(this);
	ui->listViewMinute->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
	ui->listViewSecond->setModel(new QSecondModel(this));
	ui->listViewSecond->setItemDelegate(new QScrollTimeDelegate(this));
	ui->listViewSecond->viewport()->installEventFilter(this);
	ui->listViewSecond->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
	ui->frameScroll->setFixedHeight(ITEM_HEIGHT * 5.5);

	setStyleSheet("\
				#frameScroll,#listViewHour,#listViewMinute,#listViewSecond {background:transparent;} \
				#frame {border-top:1px solid #D1D1D1;} \
				#pBScrollCancel{border:none;} \
				#pBScrollConfirm {border:none;color:#58BBE4;} \
				#pBScrollConfirm:hover {color:#79C8E9;} \
				#pBScrollConfirm:pressed {color:#4695B6;}");

	connect(ui->listViewHour, &QListView::clicked, this, &QScrollTime::slotTimeClicked);
	connect(ui->listViewMinute, &QListView::clicked, this, &QScrollTime::slotTimeClicked);
	connect(ui->listViewSecond, &QListView::clicked, this, &QScrollTime::slotTimeClicked);

	//垂直滾動條Value變化---當前時間被調整
	connect(ui->listViewHour->verticalScrollBar(), &QScrollBar::valueChanged, this, &QScrollTime::slotTimeChangedByScorll);
	connect(ui->listViewMinute->verticalScrollBar(), &QScrollBar::valueChanged, this, &QScrollTime::slotTimeChangedByScorll);
	connect(ui->listViewSecond->verticalScrollBar(), &QScrollBar::valueChanged, this, &QScrollTime::slotTimeChangedByScorll);

	connect(ui->pBScrollCancel, &QPushButton::clicked, this, [&](){
		emit this->singalCancelClicked(m_origTime);
		this->hide();
	});
	connect(ui->pBScrollConfirm, &QPushButton::clicked, this, [&](){
		if (!m_origTime.isValid())
		{
			emit this->timeChanged(m_showTime);
		}
		this->hide();
	});

	setContentsMargins(LEFT_SHAWDE,TOP_SHAWDE,RIGHT_SHAWDE,BOTTOM_SHAWDE);
	m_layerRect = QRect(contentsRect().left(), ITEM_HEIGHT * 2.5 + contentsRect().top(), contentsRect().width(), ITEM_HEIGHT);

	setAttribute(Qt::WA_TranslucentBackground, true);
}
  • 初始化時間
void QScrollTime::initTime(const QTime& time)
{
	if (time.isValid())
	{
		m_origTime = time;
		m_showTime = time;
		QTimer::singleShot(0, this, SLOT(slotDelaySetTime())); //這裡不能直接滾動,因為此刻還未顯示,放到事件迴圈尾部
	}
	else
	{
		m_showTime = QTime(0, 0, 0);
	}
}
  • 繪製時間區域底色和:號

  • 繪製背景陰影

  • 繪製區域底色

  • 繪製:號

	QPainter painter(this);
	QPixmap pix(":/images/Calendar/timeBorder.png");
	qDrawBorderPixmap(&painter, rect(), QMargins(0, 0, 0, 0), pix);
	painter.fillRect(m_layerRect, QColor(88, 187, 228));
	QRect colon1Rect(ui->listViewHour->width() + m_layerRect.left() - 2, ITEM_HEIGHT * 2.5 + contentsRect().top(), 4, ITEM_HEIGHT - 2);
	QRect colon2Rect(ui->listViewSecond->x() + m_layerRect.left() - 2, ITEM_HEIGHT * 2.5 + contentsRect().top(), 4, ITEM_HEIGHT - 2);
	QPen pen(Qt::white);
	painter.setPen(pen);
	painter.drawText(colon1Rect, Qt::AlignCenter, ":");
	painter.drawText(colon2Rect, Qt::AlignCenter, ":");
  • 過濾在當前時分秒區域滑鼠事件
bool QScrollTime::eventFilter(QObject *obj, QEvent *event)
{
	QEvent::Type t = event->type();
	if ((obj == ui->listViewHour->viewport() || obj == ui->listViewMinute->viewport() || obj == ui->listViewSecond->viewport()) \
		&& (t == QEvent::MouseMove || t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease || t == QEvent::MouseButtonDblClick))
	{
		QMouseEvent* pMouseEvent = dynamic_cast<QMouseEvent*>(event);
		QPoint pos = pMouseEvent->pos();
		if (m_layerRect.contains(pos))
		{
			return true;
		}	
	}
	return false;
}
  • 時分秒Item被點選後移到中間
void QScrollTime::slotTimeClicked(const QModelIndex &index)
{
	if (QListView* view = qobject_cast<QListView*>(sender()))
	{
		setCenterCoveredRow(view, index.row());
	}	
}
  • 當滾動條變化後,發射時間變化訊號
  • 這裡有針對滾動超出範圍後,微調整滾動條
void QScrollTime::slotTimeChangedByScorll()
{
	bool bTimeChanged = false;
	if (ui->listViewHour->verticalScrollBar() == sender())
	{
		const QModelIndex index = ui->listViewHour->indexAt(ui->listViewHour->viewport()->rect().center());
		if (index.isValid())
		{
			//調整index位置
			QRect indexRect = ui->listViewHour->visualRect(index);
			indexRect.adjust(contentsRect().left(), contentsRect().top(), contentsRect().left(), contentsRect().top());
			if (indexRect != m_layerRect.intersected(indexRect))
			{
				setCenterCoveredRow(ui->listViewHour, index.row());
				return;
			}
			QString value = index.data(Qt::DisplayRole).toString();
			if (!value.isEmpty())
			{
				int hour = value.toInt();
				if (hour != m_showTime.hour())
				{
					bTimeChanged = true;
					m_showTime.setHMS(hour, m_showTime.minute(), m_showTime.second());
				}
			}
		}
	}
	else if (ui->listViewMinute->verticalScrollBar() == sender())
	{
		const QModelIndex index = ui->listViewMinute->indexAt(ui->listViewMinute->viewport()->rect().center());
		if (index.isValid())
		{
			//調整index位置
			QRect indexRect = ui->listViewMinute->visualRect(index);
			indexRect.adjust(contentsRect().left(), contentsRect().top(), contentsRect().left(), contentsRect().top());
			if (indexRect != m_layerRect.intersected(indexRect))
			{
				setCenterCoveredRow(ui->listViewMinute, index.row());
				return;
			}
			QString value = index.data(Qt::DisplayRole).toString();
			if (!value.isEmpty())
			{
				int minu = value.toInt();
				if (minu != m_showTime.minute())
				{
					bTimeChanged = true;
					m_showTime.setHMS(m_showTime.hour(), minu, m_showTime.second());
				}
			}
		}
	}
	else if (ui->listViewSecond->verticalScrollBar() == sender())
	{
		const QModelIndex index = ui->listViewSecond->indexAt(ui->listViewSecond->viewport()->rect().center());
		if (index.isValid())
		{
			//調整index位置
			QRect indexRect = ui->listViewSecond->visualRect(index);
			indexRect.adjust(contentsRect().left(), contentsRect().top(), contentsRect().left(), contentsRect().top());
			if (indexRect != m_layerRect.intersected(indexRect))
			{
				setCenterCoveredRow(ui->listViewSecond, index.row());
				return;
			}
			QString value = index.data(Qt::DisplayRole).toString();
			if (!value.isEmpty())
			{
				int secs = value.toInt();
				if (secs != m_showTime.second())
				{
					bTimeChanged = true;
					m_showTime.setHMS(m_showTime.hour(), m_showTime.minute(), secs);
				}
			}
		}
	}

	if (bTimeChanged)
	{
		emit timeChanged(m_showTime);
	}
}
  • 設定時分秒時,將滾動條值設定
void QScrollTime::slotDelaySetTime()
{
	setCenterCoveredRow(ui->listViewHour, m_origTime.hour() + TOP_SPACEITEM_NUM);
	setCenterCoveredRow(ui->listViewMinute, m_origTime.minute() + TOP_SPACEITEM_NUM);
	setCenterCoveredRow(ui->listViewSecond, m_origTime.second() + TOP_SPACEITEM_NUM);
}

void QScrollTime::setCenterCoveredRow(QListView* view, int row)
{
	if (view == ui->listViewHour)
	{
		ui->listViewHour->verticalScrollBar()->setValue((row - TOP_SPACEITEM_NUM) * ITEM_HEIGHT);
	}
	else if (view == ui->listViewMinute)
	{
		ui->listViewMinute->verticalScrollBar()->setValue((row - TOP_SPACEITEM_NUM) * ITEM_HEIGHT);
	}
	else if (view == ui->listViewSecond)
	{
		ui->listViewSecond->verticalScrollBar()->setValue((row - TOP_SPACEITEM_NUM) * ITEM_HEIGHT);
	}
}
  • 時分秒的Model
enum TimePartValue
{
	HoursOfDay = 24,
	MinutesOfHour = 60,
	SecondsOfMin = 60,
};

class QTimeModel : public QAbstractListModel
{
	Q_OBJECT
protected:
	QTimeModel(TimePartValue v, QObject *parent) : QAbstractListModel(parent), value(v){}
	~QTimeModel() {}
public:
	int rowCount(const QModelIndex &parent = QModelIndex()) const;
	QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
	Qt::ItemFlags flags(const QModelIndex &index) const;
private:
	int value;
};

class QHourModel : public QTimeModel
{
	Q_OBJECT

public:
	QHourModel(QObject *parent) : QTimeModel(HoursOfDay, parent){}
	~QHourModel(){}
};

class QMinuteModel : public QTimeModel
{
	Q_OBJECT

public:
	QMinuteModel(QObject *parent) : QTimeModel(MinutesOfHour, parent){}
	~QMinuteModel(){}
};

class QSecondModel : public QTimeModel
{
	Q_OBJECT

public:
	QSecondModel(QObject *parent) : QTimeModel(SecondsOfMin, parent){}
	~QSecondModel(){}
};

.cpp,注意上下有兩個空Item,為了給滾動條留空間

int QTimeModel::rowCount(const QModelIndex &parent) const
{ 
	if (parent.isValid())
	{
		return 0;
	}
	return value + TOP_SPACEITEM_NUM + BUTTOM_SPACEITEM_NUM; //上下2空Item
}

QVariant QTimeModel::data(const QModelIndex &index, int role) const
{
	int row = index.row();
	if (Qt::TextAlignmentRole == role)
	{
		return Qt::AlignCenter;
	}
	if (Qt::DisplayRole == role)
	{
		if (row >= TOP_SPACEITEM_NUM && row < value + TOP_SPACEITEM_NUM)
		{
			QString strTime;
			strTime.sprintf("%02d", row - TOP_SPACEITEM_NUM);
			return strTime;
		}
	}
	return QVariant();
}

Qt::ItemFlags QTimeModel::flags(const QModelIndex &index) const
{
	int row = index.row();
	if ((row >= 0 && row < TOP_SPACEITEM_NUM) || row >= value + TOP_SPACEITEM_NUM)
	{
		return 0;
	}

	return QAbstractListModel::flags(index);
}

  • view的delegate,控制View的繪製

  • 注意第二個Item的高度是別的Item一半高度,為了滾動區域頂部顯示一半的效果,為什麼不用第一個Item呢? 因為滾動條的singlestep是第一個Item高度,這從QlistView原始碼得知

QScrollTimeDelegate::QScrollTimeDelegate(QObject *parent)
	: QItemDelegate(parent)
{

}

QScrollTimeDelegate::~QScrollTimeDelegate()
{

}

void QScrollTimeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
	QString text = index.data(Qt::DisplayRole).toString();
	if (text.isEmpty())
	{
		return;
	}
	painter->save();
	int height = option.widget->height() / 2;
	if (option.rect.y() <= height && option.rect.y() + option.rect.width() >= height)
	{
		QPen pen(Qt::white);
		painter->setPen(pen);
		painter->drawText(option.rect,Qt::AlignCenter,text);
	}
	else
	{
		if (option.state & QStyle::State_MouseOver)
		{
			painter->fillRect(option.rect, QColor(214, 238, 248));
		}
		painter->drawText(option.rect, Qt::AlignCenter, text);
	}
	painter->restore();
}

QSize QScrollTimeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
	if (SCROLLBAR_STEP_BY_INDEX == index.row())
	{
		return QSize(0, ITEM_HEIGHT >> 1);
	}
	return QSize(0, ITEM_HEIGHT);
}
  • 以上用的的巨集定義如下
#define CALENDAR_DEFINE_H
#define ITEM_HEIGHT 35
#define TOP_SPACEITEM_NUM 3
#define BUTTOM_SPACEITEM_NUM 2
#define SCROLLBAR_STEP_BY_INDEX 1
#define TOP_SHAWDE 6
#define BOTTOM_SHAWDE 18
#define LEFT_SHAWDE 12
#define RIGHT_SHAWDE 12