QObject::setProperty() signalling solution?
-
As I would with HTML/CSS/JavaScript, in my Qt widgets app I alter their stylesheet's
classselector dynamically to switch certain visual behaviour on & off as required. I wish to use stylesheets as the precise visuals can be tailored by the user.At present code looks like:
class MyWidget(QWidget): def changeSomethingAboutAWidget(self) self.setProperty("class", "backgroundColor8") # changed dynamic property value => force stylesheet recomputation widgetPropertyChanged(self) def widgetPropertyChanged(widget: QWidget): # Whenever a widget property is changed # (widget.setProperty() or something like QLineEdit.setReadOnly()) called to alter a property after initialisation) # we have to "alter" widget's stylesheet to cause Qt to refresh for the property change ss = widget.styleSheet() widget.setStyleSheet("/* */" + (ss if ss is not None else "")) widget.setStyleSheet(ss)I'm looking to neaten up on that need to call
widgetPropertyChanged()every time I change the class.I note from http://doc.qt.io/qt-5/qobject.html#setProperty :
Changing the value of a dynamic property causes a
QDynamicPropertyChangeEventto be sent to the object.So looking for advice before I go change code everywhere:
- I could make it so when I construct all my widgets they connect this signal to a slot;
- or I could add an explicit
alterClass()method to them.
They both seem to require sub-classing each
QWidget-type I use, but that's a generic problem with the way inheritance works (I'd like to alter the baseQWidgetthey all ultimately inherit from, instead I have to do each one separately & explicitly). Fortunately I have just completed making my app have minimal-class-derives for more or less every widget it uses, so it is doable.Does the property-change-event-signal approach sound the best?
-
- The fastest way to force an update of the stylesheet is
widget.style().unpolish(widget) widget.style().polish(widget)Source: https://wiki.qt.io/Dynamic_Properties_and_Stylesheets
- Are you defining the properties yourself? if so, you can just emit a signal when the property changes (usually marked as
NOTIFY) and connectwidgetPropertyChangedto that signal
-
- The fastest way to force an update of the stylesheet is
widget.style().unpolish(widget) widget.style().polish(widget)Source: https://wiki.qt.io/Dynamic_Properties_and_Stylesheets
- Are you defining the properties yourself? if so, you can just emit a signal when the property changes (usually marked as
NOTIFY) and connectwidgetPropertyChangedto that signal
@VRonin
Hmmm, food for thought (investigation), thanks!Those
polishwords, are they to do with Poland?From your doc link:
myLineEdit->setProperty("urgent", true);
myLineEdit->style()->unpolish(myLineEdit);
myLineEdit->style()->polish(myLineEdit);Note that this must be done in the widget to which the style was applied. QStyle::polish accepts either a QWidget or a QApplication as a parameter.
So I presume I must unpolish and then polish, right? But I do not understand the "Note" sentence at all: "this must be done in the widget to which the style was applied", pardon?
Are you defining the properties yourself?
Umm, at present I am particularly interested in all my calls to change the
classproperty, because I do that a lot.classis a CSS/QSS property. My application-wide, common stylesheet will have rules like:.backgroundColor8 { background-color: rgba(191, 216, 193, 1); } .backgroundColor9 { background-color: rgba(243, 227, 235, 1); }Note these are
.rules. I can just change propertyclassas I want to swap between various ones.I don't know how this relates to your question "Are you defining the properties yourself?"?
-
- The fastest way to force an update of the stylesheet is
widget.style().unpolish(widget) widget.style().polish(widget)Source: https://wiki.qt.io/Dynamic_Properties_and_Stylesheets
- Are you defining the properties yourself? if so, you can just emit a signal when the property changes (usually marked as
NOTIFY) and connectwidgetPropertyChangedto that signal
@VRonin
OOI, for your Poland suggestion I find http://dgovil.com/blog/2017/02/24/qt_stylesheets/ :This is very easy and there are two ways you can do this:
# Option 1 myWidget.setStyle(myWidget.style()) #OR # Option 2 myWidget.style().unpolish(myWidget) myWidget.style().polish(myWidget) myWidget.update()Personally I prefer the first one since it’s a one liner, but just presenting both options here for you in case you want more control over the process.
I like the one-liner :)
As for your:
Are you defining the properties yourself?
Not sure, but for that and the link about "NOTIFY notifySignal": in Python/PyQt, we don't have things like
Q_PROPERTYmacros, we just useQWidget.setProperty(name, value). I don't think there's any difference whether the property name is pre-defined or new... -
@VRonin
OOI, for your Poland suggestion I find http://dgovil.com/blog/2017/02/24/qt_stylesheets/ :This is very easy and there are two ways you can do this:
# Option 1 myWidget.setStyle(myWidget.style()) #OR # Option 2 myWidget.style().unpolish(myWidget) myWidget.style().polish(myWidget) myWidget.update()Personally I prefer the first one since it’s a one liner, but just presenting both options here for you in case you want more control over the process.
I like the one-liner :)
As for your:
Are you defining the properties yourself?
Not sure, but for that and the link about "NOTIFY notifySignal": in Python/PyQt, we don't have things like
Q_PROPERTYmacros, we just useQWidget.setProperty(name, value). I don't think there's any difference whether the property name is pre-defined or new...@JonB said in QObject::setProperty() signalling solution?:
we just use QWidget.setProperty(name, value)
So you are setting a dynamic property. In that case you can just filter for
QDynamicPropertyChangeEventto detect changes -
@JonB said in QObject::setProperty() signalling solution?:
we just use QWidget.setProperty(name, value)
So you are setting a dynamic property. In that case you can just filter for
QDynamicPropertyChangeEventto detect changes@VRonin
I should have made it explicit that these are dynamic properties, that's all I use.Yes, I started by saying I have come across
QDynamicPropertyChangeEvent.I would like all my various widgets to automatically do the
polish/setStyle() whenever theirclassdynamic property is changed by my code, so that I don't have to remember to call it explicitly every time. It's a design-approach-advice question. I can see two possibilities:-
Set up
QDynamicPropertyChangeEventfilter on every widget to dopolish. -
In my derived classes for each widget, offer a method which sets a property like
classand then doespolish.
Any advantages/comments?
-
-
I would subclass
QWidget(sorry for C++):class PolishedWidget : public QWidget { Q_OBJECT Q_DISABLE_COPY(PolishedWidget) public: explicit PolishedWidget(QWidget* parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : QWidget(parent, f) {} protected: bool event(QEvent* event) Q_DECL_OVERRIDE { if (event->type() == QEvent::DynamicPropertyChange) { style()->unpolish(this); style()->polish(this); } return QWidget::event(event); } };Now all you need to do is change your other widgets to inherit from
PolishedWidgetrather thanQWidget -
I would subclass
QWidget(sorry for C++):class PolishedWidget : public QWidget { Q_OBJECT Q_DISABLE_COPY(PolishedWidget) public: explicit PolishedWidget(QWidget* parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) : QWidget(parent, f) {} protected: bool event(QEvent* event) Q_DECL_OVERRIDE { if (event->type() == QEvent::DynamicPropertyChange) { style()->unpolish(this); style()->polish(this); } return QWidget::event(event); } };Now all you need to do is change your other widgets to inherit from
PolishedWidgetrather thanQWidget@VRonin
No, of course I would do this if I could!! :)I'm not defining my own widgets derived from
QWidget. I'm using all the various Qt widgets,QLineEdit,QPushButton, etc., etc. They all of course already derive fromQWidget, I'd like them to have derived fromPolishedWidgetbut they don't!I'm saying I have gone through code and made it so I always create my own wrapper classes, one for each Qt widget class I use (like
MyLineEditjust deriving fromQLineEdit). So we have the luxury of a derived class for each widget class to put our code in. So we can do either:-
The way above, filtering the event, in each derived class's definition. Outside world will call
myLine.setProperty("class", ...). -
In each derived class's definition, supply a method, like
setProperty()orsetPropertyClass()or whatever, which includes callingpolish()code itself (no event filtering). Outside world will callmyLine.setPropertyClass(...).
Do we prefer one approach over the other?
-
-
Do we prefer one approach over the other?
I do prefer the first option but I wouldn't subclass every single Qt Class.
In C++ You have 2 options (I'm 100% sure the second one is available to python too):
template <class T> class PolishedWidget : public T{ public: explicit PolishedWidget(QWidget* parent = Q_NULLPTR) : T(parent, f) {} protected: bool event(QEvent* event) Q_DECL_OVERRIDE { if (event->type() == QEvent::DynamicPropertyChange) { style()->unpolish(this); style()->polish(this); } return T::event(event); } };Now you can create
PolishedWidget<QLineEdit> myLine;andPolishedWidget<QPushButton> myButton;
Install an event filter:
class Polisher : public QObject { Q_OBJECT Q_DISABLE_COPY(Polisher) public: Polisher(QObject* parent = Q_NULLPTR) : QObject(parent) { } protected: bool eventFilter(QObject* obj, QEvent* event) Q_DECL_OVERRIDE { if (event->type() == QEvent::DynamicPropertyChange) { QWidget* objWidget = qobject_cast<QWidget*>(obj); if (objWidget) { objWidget->style()->unpolish(objWidget); objWidget->style()->polish(objWidget); } } return QObject::eventFilter(obj, event); } };Now you can use:
Polisher* polisher = new Polisher(parent); QLineEdit* myLine =new QLineEdit(parent); QPushButton* myButton=new QPushButton(parent); myLine ->setEventFilter(polisher); myButton->setEventFilter(polisher); -
Do we prefer one approach over the other?
I do prefer the first option but I wouldn't subclass every single Qt Class.
In C++ You have 2 options (I'm 100% sure the second one is available to python too):
template <class T> class PolishedWidget : public T{ public: explicit PolishedWidget(QWidget* parent = Q_NULLPTR) : T(parent, f) {} protected: bool event(QEvent* event) Q_DECL_OVERRIDE { if (event->type() == QEvent::DynamicPropertyChange) { style()->unpolish(this); style()->polish(this); } return T::event(event); } };Now you can create
PolishedWidget<QLineEdit> myLine;andPolishedWidget<QPushButton> myButton;
Install an event filter:
class Polisher : public QObject { Q_OBJECT Q_DISABLE_COPY(Polisher) public: Polisher(QObject* parent = Q_NULLPTR) : QObject(parent) { } protected: bool eventFilter(QObject* obj, QEvent* event) Q_DECL_OVERRIDE { if (event->type() == QEvent::DynamicPropertyChange) { QWidget* objWidget = qobject_cast<QWidget*>(obj); if (objWidget) { objWidget->style()->unpolish(objWidget); objWidget->style()->polish(objWidget); } } return QObject::eventFilter(obj, event); } };Now you can use:
Polisher* polisher = new Polisher(parent); QLineEdit* myLine =new QLineEdit(parent); QPushButton* myButton=new QPushButton(parent); myLine ->setEventFilter(polisher); myButton->setEventFilter(polisher);@VRonin
For #1: Yeah, now the question is I wonder how/whether I'm supposed to dotemplateclass in Python/PyQt... !However, the point is you prefer event filter approach over explicit "set-property-with-call-to-polish" method. I'll go down that route.
For #2: I don't want to have to insert a line of a code to
setEventFilterafter each of thousands of lines of existing code, wherever any widget is created. Not to remember to do it in the future. Since I am now in a position where everyQLineEditorQPushButonis already aJLineEditorJPushButon[code only uses, say, 10 Qt widget types, so that's the sub-classing I've done], I can move the polishing code into its event filter and not insert loads of lines across loads of files. -
@VRonin
For #1: Yeah, now the question is I wonder how/whether I'm supposed to dotemplateclass in Python/PyQt... !However, the point is you prefer event filter approach over explicit "set-property-with-call-to-polish" method. I'll go down that route.
For #2: I don't want to have to insert a line of a code to
setEventFilterafter each of thousands of lines of existing code, wherever any widget is created. Not to remember to do it in the future. Since I am now in a position where everyQLineEditorQPushButonis already aJLineEditorJPushButon[code only uses, say, 10 Qt widget types, so that's the sub-classing I've done], I can move the polishing code into its event filter and not insert loads of lines across loads of files.@JonB said in QObject::setProperty() signalling solution?:
now the question is I wonder how/whether I'm supposed to do template class in Python/PyQt
How: https://docs.python.org/3.6/library/typing.html#user-defined-generic-types
Whether: give it a try, and see if it works -
@JonB said in QObject::setProperty() signalling solution?:
now the question is I wonder how/whether I'm supposed to do template class in Python/PyQt
How: https://docs.python.org/3.6/library/typing.html#user-defined-generic-types
Whether: give it a try, and see if it works@VRonin
I have made the change (in my own way!) so that the widgets I use have an event filter on dynamic property changes to cause required polishing. I examineQDynamicPropertyChangeEvent::propertyName()and do so only if it's one of mine (likeclass). There are other cases too (e.g. I make setting/clearing read-only affect line edit's background color).How expensive do you think
polish/unpolish()(or for that matter re-assigningsetStyleSheet()if I chose to do it that way) are?The approach we have chosen re-polishes every time I set some "property". However, somewhere in the docs it states we only need to do this when we change a property on something like an already-displayed widget.
Thus, if I set properties immediately after widget construction --- as I may well do --- I will do re-polishes for each one, even though none of them are necessary. OTOH, if I want to change the properties at a later date after widget has been shown, I would indeed need the re-polishing, but only then.
I haven't found all the circumstances, but I'm wondering whether my event filter could test something like
QWidget::isVisible()before doing the re-polish, so that it does not do it when unnecessarily early in the widget's life-cycle. Any opinion/comment? -
@VRonin
I have made the change (in my own way!) so that the widgets I use have an event filter on dynamic property changes to cause required polishing. I examineQDynamicPropertyChangeEvent::propertyName()and do so only if it's one of mine (likeclass). There are other cases too (e.g. I make setting/clearing read-only affect line edit's background color).How expensive do you think
polish/unpolish()(or for that matter re-assigningsetStyleSheet()if I chose to do it that way) are?The approach we have chosen re-polishes every time I set some "property". However, somewhere in the docs it states we only need to do this when we change a property on something like an already-displayed widget.
Thus, if I set properties immediately after widget construction --- as I may well do --- I will do re-polishes for each one, even though none of them are necessary. OTOH, if I want to change the properties at a later date after widget has been shown, I would indeed need the re-polishing, but only then.
I haven't found all the circumstances, but I'm wondering whether my event filter could test something like
QWidget::isVisible()before doing the re-polish, so that it does not do it when unnecessarily early in the widget's life-cycle. Any opinion/comment? -
@JonB said in QObject::setProperty() signalling solution?:
could test something like QWidget::isVisible() before doing the re-polish
You certainly could
-
It is style dependant so there's no unique answer. On windows it looks to be relatively expensive if your widget is not visible
-
@VRonin
Do you happen to know the "cost" of unpolish/polish? If it is "cheap", or perhaps "does nothing" on an unshown widget anyway, I won't worry my little head....