PyQt5: create multiple views with designer and connect them in one app - python

I have completed a simple app in PyQt5, where I did design the UI in QT designer, converted in py code with pyuic5and ran it via python interpreter.
Now I would like to add another UI view, although I am not familiar with PyQt5, and most of the tutorial I found are only mentioning one view.
If I was using Visual Studio for example, I could create a new form, and use show and hide methods to display them, when I press a button for example, but I am not sure how to do the same with PyQt5.
Converted code from pyuic5 include also the if __name__ == "__main__" function, which create the instance and run the app, so is it enough to just take anything above it, to get the UI data only? And how do I create a view from that, so I can show and hide it as needed? Thanks.
EDIT:
Got a bit further, since I found a different way to load UI files. Seems that PyQt has a method that is able to load a UI file directly, instead of convert it in python code. This means that I can create a class that is a subclass of the type of window that I am using (example: QApplication, QMainWindow, QWidget and so on), and I can access that object as if it was a form in Visual Studio.
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow
from PyQt5.uic import loadUI
class UIObject(QMainWindow):
def __init__(self):
super(UIObject, self).__init__()
loadUI('mainapp.ui', self)
self.mybutton1.clicked.connect(self.printhello)
def printhello():
print("hello")
app = QApplication(sys.argv)
mainview = UIObject()
mainview.show()
sys.exit(app.exec_())
This will load the UI file and show it on screen; I assume that I can use the same construct to load multiple ui files and show then or hide them as I do in Visual studio? Seems straightforward but not knowing much about QT or PyQT, I am not sure why this way to handle ui files is not more commonly advertised in tutorials; I found it by chance while reading the docs.

Found the solution, mixing up various answers and posts from different forums.
You create a first class as QMainWindow, in the __init__ you use loadUi to load the QT designer file. Then you create a second class, which is the one that hold your second form/view, and in the __init__ you pass as parameter the parent view (your first class, or whatever other you may need); so you can hide the main view and show the second view when clicking a button. When you close the secondary view, the previous view will show up again.
You can add as many different windows you want; the trick is to always pass the parent on each of them and remember to show/hide them accordingly. Much more complex than Visual Studio forms, but it is doable.
class FirstForm(QMainWindow):
def __init__(self):
super(FirstForm, self).__init__()
loadUi('firstform.ui', self)
self.button1.clicked.connect(self.openOtherForm)
def openOtherForm(self):
self.hide()
otherview = SecondForm(self)
otherview.show()
class SecondForm(QDialog):
def __init__(self, parent=None):
super(SecondForm, self).__init__(parent)
loadUi('secondform.ui', self)
self.button2.clicked.connect(self.goBackToOtherForm)
def openOtherForm(self):
self.parent().show()
self.close()
app = QApplication(sys.argv)
main = FirstForm()
main.show()
sys.exit(app.exec_())

Related

How to add functions to Qt buttons from .ui file?

