findChild on object created within pyqt designer - python

I have the following problem with my pyqt:
Assuming i create an object within the Qt Designer and save it as an .ui file. Then i use pyuic to convert it to an .py file. Because i want to integrate a new module into a given program, this is the favorite way to go (because later the .ui files will be converted automatically at startup to .py files).
If i have a look at my .py file i see the following for the window:
class Ui_SubWindow(object):
def setupUi(self, SubWindow):
SubWindow.setObjectName(_fromUtf8("SubWindow"))
....
i have a RemoteWindow class as MainWindow where the SubWindow is initiated:
class RemoteWindow(QtGui.QMainWindow):
def __init__(self, subcore):
super(RemoteWindow, self).__init__(subcore.core.gui)
self.subcore = subcore
self.ui = Ui_SubWindow()
Now i have a core program:
class SubCore(object):
def __init__(self, core, identity, number):
...
self.gui = RemoteWindow(self)
self.newController = NewController(self.gui)
and the new controller class:
class NewController(object):
def __init__(self, subwindow):
self.subwindow = subwindow
self.ui = subwindow.ui
from my controller i want to call a .findChild() on that window
submitFrame = self.ui.findChild(QtGui.QFrame, "frameSubmit")
, but all i get is an:
AttributeError: 'Ui_SubWindow' object has no attribute 'findChild'
I assume this is, because the class Ui_SubWindow is not a child class of some QObject but of an object, am i right?
self.ui is the same as subwindow.ui, where subwindow is an instance of RemoteWindow which has as .ui argument the Ui_SubWindow Class.
Is there any chance to make the pyuic or the Qt Designer to make this SubWindow a child of QObject, without manipulating the automatically generated .py file?

You don't need to use findChild at all, because pyuic will automatically create attributes in the ui object for all the widgets defined in Qt Designer. The attribute names are taken from the objectName. So all you need is:
submitFrame = self.ui.frameSubmit

Qt Designer creates a design, that is, a class that serves to fill the main widget so the internal widgets are not children of this class, if you want to use findChild you must do it to the widget that returns this class after calling the method setupUi.
class RemoteWindow(QtGui.QMainWindow):
def __init__(self, subcore):
super(RemoteWindow, self).__init__(subcore.core.gui)
self.subcore = subcore
self.ui = Ui_SubWindow()
self.ui.setupUi(self) # Here the widget is filled
[...]
And then you should use it in the following way:
class NewController(object):
def __init__(self, subwindow):
self.subwindow = subwindow
submitFrame = self.subwindow.findChild(QtGui.QFrame, "frameSubmit")
[...]

I recently had to do this same thing and leaving this here in case someone sees this link vs the other related one.
https://stackoverflow.com/a/62340205/1621381
for name, obj in dict(self.__dict__).items():
# print(str(name) + str(obj))
obj_type = str(obj).strip("<PyQt5").rsplit(" ")[0].replace(".", '', 1)
# obj_type = str(obj).strip("<").rsplit(" ")[0]
# print(obj_type)
# obj_type = obj_str.strip("<PyQt5").rsplit(" ")[0].replace(".", '', 1)
label_name = "self." + str(name)
try:
label_name = self.findChild(eval(obj_type), name)
print(str(label_name) + ' created')
except:
pass
if not isinstance(obj_type, QObject):
continue

Related

Pyside6 - How to call parent method in child method class

