I am trying to write a CSS style sheet for toy PyQt5 application. I have successfully defined a classes MainWindow(QMainWindow) and DefaultWidget(QWidget), with the latter containing two push buttons, which I am trying to stylize.
Using the C++ docs, I've been trying to take advantage of the QPushButton#evilButton example, provided in: Link
However, it appears that the # notation for an instance name does not translate to the analogous self.setStyleSheet() method for an external CSS file in my python application.
Python file (apologies if there are typos - transcribing from another computer):
import sys
from PyQt5.Widgets import QApplication, QMainWindow, QWidget, QPushButton, QHBoxLayout
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setStyleSheet(open('./style.css').read())
try:
self.setCentralWidget(DefaultWidget())
else:
some_error_stuff_for_troubleshooting
self.show()
class DefaultWidget(QWidget):
def __init__(self):
super().__init__()
okButton = QPushButton('OK')
cancelButton = QPushButton('Cancel')
hbox = QHBoxLayout()
hbox.addWidget(okButton)
hbox.addWidget(cancelButton)
self.setLayout(hbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
In the CSS file...
QWidget.MainWindow {
background: blue
}
QPushButton {
background-color: beige
}
QPushButton???okButton {
background-color: green
}
Where I put the ??? I have tried a number of things (trying to walk through the classes QPushButton.okButton, QPushButton.DefaultWidget.okButton, DefaultWidget.okButton, with ., #, :: and : notation, etc.) but at this point it's more random than educated guessing.
I would like for the CSS to handle some naming convention styling, outside of the class name to help pull as much style out of the python file as possible. Even if the notation happened to be correct, am I not seeing my expected color change due to inherited style?
Edit: If anyone stumbles across this, the .setObjectName() method or declaring the object name option when instancing the widget. Also, here's a similar question on why some sort of default name isn't used, if you're interested.
PyQt: is there an better way to set objectName in code?
Try it:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QPushButton, QHBoxLayout
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# open('./style.css').read())
self.setStyleSheet("""
QWidget {
background: blue;
}
QPushButton {
background-color: beige;
}
QPushButton#okButton { /* <--- #okButton */
background-color: green;
}
""")
defaultWidget = DefaultWidget()
self.setCentralWidget(defaultWidget)
self.show()
class DefaultWidget(QWidget):
def __init__(self):
super().__init__()
okButton = QPushButton('OK', objectName="okButton") # + objectName="okButton"
cancelButton = QPushButton('Cancel')
hbox = QHBoxLayout()
hbox.addWidget(okButton)
hbox.addWidget(cancelButton)
self.setLayout(hbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
Related
what i want to achieve
I am learning how to use pyqt5 with this project of mine.
I tried downloading something called BlurWindow but it kept giving me a parameter error so switched back to trying to use QGraphicBlurEffect but it blurs everything inside my MainWindow
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow, QApplication
import sys
from PyQt5.uic import loadUi
from BlurWindow.blurWindow import blur
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
loadUi(r'D:\Workspace\Qt Designer\blur bg\blurtest.ui',self)
self.setAttribute(Qt.WA_TranslucentBackground)
hWnd = self.winId()
print(hWnd)
blur(hWnd)
self.setStyleSheet("background-color: rgba(0, 0, 0, 0)")
app=QApplication(sys.argv)
mainwindow=MainWindow()
widget=QtWidgets.QStackedWidget()
widget.setWindowOpacity(0.5)
widget.addWidget(mainwindow)
widget.setFixedHeight(600)
widget.setFixedWidth(800)
widget.show()
sys.exit(app.exec_())
BlurWindow uses system features to set the background of a top level window.
Your problem is that you're applying it to the wrong widget, which is a child widget, not a top level one. The top level has no "glass effect" set, so the result is that it won't have any effect applied on it.
The solution is simple: apply the effect to the top level window.
import sys
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUi
from BlurWindow.blurWindow import blur
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
loadUi(r'D:\Workspace\Qt Designer\blur bg\blurtest.ui', self)
self.setStyleSheet("""
MainWindow {
background-color: rgba(0, 0, 0, 0);
}
""")
app = QApplication(sys.argv)
mainwindow = MainWindow()
widget = QtWidgets.QStackedWidget()
widget.addWidget(mainwindow)
widget.setFixedHeight(600)
widget.setFixedWidth(800)
blur(widget.winId()) # <---
widget.show()
sys.exit(app.exec_())
Note that:
QMainWindow is not supposed to be used as a child widget. You should switch to a basic QWidget (or other container widgets like QFrame), meaning that you should create a new "widget" in Designer and copy/paste the content of your previous window to it, otherwise loadUi() will throw an exception;
you should never apply generic style sheet properties to parent widgets, as you would get unexpected results (especially with complex widgets, like scroll areas); you should always use proper selectors (as I did above);
if I use the setStyleSheet method in order to change the style for a specific widget, the other ones placed inside it, changes their style, but I don't want it! I can bring you two example:
when I change the border/background color for a frame (see the widgets placed inside it):
import PyQt5.QtGui as qtg
import PyQt5.QtCore as qtc
import PyQt5.QtWidgets as qtw
import sys
class MainWindow(qtw.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(520,300)
self.setWindowTitle("Treeview Example")
self.layout = qtw.QVBoxLayout()
self.frame1=qtw.QFrame()
self.frame1layout=qtw.QVBoxLayout()
self.frame1layout.setContentsMargins(5, 5, 5, 5)
self.frame1.setLayout(self.frame1layout)
self.frame1.setStyleSheet("border: 1px solid; border-color:red; background-color:white") # I change the style for the main frame
self.layout.addWidget(self.frame1)
self.frame2=qtw.QFrame()
self.frame2layout=qtw.QVBoxLayout()
self.frame2layout.setContentsMargins(10, 10, 10, 10)
self.frame2.setLayout(self.frame2layout)
self.frame1layout.addWidget(self.frame2)
self.ProgressBar=qtw.QProgressBar()
self.frame2layout.addWidget(self.ProgressBar)
self.setLayout(self.layout)
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
or when I change the border color for a treeview widget (the scrolled treeview widget is became white):
import PyQt5.QtGui as qtg
import PyQt5.QtCore as qtc
import PyQt5.QtWidgets as qtw
import sys
class MainWindow(qtw.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(520,300)
self.setWindowTitle("Treeview Example")
self.layout = qtw.QVBoxLayout()
self.treeview = qtw.QTreeView(self)
self.treeview.setStyleSheet("border: 1px solid; border-color:red") # it destroy the style of the objects inside the treeview widget!
model = qtg.QStandardItemModel()
rootNode = model.invisibleRootItem()
section1 = qtg.QStandardItem("A")
section1.appendRow([qtg.QStandardItem("A1")])
childnode = qtg.QStandardItem("A2")
section1.appendRow([childnode])
section2 = qtg.QStandardItem("B")
section2.appendRow([qtg.QStandardItem("B1")])
section2.appendRow([qtg.QStandardItem("B2")])
rootNode.appendRow([section1])
rootNode.appendRow([section2])
self.treeview.setHeaderHidden(True)
self.treeview.setModel(model)
self.layout.addWidget(self.treeview)
self.setLayout(self.layout)
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
my question is, how can I change the style of a specific widget without modifying the style of the other ones?
###########
UPDATE:
in my first example, if I don't use the instruction self.treeview.setStyleSheet("border: 1px solid; border-color:red") I realized that the progress bar widget doesn't expand as before. see this screenshot:
what is the issue?
I strongly suggest you to more carefully read the style sheet syntax and reference documentation, as everything is clearly specified and explained there:
Style sheets consist of a sequence of style rules. A style rule is made up of a selector and a declaration. The selector specifies which widgets are affected by the rule; the declaration specifies which properties should be set on the widget.
Stylesheets are, by definition, cascading.
The stylesheet set on a widget is propagated on its children, those children inherit the style of the parent.
If you set a generic property like this:
border: 1px solid black;
The result is that all its children will have that border: you only gave the declaration but without the selector, so a universal selector is used as implicit.
This is not just Qt, this is typical of CSS (from which QSS take their main concepts), and it works exactly as any other widget styling property: setting a font or a palette on a widget, propagates them to all its children.
The difference is that with stylesheets (exactly like standard CSS) you can use selectors.
If you want to style the tree widget only, then use that class selector:
self.treeview.setStyleSheet('''
QTreeView {
border: 1px solid;
border-color:red;
}
''')
The QPushButton lightsBtn is a toggle button that switches a light on and off. When the user presses lightsBtn, the function lightsBtnHandler will check whether the button is currently checked or not and calls either turnOnLights or turnOffLights.
I think self.sender() is able to access properties of the QPushButton, but I cant find any documentation on accessing the checked state.
Is it possible?
class Screen(QMainWindow):
def initUI(self):
lightsBtn= QPushButton('Turn On')
lightsBtn.setCheckable(True)
lightsBtn.setStyleSheet("QPushButton:checked {color: white; background-color: green;}")
lightsBtn.clicked.connect(self.lightsBtnHandler)
lightsBtn.show()
def lightsBtnHandler(self):
if self.sender().?? isChecked(): # How to check for checked state?
self.turnOnLights()
else:
self.turnOffLights()
following the #Matho comment I have modified your code a little bit.
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
import sys
class Screen(QMainWindow):
def __init__(self):
super(Screen, self).__init__()
self.initUI()
def initUI(self):
self.lightsBtn= QPushButton('Turn On')
self.lightsBtn.setCheckable(True)
self.lightsBtn.setStyleSheet("QPushButton:checked {color: white; background-color: green;}")
self.lightsBtn.clicked.connect(self.lightsBtnHandler)
# probaply you will want to set self.lightsBtn
# at certain spot using layouts
self.setCentralWidget(self.lightsBtn)
def lightsBtnHandler(self):
if self.lightsBtn.isChecked():
self.turnOnLights()
else:
self.turnOffLights()
def turnOnLights(self):
print("truned on")
def turnOffLights(self):
print("truned off")
app = QApplication(sys.argv)
window = Screen()
window.show()
sys.exit(app.exec_())
I don't seem to get the Stylesheets to work in PySide. Is there some special syntax that differs from PyQt?
Here's a small example code:
import sys
from PySide import QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
okButton = QtGui.QPushButton("OK")
cancelButton = QtGui.QPushButton("Cancel")
testWidget = QtGui.QWidget()
hbox2 = QtGui.QHBoxLayout()
hbox2.addWidget(okButton)
testWidget.setLayout(hbox2)
testWidget.setObjectName("testWidget")
testWidget.setStyleSheet("QWidget#testWidget { \n border: 2px solid gray; \n border-radius: 3px; \n }")
hbox = QtGui.QHBoxLayout()
hbox.addWidget(testWidget)
hbox.addWidget(cancelButton)
self.setLayout(hbox)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Expample')
self.show()
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
The result is:
If I change from PySide import QtGui to from PyQt4 import QtGui, I receive the following result:
Why doesn't the Stylesheet work in PySide?
The QWidget class may be treated differently when it comes to stylesheets. To get your example to work correctly, you need to explicitly enable the stylesheet like this:
testWidget.setAttribute(QtCore.Qt.WA_StyledBackground)
However, I don't exactly know why PyQt and PySide behave differently in this regard. This mailing-list post from the author of PyQt:
http://riverbankcomputing.com/pipermail/pyqt/2009-September/024462.html
suggests that it is only subclasses of QWidget that should need to set the WA_StyledBackground attribute. And indeed if testWidget is replaced by such a subclass:
class SubWidget(QtGui.QWidget): pass
...
testWidget = SubWidget()
# testWidget.setAttribute(QtCore.Qt.WA_StyledBackground)
then the example no longer works in either PyQt or PySide.
This means that the PySide behaviour is wrong for non-subclasses of QWidget. Possibly there is some kind of meta-object bug which is making the PySide QWidget class look like a subclass to Qt.
Consider the following example code:
from PyQt5.QtWidgets import (QApplication, QHBoxLayout, QLabel, QWidget,
QMainWindow, QVBoxLayout, QTextEdit)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
cwidget = QWidget(self)
cwidget.setStyleSheet("QWidget { background-color: red; }")
self.setCentralWidget(cwidget)
self.resize(100, 100)
vbox = QVBoxLayout(cwidget)
vbox.addWidget(QTextEdit(self))
vbox.addWidget(BlackBar(self))
class BlackBar(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setStyleSheet("* { background-color: black; color: white; }")
hbox = QHBoxLayout(self)
hbox.setSpacing(5)
hbox.addWidget(QLabel(text="eggs"))
hbox.addWidget(QLabel(text="bacon"))
if __name__ == '__main__':
app = QApplication([])
main = MainWindow()
main.show()
app.exec_()
It has:
A QMainWindow, QWidget as central widget (red), QVBoxLayout as a child of the cental widget. Inside there:
A QTextEdit (just as a filler)
A QWidget (black), which contains a QHBoxLayout. Inside that:
Two QLabels
This looks like this:
I'd expect the spaces between the labels to be black, because the QHBoxLayout is a child of BlackBar, but it seems BlackBar is just "invisible" in between and the central widget "shines through". Why is this?
The bugreport has now been answered with a solution that's easier than #ekhumoro's answer and works:
I don't think this is valid. The paint code your are looking for is not drawn in the paintEvent. Look for QWidgetPrivate::paintBackground instead. For performance reasons widgets will ignore style sheets by default, but you can set the WA_StyledBackground attribute on the widget and it should respect style sheet backgrounds.
And indeed, doing this before setting the stylesheet does the trick:
self.setAttribute(Qt.WA_StyledBackground)
Although the Style Sheet Syntax does not mention it, it seems that the QWidget class is treated differently when it comes to stylesheets.
Other widgets will work fine with your example code. For example, if QWidget is replaced everywhere with QFrame, then everything works as expected.
To get stylesheet support for QWidget subclasses, you need to reimplement the paintEvent and enable it explicitly:
class BlackBar(QWidget):
...
def paintEvent(self, event):
option = QStyleOption()
option.initFrom(self)
painter = QPainter(self)
self.style().drawPrimitive(
QStyle.PE_Widget, option, painter, self)