This question already has answers here:
QtDesigner changes will be lost after redesign User Interface
(2 answers)
Closed 1 year ago.
I want to create a GUI for a python file I wrote, so I downloaded PyQt5 and Qt Designer. When I exported my first try of the Qt Designer project I realized that the not PyQt5 was used but PySide2. What is the reason for this?
Till then everything worked fine, and I wanted to start some specific things like bringing a widget I created into a scrollArea. For this I created a custom widget and created a .ui and a .py. But they don't show up if I try to call them. Also the generated code for python looks like this:
from PySide2.QtCore import (QCoreApplication, QMetaObject, QObject, QPoint, QRect, QSize, QUrl, Qt)
from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont, QFontDatabase, QIcon, QLinearGradient, QPalette, QPainter, QPixmap, QRadialGradient)
from PySide2.QtWidgets import *
class Ui_Form(object):
def setupUi(self, Form):
if Form.objectName():
Form.setObjectName(u"Form")
Form.resize(640, 480)
self.widget = QWidget(Form)
self.widget.setObjectName(u"widget")
self.widget.setGeometry(QRect(270, 190, 77, 44))
self.verticalLayout = QVBoxLayout(self.widget)
self.verticalLayout.setObjectName(u"verticalLayout")
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.label = QLabel(self.widget)
self.label.setObjectName(u"label")
self.verticalLayout.addWidget(self.label)
self.pushButton = QPushButton(self.widget)
self.pushButton.setObjectName(u"pushButton")
self.verticalLayout.addWidget(self.pushButton)
self.retranslateUi(Form)
QMetaObject.connectSlotsByName(Form)
# setupUi
def retranslateUi(self, Form):
Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None))
self.label.setText(QCoreApplication.translate("Form", u"TextLabel", None))
self.pushButton.setText(QCoreApplication.translate("Form", u"PushButton", None))
# retranslateUi
As you can see, my widget does not inherit from "QWidget" but from "object". Also, my widget has no "init" but a "setupUi".
Why is that?
If I change to inherit from "object" to "QWidget" the code runs bot the widget does not show up. Also, when I try to show only the widget itself with the following code, it does not show up.
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = Ui_Form()
w.show()
sys.exit(app.exec_())
Empty Widget
I tried to search around and did not find anything that helped me out, so I'm asking my questions here.
Can somebody please explain to me why all this is happening like it is right now and how I can make them widgets show up and add them to a scrollArea?
Thanks in advance!
The reason for having PySide instead of PyQt is that you used the pyside-uic tool instead if pyuic, which is a tool that creates very similar files but using PyQt.
Both PySide and PyQt are python bindings to Qt, and they work almost seamlessly, except from some implementation differences; this means two important things:
they cannot be used together;
most of the times you can just change the import statement at the beginning of each script to switch between them (from PyQt5 import... instead of from PySide2 import...);
They also have different licenses, so you need to check that if you want to create a commercial program.
About the created object, that is what is generally called a "form class", and it's used to "set up" a Qt widget instance: it's not a widget, but some sort of utility.
In order to properly use those generated scripts (which should never be manually modified), you must import them and then use them as a separate instance that will create the UI or as a base class for multiple inheritance, which is the most common method; the following example assumes that you used pyuic (not pyside-uic) to generate a file named mywidget.py, and creates a QWidget using the multiple inheritance:
from PyQt5 import QtWidgets
from mywidget import Ui_Form
class MyWidget(QtWidgets.QWidget, Ui_Form):
def __init__(self):
super().__init__()
self.setupUi(self)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())
Read more about this in the official guidelines about using Designer.
Some more insight can be found in the answers to this related post
Related
I wonder if there's something I'm missing here. I used properties to modify some of my widget styles in Qt5, but it doesn't seem to work in Qt6. If it's a bug I'll report it elsewhere, but I'm just wondering if I'm doing something wrong.
I've got a simple example below, where the property change successfully triggers a style change when qt=5, but not when qt=6. The color should change as the number increments. Any help appreciated!
Qt5 working
Qt6 NOT working
qt = 6
if qt == 6:
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QApplication, QLabel, QWidget, QVBoxLayout
from PyQt6.QtCore import QTimer
elif qt == 5:
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QVBoxLayout
from PyQt5.QtCore import QTimer
import sys
class SampleWidget(QWidget):
def __init__(self):
super().__init__()
self.timer1, self.timer2, self.timer3 = QTimer(), QTimer(), QTimer()
self.timer1.singleShot(1000, self.fun1)
self.timer2.singleShot(2000, self.fun2)
self.timer3.singleShot(3000, self.close) # noqa
self.label = QLabel('0')
font = QFont()
font.setPointSize(50)
self.label.setFont(font)
self.layout = QVBoxLayout(self)
self.layout.addWidget(self.label)
self.setLayout(self.layout)
def fun1(self):
self.label.setText('1')
self.set_property(True)
def fun2(self):
self.label.setText('2')
self.set_property(False)
def set_property(self, style_red):
self.label.setProperty('StyleRed', style_red)
self.label.style().unpolish(self.label)
self.label.style().polish(self.label)
self.label.update()
QApplication.processEvents()
app = QApplication([])
app.setStyleSheet('QLabel[StyleRed=true]{color:red;} QLabel[StyleRed=false]{color:green;}')
gui = SampleWidget()
gui.show()
sys.exit(app.exec())
The behavior has slightly changed from Qt5 to 6, but in both cases the documentation already suggests the solution:
Warning: If the value of the Qt property changes after the style sheet has been set, it might be necessary to force a style sheet recomputation. One way to achieve this is to unset the style sheet and set it again.
This is the proper way to achieve this in Qt5 too, by the way, as it ensures that the stylesheet is properly [re]propagated to children widgets, and automatically causes repolishing anyway.
Since stylesheets are cascading, there's not an automatic way to know the origin of the style change.
In your case, this can be enough:
def set_property(self, style_red):
self.label.setProperty('StyleRed', style_red)
app.setStyleSheet(app.styleSheet())
But, if you want to avoid polishing the whole application (which requires some amount of time and resources), you can temporarily set another stylesheet and restore the previous one, which is valid for widgets that might or might not have any stylesheet set:
def set_property(self, style_red):
self.label.setProperty('StyleRed', style_red)
old = self.label.styleSheet()
if not old:
self.label.setStyleSheet('* {}')
self.label.setStyleSheet(old)
Note:
singleShot is a static function, there's no need to create references for those timers, since you're not actually using them; just use QTimer.singleShot(1000, self.fun1), etc.;
property values for selectors should always use quotes; they work fine for single-word values, but it's good practice to always use them anyway;
This question already has answers here:
Benefits of using pyuic vs uic.loadUi
(2 answers)
Closed 1 year ago.
Is there an advantage to:
Converting it to python
pyside6-uic mainwindow.ui > ui_mainwindow.py
and then
import sys
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtCore import QFile
from ui_mainwindow import Ui_MainWindow
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
vs. loading it directly like so: ?
ui_file = QFile("mainwindow.ui")
ui_file.open(QFile.ReadOnly)
loader = QUiLoader()
window = loader.load(ui_file)
window.show()
I suppose the app will start faster/run faster if converted beforehand.
Is there anything else to consider?
There are two main differences:
in terms of loading, QUiLoader theoretically adds a bit of overhead because it has to build the ui everytime, meaning that it has to parse the XML file, create the node structure, and then create the UI with all its contents; the uic file, instead, directly creates the UI, skipping the first two steps above;
QUiLoader can only create a new widget based on the UI file, while the uic method allows to use an already existing base widget, and the child widgets can be added to it;
The latter point is probably the most important: using QUiLoader you cannot directly use subclassing for the loaded UI.
For instance, if you create a main window in Designer, QUiLoader will return a new QMainWindow. You cannot (or, at least, you should not) do the following:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
ui_file = QFile("mainwindow.ui")
ui_file.open(QFile.ReadOnly)
loader = QUiLoader()
window = loader.load(ui_file, self)
And you should not even try to make the returned object as central widget, like the following:
self.setCentralWidget(window)
because the result would be to have a QMainWindow inside a QMainWindow, which is discouraged and unsupported, and might also create problems when using the standard features of a QMainWindow (typically, docks and toolbars).
The only alternative would be to create a basic form widget in Designer and use that as central widget, with the downside that menus, docks and toolbars have to be created by code.
For PySide, the only possibility that allows full subclassing is to use the pyside-uic method and then eventually use the multiple inheritance (but that's not a requirement, as composition is a valid alternative anyway):
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
On the other hand, PyQt provides the loadUi function that actually does what setupUi does, since the second argument is not the parent widget, but the widget itself, and the contents of the ui will be loaded into it:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi("mainwindow.ui", self)
As far as I know, PySide doesn't provide anything similar yet.
Note that loading the ui at runtime has two issues anyway, and for both bindings:
there is no prior sanity checking, if the UI file is corrupted or invalid, or has unsupported features/properties due to version mismatch, it might not load properly or even crash;
when using an IDE, there's no code completion for ui objects, since they are only loaded at runtime;
Those are not major issues, but it's important to be aware of them anyway.
I am trying to develop a small gui using PySide and the QT Creator.
As the base implementation, I have choosen a QMainWindow.
The problem is that adding any elements to that MainWindow inside the Editor results in an empty window when I run the code.
The initially generated python code looks like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.load_ui()
def load_ui(self):
loader = QUiLoader()
path = os.path.join(os.path.dirname(__file__), "form.ui")
ui_file = QFile(path)
ui_file.open(QFile.ReadOnly)
loader.load(ui_file, self)
ui_file.close()
if __name__ == "__main__":
app = QApplication([])
widget = MainWindow()
widget.show()
sys.exit(app.exec_())
I know that loader.load(ui_file, self) returns a widget. My solution was:
def load_ui(self):
loader = QUiLoader()
path = os.path.join(os.path.dirname(__file__), "form.ui")
ui_file = QFile(path)
ui_file.open(QFile.ReadOnly)
self.window = loader.load(ui_file, self)
self.window.show()
ui_file.close()
if __name__ == "__main__":
app = QApplication([])
widget = MainWindow()
#widget.show() <<<<<<--- removing this
sys.exit(app.exec_())
This works for me but this clearly cannot be the way this is supposed to work.
I am confused on why QT Creator gives me this non-working templaet.
Furthermore I am curious on how custom functions should be implemented.
When clicking on the clicked() slot for a button, it tells me
No ui_form.h found! (translated)
I ended up writing the functions into my MainWindow class like this:
self.window.menuopen_button.clicked.connect(lambda x: self.menu_animation(x))
self.window.minimise_button.clicked.connect(lambda x: self.menu_buttons('minimise'))
Your initial code only loads the UI file into the QUiLoader object - you don't do anything else with it. When you call widget.show(), you're calling the QMainWindow's show method which is a default window (and, hence, empty) since you haven't added the loaded widget to it.
Your solution similarly loads the UI but displays it via the widget object's show method. However, you're not amending it to your main window, so you're basically tossing your main window and just using the widget.
There are a couple ways to correctly accomplish what you want:
One way would be to, as #musicamante suggested, add the loaded UI widget to your MainWindow object (and by the way, this is supported by PySide2: https://doc.qt.io/archives/qtforpython-5.12/PySide2/QtUiTools/QUiLoader.html - the example in the detailed description shows exactly how to do this).
Or the other way, which I prefer, would be to use the uic utility, PySide2-uic, in the build process to generate a UI Python class based on the UI file.
The Qt documentation has tutorials showing both methods. The linked documentation shows PySide6 as support for PySide2 has been discontinued with the release of Qt6. The API for PySide2, PySide6, and PyQt5 are all very similar, so if you're migrating from either PySide2 or PyQt then the change should be relatively painless. I recommend using PySide6 as this is the Python binding officially supported by Qt.
This question already has answers here:
QtDesigner changes will be lost after redesign User Interface
(2 answers)
Closed 4 years ago.
I've been looking for a better way to work with frontends made using qtDesigner connected to a python backend. All the methods I have found have the following form:
Make GUI in designer
Output to python code using pyuic (usually with -x option)
Write backend code inside this output file
This methodology is not simple to maintain or edit. Any time you change the UI, it completely breaks workflow: you have to reconvert, generate a new file, fix that file back up to where you were before, then finally get back on track. This requires a lot of manual copy-paste of code, which is an invitation to errors on multiple levels (newly generated file layout may not be the same, manually fixing name changes while pasting, etc.). You can also end up losing work if you aren't careful, since you could accidentally overwrite the file and destroy the backend code.
Also, this doesn't use any of the control in qtDesigner like the Signal/Slot and Action editors. These must be here for something, but I can't find a way to actually direct these to call backend functions.
Is there a better way to work with this, possibly using the features of qtDesigner?
You don't have to add your code in the output file :
If you take a 'home.py' generated by PYUIC, containing a QMainWindow which name set by QtDesigner/generated by Puic would be Ui_Home(), your main code could be :
from home import Ui_Home
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class window_home(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
#set up the user interface from Designer
self.ui = Ui_Home()
self.ui.setupUi(parent)
#then do anything as if you were in your output file, for example setting an image for a QLabel named "label" (using QtDesigner) at the root QMainWindow :
self.ui.label.setPixmap(QPixmap("./pictures/log.png"))
def Home():
f=QMainWindow()
c=window_home(f)
f.show()
r=qApp.exec_()
if __name__=="__main__":
qApp=QApplication(sys.argv)
Home()
I found an even cleaner method for working with this, that does not require preemptive conversion after each edit at all. Instead it takes the .ui file itself, so all you need to do is restart the program itself to update the design.
import sys
import os
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5 import uic
path = os.path.dirname(__file__) #uic paths from itself, not the active dir, so path needed
qtCreatorFile = "XXXXX.ui" #Ui file name, from QtDesigner, assumes in same folder as this .py
Ui_MainWindow, QtBaseClass = uic.loadUiType(path + qtCreatorFile) #process through pyuic
class MyApp(QMainWindow, Ui_MainWindow): #gui class
def __init__(self):
#The following sets up the gui via Qt
super(MyApp, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
#set up callbacks
self.ui.NAME OF CONTROL.ACTION.connect(self.test)
def test(self):
#Callback Function
if __name__ == "__main__":
app = QApplication(sys.argv) #instantiate a QtGui (holder for the app)
window = MyApp()
window.show()
sys.exit(app.exec_())
Note that this is Qt5. Qt5 and Qt4 are not API compatible, so it will be a little different in Qt4 (and presumably earlier as well).
This is on PyQt4, Linux and Python 2.5
Can I make PyQt set my window "always on top" over other applications?
For example, in GTK i use the property: Modal.
Now, in PyQt I am using a QWidget, but, I can't find a way to do that.
Any ideas??
Pass the QMainWindow the WindowStaysOnTopHint window flag (or use setWindowFlags).
As in the name, this is a hint to the windowing manager (not a hard guarantee).
Simplest possible example:
import sys
from PyQt4 import QtGui, QtCore
class mymainwindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self, None, QtCore.Qt.WindowStaysOnTopHint)
app = QtGui.QApplication(sys.argv)
mywindow = mymainwindow()
mywindow.show()
app.exec_()
setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
setwindowaFlags is a method that can call it from form object and just take one parameter is a constant QtCore.Qt.WindowStaysOnTopHint that refer to make your form Stays On Top