I want to call a father method inside a child class method but I'm having some trouble and missing some points.
I'm able to call parent's method just inside def__init__(self)
How to solve?
I tried:
class MainWindow(QMainWindow):
ws = websocket.WebSocket()
threadpool = QThreadPool()
pippo = "pippo"
def __init__(self):
super().__init__()
tabs = QTabWidget()
tabs.setTabPosition(QTabWidget.North)
tabs.setMovable(False)
tabs.setDocumentMode(True)
self.voices = Widget("Voices", self)
tabs.addTab(self.voices, "Voices")
self.setCentralWidget(tabs)
def do_something(self):
print ('doing something!')
class Widget(QWidget):
def __init__(self, name, parent=None):
super().__init__(parent)
self.ui = Ui_Widget()
self.ui.setupUi(self)
self.name=name
self.lan = str(config_ini("language"))
self.username = str(config_ini("user"))
print(self.parent().pippo) #HERE MY CODE WORKS
self.parent().do_something() #HERE MY CODE WORKS
self.ui.InitializeButton.clicked.connect(self.clickedInitialize)
#Pressione tasto Initialize
def clickedInitialize(self):
self.parent().do_something() #HERE MY CODE ___DON'T___ WORKS
and I receive this error:
Traceback (most recent call last):
File "C:\Users\IENOGIUS\Documents\Cefla\VoiceCommands\VoiceChecker\voicechecker.py", line 294, in clickedInitialize
self.parent().do_something()
AttributeError: 'PySide6.QtWidgets.QStackedWidget' object has no attribute 'do_something'
As the addTab() documentation explains:
Ownership of page is passed on to the QTabWidget.
This would be true anyway whenever you add a widget to another, making it a child of that new parent.
In this case, it happens when tabs.addTab(self.voices, "Voices"), which reparents the widget to the tab widget (actually, its internal QStackedWidget): the window becomes the ancestor of the Widget instance, the actual parent is the stacked widget.
The structure will be the following:
MainWindow
QTabWidget
QStackedWidget (internally used by QTabWidget to display pages)
Widget
If you want to keep a reference to the main window, just use an instance attribute in the __init__ (eg. self.mainWindow = parent).
Note, though, that child objects should never directly call methods of their ancestors (see "separation of concerns"), and signals also exist for this very reason.
What you should actually do is to connect the signal from the window:
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.voices = Widget("Voices", self)
self.voices.ui.InitializeButton.clicked.connect(self.do_something)
And obviously remove that connection from Widget.
Alternatively, a better approach should use a custom signal instead:
class Widget(QWidget):
do_something_signal = Signal()
# ...
def clickedInitialize(self):
# do whatever you need, then:
self.do_something_signal.emit()
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.voices.do_something_signal.connect(self.do_something)
I suggest you to read more about the Qt object trees and ownerships and also possible proper usage of signals in complex object structures in this related question.
Note: please avoid using code comments that are not necessary for the understanding of the code, especially if in languages that are not English. And, really, don't use "pippo".

Proper implementation of access to ui elements for custom widgets in PySide or PyQT

I am building a program to view a video and do image processing on it. I want to know the proper way to give my individual classes access to the ui elements.
The way I implemented it now is the following:
I designed a GUI in QT Creator and save the video.ui file, then I generate the Ui_video.py file from it with pyside6-uic.
My main.py then looks like this:
import sys
from PySide6.QtWidgets import QMainWindow, QApplication
from PySide6.QtCore import QCoreApplication
from ui_video import Ui_Video
class VideoViewerMain(QMainWindow):
def __init__(self):
super().__init__()
self.ui : Ui_Video = None
def closeEvent(self, event):
self.ui.video_view.closeEvent(event)
return super().closeEvent(event)
class App(QApplication):
def __init__(self, *args, **kwargs):
super(App,self).__init__(*args, **kwargs)
self.window = VideoViewerMain()
self.ui = Ui_Video() #
self.window.ui = self.ui
self.ui.setupUi(self.window)
self.window.show()
if __name__ == "__main__":
# init application
app = App(sys.argv)
app.processEvents()
# execute qt main loop
sys.exit(app.exec())
Then I implemented my video previewer, which inherits from QOpenGLWidget as a way to display the video. (I guess this could be any widget type that supports painting)
The Qt hierarchy looks like this:
In the QT Creator I set the corresponding widget as a custom class. This means my class is instantiated when the main class calls setupUI.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ui_video import Ui_Video
class VideoPreview(QOpenGLWidget):
"""
widget to display camera feed and overlay droplet approximations from opencv
"""
def __init__(self, parent=None):
super().__init__(parent)
self.ui: Ui_Video = self.window().ui
self.reader = VideoReader(r"some/video/path")
# adjust the seekBar range with video length, this throws error
self.ui.seekBar.setMaximum(self.reader.number_of_frames - 1)
self.ui.seekBar.setMinimum(0)
self._first_show = True
def showEvent(self, event: QtGui.QShowEvent) -> None:
if self._first_show:
self._first_show = False
self.ui.seekBar.setMaximum(self.reader.number_of_frames - 1)
self.ui.seekBar.setMinimum(0)
return super().showEvent(event)
When I try to initialize the ui seekBar in the init function, it throws an error
File "src/VideoProcessor\videopreview.py", line 56, in __init__
self.ui.seekBar.setMaximum(self.reader.number_of_frames - 1)
AttributeError: 'Ui_Video' object has no attribute 'seekBar'
This is because my custom class is instantiated before the seekBar in the setupUI function which I cannot change. Currently I use the workaround with the showEvent.
So my question is: How would a proper implementation of a custom widget look like in this context?
Should I divide the functionality of the video controls from the widget entirely?
How can I ensure that the classes have access to the ui elements?
The main thing I see is your use of colons in assignment statements.
In python colons are mainly used to for setting dictionary literal values...
my_dict = {"my_key1": 1, "my_key2": 2}
ending loops/condition statements...
for index in range(12):
if index == 5:
print("FIVE")
and the occasional lambda when necessary...
sorted_my_dict = sorted(my_dict.items(), key=lambda item: item[1])
In your case I see at least 2 places where you are using colons in ways I've never personally seen in python.
self.ui : Ui_Video = None
# and
self.ui: Ui_Video = self.window().ui
If you need to set a value to None just use
self.ui = None
when setting it to an existing object from another widget either utilize the parent argument or just pass the object in as a different argument...
self.ui = parent.ui
# or
class VideoPreview(QOpenGLWidget):
"""
widget to display camera feed and overlay droplet approximations from opencv
"""
def __init__(self, ui_object, parent=None):
super().__init__(parent)
self.ui = ui_object.ui
[...]

