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
Related
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 ?
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;
}
''')
I would like to have the OK button of all the QMessageBox of my GUI (which is quite complex and so there are a lot of them) set a different color with respect to the other buttons (Cancel, No etc.).
I don't want to set its color every time I create a new QMessageBox but I would like to set it through the stylesheet of my application once and for all.
I tried different options but none of them worked:
- QMessageBox#okButton {background-color:blue}
- QMessageBox::okButton {...}
- QMessageBox:okButton {...}
- QMessageBox#ok-button {...}
- QMessageBox QPushButton#okButton {...}
and others...
Is there a way or do I have to give up?
A possible solution is to set the objectName() to be a selector for each button and for this you can use the notify() method of the QApplication:
from PySide2.QtCore import QEvent
from PySide2.QtWidgets import *
class Application(QApplication):
def notify(self, receiver, event):
if isinstance(receiver, QMessageBox) and event.type() == QEvent.Show:
for button in receiver.buttons():
sb = receiver.standardButton(button)
if not button.objectName():
button.setObjectName(sb.name.decode())
button.style().unpolish(button)
button.style().polish(button)
return super().notify(receiver, event)
def main():
app = Application()
app.setStyle("fusion")
app.setStyleSheet(
"""
QPushButton#Ok { background-color: green }
QPushButton#Cancel { background-color: red }
"""
)
QMessageBox.critical(
None, "Title", "text", buttons=QMessageBox.Ok | QMessageBox.Cancel
)
msgBox = QMessageBox()
msgBox.setText("The document has been modified.")
msgBox.setInformativeText("Do you want to save your changes?")
msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
msgBox.exec_()
if __name__ == "__main__":
main()
Unfortunately, there's no direct solution, since QDialogButtonBox (which is what QMessageBox uses for its buttons) doesn't provide selectors for button roles.
For staticly created messagebox, the only way is to use the [text="button text"] selector, but that would just be a guess, which could change depending on the localization and the style (which could set a mnemonic, and you cannot even know what that would be for sure). Also, this requires setting the stylesheet for the QApplication.
A possible application wide stylesheet for these situations would look like this:
app.setStyleSheet('''
QDialogButtonBox > QPushButton[text="&OK"] {
background-color: orange;
}
QDialogButtonBox > QPushButton[text="&Cancel"] {
background-color: green;
}
''')
Note that I used the Parent > Class selector in order to ensure that only buttons of QDialogButtonBox are styled with those rules, otherwise any "Ok" button would be orange, etc.
Nonetheless, in the case above on my computer it only works for the Ok button, since my localization (Italian) uses "&Annulla" for the other.
On the other hand, if you're creating QMessageBox instances, there's more freedom and flexibility using selectors based on object names.
The only issue is that since the object name is set after the creation, the stylesheet is not applied, so the buttons must be "unpolished" after instantiation.
A simple subclass could provide a standard interface without complicating things too much:
class ColoredMessageBox(QtWidgets.QMessageBox):
StandardNames = {
QtWidgets.QMessageBox.Ok: 'Ok',
QtWidgets.QMessageBox.Cancel: 'Cancel',
QtWidgets.QMessageBox.Save: 'Save',
# ...
}
def exec_(self):
for button in self.buttons():
objName = self.StandardNames.get(self.standardButton(button))
if objName:
button.setObjectName(objName)
self.style().unpolish(button)
return super().exec_()
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet('''
QMessageBox > QDialogButtonBox > QPushButton#Ok {
background-color: orange;
}
QMessageBox > QDialogButtonBox > QPushButton#Cancel {
background-color: blue;
}
''')
w = ColoredMessageBox(QtWidgets.QMessageBox.Information, 'Hello', 'How are you?',
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
w.exec_()
I made a Gui and on a normal screen switching with keys between buttons works and the selected button can be easily seen. This gui is designed for an underwater screen which is much smaller than a normal screen. On the underwater screen it becomes very hard to see on which button the "cursor" is.
How is this state of the button called when the button is not yet checked but the cursor is resting on it?
The button is a regular QPushButton.
How can I make the line of this bounding box thicker?
In the image the cursor is on the Exit Button and a small blue line can be seen
You can set the width of the border with Qt StyleSheet:
import sys
from PyQt5 import QtWidgets
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
QSS = """
QPushButton:pressed {
border: 4px solid;
}
QPushButton:selected {
border: 4px solid;
}
"""
app.setStyleSheet(QSS)
button_1 = QtWidgets.QPushButton("Button1")
button_2 = QtWidgets.QPushButton("Button3")
button_3 = QtWidgets.QPushButton("Button3")
w = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(w)
lay.addWidget(button_1)
lay.addWidget(button_2)
lay.addWidget(button_3)
w.show()
sys.exit(app.exec_())
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; }')