QTreeWidget: how to implement custom properties for stylesheet?



  • Hi.
    I have a QTreeWidget with a list of speech files. When a file is selected, the style for the select property is applied, which is a blue background gradient and white text.

    When the file is being played, the gradient changes to green, and when being recorded, the gradient changes to red.

    The way I implemented this was by simply using the setStylesheet() method on the QTreeWidget object, with a different styleSheet string depending on the playing/recording/selected state. However, as I found out, this incurs a performance penalty which translates into a brief but noticeable delay each time the setStyleSheet() method is invoked. I guess all the items in the tree are being updated somehow, which for large trees may cause the perceptible delay?

    My question is now: how can I define custom CSS properties like playing or recording, so that I only need to set the styleSheet once, and then set the desired property only on a single item (or the current + previous selected item) at a time? Is this at all possible? I haven't seen a setProperty() method in the QTreeWidgetItem class, so I wonder how I can do this.

    I found some info about dynamic properties, and some other one roles, but it is not clear to me if I can use them to achieve the desired formatting without the performance penalty of setting the stylesheet on the whole tree.


  • Qt Champions 2016

    Hi
    Yes you can use properties
    https://wiki.qt.io/Dynamic_Properties_and_Stylesheets

    But when we talk about Items in a TreeWidget, it sounds like you be more happy with a
    ItemDelegate to do custom drawing.

    http://doc.qt.io/qt-5/qtwidgets-itemviews-stardelegate-example.html

    Anyway, you can start with my basic test delegate and expand to draw as you want.

    #include <QApplication>
    #include <QPainter>
    #include <QStyledItemDelegate>
    struct Delegate: public QStyledItemDelegate {
      Delegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {}
      void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
        auto o = option;
        initStyleOption(&o, index);
        o.decorationSize.setWidth(o.rect.width());
        auto style =  o.widget ? o.widget->style() : QApplication::style();
        style->drawControl(QStyle::CE_ItemViewItem, &o, painter, o.widget);
        painter->drawRect(o.rect);
      }
    };
    

    and just
    ui->treeWidget->setItemDelegate( new Delegate);

    It just draw the Item as normally and then a rect.
    (yes its ugly :)
    alt text



  • @mrjj
    Thanks! I tried the dynamic property method, because the delegate method is a bit too advanced for my current Qt skills, I'm affraid. I know that eventually, I need to use a model-based implementation, but that's still some time away.

    As for the dynamic property method, I'm not sure I understand how to combine it with the QTreeWidget::item:selected selector.

    If I use a stylesheet string like

    const QString myStyle = "QTreeWidget:[rowstate=\"select\"] {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ADADFF, stop: 0.7 #4444FF, stop: 1.0 #0000FF); color: white;}";
    

    and then

    tree->setStyleSheet(myStyle);
    tree->setProperty("rowstate", "select")
    

    (I'm omitting the unpolish()/polish() part here) then of course the styling is applied to the whole tree.

    What I am trying to achieve is to have the style applied to the selected item, which I am currently doing like this:

    static const QString UM_STYLESHEET_SELECT = "QTreeWidget::item:selected {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ADADFF, stop: 0.7 #4444FF, stop: 1.0 #0000FF); color: white;}";
    static const QString UM_STYLESHEET_PLAY   = "QTreeWidget::item:selected {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #AFE4AA, stop: 0.7 #5DC256, stop: 1.0 #0BA102); color: white;}";
    static const QString UM_STYLESHEET_RECORD = "QTreeWidget::item:selected {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #FFADAD, stop: 0.7 #FF4444, stop: 1.0 #FF0000); color: white;}";
    static const QString UM_STYLESHEET_NOREC  = "QTreeWidget::item:selected {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #FFDD99, stop: 0.7 #FFAA33, stop: 1.0 #FF8400); color: white;}";
    static const QString UM_STYLESHEET_VOID   = "QTreeWidget::item:selected {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ADADAD, stop: 0.7 #939393, stop: 1.0 #545454); color: white;}";
    

    I tried combining my dynamic property rowstate with the built-in property selected as follows:

    "QTreeWidget::item:selected:[rowstate=\"select\"] {....}
     QTreeWidget::item:selected:[rowstate=\"play\"] {....}
    ..."
    

    but that doesn't work. Maybe because the QWidgetItem class has no setProperty() method.

    Is it possible to apply the property on the selected item only?


  • Moderators

    @Diracsbracket said in QTreeWidget: how to implement custom properties for stylesheet?:

    "QTreeWidget::item:selected:[rowstate="select"] {....}
    QTreeWidget::item:selected:[rowstate="play"] {....}
    ..."

    but that doesn't work. Maybe because the QWidgetItem class has no setProperty() method.

    exactly. Dynamic properties can only be applied to QWidgets!
    Qt stylesheets do not support to add custom pseudo-states and pseudo-elements. Thus it won't work what you are trying.
    QTreeWidget::item:selected:[rowstate=\"play\"] is not valid QSS --> QTreeWidget[rowstate=\"play\"]::item:selected: would be though.

    Is it possible to apply the property on the selected item only?

    no, not directly.
    Unless you subclass a custom QStyledItemDelegate and initialize the StyleOption for the painted index according to your widget's property.



  • @raven-worx
    OK then. It seems that I have no other choice then than to use the method outlined by @mrjj.

    In my current implementation which is setting a different stylesheet for the selected state every time a new item is selected, I can reduce the apparent overhead of setting a new stylesheet by first checking if the current stylesheet needs to be replaced or not:

    static inline void setTreeStyleSheet(QTreeWidget* tree, const QString& stylesheet)
    {
        if (tree->styleSheet() != stylesheet)
            tree->setStyleSheet(stylesheet);
    }
    

    Although this in itself is a long string compare, it cuts out most of the perceived delay. Only when a different stylesheet is effectively set, the item selection change is a bit slower, but I can live with that (for now.)

    Can anyone confirm this delay overhead in setting a stylesheet? If so, does this delay grow larger as the tree grows? I would hope that the implementation would only update the items currently in the viewport, so the overhead would be independent of the tree size?


  • Qt Champions 2016

    Hi
    Just as a note. the delegate i shown also works on
    an item based. The sample is a not viewtype.

    ui->treeWidget->setItemDelegate( new Delegate);



  • @mrjj
    Thanks!


  • Qt Champions 2016

    @Diracsbracket
    np :)
    so "all" you still need to do is draw it differently when selected and playing.
    for that you just use painter and the same bitmap as used in stylesheet.
    should not be overly complicated.



  • @raven-worx said in QTreeWidget: how to implement custom properties for stylesheet?:

    QTreeWidget::item:selected:[rowstate="play"] is not valid QSS --> QTreeWidget[rowstate="play"]::item:selected: would be though.

    My problem seems to be solved indeed by using the @raven-worx solution.
    I created a single long stylesheet string with custom properties as follows:

    static const QString UM_STYLESHEET_SCRIPT = "QTreeView::item:selected {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ADADAD, stop: 0.7 #939393, stop: 1.0 #545454); color: white;}"
                                                "QTreeView[scriptState=\"play\"]::item:selected {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #AFE4AA, stop: 0.7 #5DC256, stop: 1.0 #0BA102); color: white;}"
                                                "QTreeView[scriptState=\"select\"]::item:selected {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ADADFF, stop: 0.7 #4444FF, stop: 1.0 #0000FF); color: white;}"
                                                "QTreeView[scriptState=\"record\"]::item:selected {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #FFADAD, stop: 0.7 #FF4444, stop: 1.0 #FF0000); color: white;}"
                                                "QTreeView[scriptState=\"norec\"]::item:selected {background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #FFDD99, stop: 0.7 #FFAA33, stop: 1.0 #FF8400); color: white;}";
    
    

    Then, instead of replacing the stylesheet as I did before upon a state change, I just set the custom property, for example:

    UM_SET_PROPERTY(ui->scriptTree, "scriptState", "play");
    

    where I have defined the convenience macro UM_SET_PROPERTY as follows:

    #define UM_SET_PROPERTY(obj, property, value) (obj)->setProperty(property, value);\
                                                  (obj)->style()->unpolish(obj);\
                                                  (obj)->style()->polish(obj)
    

    Doing it like this seems to eliminate the delay I perceived with the previous method I used (the one where I used to replace the stylesheet)!
    Thanks again.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.