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())
Related
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.
I'm starting experimenting with Maya python, and I'm trying to do some UI.
I came across to a really strange problem, I can't get a button to stay in the center of the windows.
I've tried different things but nothing seems to work, here is the code:
import maya.cmds as cmds
cmds.window( width=200 )
WS = mc.workspaceControl("dockName", retain = False, floating = True,mw=80)
submit_widget = cmds.rowLayout(numberOfColumns=1, p=WS)
cmds.button( label='Submit Job',width=130,align='center', p=submit_widget)
cmds.showWindow()
this is a simple version but still, I can't get it to work.
can someone help me?
I honestly don't know the answer as anytime I have to dig into Maya's native UI stuff it makes me question my own life.
So I know it's not exactly what you're asking for, but I'll opt with this: Use PySide instead. At first glance it might make you go "woah, that's way too hard", but it's also a million times better (and actually easier). It's much more powerful, flexible, has great documentation, and also used outside of Maya (so actually useful to learn). Maya's own interface uses the same framework, so you can even edit it with PySide once you're more comfortable with it.
Here's a bare-bones example to create a centered button in a window:
# Import PySide libraries.
from PySide2 import QtCore
from PySide2 import QtWidgets
class MyWindow(QtWidgets.QWidget): # Create a class for our window, which inherits from `QWidget`
def __init__(self, parent=None): # The class's constructor.
super(MyWindow, self).__init__(parent) # Initialize its `QWidget` constructor method.
self.my_button = QtWidgets.QPushButton("My button!") # Create a button!
self.my_layout = QtWidgets.QVBoxLayout() # Create a vertical layout!
self.my_layout.setAlignment(QtCore.Qt.AlignCenter) # Center the horizontal alignment.
self.my_layout.addWidget(self.my_button) # Add the button to the layout.
self.setLayout(self.my_layout) # Make the window use this layout.
self.resize(300, 300) # Resize the window so it's not tiny.
my_window_instance = MyWindow() # Create an instance of our window class.
my_window_instance.show() # Show it!
Not too bad, right?
I have developed a fairly complex GUI tool using the Qt Designer.
For more details about the tool see: https://github.com/3fon3fonov/trifon
I have defined many QDoubleSpinBox entries and by default the Qt Designer sets their right-click menu policy to:
setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
Now I want to add few more actions to this menu, but I simply cannot understand how this works! There is nothing in the Qt Designer which will allow me to make a "CustomContextMenu". I understand that for this I may need some coding (with which I will need help, and thus I am asking for help here), but I also need to make it globally for all SpinBox-es.
Sorry for not posting the code since it is fairly large for this form. If interested, please look at the github under "gui.py". However, there and in the .ui file there is no sign of any possibility to control the contextmenu policy for these buttons.
Instead I am posting an image of the tool (sorry for the bad image but PrtSc does not seem to work when the right button in clicked and the menu is displayed)
see GUI image here
As we want to add a QAction to the default context menu we first overwrite the contextMenuEvent event and use a QTimer to call a function that filters the toplevels and get the QMenu that is displayed and there we add the QAction:
doublespinbox.py
from PyQt5 import QtCore, QtWidgets
class DoubleSpinBox(QtWidgets.QDoubleSpinBox):
minimize_signal = QtCore.pyqtSignal()
def __init__(self, *args, **kwargs):
super(DoubleSpinBox, self).__init__(*args, **kwargs)
self.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
def contextMenuEvent(self, event):
QtCore.QTimer.singleShot(0, self.add_actions)
super(DoubleSpinBox, self).contextMenuEvent(event)
#QtCore.pyqtSlot()
def add_actions(self):
for w in QtWidgets.QApplication.topLevelWidgets():
if isinstance(w, QtWidgets.QMenu) and w.objectName() == "qt_edit_menu":
w.addSeparator()
minimize_action = w.addAction("minimize this parameter")
minimize_action.triggered.connect(self.minimize_signal)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = DoubleSpinBox()
w.show()
sys.exit(app.exec_())
To use DoubleSpinBox in Qt Designer, first place doublespinbox.py next to your .ui:
├── ..
├── rvmod_gui.ui
├── doublespinbox.py
├── ...
then you must promote the widget to do so right click on the QDoubleSpinBox and select the option "Promote to ..." by adding the following to the dialog:
Then click on the Add button and then the Promote button.
For the other QDoubleSpinBox, right click and select the new Promote To option where the DoubleSpinBox option is.
You can find an example here
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_())
Edit: I have tried a few more things. If I move the spacer to a layout below the spacer I am adding to it doesn't exibit the same exact behavior. Its still not optimal, and isn't going to work because my end goal is to have a scrollArea with a spacer inside that i add my widgets to, but this would not look right. I think the problem is the widgets are getting to a size zero I just do not know why or how to fix it.
I have two ui files made in QtDesigner. The first file is my main window at the start of the program I load the second ui file and place it into a vertical spacer in the middle of the first one. Additional copies are placed each time a button is clicked.
This all works great until I add a vertical spacer to push the items to the top. I have tired adding it from Designer and in the code. Both have the same result.
I have looked on google quite a bit and tried a lot of suggestions.
I tried setting the second ui files parent as a Qwidget I added on the first that contained the vertical layout.
I tried setting the minimum sizes and sizing polices to various things.
Below is my current code, any ideas or suggestions would be appreciated!
#!python3
import sys
from PyQt5 import QtWidgets, QtCore, uic
class TimesheetWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TimesheetWidget, self).__init__(parent)
self.parent = parent
self.tableRows = dict()
def setup(self):
self.labelSaved.hide()
self.addTableRow()
def addTableRow(self):
thisRow = len(self.tableRows)
self.tableRows[thisRow] = uic.loadUi("gui/tableRow.ui")
self.tableRows[thisRow].addButton.clicked.connect(self.addTableRow)
self.spacer.addWidget(self.tableRows[thisRow])
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
timesheet = TimesheetWidget()
Mytimesheet = uic.loadUi("gui/timesheet.ui", baseinstance=timesheet)
Mytimesheet.setup()
Mytimesheet.show()
sys.exit(app.exec_())
here is a link to the ui files (they are to long to post):
gist link for ui files
I finally fixed this by trying random things over and over until something worked.
It turned out that on my second ui file I did not have a top level layout. (I am not sure if that's what its called.)
To fix this I right clicked on the top level widget and choose layout and selected horizontal layout, although I think any would have worked.
Here is a picture that shows the top level widget with the cancel symbol on it. Once I added the layout that went away and everything worked!