Qt Style Sheets cascading classes selectors



  • In HTML/CSS, I assign an HTML element a class explicitly via class="myClass" (JavaScript className attribute). And in all modern browsers I can set it to a (space-separated) list of classes. This means I can write stuff like:

    .colorRed: { color: red; }
    .colorGreen: { color: green; }
    .fontBold: { font-weight: bold; }
    ...
    
    <span class="colorRed fontBold ...">Label</span>
    <input class="colorGreen fontBold ..." type="text" />
    ...
    

    I can thus build up individual style attributes and apply them selectively to elements, entirely as I please.

    As I understand it, in QCSS however there is no way to associate arbitrary class names with a widget. Instead, I must use the actual code C++/Python classes to achieve this cascading, like:

    .QLabel { font-weight: bold; }
    .RedLabel { color: red; }
    .GreenLabel { color: green; }
    .QTextEdit { font-weight: bold; }
    .RedTextEdit { color: red; }
    .GreenTextEdit { color: green; }
    ...
    
    class RedLabel(QLabel): ...
    class GreenLabel(QLabel): ...
    class RedTextEdit(QTextEdit): ...
    class GreenTextEdit(QTextEdit): ...
    ...
    

    This is, of course, only example. I don't want an answer to this specific example. [Please extrapolate to what is necessary as the number of class-attributes grows.]

    There are all sorts of individual class-attributes I want to define to apply to all sorts of elements (widgets), building them up bit-by-bit in a consistent fashion. Having to define actually code classes to achieve this is a royal pain, requires code changing, difficult to maintain, etc.

    Am I right that this is the only way to do this in QCSS? There is no way to assign arbitrary CSS class names to an individual widgets quite independent of the actual code class hierarchy?



  • @JNBarchan hi, actually you can asign specific class names. For example:

    QPushButton#btnBack{border-image:url(:/data/NavIcons/btnPrev.png);}
    

    sets the stylesheet to all QPushButtons with the objectname btnBack

    you can also combine that with other default states
    or custom states(Q_Properties) as in this example:

    QPushButton#btnUnits[state = true]:pressed{border-image:url(:/data/NavIcons/Menu_Imp.png);}
    QPushButton#btnUnits:pressed{border-image:url(:/data/NavIcons/Menu_Si.png);}
    

    taken from my current project.



  • @J.Hilk

    [Forget about "state selectors, like :pressed, they are not at issue.]

    Thanks, but I'm afraid that does not solve the problem. #id is available in HTML/CSS. But it does not allow for multiple classes (let alone that it relies on widget having a particular object name"), which is what I need.

    In HTML/CSS I can have multiple elements on same or multiple pages like:

    <span class="green bold tall sunken"></span>
    <span class="red bold tall outset"></span>
    <span class="bold short outset"></span>
    ...
    

    So I can build up each CSS class definition (of what bold, short, etc. are) separately and combine them at will. This is what we do in HTML/CSS, and I am stumped as to how to achieve it in Qt across "50 pages with 20 widgets each".

    I would need Qt to offer some equivalent of:

    label1 = QLabel()
    label1.classes = "green bold tall sunken"
    label2 = QLabel()
    label2.classes = "red bold tall outset"
    

    Do you follow why? And if Qt does not offer this, it will be almost impossible to achieve a maintainable set of QCSS definitions, which would be an awful shame.... :(



  • @JNBarchan mmh, I'm afraid, I'm not that involved/knowledgable with StyleSheets to give you a defenite Yes or No answer on this one.
    Maybe someone else can give you a better answer.

    Would comes to my mind right now, is, you could create multiple QSS-files that you add to your ressource file and load a specific one from inside your classes.



  • @J.Hilk
    Thanks for your attempt. I need a Qt stylesheet expert then... :)



  • @JNBarchan I use CSS heavily in my QWidget application and unfortunately I've also not come across a way to achieve this.

    What I've done in my application is to use a "dynamic property" called class and against this, I specify a CSS class which then links through to my stylesheet.

    For example, in the designer I have a QPushButton with a class of red, my stylesheet then contains:

    QPushButton[class="red"] {
        background-color: red;
    }
    

    I know it isn't what you are looking for but given that QWidgets do not explicitly allow CSS classes to be bound to them (or chained), I think your best bet is to look for other options.



  • Hold the phone!!

    Ignore my answer.

    Assign a dynamic property called "class" and in this, specify your class names (can be many), for example "red blue". Now, in your stylesheet if you have:

    .red {
        color: red;
    }
    
    .blue {
        background-color: blue;
    }
    

    You will get a QPushButton with red text and blue background! It works



  • @webzoid
    Oooohhh, I have held the phone, and this looks perfect!

    I am new to Qt, and I'm afraid I use Python/PyQt (and no Qt Creator). How (C++ will do) do I do:

    Assign a dynamic property called "class"

    ?



  • @webzoid wait wait, could you elaborate that example a bit for all us none StyleSheet Experts x)



  • @JNBarchan I believe the QObject::setProperty function is how you would assign a dynamic property in c++. See here

    http://doc.qt.io/qt-5/qobject.html#setProperty

    @J-Hilk I will post my worked example for download shortly...



  • Here's a QWidgets example:

    https://drive.google.com/open?id=1XbOJgF6DNqsmq_JR5c9gfWiHv3poHPWV

    The "style.css" file needs to live alongside the executable in order for this to work. Or change the code...



  • @webzoid said in Qt Style Sheets cascading classes selectors:

    @JNBarchan I believe the QObject::setProperty function is how you would assign a dynamic property in c++. See here

    http://doc.qt.io/qt-5/qobject.html#setProperty

    Hopefully, you are a hero(!), though not time to try it right now.

    From the docs I note:

    If the property is defined in the class using Q_PROPERTY then true is returned on success and false otherwise. If the property is not defined using Q_PROPERTY, and therefore not listed in the meta-object, it is added as a dynamic property and false is returned.

    Being Python/PyQt, I don't know how (or even if it's possible) to do Q_PROPERTY. Assuming I can't, does your class property principle work OK if that is absent?



  • @JNBarchan Unfortunately I'm coming from the Windows QWidget application side of things so I really can't comment on what Python/PyQt will do in this instance.

    I have just done a test whereby I call the setProperty function from C++ as follows:

    ui->pushButton->setProperty("class", "red thick-border");
    

    and the result is exactly the same as if I'd done it in the designer. Hopefully PyQt works in the same way.



  • @JNBarchan Q_Property is part of QObject that is the essential part of what makes qt qt, so I would be seriously surpised if its not possible.

    @webzoid thank you very much! That will help a lot of people for a long time ;-)



  • @J.Hilk It's definitely helped me already!



  • @webzoid

    @JNBarchan Unfortunately I'm coming from the Windows QWidget application side of things so I really can't comment on what Python/PyQt will do in this instance.

    No, we're on the same page there. I inherit from QWidget OK just like you do. But (I believe) you can do something like:

    class MyWidget : QWidget
    {
        Q_PROPERTY QString cssClass;  // declare cssClass member as a known property in MyWidget
        ...
        this.cssClass = "red blue";
    }
    

    I can't (don't know how to/if I can) use that Q_PROPERTY macro you have, so just:

    class MyWidget(QWidget)
    {
        ...
        self.cssClass = "red blue";
    }
    

    But I think you are not using Q_PROPERTY anywhere yourself, you just go ui->pushButton->setProperty("class", "red thick-border");, so your class is:

    therefore not listed in the meta-object, it is added as a dynamic property

    Is that correct for your C++ situation? Which is all I can do, and looks same as what you are doing to me, so I should be OK?



  • @J.Hilk said in Qt Style Sheets cascading classes selectors:

    @JNBarchan Q_Property is part of QObject that is the essential part of what makes qt qt, so I would be seriously surpised if its not possible.

    ? a. I think it's a macro, so what is its expansion anyway? and b. in Python we do not even declare any member variables in a class (not my fault, I didn't choose Python), so ... ?



  • @JNBarchan said in Qt Style Sheets cascading classes selectors:

    @J.Hilk said in Qt Style Sheets cascading classes selectors:

    @JNBarchan Q_Property is part of QObject that is the essential part of what makes qt qt, so I would be seriously surpised if its not possible.

    ? a. I think it's a macro, so what is its expansion anyway? and b. in Python we do not even declare any member variables in a class (not my fault, I didn't choose Python), so ... ?

    Well, yes, technically its a Macro (I think), I found this webside Support for Qt Properties for PyQt5

    seems like a good place to start, I guess.


  • Moderators

    @J.Hilk
    actually this is already stated in the docs.
    Altough i am surprised that it works when you assign a string with space-separated values instead of a QVariant containing a QStringList.



  • @raven-worx
    Thanks, I needed that link. I had not come across it, its discussion of setProperty(), dynamic properties, multiple properties, use of ~=, etc. Obscure (for me)!

    I see it says:

    In addition, the special class property is supported, for the name of the class.

    This might explain why my attempts so far to set & match my own property named class, as per @webzoid's suggestion, has not been working...!



  • @raven-worx
    I'm really struggling here, because what should be working just isn't. I'm not even as far as multiple property/space-separated strings, just one item:

    1. I create a QLabel.

    2. Stylesheet: (global, nothing else in it) QLabel { color: red; }

    3. Change to: QLabel[cssClass="red"] { color: red; }, or to QLabel[cssClass~="red"] { color: red; }

    4. Change code to: label.setProperty("cssClass", "red")

    After #2 it's red. After #4 it ceases to be red :(

    I know it's Python/PyQt --- and I can't test C++ or QML --- but I really think the setProperty() will be a straight, pass-through call.

    Would someone be prepared to just try as simple as above?


  • Moderators

    @JNBarchan
    i never used PyQt in my life, so i have absolutely no experience with it.

    Do you mean you change the stylesheet at runtime? Or do you just want to specify the stylesheet once from application startup and never change it again?

    In c++ i would do this:

    QLabel* label = new QLabel;
        label->setProperty("cssClass", QVariant::fromValue<QStringList>( QStringList() << "red" ) );
    

    and in the stylesheet:

    QLabel[cssClass~="red"] { color: red; }
    

    I don't what the equivalent of a QStringList in PyQt.



  • @raven-worx

    No, no need to change at runtime, all from startup.

    I assume what you have written actually does work for you?

    In Python (PyQt), we can't have QString, or QStringList, or QVariant :( (Don't ask, that's how it is...) PyQt's declaration of setProperty() is that it accepts Any, which means you pass any type from the language....

    Please, keeping it simple, no lists, would yours still work if you wrote:

    label->setProperty("cssClass", "red" );

    Or, if that does not work, what about:

    label->setProperty("cssClass", QVariant::fromValue<QString>( "red" ) );

    (And we could stick to = not ~= in the stylesheet. I'm trying to do everything with just one item, no lists, to get anything working here...)


  • Moderators

    @JNBarchan said in Qt Style Sheets cascading classes selectors:

    In Python (PyQt), we can't have QString, or QStringList, or QVariant :( (Don't ask, that's how it is...)

    This questions everything else then, if it is even possible at all.
    I have no idea then what exactly is assigned to the property in the background.

    Do you set the property before the widget is shown? Best would be to set the property right after instantiation.



  • @raven-worx
    Please don't worry about that, I will sort out the PyQt side. I have just posted above what I need to know from you for C++, please. You are being so helpful I don't want to lose you...!

    P.S.
    Yes, I set property after instantiation, before it gets shown.


  • Moderators

    @JNBarchan
    for what you've posted so far the following should work (single strings in the property):

    Stylesheet:

    QLabel[cssClass="red"] { color: red; }
    

    Code:

    label.setProperty("cssClass", "red")
    


  • @raven-worx
    I couldn't agree more that it should work, but that's what I claim to have in PyQt, and it does not :(

    I just put in, and checked in debugger:

    result = label.setProperty("cssclass", "red")
    

    and result is getting set to false. This corresponds correctly to:

    If the property is not defined using Q_PROPERTY, and therefore not listed in the meta-object, it is added as a dynamic property and false is returned.

    Then added immediately afterward:

    q = self.label.property("cssclass")

    and sure enough that is returning "red".

    So it looks to me that the property is getting set correctly...... It's the selector that doesn't seem to be matching, and I have tried using either = or ~=... And remember that QLabel { color: red; } does match...

    EDIT: And for an in-built property it does work correctly, e.g. QLabel[class="QLabel"] does match and QLabel[class="rubbish"] does not match. But not for a dynamic property I'm adding.

    You wrote:

    the following should work

    Very, very politely, have you actually verified that your code works?

    I confirm that I am finding: everything works fine when I use "inbuilt"/"known" properties (e.g. class, flat, etc.), but nothing works when I use use "dynamic" properties created on the fly via setProperty() (the property seems to get set OK on the QObject, but it's never matched by the selector). Anyone?

    UPDATE: OK, I'm starting from scratch in a minimal app, and it is working via setProperty(). Which is good :) So now, obviously, I'll report back after I've spent a few hundred hours finding out what's any different in my proper app... ;-)

    UPDATE2: I've now sorted out what was different in the app. (I'm sorry --- it's 32K-odd lines, I didn't write it, I don't know my way around all of it. I thought I knew what I was affecting with my test changes, I was affecting the wrong things.)

    The good news it's all working OK now, with setProperty()! Nothing special about Python/PyQt.

    I can even use multiple values for my properties, and the ~= selector matcher, and everything is as I need it to be. So you can build up your desired attributes (color, size, font, whatever) into loads of different rules in the CSS and combine them as you please on any widget :)

    @raven-worx
    You wrote:

    Altough i am surprised that it works when you assign a string with space-separated values instead of a QVariant containing a QStringList.

    Actually, it does seem to work quite happily as a space-separated string like "tall red rounded", treating each "word" as though it had been an element in the QStringList I guess. CSS/HTML/JavaScript works this way too with a space-separated list of class names.

    Thanks so much for your help & patience. And everyone else.


  • Moderators

    @JNBarchan said in Qt Style Sheets cascading classes selectors:

    Actually, it does seem to work quite happily as a space-separated string like "tall red rounded", treating each "word" as though it had been an element in the QStringList I guess. CSS/HTML/JavaScript works this way too with a space-separated list of class names.

    yes CSS does work this way. But Qt stylesheets (QSS) just have taken over the basic syntax of CSS2. Everything behind it is completely unrelated to CSS.

    Glad you figured out your issue.



  • To summarise for anyone reading this. Thanks to the answers above.

    • You can assign a dynamic property, with a name and value of your choice, via QWidget.setProperty("cssClass", "large").

    • The stylesheet can match this via selector *[cssClass="large"] { ... }.

    • You can assign multiple values to the property via QWidget.setProperty("cssClass", [ "bold", "large", "rounded" ]). (Or it accepts a space-separated string via "bold large rounded".)

    • The stylesheet can match one of the values via selector *[cssClass~="large"] { ... } (note the ~=).

    Note that property named class is a special one, referring to the Python/C++ code class of the widget, which you probably should not assign to.


Log in to reply
 

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