I need to add some features to a graphics widget in a form I created using the Qt Designer.
For example I would normally do something like this:
class custom_gv(QGraphicsView):
def __init__(self):
super().__init__()
def zoom(self):
# custom code here
But in this case the graphics view is a part of the window I made in Qt Designer. I know you can use the "promote to" feature in Qt designer but I don't know how to utilise that in code, especially considering that I use this method to use Qt Designer windows:
from PyQt5.uic import loadUiType
custom_window = loadUiType('ui.ui')
class Window(QMainWindow, custom_window):
def __init__(self):
QMainWindow.__init__(self)
custom_window.__init__(self)
self.setupUi(self)
So how would I go about customising the code of the graphics view in my window when I use Qt Designer?
The most common way to solve this is by using widget promotion. This will allow you to replace a widget defined in Qt Designer with your own custom class. The steps for doing this are as follows:
In Qt Designer, select the QGraphicsView you want to replace, then right-click it and select Promote to... . In the dialog, set Promoted class name to "custom_gv", and set Header file to the python import path for the module that contains this class (e.g. "mypkg.widgets"). Then click Add, and Promote, and you will see the class change from "QGraphicsView" to "custom_gv" in the Object Inspector pane.
When the Qt Designer ui file is converted into PyQt code, it will automatically add an import statement like this:
from mypkg.widgets import custom_gv
and then in the converted code it will replace something like this:
self.graphicsView = QtWidgets.QGraphicsView(MainWindow)
with this:
self.graphicsView = custom_gv(MainWindow)
So the code in the ui file knows nothing about the custom class: it's just a name that is imported from elsewhere. That means you are completely free to write the custom class in any way you like.
In PyQt, this mechanism works in the same way with pyuic as it does with the uic module. The loadUi and loadUiType functions generate exactly the same code as pyuic does. The only difference is that the pyuic tool writes the generated code to a file, whereas the uic module loads it directly via exec.
Related
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.
My problem is that I have a file with my UI called xxx.ui. Then as many have suggested I created another python file called test.py where I have put code to use my xxx.ui:
# imports
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import uic
import sys
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('xxx.ui', self)
self.show()
app = QtWidgets.QApplication(sys.argv)
window = Ui()
app.exec_()
Up until this stage everything works ok. But now I would like to add a checkbox to my UI whe program starts without messing inside xxx.ui (so the checkbox will be created dynamicaly when the program runs).
How can I do that ???
Thank You in advance.
After many fail attempts I have found how to do it:
To add Checkbox outside of xxx.ui file. I went to my test.py file and added code below this line:
uic.loadUi('xxx.ui', self)
The code looks like that (I am using horizontal layout widget created in designer called seasonLayout and my checkbox is inside that layout):
self.checkBox = QtWidgets.QCheckBox(self.horizontalLayoutWidget)
self.checkBox.setObjectName("checkBox_0")
self.checkBox.setText('Hello')
self.seasonLayout.addWidget(self.checkBox)
Then if You want to get to that object all You have to do is to use code below:
(here i change text of this newly created checkbox):
self.checkBoxs = self.findChild(QtWidgets.QCheckBox, 'checkBox_0')
self.checkBoxs.setText('test')
Hopefuly it will be helpful for other because I have really tried to find answer for that almost everywhere and everyone were just using widgets from designer - noone explain how to add them outside of it.
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).
I need to add some features to a graphics widget in a form I created using the Qt Designer.
For example I would normally do something like this:
class custom_gv(QGraphicsView):
def __init__(self):
super().__init__()
def zoom(self):
# custom code here
But in this case the graphics view is a part of the window I made in Qt Designer. I know you can use the "promote to" feature in Qt designer but I don't know how to utilise that in code, especially considering that I use this method to use Qt Designer windows:
from PyQt5.uic import loadUiType
custom_window = loadUiType('ui.ui')
class Window(QMainWindow, custom_window):
def __init__(self):
QMainWindow.__init__(self)
custom_window.__init__(self)
self.setupUi(self)
So how would I go about customising the code of the graphics view in my window when I use Qt Designer?
The most common way to solve this is by using widget promotion. This will allow you to replace a widget defined in Qt Designer with your own custom class. The steps for doing this are as follows:
In Qt Designer, select the QGraphicsView you want to replace, then right-click it and select Promote to... . In the dialog, set Promoted class name to "custom_gv", and set Header file to the python import path for the module that contains this class (e.g. "mypkg.widgets"). Then click Add, and Promote, and you will see the class change from "QGraphicsView" to "custom_gv" in the Object Inspector pane.
When the Qt Designer ui file is converted into PyQt code, it will automatically add an import statement like this:
from mypkg.widgets import custom_gv
and then in the converted code it will replace something like this:
self.graphicsView = QtWidgets.QGraphicsView(MainWindow)
with this:
self.graphicsView = custom_gv(MainWindow)
So the code in the ui file knows nothing about the custom class: it's just a name that is imported from elsewhere. That means you are completely free to write the custom class in any way you like.
In PyQt, this mechanism works in the same way with pyuic as it does with the uic module. The loadUi and loadUiType functions generate exactly the same code as pyuic does. The only difference is that the pyuic tool writes the generated code to a file, whereas the uic module loads it directly via exec.
I have tried the procedure (Qt Charts and Data Visualization widgets) to integrate the qtchart plugin.
But it don't work. Makeing the plugin and add it to the desinger folder worked. qt designer ‎recognize the plugin but compiling the ui to a python file I get following error:
Unknown Qt widget: QtCharts.QChartView
I'm using linux with qt 5.7 and qtcharts and also pyqtcharts.
I thing the problem is the 's' at the end of QtCharts, but I have no idea how I can fix it.
Hope someone has a idea.
You don't have to integrate it.
Add a normal Widget in qt-designer, then right click it, and select Promote to ....
In the window that opens, write QChartView for the Promoted class name:, and PyQt5.QtChart for the Header file:. Press Add. It will be added to the list of Promoted Classes. Select it from the list, and press Promote. That's it.
Then in your python code, you can write something like this:
from PyQt5.QtChart import QChart, QLineSeries
...
chart = QChart()
series = QLineSeries()
series.append(1,3)
series.append(2,4)
chart.addSeries(series)
chart.setTitle('Example')
chart.createDefaultAxes()
self.ui.widget.setChart(chart) # this is the view you added in qt-designer
Make sure you have pyqtchart installed (with pip).