Search code examples
c++qtqt5qheaderview

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


I have a custom QTableView model with a custom QHeaderView in order to render a checkbox used to perform a "select all" function on the table's contents.

In my header's overloaded paintSection() function, I successfully render the checkbox used to select all with:

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

Unfortunately, the checkbox is not rendered centered, and instead justified left. This completely clashes with the properly-centered QStyledItemDelegate checkboxes in the column for each table entry.

I know I can change the first two args of QRect to change the origin of the drawn primitive, but this isn't responsive to changes in column width. Although, making the column width fixed isn't the worst solution.

How can I properly center the checkbox in the column header?

Ancillary question: the checkbox in the header is able to be toggled by clicking anywhere in the cell, not just on the box itself (unlike those rendered in the table via delegates). Is there a way to fix this?


Solution

  • The solution of @scopchanov does not work for me since the checkbox covers the whole item of the header

    enter image description here enter image description here

    A possible solution is to draw the CheckBox with styles, but apart from that you have to keep a memory of whether the element is checked or not, for this we use QMap<>, the first element is the logicalIndex since it does not change even when it is move the columns and the second element is the state.

    #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();
    }
    

    enter image description here