Using super() on tk.TopLevel, but do not generate second window? - python

I am attempting to implement a trivial MVC application.
When super().__init__() is called, two windows are generated in my python application.
What is the appropriate usage when attempting to inherit from tk.TopLevel, when I only want to generate one window?
import tkinter as tk
from tkinter import ttk
class View(tk.Toplevel):
def __init__(self, controller):
super().__init__()
self.controller = controller
def exec_main(self):
self.mainloop()
class Controller:
def __init__(self):
self.view = View(self)
if __name__ == '__main__':
app = Controller()
app.view.exec_main()
Doing something like super().__init__(master=self) or super().__init__(self) does not seem to be the solution.
An alternative approach would be to do something like this for the main:
import tkinter as tk
class View(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self, master)
class Controller:
def __init__(self, root):
self.view = View(root)
if __name__ == '__main__':
root = tk.Tk()
root.withdraw()
app = Controller(root)
root.mainloop()
But this seems wasteful.

Without exception, every widget except the root window requires a parent. If you don't create a root window then one will be created for you. When you create an instance of Toplevel and call super().__init__(), if you don't have a root window then tkinter will create one for you.
As you've observed, the correct workaround is to explicitly create a root window and then hide it. You have to make sure to give the user a way to destroy this root window since it won't automatically be destroyed when you close the Toplevel windows.

Related

How to make methods of Tkinter's class private?

Here's the code of a window, using tkinter library and OOP. I want to make methods of class App private. But some of them, like method destroy in code below should be public
from tkinter import *
from tkinter import ttk
class App(Tk):
def __init__(self):
super().__init__()
# window settings
self.title("Private Attributes")
self.resizable(width=False, height=False)
root = App() # create window
root.title("Public Attributes") # this shouldn't work
ttk.Label(root, text="Close this window").pack()
ttk.Button(root, text="Close", command=root.destroy).pack() # this should work
root.mainloop()
If you want something that doesn't expose one or more Tk methods, you should use composition rather than inheritance. For example,
class App:
def __init__(self):
self._root = Tk()
self._root.title("Private Attributes")
self._root.resizable(width=False, height=True)
def mainloop(self):
return self._root.mainloop()
root = App()
root.title("Public Attributes") # AttributeError
root.mainloop() # OK
You'll need to decide if the ability to limit access to various Tk methods (remember, you can still access self._root directly, but the name suggests that you are responsible for any errors stemming from doing so) outweighs the amount of boilerplate you'll need to write to expose the methods you do want access to. (Reducing that boilerplate is beyond the scope of this answer.)

Tkinter -- how is root connected to the Frame

I am learning to use the Python library tkinter and I found this code snippet that creates a simple window with an orange rectangle.
#!/usr/bin/python3
from tkinter import Tk, Canvas, Frame, BOTH
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.pack(fill=BOTH, expand=1)
canvas = Canvas(self)
canvas.create_rectangle(30, 10, 120, 80, outline="#fb0", fill="#fb0")
canvas.pack(fill=BOTH, expand=1)
def main():
root = Tk()
ex = Example()
root.geometry("200x100")
root.mainloop()
in the main function, could anyone explaine how the object ex is plugged in with the object root? I do not understand how these two objects are related.
Every widget must have a parent/master except for the root window. If you don't specify one, a default will be used. The default is the root window. If there is no root window, one will be created automatically.
In your case you don't pass anything to Example() so by default its parent is the root window. As a rule of thumb you should always explicitly specify the parent when creating a widget.

Tkinter Show splash screen and hide main screen until __init__ has finished

