在Item View里画一个checkbox并能改变勾选状态 - subclassing QStyledItemDelegate
最近在用Qt做一个hobby project,类似Microsoft Todo的任务清单。
最初就是用QListView,QStandardItemModel,QStandardItem,和默认使用的QStyledItemDelegate。如果每个只是显示任务名字和一个checkbox的话,这些确实足够了。
但是还想在item
view里显示截止日期和提醒时间,上面的就不够了。想自定义item
view的话,就得自建一个QStyledItemDelegate的子类,重新实现paint方法了。
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
感觉官方也没有给这个方法详细的解释和例子,搜到的很多也只是画了单纯的图片和文字,没有像checkbox这样的控件。
这个方法里最让我搞不懂的就是option
。好像都是基于option.rect
来画出自定义的界面。后来通过debug打印出来rect的size和position,才搞懂一些。
paint
方法有还有个index
参数,而option
也对应了这个index
的item,每个option.rect
的位置是对应了index
的item在QListView里显示的位置。
然后就是不同类型的option
,比如paint
方法参数的option是QStyleOptionViewItem
类型,对应的是item
view。而如果我要在item
view上画一个checkbox,那这个checkbox需要另建一个类型为QStyleOptionButton
的实例,比如把这个实例命名为checkBoxOpt
。那么checkBoxOpt.rect
的位置应该根据option.rect
的位置来设定。
下面就是我在paint方法里用来画checkbox的代码,这里还包括了通过index获得该item的TaskCompleteRole
数据,表示这个task是否已经完成,并由此设定该item的checkbox是否被勾选。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// Draw checkbox
QStyleOptionButton checkBoxOpt;
checkBoxOpt.rect = createCheckBoxRect(option, checkBoxOpt);
checkBoxOpt.state = QStyle::State_Enabled;
bool isChecked = index.data(TaskCompleteRole).toBool();
if (isChecked)
{
checkBoxOpt.state |= QStyle::State_On;
}
else
{
checkBoxOpt.state |= QStyle::State_Off;
}
QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkBoxOpt, painter);createCheckBoxRect
是用来确定checkbox的rect的位置,这个方法在之后改变checkbox状态的时候也用到。
1
2
3
4
5
6
7
8
9QRect TaskViewDelegate::createCheckBoxRect(const QStyleOptionViewItem& option, const QStyleOptionButton& checkBoxOpt) const
{
QSize checkBoxSize = QApplication::style()->sizeFromContents(QStyle::CT_CheckBox, &checkBoxOpt, QSize());
int checkBoxRectTopLeftY = option.rect.topLeft().y() + option.rect.height() / 2 - checkBoxSize.height() / 2;
QPoint checkBoxRectTopLeft(0, checkBoxRectTopLeftY);
QRect checkBoxRect(checkBoxRectTopLeft, checkBoxSize); // checkBoxRect's position should be related to the option of each item,
// not a fixed value, otherwise all the check box is at the same position
return checkBoxRect;
}
现在可以在item
view里画出来一个checkbox了,但是怎么让checkbox被点击之后能被勾选或者取消勾选呢。参考StackOverflow的一个问答,需要重新实现QStyledItemDelegate
的editorEvent
方法
bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
通过检测鼠标点击事件并且点击区域限定在checkbox的rect位置内,来更新该item的TaskCompleteRole
的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20bool TaskViewDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
{
if (event->type() == QEvent::MouseButtonRelease)
{
qDebug() << "editorEvent: option.rect.size()" << option.rect.size();
QMouseEvent* pME = static_cast<QMouseEvent*>(event);
qDebug() << "pME->pos()" << pME->pos();
QStyleOptionButton checkBoxOpt;
QRect checkBoxRec = createCheckBoxRect(option, checkBoxOpt);
if (checkBoxRec.contains(pME->pos()))
{
bool value = index.data(TaskCompleteRole).toBool();
model->setData(index, !value, TaskCompleteRole);
}
return true;
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
下面这两个个例子给了我很大启发。
自定义的item view里checkbox改变状态 https://stackoverflow.com/questions/36778577/qstyleditemdelegate-how-to-make-checkbox-button-to-change-its-state-on-click
在QListView里展示自定义wedget https://stackoverflow.com/questions/53105343/is-it-possible-to-add-a-custom-widget-into-a-qlistview