I'm creating an application which loads a couple of .ui files. The first one is of type QMainWindow and the others are of type QWidget.
I can't figure out how to load the second UI (module.ui) into self, making widgets accessible through self.<widget_name>.
How can this be achieved?
from PyQt4 import QtGui
from PyQt4 import uic
class TestApp(QtGui.QMainWindow):
def __init__(self):
super(TestApp, self).__init__()
# Load main window and the module
uic.loadUi('main_window.ui', self) # QMainWindow, contains testLayout, loads into self
ui_module = uic.loadUi('module.ui') # QWidget
# Attach module to main window
self.testLayout.addWidget(ui_module) # this works fine
# Edit widget in UI module
self.label.setText('Hello') # does not work (since self.label doesn't exist)
I could do this:
self.label = ui_module.label
self.label.setText('Hello')
...but I'd like to instead load the UI into self from the start.
If I try to load the UI into self, I get an error:
uic.loadUi('module.ui', self)
>>> QLayout: Attempting to add QLayout "" to TestApp "Form", which already has a layout
You need to create a widget to load the ui file onto
self.widget = QWidget(self)
uic.loadUi('module.ui', self.widget)
self.widget.label.setText('Hello')
That being said, it would probably be better if you created a separate class for the other widget.
class MyWidget(QWidget):
def __init__(self, **args, **kwargs):
super(MyWidget, self).__init__(*args, **kwargs)
uic.loadUi('module.ui', self)
self.label.setText('Hello')
class TestApp(QtGui.QMainWindow):
def __init__(self):
...
self.widget = MyWidget(self)
Related
getting the CPU percentage from psutil and display it on a progress bar.
i was able to display the value of the CPU percentage but it's not updating.
i have a file from QT designer called Progress i convert it using pyuic5 to py file and import it in the main file called cpuprogress.py, also i created a python file called sysinfo to get the value of CPU percentage and import it to the main file.
import sys
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication)
from progress import Ui_Form
import sysinfo
class MyTest(QWidget, Ui_Form):
def __init__(self, parent = None):
super(MyTest, self).__init__(parent)
self.setupUi(self)
self.threadclass = ThreadCLass()
self.threadclass.start()
self.threadclass.change_value.connect(self.updateProgressBar)
def updateProgressBar(self, val):
self.progressBar.setValue(val)
class ThreadCLass(QThread):
change_value = pyqtSignal(int)
def __init__(self, parent = None):
super(ThreadCLass, self).__init__(parent)
def run(self):
while 1:
val = int(sysinfo.getCPU())
self.change_value.emit(val)
a =QApplication(sys.argv)
window = QWidget()
app = MyTest()
app.setupUi(window)
app.show()
sys.exit(a.exec_())
enter image description here
tl;dr
The problem is that you called setupUi twice, and the second time it was using another widget as argument.
Why doesn't it work?
When you generate a file with pyuic, it actually creates a class, based on Python's object type. That class doesn't do anything on itself, is just an "interface" to load the elements of the ui in a "pythonic way".
If you try to open one of those files (which should never be edited!), you'll see something like this:
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
self.progressBar = QtWidgets.QProgressBar(Form)
self.progressBar.setProperty("value", 24)
# ...
def retranslateUi(self, Form):
# things related to the localization of the ui
The official guide on using Designer reports as a first example that actually does nothing, besides showing the window.
from PyQt5.QtWidgets import QApplication, QWidget
from ui_form import Ui_Form
# create an instance of the base class
window = QWidget()
# create an instance of the Ui "builder"
ui = Ui_Form()
# apply the ui to the base class created before
ui.setupUi(window)
The last step is the most important to understand: look carefully to what object the setupUi method belongs, and its argument, then go back to the contents of the file created with pyuic:
def setupUi(self, Form):
Form.setObjectName("Form")
self.progressBar = QtWidgets.QProgressBar(Form)
Form is the QWidget instance created before ("window"), while obviously self refers to the instance of Ui_Form; let's translate the variables with the name of the instancies they represent:
def setupUi(ui, window):
window.setObjectName("Form")
ui.progressBar = QtWidgets.QProgressBar(window)
In the example at the beginning it means that, while window is displayed with its children widgets, they are not attributes of the instance: there's no window.progressBar.
The second example in the documentation shows the "single inheritance" method, that allows the implementation of interactions between widgets (the "logic"). I'll use the class names as I did above:
from PyQt5.QtWidgets import QApplication, QWidget
from ui_form import Ui_Form
class MyWidget(QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
Now ui is an attribute of the window instance; let's "translate" setupUi once again, assuming that now "self" is the instance of MyWidget that is being created:
class Ui_Form(object):
def setupUi(window.ui, window):
window.setObjectName("Form")
window.ui.progressBar = QtWidgets.QProgressBar(window)
This means that now you can have access to the widgets from the window instance, but only through self.ui (as in, window.ui).
Now, let's see the multiple inheritance approach, which is very similar:
class MyWidget(QWidget, Ui_Form):
def __init__(self):
super(MyWidget, self).__init__()
self.setupUi(self)
In this case, MyWidget inherits from the methods and attributes of both Qwidget and Ui_Form. Let's translate it again (note the class):
class MyWidget(object):
def setupUi(window, window):
window.setObjectName("Form")
window.progressBar = QtWidgets.QProgressBar(window)
This approach makes all widgets as direct attributes of the instance (self.progressBar, etc), and I usually suggest it as it's more direct and simple (it often happens that you try to access a widget and forget that ui prefix, with this method it doesn't happen).
Now, finally, your problem.
window = QWidget()
app = MyTest()
setupUi is called in the __init__ of MyTest, which means that app has an attribute called progressBar, which at this point should be "visible" as soon as we call app.show().
You also created another widget, though (window), and used setupUi using that as parameter:
app.setupUi(window)
Let's translate it one last time (to avoid confusion, let's change the name of the "window" QWidget you created by mistake):
def setupUi(app, otherWindow):
otherWindow.setObjectName("Form")
app.progressBar = QtWidgets.QProgressBar(otherWindow)
As you can see, now you've "overwritten" the app.progressBar attribute, but that progress bar is actually a new one, and it is a child of window (which you never show).
The updateProgressBar function then will successfully modify the value of the progress bar, but not the one you see.
Finally, there's also another way of using ui files, and it is through the uic module, which can directly load ui files.
from PyQt5 import QtWidgets, uic
class MyTest(QtWidgets.QWidget):
def __init__(self, parent=None)
super().__init__(parent)
uic.loadUi('mywindow.ui', self)
This approach as a big advantage on the other: you don't have to create files with pyuic anymore, and it behaves exactly as the multiple inheritance method, so you can use self.progressBar as you did before.
I've used QtDesigner to make ui files that I then use to make classes, like
class MyPopup1(MyBaseClass, MyClass):
def __init__(self, parent=None):
super(MyPopup1, self).__init__(parent)
self.setupUi(self)
...
Granted I used some tutorial for this so I'm not actually sure what all of that does. But now I have written code that generates a popup that uses the QMessageBox class and I would like to move this code to a separate class so I can call it from multiple places.
How do I move this code to make a MyPopup2 class?
MyPopup2 = QtWidgets.QMessageBox(parent = self.central_widget)
MyPopup2.setWindowTitle("My Popup 2")
MyPopup2.setText("Some text")
MyPopup2.setIcon(QtWidgets.QMessageBox.Question)
MyPopup2.addButton("Btn1", QtWidgets.QMessageBox.RejectRole)
MyPopup2.addButton("Btn2", QtWidgets.QMessageBox.ActionRole)
choice = MyPopup2.exec_()
I know I probably need to connect the button signals to functions and use self.done() to send the result back to a call.
I am mostly confused on what to put as MyBaseClass and MyClass for the second popup.
Qt Designer provides a class that serves to fill a widget, so a recommended way is to inherit a widget and inherit from the generated class of Qt Designer, for example the structure that Qt Designer provides has the following structure:
class MyClass(object):
def setupUi(self, AAA):
...
self.retranslateUi(AAA)
QtCore.QMetaObject.connectSlotsByName(AAA)
def retranslateUi(self, AAA):
...
Then depending on the template you should choose as MyBaseClass to QMainWindow, QDialog or QWidget and call setupUi() which is the method that you add the other widget to the window as you point out:
class MyPopup1(MyBaseClass, MyClass):
def __init__(self, parent=None):
super(MyPopup1, self).__init__(parent)
self.setupUi(self)
...
But in the case that you are going to create the widget, MyClass is not necessary, so in your case the solution is the following:
from PyQt5 import QtWidgets
class MyPopup2(QtWidgets.QMessageBox):
def __init__(self, parent=None):
super(MyPopup2, self).__init__(parent)
self.setWindowTitle("My Popup 2")
self.setText("Some text")
self.setIcon(QtWidgets.QMessageBox.Question)
self.addButton("Btn1", QtWidgets.QMessageBox.RejectRole)
self.addButton("Btn2", QtWidgets.QMessageBox.ActionRole)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
popup = MyPopup2()
if popup.exec_() == QtWidgets.QMessageBox.Accepted:
print("Btn2")
else:
print("Btn1")
The full solution for the example is
class MyPopup2(QtWidgets.QMessageBox):
def __init__(self, parent=None):
super(NoMatch, self).__init__(parent)
self.setWindowTitle("My Popup 2")
self.setText("Some text")
self.setIcon(QtWidgets.QMessageBox.Question)
self.Btn1 = self.addButton("Btn1", QtWidgets.QMessageBox.RejectRole)
self.Btn2 = self.addButton("Btn2", QtWidgets.QMessageBox.ActionRole)
self.Btn1.clicked.connect(lambda: self.done(QtWidgets.QMessageBox.RejectRole))
self.Btn2.clicked.connect(lambda: self.done(QtWidgets.QMessageBox.ActionRole))
Which can be called with choice = MyPopup2.exec_() from anywhere
I have created a layout for my custom widget an created a class in file src\WidgetStartEndCenterWidth.py
from PyQt4 import QtGui, uic
from PyQt4 import QtCore
class WidgetStartEndCenterWidth(QtGui.QWidget):
def __init__(self):
super(WidgetStartEndCenterWidth, self).__init__()
self.initUI()
def initUI(self):
uic.loadUi('widgetStartEndCenterWidth.ui', self)
self.show()
self.setupSignalSlots()
def setupSignalSlots(self):
self.spinBoxStart.valueChanged.connect(self.onSpinBoxStartValueChanged)
#QtCore.pyqtSlot(int)
def onSpinBoxStartValueChanged(self, value):
self.spinBoxEnd.setValue(value)
I promoted the widget in my MainWindow as WidgetStartEndCenterWidth with header file (does this make sence at all?) WidgetStartEndCenterWidth.h.
However the execution fails with
ImportError: No module named 'WidgetStartEndCenterWidth'
I assume that this has to do with my code in directory src, but anyway I would like to know how this is supposed to be done in the Qt Designer.
EDIT:
The widget is embedded (or promoted) in the ui of MainWindow. Here is the code of that file, but naturally there is not code of the widget, because all that happens in the Qt Designer...
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi('MainWindow.ui', self)
self.show()
Assume I have two UI files from Qt Designer:mainform.ui stores mdiArea and figureslist.ui stores listView.
Now I'd like to create a mdi application, that can open numbers of figureList windows.
import sys
from PyQt4 import QtGui
#from PyQt4.QtGui import *
from PyQt4 import QtCore, QtGui, uic
class HelloWorldApplication(QtGui.QApplication):
def __init__(self, args):
QtGui.QApplication.__init__(self, args)
self.maindialog = MainUI(None)
class MainUI(QtGui.QMainWindow):
def __init__(self, parent):
QtGui.QMainWindow.__init__(self, parent)
self.ui = uic.loadUi("mainform.ui")
self.ui.show()
# create child and show it
child = self.createFiguresListView()
# problem here (*)
child.show()
def createFiguresListView(self):
child = FiguresListView()
self.ui.mdi.addSubWindow(child)
return child
class FiguresListView(QtGui.QWidget):
def __init__(self):
super(FiguresListView, self).__init__()
self.ui = uic.loadUi("figureslist.ui")
app = HelloWorldApplication(sys.argv)
sys.exit(app.exec_())
But unfortunately my child window shows up collapsed without layout described in figureslist.ui, but acts like mdi child, but if I replace code marked with (*) to child.ui.show() it shows actual layout, but doesn't act like mdi child.
What's wrong?
You forgot to set the parent for the ui (also, if you didn't specified minimum size in Designer, you need to do it here):
class FiguresListView(QtGui.QWidget):
def __init__(self):
super(FiguresListView, self).__init__()
self.ui = uic.loadUi("figureslist.ui", self)
#self.setMinimumSize(400, 200)
I've created a custom widget in pyqt4 that I've worked on and tested and now am ready to load it into my main window. Since it doesn't show up in designer, I need to manually add it to my main window manually.
My widget uses uic to load the ui file instead of converting it to a py file (it's been quicker less hassle so far) so it looks something like this:
class widgetWindow(QtGui.QWidget):
def __init__(self, parent = None):
super(widgetWindow, self).__init__(parent)
self.ui = uic.loadUi("widget.ui")
#everything else
now in my main class (example for brevity) I create the layout, add the widget to the layout and then add it to the main widget
class main(QtGui.QMainWindow):
def __init__(self, parent = None):
super(main, self).__init__(parent)
self.ui = uic.loadUi("testWindow.ui")
mainlayout = QtGui.QVBoxLayout()
window = widgetWindow(self)
mainlayout.addWidget(window)
centerWidget = QtGui.QWidget()
centerWidget.setLayout(mainlayout)
self.ui.setCentralWidget(centerWidget)
There are no errors thrown, and it will make space for the widget, but it simply won't show anything.
adding in the line window.ui.show() will just pop open a new window overtop the space that it should be occupying on the main window. What am I missing?
Doing some more research into the uic loader, there are two ways to load a ui file. The way I'm using it in the question is one way, the other way is with the uic.loadUiType(). This creates both the base class and the form class to be inherited by the class object instead of just the QtGui.QWidget class object.
widgetForm, baseClass= uic.loadUiType("addTilesWidget.ui")
class windowTest(baseClass, widgetForm):
def __init__(self, parent = None):
super(windowTest, self).__init__(parent)
self.setupUi(self)
This way, the widget can be loaded into another form as expected. As for exactly why, I haven't found that answer yet.
Some more info on the different setup types: http://bitesofcode.blogspot.com/2011/10/comparison-of-loading-techniques.html
Try to add the parent argument into the loadUi statements:
self.ui = uic.loadUi("widget.ui",parent)
self.ui = uic.loadUi("testWindow.ui",self)
And try the following line at the end of your main class.
self.setCentralWidget(centerWidget)
You need to specify that 'centerWidget' is the central widget of the main window.
i.e your code for class main should be have a line like:
self.setCentralWidget(centerWidget)
class main(QMainWindow):
def __init__(self, parent = None):
super(main, self).__init__(parent)
....
self.setCentralWidget(centerWidget)