PyQt - How to use the "setStyleSheet" method properly? - python

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;
}
''')

Related

How can I apply blur effect only on the background of my mainwindow?

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);

Styling the tab bar of QtADS in a PyQt project

I am working on a PyQt project, which makes use of the library QtAds (Qt Advanced Docking System). In order to redesign the software correctly, I'd like to be able to style every widget with QSS, including the ones added by QtAds, which should be able to be styled like any other standard Widget. However, I can't find the proper selector to style the tab bar of the ADS DockWidgets. Here is a MWE :
import sys
from PyQt5.QtWidgets import QPushButton,QApplication
import os
from PyQt5 import uic
from PyQtAds import QtAds
from customWidgets import *
UI_FILE = os.path.join(os.path.dirname(__file__), 'ui/MainWindow.ui')
MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE)
class MainWindow(MainWindowUI, MainWindowBase):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.dock_manager = QtAds.CDockManager(self)
self.setStyleSheet("""
QToolButton {
background-color: #2c3e50;
border : 1px solid #bdc3c7;
width : 30px;
height : 30px;
}
QToolButton:hover {
background-color: #7f8c8d;
}
QTabWidget::tab {
background : red;
}
""")
scope_settings_dockable = QtAds.CDockWidget("Test")
scope_settings_dockable.setWidget(QPushButton())
# scope_settings_dockable.tabWidget().setStyleSheet("* { background : red; }") # That works
self.menuView.addAction(scope_settings_dockable.toggleViewAction())
self.dock_manager.addDockWidget(QtAds.LeftDockWidgetArea, scope_settings_dockable)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
Here there is only one dockable widget which contains a QPushButton. The stylesheet defined here is able to change the styles on the buttons controlling the dockable widget, which are plain QToolButton and therefore are easy to edit. However, whenever I try to style the tab themselves, I can't find the right selector. I have tried ads--CDockWidgetTab, which should be the right selector according to the documentation, but it doesn't change the background. Tried with only CDockWidgetTab, and a few other syntaxes, but I can't get it to be styled.
As commented in the example, I found a way to style each tab individually, with dockable.tabWidget.setStylesheet(), but that is obviously not a nice solution since when having multiple widgets, I will have to restyle them individually, and will lead to a lot of redundancy.
Do anyone knows which selectors would allow me to style QtAds widgets ?

Space is added to the layout even if nothing was added

I have been designing a PySide6 Application but the problem is that even if I have not added any space to the layout the layout still contains a lot of visible space.
Debug Screenshot with widget borders as red:
Is there anyway to remove the space? Here is the code:
from PySide6 import QtCore
from PySide6.QtWidgets import (
QMainWindow,
QVBoxLayout,
QFormLayout,
QWidget,
QApplication,
QLabel,
QCheckBox,
QHBoxLayout,
QLineEdit,
QFileDialog,
QPushButton
)
import os
import sys
class Styler_Py(QMainWindow):
def __init__(self, parent=None) -> None:
super().__init__(parent)
# Window Setup
self.setWindowTitle("PyStyler")
# Layout setup
self.container_widget = QWidget()
self.main_layout = QVBoxLayout()
self.container_widget.setLayout(self.main_layout)
self.setCentralWidget(self.container_widget)
# Irrelevant code here
# --- New Section ---
self.main_layout.addSpacing(10)
self.main_layout.addStretch()
# --- New Section ---
# Files
self.files_label = QLabel("<h3>Files</h3>") # Note for the future, the closure tag did not have forward slash and have been fixed using an answer below
self.main_layout.addWidget(self.files_label)
self.files_layout = QVBoxLayout()
self.main_layout.addLayout(self.files_layout)
# Files -> Input File
self.input_file = FileInput()
self.input_file_layout = QHBoxLayout()
self.files_layout.addLayout(self.input_file_layout)
self.input_file_label = QLabel("Input File")
self.input_file_layout.addWidget(self.input_file_label)
self.input_file_layout.addWidget(self.input_file)
class FileInput(QWidget):
def __init__(self, start_path=os.path.expanduser("~")) -> None:
super().__init__()
self._main_layout = QHBoxLayout()
self.setLayout(self._main_layout)
# Add Text edit for File Input
self._file_input = QLineEdit(start_path)
self._main_layout.addWidget(self._file_input)
self._file_input_browse = QPushButton("Browse")
self._file_input_browse.clicked.connect(self._browse_file_dialog)
self._main_layout.addWidget(self._file_input_browse)
self._start_path = start_path
def _browse_file_dialog(self):
if os.path.isfile(self._file_input.text()):
path = os.path.abspath(f"{self._file_input.text()}/..")
if os.path.isdir(self._file_input.text()):
path = os.path.abspath(self._file_input.text())
else:
path = self._start_path
file_name = QFileDialog.getOpenFileName(
self, "Open File", path
)
if len(file_name[0]) > 0:
self._file_input.setText(file_name[0])
def get_path(self):
return self._file_input.text()
def main():
app = QApplication(sys.argv)
app.setStyleSheet("*{ border: 1px solid red; }")
window = Styler_Py()
window.show()
app.exec()
main()
I think the problem is somewhere with my custom widget but I could not find it. I have tried adding and removing a lot of the widgets but nothing happens.
Edit
A Screenshot of window when resized to be a little bigger:
The first problem comes from the wrong tag closure, as it should be like this:
self.files_label = QLabel("<h3>Files</h3>")
Then, that spacing is the result of two aspects: layout spacings/margins and rich text margins.
layout spacings are inherited by child layouts from their parent layout (so, the input_file_layout has a default spacing inherited by the files_layout, which in turns inherits that property from the main_layout;
when layout managers are installed on widgets, they use the widget's style layout margins and spacings (QStyle.PM_Layout*Margin and QStyle.PM_Layout*Spacing) unless overriden by layout.setContentsMargins() or layout.setSpacing(); this not only happens top level widgets, but for child widgets too: layouts set on children widgets do not inherit the parent layout margins or spacings, and they use the default system (or style) values; this can be overridden
rich text elements normally result in bigger space required by the label's sizeHint(); unfortunately there's no easy work around for that, as also explained in the Layout Issues documentation;
In order to better understand the boundaries of each widget, you could add a simple stylesheet for testing purposes:
app.setStyleSheet('*{ border: 1px solid black; }')
Please consider that the above will override any widget border (including buttons, for example), so use it only for debugging in order to understand how the layout works; do NOT use it for general purposes, especially if applied on the application.

Unexpected padding on wrapped QLabel in custom class

I want to display a border around a QWidget that wraps a QLabel (this is practice for a more complicated widget). I'm using setStyleSheet to create the border. When I did this manually, it worked as expected. However, when I moved the code into its own class (derived from QWidget), the padding is different, and I can't figure out why.
import sys
from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QMainWindow, QVBoxLayout
class WrappedLabel(QWidget):
def __init__(self, text=''):
super().__init__()
self.text = QLabel(text)
layout = QVBoxLayout()
layout.addWidget(self.text)
self.setLayout(layout)
self.setStyleSheet('padding: 2px; border: 2px solid red;')
class Shell(QMainWindow):
def __init__(self): # constructor
super().__init__() # call the parent's constructor
w = QWidget() # Create the main window content widget
self.setCentralWidget(w)
# First label
unwrapped_label = QLabel('This is a normal QLabel with a border and no padding.')
unwrapped_label.setStyleSheet('border: 2px solid gray; padding: 2px;')
# Second label
wrapped_label = QLabel('This QLabel is manually wrapped in a styled QWidget. ' +
'There is a slight indent compared to the normal QLabel due to padding.')
wrapped_layout = QVBoxLayout()
wrapped_layout.addWidget(wrapped_label)
manual_wrapper = QWidget()
manual_wrapper.setObjectName('wrapper')
manual_wrapper.setLayout(wrapped_layout)
self.setStyleSheet('QWidget#wrapper { border: 2px solid gray; padding: 2px; }')
# Third label
derived_wrapper = WrappedLabel('This class derives from QWidget and wraps a QLabel like above, but is indented even further and the border is in the wrong spot.')
vbox = QVBoxLayout()
vbox.addWidget(unwrapped_label)
vbox.addWidget(manual_wrapper)
vbox.addWidget(derived_wrapper)
vbox.addStretch(1) # Squish them together to better see the spacing
w.setLayout(vbox)
# Setup the rest of the main window appearance
self.setGeometry(300,300,640,180)
self.setWindowTitle('Testing wrapped labels')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
shell = Shell() # create and show the main window
sys.exit(app.exec_())
To begin with, the code in the custom class WrappedLabel is not exactly the same as for the manual widget. For the manual widget you make sure that the stylesheet is only applied to the widget itself, but not to any child widgets via QWidget#wrapper. For you custom class you simply apply the stylesheet to the WrappedLabel instance which will cause it to cascade to all its child widgets (and also to the QLabel instance). This is why your QLabel instance ends up with the padding and the red border.
So why doesn't the same happen for the wrapper? Apparently custom base classes of QWidgets reject all applied style sheets by default (see this answer). You can make this work by adding self.setAttribute(QtCore.Qt.WA_StyledBackground) in WrappedLabel.__init__. Now you'll see that you end up with two borders, one for the wrapper and one for the label. To restrict the stylesheet to the wrapper you need to apply a similar identifier as for the manual widget: self.setStyleSheet('WrappedLabel { padding: 2px; border: 2px solid red; }').
So to make it work you can add this to WrappedLabel.__init__:
self.setAttribute(QtCore.Qt.WA_StyledBackground)
self.setStyleSheet('WrappedLabel { padding: 2px; border: 2px solid red; }')

(Py)Qt: Spacing in QHBoxLayout shows centralwidget's background, not parent's

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)

Categories

Resources