Issue
I have two widgets
first, a QLineEdit
that has a stylesheet (assume not accessible or changeable) that sets the color of the text to some color (e.g., red).
second, QLabel
that has a default text color (e.g., black)
How can I get the color of the text in the QLineEdit
, and set it as the color in the QLabel
l1 = QLineEdit()
color_to_be_set= l1.palette().highlight().color().name() # this approach is giving the wrong color
q1 = QLabel()
q1.setText("the text")
# then set the new color
"Trial#1 (Not working at all, not changing the color)"
palette = q1.palette()
palette.setColor(QPalette.WindowText,QColor(color_to_be_set))
q1.setPalette.....
"Trial#2, gives the wrong color"
q1.setStyleSheet("color:{}".format(color_to_be_set))
Solution
As already explained in comments, there's no public API to access properties set by stylesheets, and I sincerely doubt there could be any (at least, with the current implementation, including Qt6).
That said, there is some level of access to those properties, but it only works for the following:
background
andbackground-color
(which are the same);color
;selection-background-color
;selection-color
;alternate-background-color
;
Whenever any of the properties above is set, the palette of the widget is altered with the following palette color roles:
QSS property | QPalette role(s) |
---|---|
background , background-color |
Base , Button , Window , backgroundRole() * |
color |
Text , ButtonText , WindowText , foregroundRole() * |
selection-background-color |
Highlight |
selection-color |
HighlightedText |
alternate-background-color |
AlternateBase |
The * values are references to the widget class definitions (for instance, QPushButton normally uses Button
as foreground role). They may be overridden by their respective setter functions.
Also, those properties only support the QSS Brush
types: plain colors, gradients or palette(<role>)
. No pixmaps here!
Remember that in order to get proper access to those properties, the widget must be polished, which means that, if the widget has not been ever shown yet, calling ensurePolished()
is mandatory:
Ensures that the widget and its children have been polished by QStyle (i.e., have a proper font and palette).
This means that any new Qt widget created without any already polished parent will use the system palette.
Consider the following:
from PyQt5.QtWidgets import *
app = QApplication([])
app.setStyleSheet('* {color: green;}')
source = QWidget()
# source.ensurePolished() # leave this commented for now
color = source.palette().windowText().color()
target = QWidget(styleSheet='background: {}'.format(color.name()))
target.show()
app.exec()
Then uncomment the fifth line, and see the result.
Now, there are a few catches.
When a stylesheet is set, any widget affected by it gets it's style()
overridden by a private QStyleSheetStyle. That style is not exposed by the API and completely takes control over the style set for each affected widget.
But not always.
Take this snippet:
from PyQt5.QtWidgets import *
app = QApplication([])
app.setStyleSheet('* {background-color: black;}')
topLevel = QTextEdit()
topLevel.show()
parent = QWidget()
layout = QVBoxLayout(parent)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(QTextEdit())
parent.show()
app.exec()
With my current style (oxygen), the two backgrounds are slightly different:
On the left there's the topLevel
widget, on the right the parent
. As you can see, the underlying style still has some level of control.
This is an important aspect that has to be kept in mind, and trying to override it with a QProxyStyle won't be enough: the QStyleSheetStyle will "take control" and completely ignore the overridden functions, except for a very few like drawItemText()
. Any other function will be called on "the C++ side" implementation, for which there's no useful override in the python bindings.
Also, whenever the underlying style is still used internally by the QSS, the result is that the color properties will be used just like any QPalette: they are reference colors, not the actual ones. Any widget will use the style implementation, which might use different color roles, or completely alter the color depending on the situation.
Whenever the default style is used (even through QStyleSheetStyle modifications), the palette is just a reference, not unlike a physical palette: give the same basic color palette to two styled painters, and they will use its colors in different ways. The actual color shown to the user might be slightly or even completely different and possibly ignored.
Concluding, the following aspects are of utmost importance:
- just created widgets will use the default palette;
- there is no access to border properties, nor margins and paddings (with the last two also depending on the inherited style);
- top level widgets might behave differently;
- the style might not completely adhere to the colors set (and even ignore them);
- palette colors are references: the style might use a role for an unexpected purpose, or completely ignore it; some common styles use the
Button
role for item view headers, other use theWindow
role; - while normally respected, the above color properties might be altered by the style, just like any palette;
- basic QSS color properties can be properly accessed only after the widget has been polished:
ensurePolished()
has to be called, which normally happens after the first visibility change and anysetStyleSheet()
call that affects the widget (seeQStyle.unpolish()
andQStyle.polish()
); - QPalette uses QBrush for any of its roles and groups, which could be any of the following: a QColor, a QGradient or a QPixmap; this means that if a palette role uses a gradient or pixmap, the returned
color()
will be invalid, etc.; background-image
does not update the palette (in fact, it's not listed in the table above); the same goes forborder-image
(as said above, there is no access to border properties);- the
fusion
style is (usually) the most reliable style to be used for extended stylesheet implementation; when unsure or getting unexpected behavior, useQApplication.setStyle('fusion')
orwidget.setStyle(QStyleFactory.create('fusion'))
; - OS styles potentially override drawing with private pixmaps in order to respect the standard OS appearance;
- complex widgets require full and explicit values for their properties; never set generic properties (aka, no selectors) for parent widgets or the application[1], including QComboBox, QScrollBar and all QAbstractScrollArea subclasses (including item views);
So, considering all this, as long as a standard QStyle is used and the widget is polished, the roles explained in the above table should return the colors set in the supported properties of any stylesheet, including inherited ones.
But you can never be sure about that, and you always have to be aware of it.
[1] in the code examples above I did use generic stylesheets, but that was on purpose and for simplicity; that practice is usually highly discouraged for normal usage. Always use selectors when setting stylesheets for parents and complex widgets (see above).
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.