I have a QDial widget that I want to beautify the circular edge of this widget by adding a QLable as the following figure. However, I think this makes the QLabel the parent widget, and the QDial no further works!
Below is also my simple code.
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(200, 200, 500, 500)
self.UiComponents()
self.show()
def UiComponents(self):
dial = QDial(self)
dial.setGeometry(150, 150, 200, 200)
label_1 = QLabel('', self)
label_1.move(168, 168)
label_1.resize(164, 164)
label_1.setStyleSheet("border: 4px solid gray; border-radius: 82px;")
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
The "main" problem is that you're adding the label over the dial, so it won't be able to receive mouse events.
A theoretical solution could be to use label_1.setAttribute(Qt.WA_TransparentForMouseEvents), but that won't be a good idea, for the following reasons:
widget geometries should normally be managed by a layout manager, so you cannot rely on a "guess" done by trial and error: as soon as the window is resized, all geometries will change and you'll end up with a floating circle that will make everything worse;
even assuming you get the positioning right by intercepting the resize event with an event filter, you'd need to manually reset the stylesheet everytime and ensure that it's properly aligned, but that cannot be guaranteed because different size policies and other widgets could change the final radius of the dial;
what you see on your screen is almost never what users will see in theirs, due to lots of reasons including the current OS and QStyle in use; see the following screenshots taken with 3 Qt common styles (Breeze, Oxygen and Windows):
Unfortunately, QDial has never received lots of care from developers, as it's a scarcely used widget that is hard to implement for custom usage. As such, it doesn't support many any appearance features, and there's also no stylesheet configuration.
If you want to change the look of the dial, the only safe possibility is to subclass it, override its paintEvent() and paint it on your own.
Related
I'm using radio buttons and checkboxes and I was wondering if there was a way to change the color of the border around the checkmark/radio indications. The reason is because when I switch to dark mode on my layout, the border is no longer visible.
By going to dark mode, I just inverse the foreground and background colors.
class ChannelWindow(QWidget):
"""channel real-time display class (used for both default and custom types)"""
def __init__(self, channel, this_type, comPort, spot):
super().__init__()
self.setGeometry(myWin.pos().x() - 535 + spot, myWin.pos().y() + 31, 520, 775)
self.setWindowTitle(f"{channel} Data Window")
self.setWindowIcon(QtGui.QIcon('files/images/ham.ico'))
self.setStyleSheet(f"color: {foreground}; background-color: {background}")
Setting a basic stylesheet alone is insufficient, unless you are really thorough. Most of the times, its results are inconsistent.
There are two reasons for that:
Qt uses QStyle to draw widgets, compute their sizes, position child elements (like the indicator of a QCheckBox) and properly draw them;
QSS (Qt Style Sheets) are propagating (since they follow the cascading feature of CSS) and require specific selectors for sub-controls of complex widgets; setting style sheet makes Qt use an internal QStyle (QStyleSheetStyle) that sometimes completely overrides the default behavior of the basic style;
Most importantly, complex widgets usually require to write the full specifications of their contents, especially for sub-controls; and using generic properties is always discouraged for such widgets (including doing it for their parents).
There are various solutions to this, starting by using customized dark "themes" (see this post and the related answers). This has some caveats: it's actually a new style, so you might not like it, or would want to use custom style sheets that would create some compatibility issues.
The custom stylesheet solution, though, is quite more complex, as you should specify all sub controls colors, because even if you use class selectors, that won't be enough. For instance, QCheckBox requires that you specify the properties of ::indicator but, as explained above, once you set a property for a sub control, you must set all other properties along with it; in the case of QCheckBox this means that you have to write rules for all states (disabled, checked, unchecked, etc) and provide an image for the check mark symbol.
Other complex widgets (like scroll bars) require carefully written style sheets, otherwise they would look terrible.
You should also consider that some colors might not be compatible with the chosen ones (like the selection color).
A simpler (and, usually, more compliant) solution, is to set the application palette using the color constructor and eventually use carefully written style sheets to override some widget-specific colors if needed.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
Palettes = []
class Widget(QWidget):
def __init__(self):
super().__init__()
self.setStyleSheet('''
QGroupBox::title {
color: red;
}
''')
layout = QVBoxLayout(self)
darkCheck = QCheckBox('Switch dark mode')
layout.addWidget(darkCheck)
# just some random widgets
group = QGroupBox('Hello!')
layout.addWidget(group)
groupLayout = QVBoxLayout(group)
groupLayout.addWidget(QTextEdit('lorem ipsum ' * 1000))
combo = QComboBox()
combo.addItems(('a', 'b', 'c'))
groupLayout.addWidget(combo)
groupLayout.addWidget(QLineEdit('line edit'))
darkCheck.clicked.connect(setDarkMode)
def setDarkMode(dark):
app = QApplication.instance()
if not Palettes:
Palettes.append(app.palette())
Palettes.append(QPalette(Qt.darkGray))
app.setPalette(Palettes[dark])
for window in QApplication.topLevelWidgets():
old = window.styleSheet()
if not old:
window.setStyleSheet(' ')
window.setStyleSheet(old)
if __name__ == "__main__":
app = QApplication(sys.argv)
test = Widget()
test.show()
sys.exit(app.exec())
As you can see, I altered the appearance of the group box title, but I specifically used a selector for the subcontrol of its class.
The last part is to ensure that all the top level widgets (and their children) compute again their stylesheets, even if they do not have any.
A long time ago, I wanted to make a logo appear on top of the text in a QPushButton stacked on top of each other, but I couldn't find anyway
I read some stylesheets (couldn't find a single doc to read it all about all styles I can apply to a button)
tried the setLayoutDirection (RightToLeft and LeftToRight were there, but no UpToDown direction)
In my (I wish) last attempt I tried to inherit a QAbstractButton (I didn't find QAbstractPushButton, so I guess QAbstractButton is the answer) and change its paintEvent/paintEngine to draw an image or maybe add a vbox inside it as a layout to draw to components, but I can't find anything in python (specially PySide) which has an example in any possible way close to that. The best thing I found was the analogue clock example which was not very helpful because it was trying to work a QWidget and not a QAbstractButton and I want to keep the feel of a Native looking button.
I like my final product to be something like this.
source of the implemention of that
Python Enaml toolkit supported this feature out of the box (in one of its widgets), and I know it is QT based, so I really wish to know how it is possible?
p.s.: Also, is there a market for qt widgets? e.g.: a plugin system. Because rewriting an android like switch doesn't seem like the correct thing that I should do! even a good tutorial or doc would be appreicated (excluding official doc)
It is easier than you think, you can use QToolButton() like this:
import sys
from PySide6.QtCore import Qt, QSize
from PySide6.QtWidgets import QApplication, QVBoxLayout,QStyle, QWidget,
QToolButton
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
button = QToolButton()
# here you choose the position of the icon and its text
button.setToolButtonStyle(
Qt.ToolButtonStyle.ToolButtonTextUnderIcon)
# here I just use built-in icon by PySide6 for this example
name = 'SP_DialogSaveButton'
pixmapi = getattr(QStyle, name)
icon = self.style().standardIcon(pixmapi)
# here we set text and icon of size 32x32 to the button
button.setIcon(icon)
button.setText("Sample text")
button.setIconSize(QSize(32, 32))
# finally we add our button to the layout
lay = QVBoxLayout(self)
lay.addWidget(button, alignment=Qt.AlignCenter)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
I have a list of pyqt4 push button and want to move the position. Since it is troublesome it make lots of buttons variable I create them through a list. The code below
import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout(self)
self.buttons = []
for i in range(3):
self.buttons.append(QPushButton('',self))
self.buttons[-1].setFixedWidth(50)
self.buttons[-1].setFixedHeight(50)
self.buttons[-1].move(70*i+50,300)
layout.addWidget(self.buttons[-1])
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.resize(500,500)
window.show()
sys.exit(app.exec_())
don't work for specify the position
but
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout(self)
self.button1 = QPushButton('',self)
self.button1.setFixedWidth(50)
self.button1.setFixedHeight(50)
self.button1.move(50,300)
self.button2 = QPushButton('',self)
self.button2.setFixedWidth(50)
self.button2.setFixedHeight(50)
self.button2.move(120,300)
self.button3 = QPushButton('',self)
self.button3.setFixedWidth(50)
self.button3.setFixedHeight(50)
self.button3.move(190,300)
layout = QVBoxLayout(self)
layout.addWidget(self.button1)
layout.addWidget(self.button2)
layout.addWidget(self.button3)
works fine.
What's the reason behind?
If you want to manually specify the geometry (position and size) of widgets, you should not add them to a layout.
Your second example "works" just because you already created and set a layout to the top-level widget (the Window class), and since a layout already exists the second one is not "installed". Running it from the console will shows this error:
StdErr: QLayout: Attempting to add QLayout "" to Window "", which already has a layout
When a widget is added to a layout, the ownership of the "layout item" (an abstract item used by layouts to manage its widgets) is transferred to the layout, and the widget is reparented to the widget that uses that layout.
Since the second layout cannot be set, it will not manage the geometries of the items you tried to add, and the result is that they will keep the geometries you set before.
The same result can be obtained if you remove all the last lines referencing the other layout, which is exactly what you need.
Also, note that in order to add a widget that is not managed by a layout, a parent is required. Your last example also works because you specified the window as the parent while instantiating them; if you don't do that the buttons will not be shown, but if you do show them (with show() or setVisible(True)) they will each appear in separate windows, as they will become their own top level windows.
If you don't have other widgets that should be managed by a layout, you can also avoid creating the first layout at all (but, still, the parent is required).
Finally, let me tell you that using manual geometries is generally discouraged, and there are very few and specific cases for which it's a good idea to go with.
The main reason behind this is that widgets tend to show very differently from device to device, and that depends on various aspects:
different OS and OS versions draw widgets differently (sometimes dramatically), and this involves varying the widget size and behavior; while this might not be a major issue for simple widgets, it can be a problem for things like item views, spinboxes, etc;
different OS and systems use different styles, which also includes things like internal widget content margins and minimum size;
specific system settings (most importantly, the default font) could make widgets mostly unreadable, specifically with text being clipped within the widget margins if the font size is too big;
depending on the OS, you might face issues if the system uses High DPI displays, and you could end up with very tiny widgets that are almost impossible to interact with;
fixed geometries force you (and the user) to have a fixed widget/window size, and (along with the DPI issue above) this can be a problem: the same widget could look too big or too small;
I'm trying to change the current color group fora QPalette, but it seems that the setCurrentColorGroup method of QPalette simply does not work.
I'm running this code:
app = QtGui.QApplication(sys.argv)
button = QPushButton()
svgWidget = QSvgWidget(resources_paths.getPathToIconFile("_playableLabels/42-labelPlay-disabled-c.svg"))
button.setLayout(QHBoxLayout())
button.layout().addWidget(svgWidget)
button.setFixedSize(QSize(300, 300))
print button.palette().currentColorGroup()
button.setEnabled(False)
print button.palette().currentColorGroup()
button.palette().setCurrentColorGroup(QPalette.ColorGroup.Normal)
print button.palette().currentColorGroup()
button.show()
print button.palette().currentColorGroup()
app.exec_()
This is the output I get:
PySide.QtGui.QPalette.ColorGroup.Normal
PySide.QtGui.QPalette.ColorGroup.Disabled
PySide.QtGui.QPalette.ColorGroup.Disabled
PySide.QtGui.QPalette.ColorGroup.Disabled
Process finished with exit code -1
So... It seems that setCurrentColorGroup does exactly nothing. Any ideas on how could I change the current color group?
Thanks in advance!
(BTW, I'm running PySide 1.2.4 with Qt 4.8 on a Windows 7 system)
It seems that you are trying to change the way icons are rendered, rather than the way widgets are painted, so the palette is not the right API to use. Instead, you should use a QIcon, which allows different images to be used for various modes and states.
To use the same image for both Normal and Disabled modes, you would use code like this:
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap('image.svg'), QtGui.QIcon.Normal)
icon.addPixmap(QtGui.QPixmap('image.svg'), QtGui.QIcon.Disabled)
button = QtGui.QPushButton()
button.setIcon(icon)
However, you should take careful note of this warning from the Qt docs:
Custom icon engines are free to ignore additionally added pixmaps.
So there is no guarantee that this will work with all widget styles on all platforms.
UPDATE:
If the above method doesn't work, it probably means the widget style is controlling how disabled icons are rendered. The relevant QStyle API is generatedIconPixmap, which returns a copy of the pixmap modified according to the icon mode and style options. It seems that this method may sometimes also take the palette into account (somewhat contrary to what I stated above) - but when I tested this, it did not have any affect. I reset the palette like this:
palette = self.button.palette()
palette.setCurrentColorGroup(QtGui.QPalette.Normal)
palette.setColorGroup(QtGui.QPalette.Disabled,
palette.windowText(), palette.button(),
palette.light(), palette.dark(), palette.mid(),
palette.text(), palette.brightText(),
palette.base(), palette.window(),
)
button.setPalette(palette)
which made the colours look normal when the button was disabled - but the icon was still greyed out. Still, you might want to try it in case things work differently on your platform (don't be surprised if they don't).
It seems that the correct way to control the disabling of icons is to create a QProxyStyle and override the generatedIconPixmap method. Unfortunately, this class is not available in PyQt4, but I have tested it in PyQt5, and it works. So the only working solution I have at the moment is to upgrade to PyQt5, and use QProxyStyle. Here is a demo script that shows how to implement it:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class ProxyStyle(QtWidgets.QProxyStyle):
def generatedIconPixmap(self, mode, pixmap, option):
if mode == QtGui.QIcon.Disabled:
mode = QtGui.QIcon.Normal
return super(ProxyStyle, self).generatedIconPixmap(
mode, pixmap, option)
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtWidgets.QPushButton(self)
self.button.setIcon(QtGui.QIcon('image.svg'))
self.button2 = QtWidgets.QPushButton('Test', self)
self.button2.setCheckable(True)
self.button2.clicked.connect(self.button.setDisabled)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.button2)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setStyle(ProxyStyle())
window = Window()
window.setGeometry(600, 100, 300, 200)
window.show()
sys.exit(app.exec_())
The QMainWindow below is assigned a dark-gray background-color using QSS.
I would also like to change the color of the borders and the color of the title bar.
How to achieve control of the appearance of the QMainWindow borders and titlebar?
I would like to know how to change their colors and how to control the borders width and the title-bar's height.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
appStyle="""
QMainWindow{
background-color: darkgray;
}
"""
class GUI(QMainWindow):
def __init__(self):
super(GUI, self).__init__()
self.setStyleSheet(appStyle)
if __name__ == '__main__':
if not QApplication.instance(): app=QApplication([])
w=GUI()
w.setStyleSheet(appStyle)
w.show()
w.raise_()
sys.exit(app.exec_())
To my knowledge you cannot (on Windows maybe with some special OS dependent calls) modify the borders and header of a top-level widget (real window on the desktop) in Qt because these are delivered from the operating system.
You can however, set the widget frameless and add borders for yourself.
Example:
from PySide import QtGui, QtCore
app = QtGui.QApplication([])
app.setStyleSheet('QMainWindow{background-color: darkgray;border: 1px solid black;}')
w = QtGui.QMainWindow()
w.setWindowFlags(QtCore.Qt.FramelessWindowHint)
w.show()
app.exec_()
And looks like
You see that unfortunately the usually header bar vanishes, so no dragging, no resizing, no closing or minimizing. This then all must be implemented yourself. See for example How can I handle events in the titlebar and change its color etc ?.
This can be implemented in QT C++ by detecting current system theme from Windows registry and applying the Dark Window hints. A dark style in addition to this can make dark light window modes.
A sample project demonstrating this is shared here if it helps to adapt
https://envyen.com/posts/2021-10-24-QT-Windows-Dark-theme/