Python Tkinter safe to self.quit or sys.exit? - python

In the spirit of decoupling, rather than cramming many widgets into one giant class, I tried splitting them into separate classes.
The problem I ran into is the FileMenu class does not know about root, and so cannot call root.quit or root.destroy. On a whim I tried self.quit, and it works, but I have not seen this used before.
My questions are:
Is self.quit a safe way to quit the app?
Is sys.exit a safe way to quit the app?
If neither are safe, is there a good way to approach decoupling the design and safely quit without passing root as a parameter everywhere or making it a global?
My example:
import Tkinter as tk
class FileMenu(tk.Menu):
def __init__ (self, parent):
tk.Menu.__init__(self, parent, tearoff=False)
self.add_command(label='Exit', command=self.quit)
class MainMenu(tk.Menu):
def __init__ (self, parent):
tk.Menu.__init__(self, parent, tearoff=False)
self.file_menu = FileMenu(self)
self.add_cascade(label='File', menu=self.file_menu)
class View:
def __init__ (self, parent):
self.frame = tk.Frame(parent)
self.parent = parent
self.menu = MainMenu(self.frame)
self.parent.configure(menu=self.menu)
self.parent.geometry('200x200')
self.frame.pack(fill='both', expand=True)
class App:
def __init__ (self):
self.root = tk.Tk()
self.view = View(self.root)
def run (self):
self.root.title('Window Title')
self.root.mainloop()
if __name__ == '__main__':
app = App()
app.run()

Both use for different purpose:
sys.exit(): Close complete application.
self.quit: If you have multiple windows in your application and you don't want to close your whole application then you should use self.quit().
Both are safe to use but uses are different.

If the class doesn't know about the root object but you expect it to act on the root object, you should pass it in.
In App, you pass the root object into View:
self.view = View(self.root)
In View, you create a MainMenu and pass it self.frame:
self.menu = MainMenu(self.frame)
Instead, also pass it the root object:
self.menu = MainMenu(self.frame, parent)
And make MainMenu expect a parameter of that object, and pass it to FileMenu:
class MainMenu(tk.Menu):
def __init__ (self, parent, root):
tk.Menu.__init__(self, parent, tearoff=False)
self.file_menu = FileMenu(self, root)
self.add_cascade(label='File', menu=self.file_menu)
Then make FileMenu expect a parameter of that root object. Now it has access to it:
class FileMenu(tk.Menu):
def __init__ (self, parent, root):
tk.Menu.__init__(self, parent, tearoff=False)
self.add_command(label='Exit', command=root.destroy)
And you can use the Tkinter root object's destroy method to quit the application properly.

Related

Fundamental OOP in Tkinter

I'm relatively new to OOPs and thus know it theoretically. I need a project to be implemented in Tkinter. So, I was going through it. I was just playing around with the most basic code like below
# https://python-textbok.readthedocs.io/en/1.0/Introduction_to_GUI_Programming.html
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
root = tk.Tk()
app = MainApplication(root)
app.mainloop()
Here, I can say confidently that MainApplication inherits tk.Frame. So, the root is a TK object and it is passed in MainApplication constructor which assigns it as the parent of the MainApplication object.
So, when we call the app.mainloop(), the MainApplication object is called which in turn can invoke the root somehow because we have the root as a parent of the app object.
Correct me if I understood it wrong.
Now, as I browsed and another popular method people are using is
# https://docs.python.org/2/library/tkinter.html#a-simple-hello-world-program
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
root = tk.Tk()
MainApplication(root)
root.mainloop()
Here also, as I can understand the MainApplication is invoked and root becomes the parent of it. But how does that link the MainApplication object with the root object, so that when the root.mainloop() is called, it can refer back to MainApplication object?
Any help is appreciated. Thanks in advance.
This is more about the actual structure of the tkinter module than it is about OOP principles.
mainloop is a method of the Misc class, which is an ancestor of both Tk and Frame. When called, it essentially calls the mainloop method using a reference it has to the root Tk object, so whether you invoke it from root or app, the result is the same.

How to hide a window in the constructor immediately after creation?

