Access items and attributes across multiple windows - python

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.

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".

PyQt - Tab Management from Outside Class

I am designing an application using PyQt that will manage multiple instances of Selenium. Each instance has a QFrame with unique information and controls and can be tabbed through from the main window.
class Instance(QFrame):
def __init__(self):
super().__init__()
self.username = "whatever"
...
self.startButton = QPushButton('Start')
self.startButton.clicked.connect(lambda: self.engineStart())
self.exitButton = QPushButton('Exit')
self.exitButton.clicked.connect(lambda: self.engineExit())
...
How it looks
Users should be able to create and delete instances at will.
Creating a tab is no problem. I have a "+" button set as the QTabWidget's cornerWidget. It is connected to a simple method to add the tab.
class App(QFrame):
def __init__(self):
...
def addNewTab(self):
t = Instance()
self.tabs.addTab(t, t.username)
The problem is, how can I use the "Exit" button from the "inside" Instance class to remove the tabs that are managed from the main window's "outside" class? I need some way to call removeTab()
To do what you want you must create a slot in the main window, and connect it to the clicked signal of the button as shown below:
class App(QFrame):
def __init__(self):
...
def addNewTab(self):
t = Instance()
self.tabs.addTab(t, t.username)
t.exitButton.clicked.connect(self.slot)
def slot(self):
self.tabs.removeTab(your_index)

pyqt dialog not showing a second time

