Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set different images for different checkboxes in a QTreeView

Tags:

css

qt

qtreeview

I subclassed a QTreeView and I have two columns where there are checkboxes. I would like to set two different images: one for the first column, and another one for the second column. I know I can change the image in the stylesheet with:

QTreeView::indicator:checked{
    image: url(:/checked);
}

QTreeView::indicator:unchecked{
    image: url(:/unchecked);
}

but it will change all the checkboxes in the tree view. Is there a way to do it with the stylesheets, or do I need to use a delegate?

like image 947
IAmInPLS Avatar asked Oct 17 '17 12:10

IAmInPLS


1 Answers

Short answer: Stylesheets can't do that (as far as I know). They are a pretty immature feature in Qt, and there seems to be no development on them either.

What you can do:

Stylesheets

You cannot assign properties to a column or an item and you cannot access the columns by index.

But you can use some of the pseudo-states selectors like :first, :middle and :last:

QTreeView::indicator:first:checked{
    background: red;
}
QTreeView::indicator:middle:checked{
    background: blue;
}
QTreeView::indicator:unchecked{
    background: lightgray;
}

I used colors instead of images for the sake of simplicity. Note however, that these pseudo-states are actual currently visible states, so if the user is allowed to reorder columns, the style of the column might change. For example if the user drags one of the :middlecolumns and drops it at the end, the box will not be blue anymore.

DecorationRole

You can fake it using Qt::DecorationRole.

To do so, you have to receive the mousePressEvent either by subclassing QTreeView or by installing an event filter. You can then change the icon (via Qt::DecorationRole + emit dataChanged()) when the user clicks in the area of the icon.

This does not work with the keyboard of course.

Custom ItemDelegate

Subclass QStyledItemDelegate and override paint(), just as you suggested.

Custom Style

If you are creating a heavily styled application, you probably have to create a custom Style sooner or later. Stylesheets just don't support some features.

To do so, subclass QProxyStyle, override drawPrimitive and handle the drawing if QStyle::PE_IndicatorViewItemCheck is passed. You will also receive a QStyleOptionViewItem of which has some useful properties like checkState, features (contains QStyleOptionViewItem::HasCheckIndicator if there is a checkbox), and of course index so you can determine what kind of checkbox you want to draw.

Edit: Appendix

Example Using a Custom QStyledItemDelegate

void MyItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
    QStyledItemDelegate::paint(painter, option, index);
    QStyleOptionViewItem opt = option;
    initStyleOption(&opt, index);

    const QWidget *widget = option.widget;
    QStyle *style = widget ? widget->style() : QApplication::style();

    QRect checkRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt, widget);
    drawCheckBox(painter, checkRect, opt.checkState, index);
}

void MyItemDelegate::drawCheckBox(QPainter * painter, const QRect & checkRect, Qt::CheckState checkState, const QModelIndex & index) const
{
    if (checkState == Qt::Checked)
    {
        switch (index.column())
        {
            case 0:
                painter->fillRect(checkRect, Qt::red);
                break;
            default:
                painter->fillRect(checkRect, Qt::blue);
        }
    }
    else
    {
        painter->fillRect(checkRect, Qt::lightGray);
    }
}

This example is quick and easy. Simply paint over the checkbox drawn by QStyledItemDelegate. Requires you to fill the whole box however, otherwise the original will be visible.

You can try to use QStyledItemDelegate to draw anything but the checkbox, and draw the checkbox afterwards, but that is a little harder and will leave you with some minor drawing artifacts if you don't want to spend too much time on it.

Example Using a Custom QProxyStyle

void MyStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption * opt, QPainter * p, const QWidget * w) const
{
    if (pe == QStyle::PE_IndicatorViewItemCheck)
    {
        const QStyleOptionViewItem * o = static_cast<const QStyleOptionViewItem *>(opt);
        drawCheckBox(p, opt->rect, o->checkState, o->index);
        return;
    }
    QProxyStyle::drawPrimitive(pe, opt, p, w);
}

The drawCheckBox() function is the same as in the first example. As you can see, this way is much simpler, cleaner and has none of the drawbacks. You can apply the style globally, or only for a single widget.

like image 167
SteakOverflow Avatar answered Nov 09 '22 17:11

SteakOverflow