I have just started using and discovering PyQt5 and Qt. I installed it with
pip install pyqt5. Then I opened the designer.exe that comes with pyqt-tools and made my design.
After that I converted it from .ui to .py using pyuic design.ui > dialog.py.
When I run app.py it runs fine and the ui is working fine. But now my problem is: How do I add functions that get called when a button is pressed?
Here is my app.py code:
import sys
from PyQt5.QtWidgets import QDialog, QApplication
from dialog import Ui_Form
class AppWindow(QDialog)
def __init__(self):
super().__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
self.show()
app = QApplication(sys.argv)
w = AppWindow()
w.show()
sys.exit(app.exec_())
Your approach is actually correct (at least, one of the correct ones): the pyuic generated files should never be modified unless you really (REALLY) know what you're doing and why. Also, their behavior should never be mimicked, as there's no reason to do that.
As a (I'd say, mandatory) reference, you can read more about this topic on the official PyQt guidelines about using Designer.
Let me do an important premise about this topic.
What you're doing is known as the single inheritance approach, meaning that you're creating a subclass that only inherits from the QWidget subclass you're using (QDialog, in this case) and you're building the ui on top of it using what's referred to as the form class. The form class is a standard Python object type that is responsible of creating all child widgets on top of the main subclass instance when its setupUi function is called.
The result is that the subclassed instance will be always referred to self within its scope, while all its children are available through the self.ui object.
A similar (and more common) approach is the multiple inheritance approach. In this way you inherit from both the QWidget subclass (QDialog) and the ui object. The UI result will be the same, but the widgets will be more directly available using self.objectName instead of self.ui.objectName.
Whether you use one or the other is a matter of choice, just remember that whenever you use the multiple inheritance approach the setupUi will potentially overwrite any previously set instance attribute, and if you create a new attribute for an already existing object name, that object will not be (directly) accessible anymore.
Finally, let me give you another suggestion: while not completely wrong, the link posted in the other answer not only gives a very misleading suggestion, but doesn't even dig more in why the previous post it refers to was wrong about. The bottom line is that pyuic created files should never be modified (nor you should try to mimic their behavior) for lots of reasons: not only if you need to change your ui you'll need to merge the already modified code with the new one (hoping that you've not overwritten it using pyuic), but it also leads to misconceptions about the whole object structure.
So, in your case (with the single inheritance approach), all objects created within Designer are actually accessible using self.ui.objectName, and that objectName is what is shown in the object inspector of Designer.
Let's assume that you created a UI with a single push button and you want to print something when that button is pressed. If you didn't do too many tests and modifications to your ui, that button's object name is probably "pushButton".
Then, your code will probably look like this:
import sys
from PyQt5.QtWidgets import QDialog, QApplication
from dialog import Ui_Form
class AppWindow(QDialog)
def __init__(self):
super().__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton.clicked.connect(self.buttonClicked)
def buttonClicked(self):
print('button clicked!')
app = QApplication(sys.argv)
w = AppWindow()
w.show()
sys.exit(app.exec_())
Note that Qt object names are not unique. You could theoretically set the same object name for more than one QObject (or QWidget), but there's no use in that. Consider that from the python perspective: each variable should have its own name, if you overwrite an already existing variable name, the previous one will become unaccessible (if not even garbage collected).
Designer is smart enough (not that it requires so much...) to prevent creating objects sharing the same object name, and that's why if you add another button, it will be named pushButton_2, pushButton_3, etc. PyQt takes benefit from that by creating unique instance attributes for all object names whether you're using the pyuic file or uic.loadUi().
For the sake of completeness, here's how a multiple inheritance approach would look, while behaving as the example above:
import sys
from PyQt5.QtWidgets import QDialog, QApplication
from dialog import Ui_Form
class AppWindow(QDialog, Ui_Form)
def __init__(self):
super().__init__()
self.setupUi(self)
self.pushButton.clicked.connect(self.buttonClicked)
def buttonClicked(self):
print('button clicked!')
app = QApplication(sys.argv)
w = AppWindow()
w.show()
sys.exit(app.exec_())
In light of what explained above, consider the attribute name issue: if you create an attribute named self.pushButton before calling self.setupUi, after that call self.pushButton will be a reference to the push button and the previously set value will be lost; if you create a new attribute named self.pushButton after calling self.setupUi you won't be able to access the QPushButton instance anymore.
Now, while for normal usage they usually behave in the same way, there are small but still important differences between using the pyuic generated files, or uic.loadUiType(), and the more direct uic.loadUi, and unfortunately those differences are not officially documented (and, frankly, I don't remember all of them).
An example has been previously reported by eyllanesc in the question Size of verticalLayout is different in Qt Designer and PyQt program.
Consider that those are very specific exceptions (which might even be addressed in future releases of PyQt, especially now that Qt6 is on the way) and they normally don't create major issues for standard programming.
Finally (at last!), while Designer is usually smart, is not quite perfect. For example, "technically unimportant" objects as QAction separators are considered as "anonymous" and designer doesn't even apply their object names: if you add a separator to a menu, there's no way to directly access it, even if you actively name it.
I would recommend that you don't programm in that converted pythonscript. Just because if you want to change your layout and convert it again you overwrite everything programmed there.
Import this converted ui into a new pythonscript. There are tons of introductions like this (google them)
https://nitratine.net/blog/post/how-to-import-a-pyqt5-ui-file-in-a-python-gui/
Then you should read about slots and signals (you can also add them in the qt designer) or add the functions in pythonscript:
https://www.tutorialspoint.com/pyqt/pyqt_signals_and_slots.htm
its the easiest way to learn about them when following the tutorials ...

How to start project with TabWidget without QWidget

Everytime you create new project from Qt Creator there is on top QWidget which you can delete.
You can of course write the code manually as this:
app = QApplication(sys.argv)
ex = Main()
sys.exit(app.exec_())
where Main class is inherited from QTabWidget and everything works.
class Main(QTabWidget):
def __init__(self):
super().__init__()
Is there any way how to achieve this from Qt Designer?
How can i delete QWidget and put QTabWidget on top of hierarchy?
When you create a GUI with Qt Designer you can choose the widget that you take as base as shown in the following image:
Generally we choose from the first option templates/forms, but we can also choose from Widgets (Second option)
In this second option we can choose as a basis for QTabWidget.

Getting unittest PySide and Maya on commandline to work

I have a Maya environment with a PySide window which gets generated on the fly with whatever is in that Maya scene. I'm trying to now take that to command-line and make unittests out of it.
I have everything working, minus one problem.
Most PyQt/PySide unittest documentation state to create a QApplication like this:
app = QApplication(sys.argv)
win = SomeWindow()
sys.exit(app.exec_())
This doesn't work because there's already a QApplication instance, built from Maya.
RuntimeError: A QApplication instance already exists.
Excluding these steps though yields this error and the tests fail:
QWidget: Cannot create a QWidget when no GUI is being used
I know that there's a QApplication instance in the scene, because this command yields a QApplication instance:
QApplication.instance()
So how do I associate the GUI that I want to create with that instance? You can't exec_() Maya's running QApplication so I'm not sure how to get my GUI to see the QApplication.
_
Found the solution. The issue was that I wasn't holding a global reference to the same instance across all of my tests (multiple Maya files were being created/destroyed over and over).
So somewhere at the top of the file, you just write
APP = None
Then in each test, import another file that keeps a link to that QApplication instance as a singleton and set APP equal to it

Using QUiLoader and UI files in PySide to dynamically create user interface at run-time

I'm really having a hard time connecting slots from Python to Qt Designer UI files.
I've been through all tutorials I could find on PySide (ex: http://zetcode.com/gui/pysidetutorial/eventsandsignals/)
Its quite easy when you set up the GUI in code, but we really would like to use Qt Designer and UI files.
Some other threads just points to the use of pyuic to convert .ui to .py files, but if its possible I would really like to do this at run-time.
Here is my code so far.
I have no clue how to connect the connectBtn to the Connect in the UI file :
def initUI(self):
loader = QUiLoader()
file = QFile("designer_test.ui")
file.open(QFile.ReadOnly)
myWidget = loader.load(file, self)
#print(dir(myWidget))
file.close()
layout = QtGui.QVBoxLayout()
layout.addWidget(myWidget)
self.setLayout(layout)
connectBtn = QtGui.QPushButton("Connect", self)
connectBtn.clicked.connect(self.connectClicked)
myWidget.setWindowTitle('Window')
myWidget.show()
def connectClicked(self):
print("works")
Have you checked this page: Using a Designer UI File in Your Application
It is for C++, but I think the concepts are the same as what you're trying to do in python.
According to that page, to get the widgets that are created by the Ui file you need to call findChild().
Also, this question.
I've made this auto-connector to help me with this... please take a look at it.

Can I create a panel-like thing on Windows with Python?

Sorry for the vague title, couldn't come up with anything more informative %)
What I want is a 5px horizontal panel on the top of the screen that I can draw on (and, possible, handle clicks on too).
One of the following features would be awesome (although I understand it's probably not really possible to combine both of them):
the panel should be just like the Windows's own taskbar, i.e., maximized windows should not overlap it, but start below it instead
the panel should show in fullscreen apps too
Is it possible to do this in Python?
Thanks.
Yes, it's possible. The "how" part depends on the GUI library you choose for which there are many options, but most people will recommend the following two: wxPython or PySide which is Qt for Python.
PySide has good documentation and tutorials.
What you will want to do is create a QMainWindow instance and set the WindowFlags to your requirements. You probably want the following combination Qt::Window | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint.
Something like this:
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class Form(QMainWindow):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint)
if __name__ == '__main__':
# Create the Qt Application
app = QApplication(sys.argv)
# Create and show the form
form = Form()
form.show()
# Run the main Qt loop
sys.exit(app.exec_())
Note, that there is a limit to the "staying on top" nature of such windows. There are Win32-specific ways to fight it and get even higher, but such requirement would be a design error.

Categories

Resources