QStyledItemDelegate:如何使复选框按钮在单击时更改其状态
QStyledItemDelegate: how to make checkbox button to change its state on click
我有一个委托 MyDelegate
用于 QListWidget
。该委托派生自 QStyledItemDelegate
。 MyDelegate
的目标之一是在 ListWidget
的每一行放置一个复选框按钮。它是在 MyDelegate
的 paint()
事件中完成的:
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
// ... drawing other delegate elements
QStyleOptionButton checkbox;
// setting up checkbox's size and position
// now draw the checkbox
QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkbox, painter);
}
起初我以为复选框会在单击时自动更改其状态,因为我指定了 QStyle::CE_CheckBox
。但事实并非如此。看来我必须手动指定复选框的视觉行为。
数据方面,当用户点击该复选框时,会发出特定信号并更改场景数据。我在 editorEvent()
:
中执行此操作
bool MyDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease) {
if (/* within checkbox area */)
emit this->clickedCheckbox(index);
}
}
后端部分有效。但是,我无法弄清楚如何使复选框按钮将其视觉状态从选中更改为未选中,再向后更改。
我意识到我可以通过从 paint()
:
做这样的事情来手动更改复选框状态
checkbox.state = someCondition? (QStyle::State_Enabled | QStyle::State_On) :
(QStyle::State_Enabled | QStyle::State_Off) ;
QStyle::State_On/Off
实现了手动更改复选框状态的技巧。
但我不知道如何设置 someCondition
以及我应该在哪里设置它。我试图将它作为私有 bool
变量引入,当复选框区域被点击时,它将在 editorEvent()
中设置,但是,它不会产生所需的行为:它设置 editorEvent()
的所有其他复选框=17=]到相同的视觉状态。因此,它的行为就像所有复选框的一些全局条件。
我觉得,要完成我的任务,我必须重新实现按钮并使其在单击时更改复选框状态。但是我迷路了,不知道如何解决这个问题。从 QStyleOptionButton
API 我没有看到 clicked()
或我可以使用的任何其他方法。
所以,问题是:如何使复选框在视觉上表现得像一个复选框?如果我需要重新实现一个复选框,那么我要继承什么class?
您可以在 MyDelegate::editorEvent
中设置一些描述您的复选框状态的值,然后使用它来绘制适当的复选框:
const int CHECK_ROLE = Qt::UserRole + 1;
bool MyDelegate::editorEvent(QEvent *event,
QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease)
{
bool value = index.data(CHECK_ROLE).toBool();
// invert checkbox state
model->setData(index, !value, CHECK_ROLE);
return true;
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
void MyDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionButton cbOpt;
cbOpt.rect = option.rect;
bool isChecked = index.data(CHECK_ROLE).toBool();
if (isChecked)
{
cbOpt.state |= QStyle::State_On;
}
else
{
cbOpt.state |= QStyle::State_Off;
}
QApplication::style()->drawControl(QStyle::CE_CheckBox, &cbOpt, painter);
}
我添加了以下内容:
- data(Qt:CheckStateRole) 已切换。
- enable/disable 项目。
- 使单元格中的框居中。
- 仅使用鼠标左键切换复选标记,并且在单击复选标记时(不是整个单元格)。
代码如下:
// -------------------------------- //
class GFQtCheckboxItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
const int CHECK_ROLE = Qt::CheckStateRole;
// dont't let the default QStyledItemDelegate create the true/false combobox
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE
{
(void)parent;
(void)option;
(void)index;
return nullptr;
}
QRect GetCheckboxRect(const QStyleOptionViewItem &option)const
{
QStyleOptionButton opt_button;
opt_button.QStyleOption::operator=(option);
QRect sz = QApplication::style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt_button);
QRect r = option.rect;
// center 'sz' within 'r'
int dx = (r.width() - sz.width())/2;
int dy = (r.height()- sz.height())/2;
r.setTopLeft(r.topLeft() + QPoint(dx,dy));
r.setWidth(sz.width());
r.setHeight(sz.height());
return r;
}
// click event
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease)
{
QMouseEvent* pME = static_cast<QMouseEvent*>(event);
if(pME->button() == Qt::LeftButton)
{
QRect ro = GetCheckboxRect(option);
QPoint pte = pME->pos();
if(ro.contains(pte) )
{
bool value = index.data( CHECK_ROLE).toBool();
// invert checkbox state
model->setData(index, !value, CHECK_ROLE);
return true;
}
}
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionButton cbOpt;
cbOpt.rect = GetCheckboxRect(option);
bool isChecked = index.data(CHECK_ROLE ).toBool();
if (isChecked)
{
cbOpt.state |= QStyle::State_On;
}
else
{
cbOpt.state |= QStyle::State_Off;
}
QVariant enabled = index.data(Qt::ItemIsEnabled);
if(enabled.isNull() || enabled.toBool() )
{
cbOpt.state |= QStyle::State_Enabled;
}
QApplication::style()->drawControl(QStyle::CE_CheckBox, &cbOpt, painter);
}
};
为了完整起见,以下是如何应用它:
m_p_QTableView->setItemDelegateForColumn(iTableColumnIndex, new GFQtCheckboxItemDelegate() );
...
bool yesno ...;
pGrid->setData(index, yesno, Qt::CheckStateRole);
如果将 QListWidget 更改为 QListModel 和 QListView,这会容易得多。
Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
{
return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable;
}
QVariant ListModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::EditRole) {
return m_checkStateForRow[index.row()];
}
}
bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::CheckStateRole) {
m_checkStateForRow[index.row()] = value.toInt();
emit dataChanged(index, index, {role});
return true;
}
return false;
}
我有一个委托 MyDelegate
用于 QListWidget
。该委托派生自 QStyledItemDelegate
。 MyDelegate
的目标之一是在 ListWidget
的每一行放置一个复选框按钮。它是在 MyDelegate
的 paint()
事件中完成的:
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
// ... drawing other delegate elements
QStyleOptionButton checkbox;
// setting up checkbox's size and position
// now draw the checkbox
QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkbox, painter);
}
起初我以为复选框会在单击时自动更改其状态,因为我指定了 QStyle::CE_CheckBox
。但事实并非如此。看来我必须手动指定复选框的视觉行为。
数据方面,当用户点击该复选框时,会发出特定信号并更改场景数据。我在 editorEvent()
:
bool MyDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease) {
if (/* within checkbox area */)
emit this->clickedCheckbox(index);
}
}
后端部分有效。但是,我无法弄清楚如何使复选框按钮将其视觉状态从选中更改为未选中,再向后更改。
我意识到我可以通过从 paint()
:
checkbox.state = someCondition? (QStyle::State_Enabled | QStyle::State_On) :
(QStyle::State_Enabled | QStyle::State_Off) ;
QStyle::State_On/Off
实现了手动更改复选框状态的技巧。
但我不知道如何设置 someCondition
以及我应该在哪里设置它。我试图将它作为私有 bool
变量引入,当复选框区域被点击时,它将在 editorEvent()
中设置,但是,它不会产生所需的行为:它设置 editorEvent()
的所有其他复选框=17=]到相同的视觉状态。因此,它的行为就像所有复选框的一些全局条件。
我觉得,要完成我的任务,我必须重新实现按钮并使其在单击时更改复选框状态。但是我迷路了,不知道如何解决这个问题。从 QStyleOptionButton
API 我没有看到 clicked()
或我可以使用的任何其他方法。
所以,问题是:如何使复选框在视觉上表现得像一个复选框?如果我需要重新实现一个复选框,那么我要继承什么class?
您可以在 MyDelegate::editorEvent
中设置一些描述您的复选框状态的值,然后使用它来绘制适当的复选框:
const int CHECK_ROLE = Qt::UserRole + 1;
bool MyDelegate::editorEvent(QEvent *event,
QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease)
{
bool value = index.data(CHECK_ROLE).toBool();
// invert checkbox state
model->setData(index, !value, CHECK_ROLE);
return true;
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
void MyDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionButton cbOpt;
cbOpt.rect = option.rect;
bool isChecked = index.data(CHECK_ROLE).toBool();
if (isChecked)
{
cbOpt.state |= QStyle::State_On;
}
else
{
cbOpt.state |= QStyle::State_Off;
}
QApplication::style()->drawControl(QStyle::CE_CheckBox, &cbOpt, painter);
}
我添加了以下内容:
- data(Qt:CheckStateRole) 已切换。
- enable/disable 项目。
- 使单元格中的框居中。
- 仅使用鼠标左键切换复选标记,并且在单击复选标记时(不是整个单元格)。
代码如下:
// -------------------------------- //
class GFQtCheckboxItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
const int CHECK_ROLE = Qt::CheckStateRole;
// dont't let the default QStyledItemDelegate create the true/false combobox
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE
{
(void)parent;
(void)option;
(void)index;
return nullptr;
}
QRect GetCheckboxRect(const QStyleOptionViewItem &option)const
{
QStyleOptionButton opt_button;
opt_button.QStyleOption::operator=(option);
QRect sz = QApplication::style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt_button);
QRect r = option.rect;
// center 'sz' within 'r'
int dx = (r.width() - sz.width())/2;
int dy = (r.height()- sz.height())/2;
r.setTopLeft(r.topLeft() + QPoint(dx,dy));
r.setWidth(sz.width());
r.setHeight(sz.height());
return r;
}
// click event
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease)
{
QMouseEvent* pME = static_cast<QMouseEvent*>(event);
if(pME->button() == Qt::LeftButton)
{
QRect ro = GetCheckboxRect(option);
QPoint pte = pME->pos();
if(ro.contains(pte) )
{
bool value = index.data( CHECK_ROLE).toBool();
// invert checkbox state
model->setData(index, !value, CHECK_ROLE);
return true;
}
}
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionButton cbOpt;
cbOpt.rect = GetCheckboxRect(option);
bool isChecked = index.data(CHECK_ROLE ).toBool();
if (isChecked)
{
cbOpt.state |= QStyle::State_On;
}
else
{
cbOpt.state |= QStyle::State_Off;
}
QVariant enabled = index.data(Qt::ItemIsEnabled);
if(enabled.isNull() || enabled.toBool() )
{
cbOpt.state |= QStyle::State_Enabled;
}
QApplication::style()->drawControl(QStyle::CE_CheckBox, &cbOpt, painter);
}
};
为了完整起见,以下是如何应用它:
m_p_QTableView->setItemDelegateForColumn(iTableColumnIndex, new GFQtCheckboxItemDelegate() );
...
bool yesno ...;
pGrid->setData(index, yesno, Qt::CheckStateRole);
如果将 QListWidget 更改为 QListModel 和 QListView,这会容易得多。
Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
{
return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable;
}
QVariant ListModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::EditRole) {
return m_checkStateForRow[index.row()];
}
}
bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::CheckStateRole) {
m_checkStateForRow[index.row()] = value.toInt();
emit dataChanged(index, index, {role});
return true;
}
return false;
}