I have a pyqt app where I want a dialog to display when I click a menu item. If the dialog loses focus and the menu item is clicked again, it brings the dialog to the front. This is working fine so far.
The problem is that when the dialog is opened and then closed, clicking the menu item doesnt create/display a new dialog. I think I know why, but can't figure out a solution
Heres the code:
from ui import mainWindow, aboutDialog
class ReadingList(QtGui.QMainWindow, mainWindow.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)
self.about = None
self.actionAbout.triggered.connect(self.showAbout)
def showAbout(self):
# If the about dialog does not exist, create one
if self.about is None:
self.about = AboutDialog(self)
self.about.show()
# If about dialog exists, bring it to the front
else:
self.about.activateWindow()
self.about.raise_()
class AboutDialog(QtGui.QDialog, aboutDialog.Ui_Dialog):
def __init__(self, parent=None):
super(self.__class__, self).__init__()
self.setupUi(self)
def main():
app = QtGui.QApplication(sys.argv)
readingList = ReadingList()
readingList.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The problem lies in the fact that when the dialog is created the first time, self.about is no longer None. This is good because the conditional in showAbout() allows me to bring the dialog to the front instead of creating a new one (the else condition)
However, when the dialog is closed, self.about is no longer None due to the previous dialog creation, which means it doesn't create a new one and just jumps to the else condition
How can I make it so that dialogs can be created after the first?
I thought about overriding the closeEvent method in the AboutDialog class but I'm not sure how to get a reference to readingList to send a message back saying the dialog has been closed. Or maybe I'm overthinking it, maybe the return from self.about.show() can be used somehow?
(I know I can probably avoid all of this using modal dialogs but want to try to figure this out)
There are probably several ways to do this, but here's one possibility:
class ReadingList(QtGui.QMainWindow, mainWindow.Ui_MainWindow):
def __init__(self):
super(ReadingList, self).__init__()
self.setupUi(self)
self.actionAbout.triggered.connect(self.showAbout)
self.about = None
def showAbout(self):
if self.about is None:
self.about = AboutDialog(self)
self.about.show()
self.about.finished.connect(
lambda: setattr(self, 'about', None))
else:
self.about.activateWindow()
self.about.raise_()
class AboutDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(AboutDialog, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
(NB: you should never use self.__class__ with super as it can lead to an infinite recursion under certain circumstances. Always pass in the subclass as the first argument - unless you're using Python 3, in which case you can omit all the arguments).

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.

How to create child window and communicate with parent in TkInter

I'm creating some dialogs using TkInter and need to be able to open a child sub-window (modal or modeless) on clicking a button in the parent. The child would then allow a data record to be created and this data (either the record or if the operation was cancelled) needs to be communicated back to the parent window. So far I have:
import sel_company_dlg
from Tkinter import Tk
def main():
root = Tk()
myCmp = sel_company_dlg.SelCompanyDlg(root)
root.mainloop()
if __name__ == '__main__':
main()
This invokes the top level dialog which allows the user to select a company. The company selection dialog looks like this:
class SelCompanyDlg(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent_ = parent
self.frame_ = Frame( self.parent_ )
// .. more init stuff ..
self.btNew_ = Button( self.frame_, text="New ...", command=self.onNew )
def onNew(self):
root = Toplevel()
myCmp = company_dlg.CompanyDlg(root)
On clicking the New ... button, a Create Company dialog is displayed which allows the user to fill in company details and click on create or cancel. Here's the opening bit of that:
class CompanyDlg(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
// etc.
I'm struggling with the best way of invoking the child dialog in onNew() - what I have works but I'm not convinced it's the best approach and also, I can't see how to communicate the details to and from the child dialog.
I've tried looking at online tutorials / references but what I've found is either too simplistic or focuses on things like tkMessageBox.showinfo() which iss not what I want.
There are at least a couple ways to solve your problem. Either your dialog can directly send information to the main application, or your dialog can generate an event that tells the main application that data is really to be pulled from the dialog. If the dialog simply changes the appearance of something (for example, a font dialog) I usually generate an event. If the dialog creates or deletes data I typically have it push information back to the application.
I typically have an application object that acts as the controller for the GUI as a whole. Often this is the same class as the main window, or it can be a separate class or even defined as a mixin. This application object has methods that dialogs can call to feed data to the application.
For example:
class ChildDialog(tk.Toplevel):
def __init__(self, parent, app, ...)
self.app = app
...
self.ok_button = tk.Button(parent, ..., command=self.on_ok)
...
def on_ok(self):
# send the data to the parent
self.app.new_data(... data from this dialog ...)
class MainApplication(tk.Tk):
...
def on_show_dialog(self):
dialog = ChildDialog(self)
dialog.show()
def new_data(self, data):
... process data that was passed in from a dialog ...
When creating the dialog, you pass in a reference to the application object. The dialog then knows to call a specific method on this object to send data back to the application.
If you're not into the whole model/view/controller thing you can just as easily pass in a function rather than an object, effectively telling the dialog "call this function when you want to give me data".
In one of my projects I was trying to check within a child tk.Toplevel window (child1) of my root window (self), if a tk.Toplevel window (child2) was created by the user from within the root window, and if this window (child2) is present at the users screen at the moment.
If this wouldn't be the case, the new tk.Toplevel window should gets created by the child window (child1) of the root window, instead of the root window itself. And if it was already created by the root window and is currently present at the users screen, it should get focus() instead of getting reinitialized by "child1".
The root window was wrapped inside a class called App() and both "children" windows were created by methods inside the root class App().
I had to initialize "child2" in a quiet mode if an argument given to the method was True. I suppose that was the entangled mistake. The problem occurred on Windows 7 64 bit, if that's significant.
I tried this (example):
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
top = self.winfo_toplevel()
self.menuBar = tk.Menu(top)
top['menu'] = self.menuBar
self.menuBar.add_command(label='Child1', command=self.__create_child1)
self.menuBar.add_command(label='Child2', command=lambda: self.__create_child2(True))
self.TestLabel = ttk.Label(self, text='Use the buttons from the toplevel menu.')
self.TestLabel.pack()
self.__create_child2(False)
def __create_child1(self):
self.Child1Window = tk.Toplevel(master=self, width=100, height=100)
self.Child1WindowButton = ttk.Button(self.Child1Window, text='Focus Child2 window else create Child2 window', command=self.CheckForChild2)
self.Child1WindowButton.pack()
def __create_child2(self, givenarg):
self.Child2Window = tk.Toplevel(master=self, width=100, height=100)
if givenarg == False:
self.Child2Window.withdraw()
# Init some vars or widgets
self.Child2Window = None
else:
self.Child2Window.TestLabel = ttk.Label(self.Child2Window, text='This is Child 2')
self.Child2Window.TestLabel.pack()
def CheckForChild2(self):
if self.Child2Window:
if self.Child2Window.winfo_exists():
self.Child2Window.focus()
else:
self.__create_child2(True)
else:
self.__create_child2(True)
if __name__ == '__main__':
App().mainloop()
Here comes the problem:
I wasn't able to check if "child2" is already present. Got error: _tkinter.TclError: bad window path name
Solution:
The only way to get the right 'window path name' was, instead of calling the winfo_exists() method directly onto the "child2" window, calling the master of the "child1" window and adding the according attributes followed by the attributes of the master window you want to use.
Example (edit of the method CheckForChild2):
def CheckForChild2(self):
if self.Child2Window:
if self.Child1Window.master.Child2Window.winfo_exists():
self.Child1Window.master.Child2Window.focus()
else:
self.__create_child2(True)
else:
self.__create_child2(True)

Categories

Resources