The most obvious approach for me is to declare each window (window, dialog or widget) in the constructor and call the show() method when needed. Something like this:
class MultiWindowApp():
def __init__(self):
self.window_1 = self.init_window_1()
self.window_2 = self.init_window_2()
def init_window_1(self):
gui = uic.loadUi(...)
# other settings
return gui
def init_window_2(self):
gui = uic.loadUi(...)
# other settings
return gui
def show_window_1(self):
self.window_1.show()
def show_window_2(self):
self.window_2.show()
Nevertheless, it does not seem to be memory efficient, because I store the windows in the memory all the time, even when I am not showing it.
Alternative solution that comes to my mind is to create a separate class for each window (or other widget) and have one placeholder for all in the main class. Assign an instance of the respective class and delete on closing the window. A minimal example below:
class Window_1(QWidget):
def __init__(self):
QWidget.__init__(self)
uic.loadUi(...)
# other settings
self.show()
class Window_2(QWidget):
def __init__(self):
QWidget.__init__(self)
uic.loadUi(...)
# other settings
self.show()
class MultiWindowApp():
def __init__(self):
self.widget_placeholder = None
def show_window_1(self):
self.widget_placeholder = Window_1()
def show_window_2(self):
self.widget_placeholder = Window_1()
This would be a bit slower, but I would avoid keeping in memory unnecessary stuff. Nevertheless, I still have a feeling that there is a better way. What is the proper way of designing such an application?
I didn't run the examples above, so there can be some errors, but I think that the concepts behind them are clear.
Related
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".
I would like to know, what is the concept of information flow in GUI based apps, or any other app with same problem. When you have two seperate classes and their objects, how is the messeging process done between them. For example you have a GUI and AppLogic.
Scenario 1: Button is pressed -> GUI is processing event -> calls AppLogic method image_clicked()
Scenario 2: HTTPServer gets a message -> AppLogic receives image -> AppLogic calls GUI method render_image()
The problem is that you cannot reference classes each other because the first class does not know the second one (here AppLogic does not know GUI class):
class AppLogic():
gui : GUI
def image_clicked(self):
pass #not important
class GUI():
app_logic : AppLogic
def render_image(self):
pass #not important
I know this is more like go to school and study problem, but I would like to know how these problems are sovled, or some common practices. At least link with some detailed information. I am not able to name the problem right to find the answer.
Edit:
I can use this code without explicit type declaration and it works. But when I want to call functions of gui in AppLogic class definition, intellisense does not hint anything, because it does not know the type of attribute gui. And I don't think that it is good practice to use code like that.
class AppLogic():
def __init__(self) -> None:
self.gui = None
def image_clicked(self):
pass #not important
class GUI():
def __init__(self) -> None:
self.app_logic = None
def render_image(self):
pass #not important
app = AppLogic()
gui = GUI()
app.gui = gui
gui.app_logic = app
You need to initialize your variables.
gui = Gui()
then you can call the methods
For example:
class AppLogic:
gui: Gui
def image_clicked(self):
gui = Gui()
gui.render_image()
class Gui:
logic: AppLogic
def render_image(self) :
pass
Or you can initialize your variable directly
gui: Gui = Gui()
I hope this answers your question
from LogicClass import Logic
class Gui:
logic: AppLogic = AppLogic()
def render_image(self) :
pass
and:
from GuiClass import Gui
class AppLogic:
gui: Gui
def image_clicked(self):
gui = Gui()
gui.render_image()
from Gui import Gui
class Logic:
def __init__(self):
self.gui = Gui()
if __name__ == "__main__":
Logic()
and
class Gui:
def __init__(self):
print("GUI")
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.
Goal of the script:
(3) different windows, each in its own class, with its own widgets and layout, are created via Toplevel and callbacks.
When a new (Toplevel) window is created, the previous one is destroyed. Thus, only one window is visible and active at a time.
Problem?
Basically, I've tried many things and failed, so I must understand too little of ["parent", "master", "root", "app", "..."] :(
Note on raising windows:
I have implemented a successful example of loading all frames on top of each other, and controlling their visibility via the .raise method.
For this problem, however, I don't want to load all the frames at once.
This is an abstracted version of a quiz program that will require quite a lot of frames with images, which makes me reluctant to load everything at once.
Script (not working; bugged):
#!/usr/bin/env python
from Tkinter import *
import tkMessageBox, tkFont, random, ttk
class First_Window(Frame):
"""The option menu which is shown at startup"""
def __init__(self, master):
Frame.__init__(self, master)
self.gotosecond = Button(text = "Start", command = self.goto_Second)
self.gotosecond.grid(row = 2, column = 3, sticky = W+E)
def goto_Second(self):
self.master.withdraw()
self.master.update_idletasks()
Second_Window = Toplevel(self)
class Second_Window(Toplevel):
"""The gamewindow with questions, timer and entrywidget"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.gotothird = Button(text = "gameover", command = self.goto_Third)
self.gotothird.grid(row = 2, column = 3, sticky = W+E)
def goto_Third(self):
Third_Window = Toplevel(self)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.master = First_Window
self.gotofirst = Button(text = "startover", command = self.goto_First)
self.gotofirst.grid(row = 2, column = 3, sticky = W+E)
def goto_First(self):
self.master.update()
self.master.deiconify()
self.destroy()
def main():
root = Tk()
root.title("Algebra game by PJK")
app = First_Window(root)
root.resizable(FALSE,FALSE)
app.mainloop()
main()
The problem is not really a Tkinter problem, but a basic problem with classes vs. instances. Actually, two similar but separate problems. You probably need to read through a tutorial on classes, like the one in the official Python tutorial.
First:
self.master = First_Window
First_Window is a class. You have an instance of that class (in the global variable named app), which represents the first window on the screen. You can call update and deiconify and so forth on that instance, because it represents that window. But First_Window itself isn't representing any particular window, it's just a class, a factory for creating instances that represent particular windows. So you can't call update or deiconify on the class.
What you probably want to do is pass the first window down through the chain of windows. (You could, alternatively, access the global, or do various other things, but this seems cleanest.) You're already trying to pass it to Second_Window, you just need to stash it and pass it again in the Second_Window (instead of passing self instance, which is useless—it's just a destroyed window object), and then stash it and use it in the Third_Window.
Second:
Second_Window = Toplevel(self)
Instead of creating an instance of the Second_Window class, you're just creating an instance of the generic Toplevel class, and giving it the local name Second_Window (which temporarily hides the class name… but since you never use that class, that doesn't really matter).
And you have the same problem when you try to create the third window.
So:
class First_Window(Frame):
# ...
def goto_Second(self):
# ...
second = Second_Window(self)
class Second_Window(Toplevel):
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_Third(self):
third = Third_Window(self.first)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_First(self):
self.first.update()
self.first.deiconify()
self.destroy()
I am using PyQt4 in Python 3.3, making a GUI and have multiple classes, some of which I don't want running until I have clicked a certain button to do so. How can I connect such a class to only run when the button is clicked, and not on the start-up of the program.
Here is how I am currently connecting this class to my button within another class.
btns.clicked.connect(self.tableshow2)
def tableshow2(self):
table5.show()
This is the first class where the button is.
class CustTableSearch(QtGui.QDialog):
def __init__(self, parent=None):
super(CustTableSearch, self).__init__(parent)
with sqlite3.connect('database.db') as db:
cursor=db.cursor()
num = QtGui.QInputDialog.getText(self, 'Insert TelephoneNumber',
'Enter TeleNum:')
table5 = CustTableSearch()
This is part of the class which the button activates, which runs on start-up of the python shell. I have tried putting this in a function within the class with the button, but then i can't have it show up using .show() (it's a screen with a table).
Assuming that both classes are in the same module, you can create an instance of CustomTableSearch in tableshow2(self) method.
...
def tableshow2(self):
self.table5 = CustomTableSearch(self)
self.table5.show()
...
One way to do this is to create the dialog only on demand, rather than creating it straight away when the module is loaded.
class ProfilePage(QtGui.QMainWindow):
def __init__(self):
super(ProfilePage, self).__init__()
self.table5 = None
self.initUI()
def initUI(self):
...
btns.clicked.connect(self.tableshow2)
def tableshow2(self):
if self.table5 is None:
self.table5 = CustomTableSearch()
self.table5.show()