7

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?

IAmInPLS
  • 4,051
  • 4
  • 24
  • 57

1 Answers1

7

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.

SteakOverflow
  • 1,953
  • 13
  • 26
  • Good answer so far. I'm using a delegate currently but I'm struggling to see what to use for the checkbox. I know there is `option.checkstate` available, but have no idea how to use it. Could you add an example to your answer? – IAmInPLS Oct 19 '17 at 15:44
  • I even found a way to do it with a delegate without having to fill the whole checkbox :-). Instead of `QStyledItemDelegate::paint(painter, option, index);`, I used `style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget);`. Works like a charm! Thank you very much. – IAmInPLS Oct 23 '17 at 08:44
  • Hm... That should still draw the checkbox? And if it doesn't, it should not leave the space for the checkbox – SteakOverflow Oct 23 '17 at 08:46
  • From Qt's doc, `The primitive element PE_PanelItemViewItem is responsible for painting the background of items` so I'm guessing it's only painting the background of the item in the tree view, not the checkbox. – IAmInPLS Oct 23 '17 at 09:23
  • Ok, that makes sense. However you will be missing the text and decoration icon then? Might be fine for your needs of course. – SteakOverflow Oct 23 '17 at 09:58