c++qt5qtableviewmodel-viewqheaderview

QHeaderView with checkbox - how to differentiate clicking right on the header checkbox from clicking anywhere in the cell


I have a custom header QProduitCartoHeaderView defined in a class inheriting from QHeaderView.

In my header I have a checkbox used to check/uncheck all the checkboxes in the same column for all rows of data.

enter image description here

Currently when I click anywhere in the header cell of the first column (the one with checkbox), It checks the header checkbox.

What I would like to do is to only check the checkbox when I click exactly on it and if I click outside (but still within the cell) to sort the first column.

I have tried to resize the checkbox rectangle (as seen in QProduitCartoHeaderView::paintSection() in the snippet below) but when I click anywhere in the cell (not on the checkbox), I still get it checked.

Note: I already managed to make the code to sort and to check. What I cannot do is to check whether I click on the checkbox or outside the checkbox within the cell.

Here is a snippet of my custom header:

#ifndef QPRODUITCARTOHEADERVIEW_H
#define QPRODUITCARTOHEADERVIEW_H

#include <QWidget>
#include <QPainter>
#include <QHeaderView>
#include <QMessageBox>

class QProduitCartoHeaderView :public QHeaderView
{
    Q_OBJECT
public:
    QProduitCartoHeaderView(QWidget * parent = 0);
    ~QProduitCartoHeaderView();
    void on_sectionClicked(int logicalIndex);
    bool all_first_columns_checked(int logicalIndex = 0);
    bool all_first_columns_unchecked(int logicalIndex=0);
    void setIsOn(bool val);
    void setPasTousLesMeme(bool val);

protected:
    virtual void paintSection(QPainter* poPainter, const QRect & oRect, int index) const;
    virtual void mouseReleaseEvent(QMouseEvent *e);


private:
    bool isOn = false, pasTousLesMeme = false;


signals:
    void dataModifiedSig();

};
#endif
#ifndef QPRODUITCARTOHEADERVIEW_H
#include "QProduitCartoHeaderView.h"
#endif
#ifndef QPRODUITCARTODATAMODEL_H
#include "QProduitCartoDataModel.h"
#endif

QProduitCartoHeaderView::QProduitCartoHeaderView(QWidget* parent) : QHeaderView(Qt::Horizontal, parent)
{
}

QProduitCartoHeaderView::~QProduitCartoHeaderView()
{
}

void QProduitCartoHeaderView::setIsOn(bool val)
{
    isOn = val; 
}

void QProduitCartoHeaderView::setPasTousLesMeme(bool val)
{
    //set if all checkboxes have different value or not
    pasTousLesMeme = val;
}

void QProduitCartoHeaderView::paintSection(QPainter* poPainter, const QRect & oRect, int index) const
{
    poPainter->save();

    QHeaderView::paintSection(poPainter, oRect, index);
    poPainter->restore();

    QStyleOptionButton option;


    QRect checkbox_rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &option);
    checkbox_rect.moveCenter(oRect.center());
    // pour la colonne 1
    if (index == 0)
    {
        qDebug() << checkbox_rect << " -- " << oRect;
        // position
        option.state = QStyle::State_Enabled | QStyle::State_Active;
        option.rect = checkbox_rect;
        QHeaderView::paintSection(poPainter, checkbox_rect, index);
        if (isOn)
        {
            // on a tout coché
            option.state |= QStyle::State_On;
        }
        else
        { // traite pas tout coché 

            // on rajoute le troisième état (third state)
            // quand on n'a pas tout coché mais pas tout décoché non plus
            if ( pasTousLesMeme )
            {
                option.state |= QStyle::State_NoChange;
            }
            else
            {   
                // et si on a tout décoché, on décoche (isOn = false et pasTouslesMemes=false)
                option.state |= QStyle::State_Off;
            }
            
        }

        // on redessine alors la checkbox
        this->style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, poPainter);
    }
}

void QProduitCartoHeaderView::mouseReleaseEvent(QMouseEvent *e)
{
   QHeaderView::mouseReleaseEvent(e);
}

bool QProduitCartoHeaderView::all_first_columns_checked(int logicalIndex)
{
//code to check if all first colum is checked

}

bool QProduitCartoHeaderView::all_first_columns_unchecked(int logicalIndex)
{
    
//code to check if all first colum is unchecked
}

