Solved QObject::setProperty() signalling solution?
-
As I would with HTML/CSS/JavaScript, in my Qt widgets app I alter their stylesheet's
class
selector 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
QDynamicPropertyChangeEvent
to 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 baseQWidget
they 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 connectwidgetPropertyChanged
to that signal
-
@VRonin
Hmmm, food for thought (investigation), thanks!Those
polish
words, 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
class
property, because I do that a lot.class
is 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 propertyclass
as I want to swap between various ones.I don't know how this relates to your question "Are you defining the properties yourself?"?
-
@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_PROPERTY
macros, 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
QDynamicPropertyChangeEvent
to 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 theirclass
dynamic 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
QDynamicPropertyChangeEvent
filter on every widget to dopolish
. -
In my derived classes for each widget, offer a method which sets a property like
class
and 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
PolishedWidget
rather 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 fromPolishedWidget
but 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
MyLineEdit
just 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);
-
@VRonin
For #1: Yeah, now the question is I wonder how/whether I'm supposed to dotemplate
class 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
setEventFilter
after 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 everyQLineEdit
orQPushButon
is already aJLineEdit
orJPushButon
[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 -
@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
-
@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.... -
It is style dependant so there's no unique answer. On windows it looks to be relatively expensive if your widget is not visible
-
@JonB it should be at least less expensive than setStyleSheet