如何将原始复选框与 QHeaderView 列 header 的中心对齐?

How to align a primitive checkbox with the center of QHeaderView's column header?

我有一个带有自定义 QHeaderView 的自定义 QTableView 模型,以便呈现用于对 table 的内容执行 "select all" 功能的复选框。

在我的 header 重载的 paintSection() 函数中,我成功渲染了用于 select 的复选框:

QStyleOptionButton option;
option.rect = QRect(3,10,16,16);
option.state = QStyle::State_Enabled | QStyle::State_Active;
if (isChecked_)
    option.state |= QStyle::State_On;
else
    option.state |= QStyle::State_Off;
style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter);

不幸的是,复选框呈现为居中,而是左对齐。这与列中每个 table 条目的 properly-centered QStyledItemDelegate 复选框完全冲突。

我知道我可以更改 QRect 的前两个参数来更改绘制图元的原点,但这对列宽的变化没有反应。虽然,固定列宽并不是最糟糕的解决方案。

如何正确地将复选框置于 header 列的中心?

辅助问题:header 中的复选框可以通过单击单元格中的 任意位置 来切换,而不仅仅是在框本身上(不同于在table 通过代表)。有办法解决这个问题吗?

解决方案

QHeaderView::paintSection takes a QRect &rect as an argument. Set QStyleOption::rect等于rect,即替换

option.rect = QRect(3,10,16,16);

option.rect = rect;

注意:此解决方案响应列宽的变化。

例子

使用您对 paintSection 的实施,我创建了一个最小示例,以演示如何实施建议的解决方案。该代码可在 GitHub.

上获得

结果

提供的示例在 Windows 7 上产生以下结果:

Windows10 的结果:

注意: 此解决方案不适用于 Linux,因为它会产生以下结果 (Ubuntu 17):

The solution of @scopchanov 对我不起作用,因为复选框覆盖了 header

的整个项目

一个可能的解决方案是用样式绘制 CheckBox,但除此之外你必须记住元素是否被选中,为此我们使用 QMap<>,第一个元素是logicalIndex 因为即使移动列它也不会改变,第二个元素是状态。

#include <QApplication>
#include <QHeaderView>
#include <QMouseEvent>
#include <QPainter>
#include <QStandardItemModel>
#include <QTableView>

class CheckedHeaderView : public QHeaderView
{
    Q_OBJECT
public:
    using QHeaderView::QHeaderView;
protected:
    void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override
    {
        painter->save();
        QHeaderView::paintSection(painter, rect, logicalIndex);
        painter->restore();
        QStyleOptionButton opt;

        QRect checkbox_rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt);
        checkbox_rect.moveCenter(rect.center());
        opt.rect = checkbox_rect;
        opt.state = QStyle::State_Enabled | QStyle::State_Active;
        if(logicalIndex == columnDown)
            opt.state |= QStyle::State_Sunken;
        if (states[logicalIndex])
            opt.state |= QStyle::State_On;
        else
            opt.state |= QStyle::State_Off;
        style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, painter);
    }
    void mousePressEvent(QMouseEvent *event) override
    {
        QHeaderView::mousePressEvent(event);
        int li = logicalIndexAt(event->pos());
        if(li == -1) return;
        columnDown = li;
        updateSection(li);
    }
    void mouseReleaseEvent(QMouseEvent *event) override
    {
        QHeaderView::mouseReleaseEvent(event);
        int li = logicalIndexAt(event->pos());
        if(li == -1) return;
        states[li] = !states[li];
        Q_EMIT checked(li, states[li]);
        columnDown = -1;
        updateSection(li);
    }
Q_SIGNALS:
    void checked(int logicalIndex, bool state);
private:
    QMap<int, bool> states;
    int columnDown = -1;
};

class TableView : public QTableView
{
    Q_OBJECT
public:
    TableView(QWidget *parent = nullptr):
        QTableView(parent)
    {
        CheckedHeaderView *header = new CheckedHeaderView(Qt::Horizontal, this);
        setHorizontalHeader(header);
        connect(header, &CheckedHeaderView::checked, this, &TableView::on_checked);
    }
private Q_SLOTS:
    void on_checked(int logicalIndex, bool state){
        QItemSelectionModel::SelectionFlags command = state? QItemSelectionModel::Select : QItemSelectionModel::Deselect;
        for(int r=0; r < model()->rowCount(); r++){
            QModelIndex ix = model()->index(r, logicalIndex);
            selectionModel()->select(ix, command);
        }
    }
};


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    TableView w;
    QStandardItemModel *model = new QStandardItemModel(8, 6, &w);
    w.setModel(model);
    w.show();
    return a.exec();
}