I want to hide a window immediately after it is created. It works only if I do this with the help of button or something.
class Example(QWidget):
def __init__(self, parent=None):
super(Example, self).__init__(parent)
self.hide() # doesn't work
self.btn = QPushButton('Hide', self)
self.btn.clicked.connect(self.click) # works
self.btn.show()
def click(self): # works
self.hide()
Apparently it seems that the code should work. What may be happening is that you are calling show() after creating the object. For example:
example = Example()
example.show()
Read this answer about hide() and show(): What's the difference in Qt between setVisible, setShown and show/hide
You can use QtCore.QTimer
class Example(QWidget):
def __init__(self, app):
QWidget.__init__(self)
QTimer.singleShot(0, self.hide)

From where comes the parent argument (PySide)?

In this example, from where comes the parent argument, who provides it?
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.do_something() #sanity check
self.cw = ChildWidget(self)
self.setCentralWidget(self.cw)
self.show()
def do_something(self):
print 'doing something!'
class ChildWidget(QtGui.QWidget):
def __init__(self, parent):
super(ChildWidget, self).__init__(parent)
self.button1 = QtGui.QPushButton()
self.button1.clicked.connect(self.do_something_else)
self.button2 = QtGui.QPushButton()
self.button2.clicked.connect(self.parent().do_something)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(self.button1)
self.layout.addWidget(self.button2)
self.setLayout(self.layout)
self.show()
def do_something_else(self):
print 'doing something else!'
Parent and children are specific to Qt (C++). From the doc of QObject:
QObjects organize themselves in object trees. When you create a
QObject with another object as parent, the object will automatically
add itself to the parent's children() list. The parent takes ownership
of the object; i.e., it will automatically delete its children in its
destructor.
QWidget and a lot of other class inherits fromQObject, so it apply to them too. For every children, the parent() method returns a pointer to the parent object.
Basically, your creates widget with parents so they can be deleted properly.
Common case, your main window is the parent - or grandparent - of all your widgets: when you close the window, everything is deleted in the right order.
From your comment, I think your also confused with the use ofsuper(). It does not call the parent of the widget.
Another way to write this:
class ChildWidget(QtGui.QWidget):
def __init__(self, parent):
super(ChildWidget, self).__init__(parent)
is to call the init method of QWidget directly:
class ChildWidget(QtGui.QWidget):
def __init__(self, parent):
QtGui.QWidget.__init__(parent)
ChildWidget inherits from QWidget (that's how we defined the class). In the init method, you need to call the default constructor of QWidget. Otherwise you won't be able to use the default methods and attributes of QWidget (try and see...).
If given a parent, it will also organize itself accordingly. It will add itself to the parent children method, and keep a reference to its parent.

Tkinter button command to switch classes with frames

I am new to Tkinter and I am having some trouble getting my button command to switch to a different class.
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
button = tk.Button(self, text="Login", command=self.gotoMainMenu)
button.pack()
def gotoMainMenu(self):
root2=tk.Toplevel(self)
myGUI=MainMenu(root2)
def finish(self):
self.parent.destroy()
class MainMenu(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
button = tk.Button(self, text="Visit Page 1")
button.pack()
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
This is the current code I am working on. I have tried looking examples, but I can't seem to figure it out. I currently receive an attribute error. "AttributeError: MainApplication instance has no attribute 'gotoMainMenu'." Also, any errors you find or advice you have is greatly appreciated.
You simply forgot to pack your MainMenu frame. Add the following line to the end of your MainMenu class's constructor:
self.pack()

How to dynamically change child widgets with Python and Qt?

I would like to create a widget that has a child widget that I can dynamically change. Here is what I tried:
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class Widget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setLayout(QVBoxLayout())
self.child = QLabel("foo", self)
self.layout().addWidget(self.child)
def update(self):
self.layout().removeWidget(self.child)
self.child = QLabel("bar", self)
self.layout().addWidget(self.child)
app = QApplication(sys.argv)
widget = Widget()
widget.show()
widget.update()
app.exec_()
The problem is that this doesn't actually remove the "foo" label visually. It is still rendered on top of "bar". Screenshot of the problem. How do I remove the old widget so that only the new widget is shown?
I know that I can change the text property of the label. This is not what I want in my application, I need to change the actual widget (to a different widget type).
removeWidget() only removes the item from the layout, it doesn't delete it. You can delete the child widget by calling setParent(None).
def update(self):
self.layout().removeWidget(self.child)
self.child.setParent(None)
self.child = QLabel("bar", self)
self.layout().addWidget(self.child)

Categories

Resources