I want to have a simple column header with a checkbox that selects/ deselects all rows in a QTableView. Clicking the check box in the header causes either to select or deselect all rows.
When I want to add a check box in a table cell, I have to just return the check state for the Qt::CheckStateRole in the data(..) for the required model indices as below. This is working as expected.
QVariant MyModel::data( const QModelIndex & rIndex, int iRole) const
{
...
if (iRole == Qt::Qt::CheckStateRole)
{
return checkstate;
}
}
But when I want to add a checkbox in a header cell, the above method is not working. Hear is my sample code.
QVariant MyModel::headerData( int iSection, Qt::Orientation eOrientation, int iRole) const
{
...
if (iRole == Qt::CheckStateRole)
{
return checkstate;
}
}
The QTableView does not call headerData() function in my model with the Qt::CheckStateRole, as it does with data() function.
Why is this behavior? How can I insert a check box in a header cell by only modifying my custom table model?
(I do not want to create a custom QTableView or QHeaderView for this purpose)
You cannot do it - Qt doesn't support check boxes in headers by default. You can read https://wiki.qt.io/Qt_project_org_faq#How_can_I_insert_a_checkbox_into_the_header_of_my_view.3F for further information and its realization using custom QHeaderView
Here is a bit modified/fixed (checkbox was displayed as disabled and redrawing didn't work) code from the link in the accepted answer.
.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();
}
}
usage
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);
I am surprised such a hacky solution is recommend and used.
First: The check state should be stored in the model. All the tools are already there.
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);
}
Second: We toggle checked state simply by handling the click signal on the header.
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);
});
Third:
At this point we have fully functional checking behavior, only part missing is the visualization. The smartest way to go is to again use the model, taking advantage of the Qt::DecorationRole
. Here is a dummy implementation:
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);
}
Of course, one can draw a real checkbox there using the styled drawing.
Notice, this solution does not require sub-classing and custom widgets.
Also, the check state is decoupled from the view/UI. The only downside is fact the visuals are handled by the model, but this is optional - any way can be used to draw the check state, the one from the alternative answers included.