Splitting Python classes in PyQt5

I want to split one big class (UI) into several smaller ones and I have a problem with accessing the other fields in the rest of GUI.
I created a simple GUI using QT Creator and now I want to program it using PyQt5.
When I split the functions into separate classes and use the verifyBtn button I get an error:
self.hostname = self.IPInp.text()
AttributeError: 'bool' object has no attribute 'IPInp'
If verify_button function is in main UI class then program works fine. Do you have any idea how the code should look like so I can split the function into another class?
from PyQt5.QtWidgets import *
from PyQt5 import uic
import os, sys
class ButtonHandler(QMainWindow):
def __init__(self) -> None:
super().__init__()
def verify_button(self):
self.hostname = self.IPInp.text()
response = os.system("ping -n 1 " + self.hostname)
if response == 0:
self.verifyBtn.setStyleSheet('QPushButton { color: green;}')
self.verifyBtn.setText("OK")
else:
self.verifyBtn.setStyleSheet('QPushButton { color: red;}')
self.verifyBtn.setText("NO")
class UI(QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi("Test_gui.ui", self)
self.vBtn = ButtonHandler()
self.verifyBtn.clicked.connect(self.vBtn.verify_button)
if __name__ == '__main__':
app = QApplication([])
win = UI()
win.show()
sys.exit(app.exec_())
In the __init__-method of ButtonHandler you can pass a reference to UI in the following way (if you use toplevel_ui for the reference to UI)
class ButtonHandler(QMainWindow):
def __init__(self, toplevel) -> None:
super().__init__()
self.toplevel_ui = toplevel_ui
so when you create an instance of ButtonHandler from UI, you now write
self.vBtn = ButtonHandler(self)
and then when you want to access properties of the class UI from the class ButtonHandler you write self.toplevel_ui.<attribute_name> where you replace <attribute_name> with whatever you want. For example verifyBtn or verify_button.

Access items and attributes across multiple windows

i have a main GUI-Window from which i open a new Window (FCT-popup) with a buttonclick:
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow() # sets ui = to the main window from the ui-file
self.ui.setupUi(self)
[...]
def enter_fct_results(self):
self.FCTpopup = FCT_Window()
self.FCTpopup.show()
In the Window i have a QTable to fill and a button to submit the data and close the window:
class FCT_Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_FCT_Window()
self.ui.setupUi(self)
[...]
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
def on_submit(self): # event when user clicks
fct_nparray = np.zeros((self.ui.tableFCTinputs.rowCount(), self.ui.tableFCTinputs.columnCount()))
for j in range(self.ui.tableFCTinputs.columnCount()):
for i in range(self.ui.tableFCTinputs.rowCount()):
fct_nparray[i, j] = float(self.ui.tableFCTinputs.item(i, j).text())
return fct_nparray, lambda: self.close()
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
The receiving function iin the main window looks like ths:
def store_fct_data(self, data):
self.fct_data = data
Now i just want to understand how i can make either the mainwindow or the pushbutton which opens the 2nd window disabled. Disabling inside enter_fct_results() works, but if i want to enable it again with either store_fct_data or on_submit provides errors like this:
self.ui.pushButton_FCTresults.setEnabled(1)
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
AttributeError: type object 'MainWindow' has no attribute 'ui'
I dont think i have understood it here how to deal with multiple windows and stuff. For example how would i change a the color of a button in the main window by using a button in window2. How do i access the widgets? if i am inside the same Window i do that easily by
self.ui.pushbutton.setText("New Text")
I dont get how to access items and attributes across Windows :( Can you help me?
Access to attributes of another instance
There is a fundamental difference between disabling the button of the second window in enter_fct_results and what you tried in the lambda: in the first case, you're accessing an instance attribute (for instance, self.FCTpopup.ui.pushButton), while in the second you're trying to access a class attribute.
The self.ui object is created in the __init__ (when the class instance is created), so the instance will have an ui attribute, not the class:
class Test:
def __init__(self):
self.value = True
test = Test()
print(test.value)
>>> True
print(Test.value)
>>> AttributeError: type object 'Test' has no attribute 'value'
Provide a reference
The simple solution is to create a reference of the instance of the first window for the second:
def enter_fct_results(self):
self.FCTpopup = FCT_Window(self)
self.FCTpopup.show()
class FCT_Window(QMainWindow):
def __init__(self, mainWindow):
QMainWindow.__init__(self)
self.mainWindow = mainWindow
self.ui.pushButton_submitFCT.clicked.connect(self.doSomething)
def doSomething(self):
# ...
self.mainWindow.ui.pushButton.setEnabled(True)
Using modal windows (aka, dialogs)
Whenever a window is required for some temporary interaction (data input, document preview, etc), a dialog is preferred: the main benefit of using dialogs is that they are modal to the parent, preventing interaction on that parent until the dialog is closed; another benefit is that (at least on Qt) they also have a blocking event loop within their exec() function, which will only return as soon as the dialog is closed. Both of these aspects also make unnecessary disabling any button in the parent window. Another important reason is that QMainWindow is not intended for this kind of operation, also because it has features that generally unnecessary for that (toolbars, statusbars, menus, etc).
def enter_fct_results(self):
self.FCTpopup = FCT_Window(self)
self.FCTpopup.exec_()
class FCT_Window(QDialog):
def __init__(self, parent):
QMainWindow.__init__(self, parent)
self.ui.pushButton_submitFCT.clicked.connect(self.doSomething)
def doSomething(self):
# ...
self.accept()
The above makes mandatory to recreate the ui in designer using a QDialog (and not a QMainWindow) instead. You can just create a new one and drag&drop widgets from the original one.
i finally found my mistake: It was the place of the signal connection. It has to be right before the 2nd window is opened:
def enter_fct_results(self):
self.FCTpopup = Dialog(self.fct_data)
self.FCTpopup.submitted.connect(self.store_fct_data)
self.FCTpopup.exec_()
With this now i can send the stored data from the mainwindow to the opened window, import into the table, edit it and send it back to the main window on submit.

PyQt - open only one child window and minimize it with parent window

The idea is to open a child window from parent window menu and when I minimize the parent window, the child window must be minimized also and only one child window can be opened.
I have the solution for minimizing the child when parent is minimized, but I can open child window multiple-times (although the child is already opened) and I would like to disable opening of multiple child windows.
The parent window is MainWindow.py:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle('Parent window')
self.flags = QtCore.Qt.Window
self.ControlPanel = Control_panel_window()
self.createActions()
self.createMenus()
def createActions(self):
# window - menu
self.windowShowControlPanelAction = QtGui.QAction(self.tr("&Control panel"), self, statusTip='Control panel')
self.connect(self.windowShowControlPanelAction, QtCore.SIGNAL("triggered()"), self.ShowControlPanel)
def createMenus(self):
# window
self.windowMenu = QtGui.QMenu(self.tr("&Window"), self)
self.windowMenu.addAction(self.windowShowControlPanelAction)
self.menuBar().addMenu(self.windowMenu)
def ShowControlPanel(self):
self.ControlPanel = Control_panel_window(self)
self.ControlPanel.setWindowFlags(QtCore.Qt.Window)
self.ControlPanel.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
win.show()
sys.exit(app.exec_())
The child window is ChildWindow.py:
class Control_panel_window(QWidget):
def __init__(self, parent = None):
super(Control_panel_window, self).__init__(parent)
self.setFixedSize(200, 300)
def setWindowFlags(self, flags):
print "flags value in setWindowFlags"
print flags
super(Control_panel_window, self).setWindowFlags(flags)
The problem is: how can I set that only one child window is opened?
In your ShowControlPanel function you are creating a new control panel each time the signal is triggered. Since you already have an instance available why don't you use that instead?
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle('Parent window')
self.flags = QtCore.Qt.Window
self.control_panel = ControlPanelWindow(self)
self.control_panel.setWindowFlags(self.flags)
#...
def create_actions(self):
self.show_control_panel_action = QtGui.QAction(
self.tr("&Control panel"),
self,
statusTip='Control panel'
)
self.show_control_panel_action.triggered.connect(self.show_control_panel)
#...
def show_control_panel(self):
self.control_panel.show()
Some stylistic notes:
Try to follow PEP8 official python coding-style guide. This include using CamelCase for classes, lowercase_with_underscore for almost everything else. In this case, since Qt uses halfCamelCase for methods etc you may use it too for consistency.
Use the new-style signal syntax:
the_object.signal_name.connect(function)
instead of:
self.connect(the_object, QtCore.SIGNAL('signal_name'), function)
not only it reads nicer, but it also provides better debugging information. Using QtCore.SIGNAL you will not receive an error if the signal doesn't exist (e.g. you wrote a typo like trigered() instead of triggered()). The new-style syntax does raise an exception in that case you will be able to correct the mistake earlier, without having to guess why something is not working right and searching the whole codebase for the typo.

Categories

Resources