Solved QTreeWidget: how to implement custom properties for stylesheet?
-
Hi.
I have aQTreeWidget
with a list of speech files. When a file is selected, the style for theselect
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 theQTreeWidget
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 thesetStyleSheet()
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
orrecording
, 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 asetProperty()
method in theQTreeWidgetItem
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.
-
Hi
Yes you can use properties
https://wiki.qt.io/Dynamic_Properties_and_StylesheetsBut 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 :)
-
@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 propertyselected
as follows:"QTreeWidget::item:selected:[rowstate=\"select\"] {....} QTreeWidget::item:selected:[rowstate=\"play\"] {....} ..."
but that doesn't work. Maybe because the
QWidgetItem
class has nosetProperty()
method.Is it possible to apply the property on the selected item only?
-
@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?
-
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! -
@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.