废话少说,直入主题,今天我们来实现一个时分秒滚动控件,类似前端组件
element时间控件
Qt实现的时间控件效果,因为不会传动态效果,所以没有滚动效果。
注意本文只介绍了时分秒滚动区域的实现,只是当前日期组件的一部分,整个日期控件在后面的博客中介绍
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(event);
QPoint pos = pMouseEvent->pos();
if (m_layerRect.contains(pos))
{
return true;
}
}
return false;
}
void QScrollTime::slotTimeClicked(const QModelIndex &index)
{
if (QListView* view = qobject_cast(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);
}
}
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