qtqcheckboxqstyleditemdelegate

QStyledItemDelegate: how to make checkbox button to change its state on click


I have a delegate MyDelegate which is used for QListWidget. The delegate is derived from QStyledItemDelegate. One of the goals of MyDelegate is to place a checkbox button on each row of ListWidget. It is done within the paint() event of MyDelegate:

void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyledItemDelegate::paint(painter, option, index);
    // ... drawing other delegate elements

    QStyleOptionButton checkbox;
    // setting up checkbox's size and position

    // now draw the checkbox
    QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkbox, painter);
}

At first I thought the checkbox would automatically change its state on click, since I specified QStyle::CE_CheckBox. But it is not the case. Looks like I have to specify the checkbox visual behavior manually.

Data-wise, When user clicks on that checkbox, certain signal is emitted and the scene data is changed. I perform this action in editorEvent():

bool MyDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)  
{
    if (event->type() == QEvent::MouseButtonRelease) {
        if (/* within checkbox area */)
            emit this->clickedCheckbox(index);
    }
}  

The backend part works. However, I cannot figure out how to make the checkbox button to change its visual state from checked to unchecked, and backwards.

I realized that I can change the checkbox state manually by doing something like this from paint():

checkbox.state = someCondition? (QStyle::State_Enabled | QStyle::State_On) :
                           (QStyle::State_Enabled | QStyle::State_Off) ;

QStyle::State_On/Off does the trick of manual checkbox state change.

But I do not know how to set up that someCondition and where I should set it up. I tried to introduce it as a private bool variable which would be set in editorEvent() when the checkbox area gets a click, however, it does not produce the desired behavior: it sets all the other checkboxes of ListWidget to the same visual state. So, it behaved like some global condition for all the checkboxes.

I feel like, to accomplish my task, I have to re-implement the button and make it to change the checkbox state on click. But I'm lost on this way and not sure how to approach the problem. From the QStyleOptionButton API I do not see a clicked() or any other method I could use.

So, the question is: how do I make checkbox to behave like a checkbox, visually? If I need to re-implement a checkbox, then what class do I inherit?


Solution

  • You can set some value that describes your checkbox state in MyDelegate::editorEvent and then use it to paint a proper checkbox:

    const int CHECK_ROLE = Qt::UserRole + 1;
    
    bool MyDelegate::editorEvent(QEvent *event,
                                QAbstractItemModel *model,
                                const QStyleOptionViewItem &option,
                                const QModelIndex &index)
    {
        if (event->type() == QEvent::MouseButtonRelease)
        {
            bool value = index.data(CHECK_ROLE).toBool();
    
            // invert checkbox state
            model->setData(index, !value, CHECK_ROLE);
    
            return true;
        }
    
        return QStyledItemDelegate::editorEvent(event, model, option, index);
    }
    
    void MyDelegate::paint(QPainter *painter, 
                          const QStyleOptionViewItem &option, 
                          const QModelIndex &index) const
    {
        QStyleOptionButton cbOpt;
        cbOpt.rect = option.rect;
    
        bool isChecked = index.data(CHECK_ROLE).toBool();
        if (isChecked)
        {
            cbOpt.state |= QStyle::State_On;
        }
        else
        {
            cbOpt.state |= QStyle::State_Off;
        }
    
        QApplication::style()->drawControl(QStyle::CE_CheckBox, &cbOpt, painter);
    }