Unexpected padding on wrapped QLabel in custom class - python

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

Related

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 ?

Designer widget shadow?

I have added a widget in my mainwindows and I would like to give it a show something like box-shadow: 3px 3px 25px #111;
I tried above by going to widget change stylesheet option and adding the code as below:
background-color:#fff;
border:4px solid blue;
box-shadow: 0px -3px 5px #a6a6a6;
The first two attribute give the expected effect but box-shadow don't work.
How to use Python QT Designer and add box shadow to a widget?
Qt StyleSheet is not CSS but it is a technology that implements some features, and among them is not the box-shadow. If you want to implement something similar then you should use QGraphicsDropShadowEffect:
import sys
from PyQt5.QtCore import QPoint, Qt
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import (
QApplication,
QGraphicsDropShadowEffect,
QMainWindow,
QVBoxLayout,
QWidget,
)
def main():
app = QApplication(sys.argv)
main_window = QMainWindow()
container = QWidget()
container.setContentsMargins(3, 3, 3, 3)
main_window.setCentralWidget(container)
widget = QWidget()
widget.setAutoFillBackground(True)
lay = QVBoxLayout(container)
lay.addWidget(widget)
effect = QGraphicsDropShadowEffect(
offset=QPoint(3, 3), blurRadius=25, color=QColor("#111")
)
widget.setGraphicsEffect(effect)
main_window.resize(640, 480)
main_window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
It is recommended that you read the Qt Stylesheet references:
https://doc.qt.io/qt-5/stylesheet-reference.html
https://doc.qt.io/qt-5/stylesheet-syntax.html

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

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

How to make Icon in QMenu larger (PyQt)?

I did not yet find out how to make the icons in my QMenu larger. I have tried to define a stylesheet in which the icon size is enlarged. But it doesn't work. Here is my code:
menuStyleSheet = ("""
QMenu {
font-size: 18px;
color: black;
border: 2px solid black;
left: 20px;
background-color:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #cccccc, stop: 1 #ffffff);
}
QMenu::item {
padding: 2px 20px 2px 30px;
border: 1px solid transparent; /* reserve space for selection border */
spacing: 20px;
height: 60px;
}
QMenu::icon {
padding-left: 20px;
width: 50px; /* <- unfortunately, doesn't work */
height: 50px; /* <- unfortunately, doesn't work */
}
""")
#####################################################
# THE PYQT APPLICATION #
#####################################################
class GMainWindow(QMainWindow):
def __init__(self, title):
super(GMainWindow, self).__init__()
...
def setCustomMenuBar(self):
myMenuBar = self.menuBar()
global menuStyleSheet
myMenuBar.setStyleSheet(menuStyleSheet)
# Now add Menus and QActions to myMenuBar..
The result of this code is as follows:
I know that there is an old StackOverflow question about a similar topic, but it assumes that one is coding the Qt application in C++. So the situation is different. Here is the link: How to make Qt icon (in menu bar and tool bar) larger?
Any help is greatly appreciated :-)
EDIT :
Here are some details about my machine:
OS: Windows 10
Python: v3 (Anaconda package)
Qt: PyQt5
After a long search, I finally found the solution.
Just copy-paste the code below, and paste it in a *.py file. Of course, you should replace the path to the icon with a valid path on your local computer. Just provide a complete path ("C:\..") to be 100% sure that Qt finds the icon drawing.
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
# Create a custom "QProxyStyle" to enlarge the QMenu icons
#-----------------------------------------------------------
class MyProxyStyle(QProxyStyle):
pass
def pixelMetric(self, QStyle_PixelMetric, option=None, widget=None):
if QStyle_PixelMetric == QStyle.PM_SmallIconSize:
return 40
else:
return QProxyStyle.pixelMetric(self, QStyle_PixelMetric, option, widget)
# This is the main window class (with a simple QMenu implemented)
# ------------------------------------------------------------------
class TestWindow(QMainWindow):
def __init__(self):
super(TestWindow, self).__init__()
# 1. Set basic geometry and color.
self.setGeometry(100, 100, 400, 400)
self.setWindowTitle('Hello World')
palette = QPalette()
palette.setColor(QPalette.Window, QColor(200, 200, 200))
self.setPalette(palette)
# 2. Create the central frame.
self.centralFrame = QFrame()
self.centralFrame.setFrameShape(QFrame.NoFrame)
self.setCentralWidget(self.centralFrame)
# 3. Create a menu bar.
myMenuBar = self.menuBar()
fileMenu = myMenuBar.addMenu("&File")
testMenuItem = QAction(QIcon("C:\\my\\path\\myFig.png"), "&Test", self)
testMenuItem.setStatusTip("Test for icon size")
testMenuItem.triggered.connect(lambda: print("Menu item has been clicked!"))
fileMenu.addAction(testMenuItem)
# 4. Show the window.
self.show()
# Start your Qt application based on the new style
#---------------------------------------------------
if __name__== '__main__':
app = QApplication(sys.argv)
myStyle = MyProxyStyle('Fusion') # The proxy style should be based on an existing style,
# like 'Windows', 'Motif', 'Plastique', 'Fusion', ...
app.setStyle(myStyle)
myGUI = TestWindow()
sys.exit(app.exec_())
You will see a window like this, with a huge icon:
So how did I solve it? Well, apparently you cannot increase the icon size of a QMenu item in the usual way - defining the size in the stylesheet. The only way to do it is to provide a new QStyle, preferably derived from an existing QStyle. I found sources on how to do that in C++ (see http://www.qtcentre.org/threads/21187-QMenu-always-displays-icons-aty-16x16-px), but no explanation for PyQt.
After long trials and errors, I got it working :-)
Apparently more people struggle with this situation: http://www.qtcentre.org/threads/61860-QMenu-Icon-Scale-in-PyQt

(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