I have a main tkinter window that can take up to a few seconds to load properly. Because of this, I wish to have a splash screen that shows until the init method of the main class has finished, and the main tkinter application can be shown. How can this be achieved?
Splash screen code:
from Tkinter import *
from PIL import Image, ImageTk
import ttk
class DemoSplashScreen:
def __init__(self, parent):
self.parent = parent
self.aturSplash()
self.aturWindow()
def aturSplash(self):
self.gambar = Image.open('../output5.png')
self.imgSplash = ImageTk.PhotoImage(self.gambar)
def aturWindow(self):
lebar, tinggi = self.gambar.size
setengahLebar = (self.parent.winfo_screenwidth()-lebar)//2
setengahTinggi = (self.parent.winfo_screenheight()-tinggi)//2
self.parent.geometry("%ix%i+%i+%i" %(lebar, tinggi, setengahLebar,setengahTinggi))
Label(self.parent, image=self.imgSplash).pack()
if __name__ == '__main__':
root = Tk()
root.overrideredirect(True)
progressbar = ttk.Progressbar(orient=HORIZONTAL, length=10000, mode='determinate')
progressbar.pack(side="bottom")
app = DemoSplashScreen(root)
progressbar.start()
root.after(6010, root.destroy)
root.mainloop()
Main tkinter window minimum working example:
import tkinter as tk
root = tk.Tk()
class Controller(tk.Frame):
def __init__(self, parent):
'''Initialises basic variables and GUI elements.'''
frame = tk.Frame.__init__(self, parent,relief=tk.GROOVE,width=100,height=100,bd=1)
control = Controller(root)
control.pack()
root.mainloop()
EDIT: I can use the main window until it has finished loading using the .withdraw() and .deiconify() methods. However my problem is that I cannot find a way to have the splash screen running in the period between these two method calls.
a simple example for python3:
#!python3
import tkinter as tk
import time
class Splash(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title("Splash")
## required to make window show before the program gets to the mainloop
self.update()
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.withdraw()
splash = Splash(self)
## setup stuff goes here
self.title("Main Window")
## simulate a delay while loading
time.sleep(6)
## finished loading so destroy splash
splash.destroy()
## show window again
self.deiconify()
if __name__ == "__main__":
app = App()
app.mainloop()
one of the reasons things like this are difficult in tkinter is that windows are only updated when the program isn't running particular functions and so reaches the mainloop. for simple things like this you can use the update or update_idletasks commands to make it show/update, however if the delay is too long then on windows the window can become "unresponsive"
one way around this is to put multiple update or update_idletasks command throughout your loading routine, or alternatively use threading.
however if you use threading i would suggest that instead of putting the splash into its own thread (probably easier to implement) you would be better served putting the loading tasks into its own thread, keeping worker threads and GUI threads separate, as this tends to give a smoother user experience.

Python Tkinter and GUI

I am trying to create a program/window that has one action button. This button when clicked, should open a second program/window and close the previous first program. This second program should have an action button that will open the first and close the second.
I found this script from another user and it works the same way I want the code to run. However, the root window is just being hidden with the deconify() when the otherframe is created.
What would be the best way to destroy the root window when the otherframe is created and still be able to be looped back.
Hopefully this made sense and thanks in advance.
import Tkinter as Tk
########################################################################
class OtherFrame(Tk.Toplevel):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
Tk.Toplevel.__init__(self)
self.geometry("100x100")
self.title("otherFrame")
########################################################################
class MyApp(object):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
self.root = parent
self.root.title("Main frame")
self.frame = Tk.Frame(parent)
self.frame.pack()
btn = Tk.Button(self.frame, text="Open Frame", command=self.openFrame)
btn.pack()
#----------------------------------------------------------------------
def hide(self):
""""""
self.root.withdraw()
#----------------------------------------------------------------------
def openFrame(self):
""""""
self.hide()
subFrame = OtherFrame()
handler = lambda: self.onCloseOtherFrame(subFrame)
btn = Tk.Button(subFrame, text="Close", command=handler)
btn.pack()
#----------------------------------------------------------------------
def onCloseOtherFrame(self, otherFrame):
""""""
otherFrame.destroy()
self.show()
#----------------------------------------------------------------------
def show(self):
""""""
self.root.update()
self.root.deiconify()
#----------------------------------------------------------------------
if __name__ == "__main__":
root = Tk.Tk()
root.geometry("800x600")
app = MyApp(root)
root.mainloop()
There is no best way to destroy the root window and then get it back. There is only one way to destroy it, which is to call the destroy() method. When you do that, all children windows will be destroyed and mainloop will exit.
While it's possible to destroy and recreate a root window, that's not how tkinter is designed to work, and it will not behave the way you expect it to behave. For example, all instances of StringVar, etc. will be destroyed. For another, you must have a root window, so by destroying the root window you will destroy all of its children including the Toplevel.
By far, the most common scenario is to simply hide the root window. If you truly want to destroy each window, put nothing in the root window and just leave it hidden. You can then use instances of Toplevel, which you can easily destroy and recreate at will.
Short answer... YOU CANT DO IT. The script that you have already is the best bet.
Try this:
import tkinter
class abc:
def __init__(self):
self.root = tkinter.Tk()
tkinter.Button(self.root, text="Click Me", command=lambda:abc.com(self)).pack()
def com(self):
self.root.destroy()
some = abc()
q = abc()
This doesn't actually jump between the two but creates a new one every time.

Using Tkinter in python to edit the title bar

I am trying to add a custom title to a window but I am having troubles with it. I know my code isn't right but when I run it, it creates 2 windows instead, one with just the title tk and another bigger window with "Simple Prog". How do I make it so that the tk window has the title "Simple Prog" instead of having a new additional window. I dont think I'm suppose to have the Tk() part because when i have that in my complete code, there's an error
from tkinter import Tk, Button, Frame, Entry, END
class ABC(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.parent = parent
self.pack()
ABC.make_widgets(self)
def make_widgets(self):
self.root = Tk()
self.root.title("Simple Prog")
If you don't create a root window, Tkinter will create one for you when you try to create any other widget. Thus, in your __init__, because you haven't yet created a root window when you initialize the frame, Tkinter will create one for you. Then, you call make_widgets which creates a second root window. That is why you are seeing two windows.
A well-written Tkinter program should always explicitly create a root window before creating any other widgets.
When you modify your code to explicitly create the root window, you'll end up with one window with the expected title.
Example:
from tkinter import Tk, Button, Frame, Entry, END
class ABC(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.parent = parent
self.pack()
self.make_widgets()
def make_widgets(self):
# don't assume that self.parent is a root window.
# instead, call `winfo_toplevel to get the root window
self.winfo_toplevel().title("Simple Prog")
# this adds something to the frame, otherwise the default
# size of the window will be very small
label = Entry(self)
label.pack(side="top", fill="x")
root = Tk()
abc = ABC(root)
root.mainloop()
Also note the use of self.make_widgets() rather than ABC.make_widgets(self). While both end up doing the same thing, the former is the proper way to call the function.
Here it is nice and simple.
root = tkinter.Tk()
root.title('My Title')
root is the window you create and root.title() sets the title of that window.
Try something like:
from tkinter import Tk, Button, Frame, Entry, END
class ABC(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
root = Tk()
app = ABC(master=root)
app.master.title("Simple Prog")
app.mainloop()
root.destroy()
Now you should have a frame with a title, then afterwards you can add windows for
different widgets if you like.
One point that must be stressed out is:
The .title() method must go before the .mainloop()
Example:
from tkinter import *
# Instantiating/Creating the object
main_menu = Tk()
# Set title
main_menu.title("Hello World")
# Infinite loop
main_menu.mainloop()
Otherwise, this error might occur:
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/tkinter/__init__.py", line 2217, in wm_title
return self.tk.call('wm', 'title', self._w, string)
_tkinter.TclError: can't invoke "wm" command: application has been destroyed
And the title won't show up on the top frame.
Example of python GUI
Here is an example:
from tkinter import *;
screen = Tk();
screen.geometry("370x420"); //size of screen
Change the name of window
screen.title('Title Name')
Run it:
screen.mainloop();
I found this works:
window = Tk()
window.title('Window')
Maybe this helps?
Easy method:
root = Tk()
root.title('Hello World')
Having just done this myself you can do it this way:
from tkinter import Tk, Button, Frame, Entry, END
class ABC(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.parent = parent
self.pack()
ABC.make_widgets(self)
def make_widgets(self):
self.parent.title("Simple Prog")
You will see the title change, and you won't get two windows. I've left my parent as master as in the Tkinter reference stuff in the python library documentation.
For anybody who runs into the issue of having two windows open and runs across this question, here is how I stumbled upon a solution:
The reason the code in this question is producing two windows is because
Frame.__init__(self, parent)
is being run before
self.root = Tk()
The simple fix is to run Tk() before running Frame.__init__():
self.root = Tk()
Frame.__init__(self, parent)
Why that is the case, I'm not entirely sure.
self.parent is a reference to the actual window, so self.root.title should be self.parent.title, and self.root shouldn't exist.
widget.winfo_toplevel().title("My_Title")
changes the title of either Tk or Toplevel instance that the widget is a child of.
I found a solution that should help you:
from tkinter import Tk, Button, Frame, Entry, END
class ABC(Frame):
def __init__(self,master=None):
super().__init__(master)
self.pack()
self.master.title("Simple Prog")
self.make_widgets()
def make_widgets(self):
pass
root = Tk()
app = ABC(master=root)
app.mainloop()
Found at: docs.python.org

Categories

Resources