QTableView 中标题单元格中的复选框

Posted

技术标签:

【中文标题】QTableView 中标题单元格中的复选框【英文标题】:Checkbox in a header cell in QTableView 【发布时间】:2014-02-04 16:27:38 【问题描述】:

我想要一个带有复选框的简单列标题,用于选择/取消选择 QTableView 中的所有行。单击标题中的复选框会导致选择或取消选择所有行。

当我想在表格单元格中添加一个复选框时,我只需返回 data(..) 中 Qt::CheckStateRole 的检查状态,以获得所需的模型索引,如下所示。这按预期工作。

QVariant MyModel::data( const QModelIndex & rIndex, int iRole) const

    ...

    if (iRole == Qt::Qt::CheckStateRole)
    
        return checkstate;
    


但是当我想在标题单元格中添加一个复选框时,上述方法不起作用。听听是我的示例代码。

QVariant MyModel::headerData( int iSection, Qt::Orientation eOrientation, int iRole) const

    ...

    if (iRole == Qt::CheckStateRole)
    
        return checkstate;
    


QTableView 不会像使用 data() 函数那样使用 Qt::CheckStateRole 在我的模型中调用 headerData() 函数。

为什么会出现这种行为?如何仅通过修改我的自定义表格模型在标题单元格中插入复选框?

(我不想为此创建自定义 QTableView 或 QHeaderView)

【问题讨论】:

【参考方案1】:

你不能这样做 - 默认情况下 Qt 不支持标题中的复选框。您可以阅读https://wiki.qt.io/Qt_project_org_faq#How_can_I_insert_a_checkbox_into_the_header_of_my_view.3F 以了解更多信息及其使用自定义QHeaderView 的实现

【讨论】:

如果有时间,请从第三方资源中复制或重新实现代码。仅链接答案的问题在于,链接到的网站经常消失,答案变得毫无用处。【参考方案2】:

这是来自已接受答案中链接的一些修改/修复(复选框显示为禁用且重绘不起作用)代码。

.h

class CheckBoxHeader : public QHeaderView

    Q_OBJECT

public:
    CheckBoxHeader(Qt::Orientation orientation, QWidget* parent = 0);

    bool isChecked() const  return isChecked_; 
    void setIsChecked(bool val);

signals:
    void checkBoxClicked(bool state);

protected:
    void paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const;

    void mousePressEvent(QMouseEvent* event);

private:
    bool isChecked_;

    void redrawCheckBox();
;

.cpp

#include "CheckBoxHeader.h"

CheckBoxHeader::CheckBoxHeader(Qt::Orientation orientation, QWidget* parent /*= 0*/)
    : QHeaderView(orientation, parent)

    isChecked_ = true;


void CheckBoxHeader::paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const

    painter->save();
    QHeaderView::paintSection(painter, rect, logicalIndex);  
    painter->restore();
    if (logicalIndex == 0)
    
        QStyleOptionButton option;

        option.rect = QRect(1,3,20,20);

        option.state = QStyle::State_Enabled | QStyle::State_Active;

        if (isChecked_)
            option.state |= QStyle::State_On;
        else
            option.state |= QStyle::State_Off;
        option.state |= QStyle::State_Off;

        style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter);
    


void CheckBoxHeader::mousePressEvent(QMouseEvent* event)

    setIsChecked(!isChecked());

    emit checkBoxClicked(isChecked());


void CheckBoxHeader::redrawCheckBox()

    viewport()->update();


void CheckBoxHeader::setIsChecked(bool val)

    if (isChecked_ != val)
    
        isChecked_ = val;

        redrawCheckBox();
    

用法

CheckBoxHeader* header = new CheckBoxHeader(Qt::Horizontal, &table);
table.setHorizontalHeader(header);
// handle signal if needed (to set checkboxes in all rows, etc.)
//connect(header, &CheckBoxHeader::checkBoxClicked, this, &MyForm::on_header_checkBoxClicked);

【讨论】:

这个答案用 pyqt 移植到 python,然后改进,见Adding checkBox as vertical header in QtableView【参考方案3】:

我很惊讶这样一个 hacky 解决方案被推荐和使用。

首先:检查状态应该存储在模型中。所有工具都已经存在。

bool MyModel::setHeaderData(int index, Qt::Orientation orient, const QVariant& val, int role)

  if(Qt::Vertical != orient)
    return Base::setHeaderData(index, orient, val, role);

  storeCheckState(index, val);
  emit headerDataChanged(orient, index, index);
  return true;


QVariant MyModel::headerData(int index, Qt::Orientation o, int role) const

    if(Qt::Vertical != orient)
      return Base::headerData(index, o, role);

    switch(role)
    
    ...
    case Qt::CheckStateRole:
      return fetchCheckState(index);
    

  return Base::headerData(index, o, role);

第二:我们只需处理标题上的点击信号即可切换选中状态。

connect(header, &QHeaderView::sectionClicked, receiver
        , [receiver](int sec)

  const auto index = logicalIndex(sec);
  model()->setHeaderData(index
                         , Qt::Vertical
                         , Qt::CheckState(model()->headerData(index, Qt::Vertical, Qt::CheckStateRole).toUInt()) != Qt::Checked ? Qt::Checked : Qt::Unchecked
                         , Qt::CheckStateRole);
);

第三: 在这一点上,我们有完整的功能检查行为,唯一缺少的是可视化。最聪明的方法是再次使用该模型,利用Qt::DecorationRole。这是一个虚拟实现:

QVariant MyModel::headerData(int index, Qt::Orientation o, int role) const

    if(Qt::Vertical != orient)
      return Base::headerData(index, o, role);

    switch(role)
    
      case Qt::DecorationRole:
      
        QPixmap p12,12;
        p.fill(Qt::CheckState(headerData(index, o, Qt::CheckStateRole).toUInt()) ? Qt::green : Qt::red);
        return p;
       
       break;
    ...
    

  return Base::headerData(index, o, role);

当然,可以使用样式图在此处绘制一个真正的复选框。

注意,此解决方案不需要子类和自定义小部件。

此外,检查状态与视图/UI 分离。唯一的缺点是视觉效果由模型处理,但这是可选的 - 可以使用任何方式绘制检查状态,包括替代答案中的一种。

【讨论】:

以上是关于QTableView 中标题单元格中的复选框的主要内容,如果未能解决你的问题,请参考以下文章

在 QTableView 的单个单元格中显示多个图标

单击表格单元格后,QtableView 中的某些单元格不会自动重绘

勾选一个单元格中的复选框,具体取决于同一行级别的另一个单元格中的内容

您如何使复选框单元格中的文本能够被编辑?

仅使用 Ag-grid 中标题上的复选框从当前页面中选择行

在Jtable单元格中的复选框旁边插入文本