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

问题描述 投票:0回答:3

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

enter image description here

当我想在表格单元格中添加复选框时,我只需返回所需模型索引的 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)

c++ qt qt4
3个回答
7
投票

第一:检查状态应该存储在模型中。所有工具都已经在那里了。

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 p{12,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 分离。唯一的缺点是视觉效果是由模型处理的,但这是可选的 - 可以使用任何方式来绘制检查状态,包括来自替代答案的方法。


6
投票

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

.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);

5
投票

你不能这样做——Qt 默认不支持标题中的复选框。您可以阅读如何在视图的标题中插入复选框?以获取更多信息及其使用自定义

QHeaderView
的实现:

如何在视图标题中插入复选框?

目前没有 API 可以在标题中插入小部件,但您可以自己绘制复选框以将其插入标题中。

你可以做的是继承 QHeaderView,重新实现 paintSection() 然后调用 [drawPrimitive()](http://doc.qt.io/qt-5/qstyle.html#drawPrimitivewith]

PE_IndicatorCheckBox
在您想要此复选框的部分中。

您还需要重新实现 mousePressEvent() 来检测何时单击复选框,以便绘制选中和未选中状态。

下面的示例说明了如何做到这一点:

#include <QtWidgets>

class MyHeader : public QHeaderView
{
public:
   MyHeader(Qt::Orientation orientation, QWidget * parent = nullptr) : QHeaderView(orientation, parent) {}

protected:
   void 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(10,10,10,10);
            if (isOn)
                option.state = QStyle::State_On;
            else
                option.state = QStyle::State_Off;
            this->style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter);
       }

   }
   void mousePressEvent(QMouseEvent *event)
   {
       if (isOn)
           isOn = false;
       else
           isOn = true;
       this->update();
       QHeaderView::mousePressEvent(event);
   }
private:
    bool isOn;
};

int main(int argc, char **argv)
{
   QApplication app(argc, argv);
   QTableWidget table;
   table.setRowCount(4);
   table.setColumnCount(3);

   MyHeader *myHeader = new MyHeader(Qt::Horizontal, &table);
   table.setHorizontalHeader(myHeader);
   table.show();
   return app.exec();
}

要选中所有其他复选框,您可以使用

pressed()
信号,该信号应从 mousePressEvent() 触发,并将其连接到自定义插槽,您可以在其中将所有复选框设置为选中或取消选中。

© www.soinside.com 2019 - 2024. All rights reserved.