void QProduitCartoHeaderView::on_sectionClicked(int logicalIndex)
{
    // code pour le click du header contenant les checkboxes
  
    QProduitCartoDataModel* the_Model = (QProduitCartoDataModel*) this->model();

    if (logicalIndex == 0)
    {
        
        // on update l'état du checkbox principale (si isOn = true, la checkbox est cochée)
        if (isOn)
        {
            // si elle est cochée, on la met à false (pour flip/flop)
            isOn = false;
            // mais si tout est décoché, alors, on met la checkbox à false
            if (all_first_columns_unchecked(logicalIndex))
            {
                isOn = true;
            }

        }
        else
        {
            isOn = true;
            if (all_first_columns_checked(logicalIndex))
            {
                isOn = false;
            }
        }
        
        this->update();
        // fin update

        int nbRow = this->model()->rowCount();

        // on met tout à false si la majorité est à true
        int nbTrue = 0;


        if (all_first_columns_checked(logicalIndex))
        {
            for (int r = 0; r < nbRow; r++)
            {
                QModelIndex index = this->model()->index(r, logicalIndex);

                the_Model->setData(index, false, Qt::CheckStateRole);
            }
        }
        else
        {
            // on efface d'abord tout
            for (int r = 0; r < nbRow; r++)
            {
                QModelIndex index = this->model()->index(r, logicalIndex);

                bool checked = this->model()->data(index, Qt::DisplayRole).toBool();
                if (checked)
                    the_Model->setData(index, false, Qt::CheckStateRole);
            }



            // Ensuite, on fait le flip/flop
            for (int r = 0; r < nbRow; r++)
            {
                QModelIndex index = this->model()->index(r, logicalIndex);
                // update each row of real data
                the_Model->setData(index, true, Qt::CheckStateRole);
                
            }
        }
    }

    emit dataModifiedSig();
}

Solution

  • You can track the mouse and reimplement mousePressEvent(QMouseEvent *event) and mouseReleaseEvent(QMouseEvent *event) to determine where the section was clicked. I created an example below:

    myheaderview.h

    #ifndef MYHEADERVIEW_H
    #define MYHEADERVIEW_H
    
    #include <QHeaderView>
    
    class MyHeaderView : public QHeaderView
    {
    public:
        MyHeaderView(QWidget *parent = nullptr);
    
        QRect visualRectOfColumn(int column) const;
    
    protected:
        virtual void paintSection(QPainter *painter, const QRect &rect, int index) const;
        virtual void mousePressEvent(QMouseEvent *event);
        virtual void mouseReleaseEvent(QMouseEvent *event);
    
    private:
        int press_column_{-1};
        bool checkbox_pressed_{false};
    };
    
    #endif // MYHEADERVIEW_H
    

    myheaderview.cpp

    #include "myheaderview.h"
    
    #include <QPainter>
    #include <QMouseEvent>
    #include <QDebug>
    
    MyHeaderView::MyHeaderView(QWidget *parent) : QHeaderView(Qt::Horizontal, parent)
    {
        this->setMouseTracking(true);
    }
    
    QRect MyHeaderView::visualRectOfColumn(int column) const
    {
        int x = sectionViewportPosition(column);
        int y = 0;
        int h = this->height();
        int w = this->sectionSize(column);
        return QRect(x, y, w, h);
    }
    
    void MyHeaderView::paintSection(QPainter *painter, const QRect &rect, int index) const
    {
        painter->save();
        QHeaderView::paintSection(painter, rect, index);
        painter->restore();
    
        if (index == 0)
        {
            QStyleOptionButton option;
            QRect checkbox_rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &option);
            checkbox_rect.moveCenter(rect.center());
    
            option.state = QStyle::State_Enabled | QStyle::State_Active;
            option.rect = checkbox_rect;
            QHeaderView::paintSection(painter, checkbox_rect, index);
            this->style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter);
        }
    }
    
    void MyHeaderView::mousePressEvent(QMouseEvent *event)
    {
        press_column_ = this->visualIndexAt(event->pos().x());
        if (press_column_ == -1)
        {
            checkbox_pressed_= false;
            return QHeaderView::mousePressEvent(event);;
        }
    
        QStyleOptionButton option;
        QRect checkbox_rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &option);
        checkbox_rect.moveCenter(this->visualRectOfColumn(press_column_).center());
        checkbox_pressed_= checkbox_rect.contains(event->pos());
    
        QHeaderView::mousePressEvent(event);
    }
    
    void MyHeaderView::mouseReleaseEvent(QMouseEvent *event)
    {
        int release_column = this->visualIndexAt(event->pos().x());
        if (release_column != -1 && press_column_ == release_column)
        {
            if (release_column != 0)
                qDebug() << "sort";
            else
            {
                QStyleOptionButton option;
                QRect checkbox_rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &option);
                checkbox_rect.moveCenter(this->visualRectOfColumn(release_column).center());
                if (checkbox_pressed_&& checkbox_rect.contains(event->pos()))
                    qDebug() << "checkbox";
                else if (!checkbox_pressed_&& !checkbox_rect.contains(event->pos()))
                    qDebug() << "sort";
            }
        }
    
        press_column_ = -1;
        checkbox_pressed_= false;
        QHeaderView::mouseReleaseEvent(event);
    }