Python tkinter 2 frames merge together - python

Hey guys I have to classes that both create a Frame. The first one contains a button that is supposed to close its frame. The second frame simply contains a Label. My code should first create the frame with the button and when the button is pressed the second window should show up. What happens is that when pressing the button a "merged" window is created that contains the button and the label.
import tkinter as tk
class Window1(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.grid()
self.btn = tk.Button(self,text = "button",command = self.run)
self.btn.grid(row = 0,column = 0)
def run(self):
tk.Frame.quit(self)
class Window2(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.grid()
self.label = tk.Label(self,text = "label ")
self.label.grid(row = 0,column = 0)
w = Window1()
w.mainloop()
v = Window2()
v.mainloop()
The first picture is before you press the button, the next one after you pressed the button. The problem seems that tk.Frame.quit(self) doesn't work correctly. I tried similar ways to close the window such as:
tk.Frame.destroy(self)
but that doesn't help either.
edit: I solved it by inheriting the class from tk.TK instead of tk.Frame

Frame doesn't create window - it only group elements. Tk() creates window.
To close window you have to destroy() object create by Tk(). But you don't creat it manually root = tk.Tk() so tkinter create it automatically, but you have no access to this root to close it.
If widget doesn't have parent then it uses root and your Frame does it too.
import tkinter as tk
class Window1(tk.Frame):
def __init__(self, master):
# send `root` to `Frame` as its parent
tk.Frame.__init__(self, master)
# `Frame` will keep `master as `self.master`
# so we don't have to do `self.master = master` manually
self.grid()
self.btn = tk.Button(self, text="Hello Button", command=self.run)
self.btn.grid(row=0, column=0)
def run(self):
# use `master` (`root`) to destroy it
self.master.destroy()
class Window2(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.grid()
self.label = tk.Label(self, text="Hello Label")
self.label.grid(row=0, column=0)
root = tk.Tk() # create main window as `root`
Window1(root) # send `root` to `Window1` and later to `Frame`
root.mainloop()
root = tk.Tk()
Window2(root)
root.mainloop()

Related

How to destroy a window after opening a new one?

I am working on a game called 'Flag Quiz' using tkinter. I have a script called mainmenu where I can choose between an easy mode and a hard mode. If I click on one of the buttons the recent mainmenu window disappears and a new tkinter window opens.
Here is my mainmenu script:
from tkinter import *
import tkinter as tk
from hardmode import HardApp
from easymode import EasyApp
class TitleScreen(tk.Tk):
def __init__(self):
super().__init__()
self.title('Flag Quiz')
self.geometry('600x600')
self.resizable(0,0)
self.make_widgets()
def make_widgets(self):
self.background = PhotoImage(file = './background.png')
self.label = Label(self, image=self.background)
self.label.place(x=0, y=0, relwidth=1, relheight=1)
self.easy = Button(self, text="Easy Mode", height=2, width=6, font=('default', 20), command=self.play_easy)
self.hard = Button(self, text="Hard Mode", height=2, width=6, font=('default', 20), command=self.play_hard)
self.easy.place(relx=0.5, rely=0.45, anchor=CENTER)
self.hard.place(relx=0.5, rely=0.55, anchor=CENTER)
def play_easy(self):
self.withdraw()
self.app = EasyApp()
#self.app.start()
def play_hard(self):
self.withdraw()
self.app = HardApp()
#self.app.start()
def start(self):
self.mainloop()
TitleScreen().start()
And here is my easy mode script:
import tkinter as tk
from tkinter import *
import random
import os
import json
class EasyApp(tk.Toplevel):
def __init__(self):
super().__init__()
self.title('Flag Quiz')
self.geometry('')
self.resizable(0,0)
self.score = 0
self.create_widgets()
def create_widgets(self):
# variables
self.user_guess = StringVar(self)
self.text = StringVar(self)
self.text.set(" ")
# initial image
self.scoretext = Label(self, text="Score: ").pack(side='top', fill='x')
self.scorevalue = Label(self, text=self.score).pack(side='top', fill='x')
self.file = random.choice(os.listdir('pngs'))
self.randimg = PhotoImage(file='pngs/{}'.format(self.file))
self.randimg = self.randimg.subsample(2, 2)
self.panel = Label(self, image=self.randimg)
self.panel.pack()
self.country, self.ext = self.file.split('.')
self.countries = self.load_lookup()
self.countryname = [country for country in self.countries if country['alpha2'] == self.country]
self.s = []
for i in range(0,3):
country = random.choice(self.countries)
self.s.append(country['de'])
self.s.append(self.countryname[0]['de'])
random.shuffle(self.s)
self.btndict = {}
for i in range(4):
self.btndict[self.s[i]] = Button(self, text=self.s[i], height=2, width=35, font=('default', 20), command=lambda j=self.s[i]: self.check_input(j))
self.btndict[self.s[i]].pack()
def check_input(self, d):
if d != self.countryname[0]['de']:
print("Falsch")
else:
self.score += 5
for widget in self.winfo_children():
widget.destroy()
self.create_widgets()
def load_lookup(self):
with open('lookup.json') as file:
self.obj = file.read()
self.countryobj = json.loads(self.obj)
return self.countryobj
# def start(self):
# self.mainloop()
After clicking the close button (the default button on windows/osx to close a window) the window from my easy mode app disappears but PyCharm says that my program is still running.
I made some investigations and removed the self.withdraw() function in the function play_easy(self) in my mainmenu script. So now the mainmenu is still open after I click on the easy mode button. If I'm closing both windows now, the program fully ends.
Replacing self.withdraw() with self.destroy() is not working. The main menu is closed, but a new empty window opens instead.
Any suggestions on how to handle this problem so that my program fully ends if I click the close button within the easy/hard mode window?
You have two windows - main window created with Tk and subwindow created with Toplevel. When you use close button to close main window then it should also close all subwindows but when you close subwindow then it doesn't close main window (parent window) but only own subwindows - because usually it can be useful to display again main window to select other options and open again subwindow.
One of the methods is to destroy first window and use Tk to create new window.
But in this method you can't use some button in second window to go back to first window - and sometimes it can be problem. Even if you create again first window then it will not remeber previous values (if you have some Entry or other widgets to set values)
# from tkinter import * # PEP8: `import *` is not preferred`
import tkinter as tk
class EasyApp(tk.Tk): # use `Tk` instead of `Toplevel`
def __init__(self):
super().__init__()
self.scoretext = tk.Label(self, text="EasyApp")
self.scoretext.pack()
#def start(self):
# self.mainloop()
class TitleScreen(tk.Tk):
def __init__(self):
super().__init__()
self.button = tk.Button(self, text="Easy Mode", command=self.play_easy)
self.button.pack()
def play_easy(self):
self.destroy() # destroy current window
self.app = EasyApp()
def start(self):
self.mainloop()
TitleScreen().start()
Other method is to use self.wm_protocol("WM_DELETE_WINDOW", self.on_close) to execute function on_close when you use close button and in this function destroy main window (master).
This way you can still use Button to go back to main window which will remember previous content.
# from tkinter import * # PEP8: `import *` is not preferred`
import tkinter as tk
class EasyApp(tk.Toplevel): # still use `Toplevel`
def __init__(self, master): # send main window as master/parent
super().__init__(master) # it will also set `self.master = master`
self.scoretext = tk.Label(self, text="EasyApp")
self.scoretext.pack()
self.button = tk.Button(self, text="Go Back", command=self.go_back)
self.button.pack()
# run `on_close` when used `close button`
#self.protocol("WM_DELETE_WINDOW", self.on_close)
self.wm_protocol("WM_DELETE_WINDOW", self.on_close)
def go_back(self):
self.destroy() # destroy only current window
self.master.deiconify() # show again main window
def on_close(self):
self.destroy() # destroy current window
self.master.destroy() # destroy main window
class TitleScreen(tk.Tk):
def __init__(self):
super().__init__()
self.entry = tk.Entry(self)
self.entry.pack()
self.entry.insert('end', "You can change text")
self.button = tk.Button(self, text="Easy Mode", command=self.play_easy)
self.button.pack()
def play_easy(self):
self.withdraw()
self.app = EasyApp(self) # send main window as argument
def start(self):
self.mainloop()
TitleScreen().start()
Here's a fairly simple architecture for doing what you want. An application class is derived from Tk() which hides the default "root" window it normally displays and all the windows it does display are subclasses of a custom Toplevel subclass I've named BaseWin.
This class is just a Toplevel with its protocol for being delete (closed) set to call an a method named on_close(). This additional method simply destroys the current window before quits the application's mainloop() causing it to terminate.
The first window—an instance of the TitleScreen class—is displayed automatically when an instance of the application class is created. This window has two Buttons one labelled Easy Mode and the other Hard Mode. When one of them is clicked, an instance of the appropriate Toplevel subclass is created after the current window is removed by it call its destroy() method.
mainmenu.py
import tkinter as tk
from tkinter.constants import *
from tkinter import font as tkfont
from basewin import BaseWin
from easymode import EasyApp
from hardmode import HardApp
class SampleApp(tk.Tk):
def __init__(self):
super().__init__()
self.title('Flag Quiz')
self.geometry('600x600')
self.resizable(FALSE, FALSE)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold")
self.withdraw() # Hide default root Tk window.
startpage = TitleScreen(self.master)
self.mainloop()
class TitleScreen(BaseWin):
def __init__(self, master):
super().__init__(master)
self.make_widgets()
def make_widgets(self):
label = tk.Label(self, text="This is the Start Page", font=self.master.title_font)
label.pack(side="top", fill="x", pady=10)
self.easy = tk.Button(self, text="Easy Mode", font=('default', 20),
command=self.play_easy)
self.hard = tk.Button(self, text="Easy Mode", font=('default', 20),
command=self.play_hard)
self.easy.pack()
self.hard.pack()
def play_easy(self):
self.destroy()
self.app = EasyApp(self.master)
def play_hard(self):
self.destroy()
self.app = HardApp(self.master)
if __name__ == '__main__':
SampleApp()
basewin.py
import tkinter as tk
from tkinter.constants import *
class BaseWin(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.protocol("WM_DELETE_WINDOW", self.on_close)
def on_close(self):
self.destroy() # Destroy current window
self.master.quit() # Quit app.
easymode.py
import tkinter as tk
from tkinter.constants import *
from basewin import BaseWin
class EasyApp(BaseWin):
def __init__(self, master):
super().__init__(master)
self.title('Flag Quiz')
self.resizable(FALSE, FALSE)
self.make_widgets()
def make_widgets(self):
label = tk.Label(self, text="This is the Easy App", font=self.master.title_font)
label.pack(side="top", fill="x", pady=10)
hardmode.py
import tkinter as tk
from tkinter.constants import *
from basewin import BaseWin
class HardApp(BaseWin):
def __init__(self, master):
super().__init__(master)
self.title('Flag Quiz')
self.resizable(FALSE, FALSE)
self.make_widgets()
def make_widgets(self):
label = tk.Label(self, text="This is the Hard App", font=self.master.title_font)
label.pack(side="top", fill="x", pady=10)

tkinter - Cant hide nested toplevel window

I create an instance of TopLevel class inside another class, but can't "withdraw()" the child window
with a call from parent.
import tkinter as tk
class Table(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self,master)
self.var_new_t=tk.Toplevel(self.master)
self.t2=Table_2(self.var_new_t)
#Goal is to create a child window and immediately hide it.
self.t2.hide_me()
#self.t2.withdraw() ##Tried a few differnt ways
class Table_2(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self,master)
self.master = master
label = tk.Label(self.master, bg='green', text='Second Table')
label.grid()
def hide_me(self):
self.master.withdraw()
root = tk.Tk()
n= Table(root)
tk.Button(root, text="Quit", command=root.destroy).pack()
root.mainloop()
I tried a few other variations to no avail.
Your code is creating two windows. Consider this code:
Table is a toplevel window because it inherits from Toplevel. So, this line creates the Table window:
tk.Toplevel.__init__(self,master)
Then, you create another window when you do this:
self.var_new_t=tk.Toplevel(self.master)
If you inherit from Toplevel you typically shouldn't create a second Toplevel inside unless you explicitly intend to create two windows.
Your code needs to look something like this:
class Table(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self,master)
self.t2=Table_2(self)
self.t2.hide_me()
class Table_2(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self,master)
self.master = master
label = tk.Label(self, bg='green', text='Second Table')
label.grid()
def hide_me(self):
self.withdraw()

Pararell instance of tkinter application window python

I want to create some simple tkinter python app (like StickyNotes on Windows), i have create the class mainApplication and i do not know how to by just simply triggering the button create another instance of this class which will be displayed pararell to other window (or even multiple windows). I know how to assigned function to pushButton, and other simple stuff but the problem is with this pararell window displaying. Thanks in advance for help.
class mainApplication(Frame):
_ids = count(0)
def __init__(self, parent):
""" """
self.id = next(self._ids)
Frame.__init__(self, parent)
self.parent = parent
self.parent.minsize(width=200,height=100)
self.parent.geometry(('%dx%d+%d+%d' % (200, 100, 1700, 0+self.id*100)))
self.initUI()
def initUI(self):
""" """
self.parent.title("a2l")
self.pack(fill=BOTH, expand=True)
style = Style()
style.configure("TFrame", background="#333")
frame1 = Frame(self, style="TFrame")
frame1.pack(fill=X)
self.lbl0 = Label(frame1, text="api", width=7, background="#333", foreground = "red")
self.lbl0.pack(side=TOP, padx=5, pady=5)
self.closeButton = Button(self, text="new", command = self.createNewInstance)
self.closeButton.pack(side=RIGHT, padx=5, pady=5)
#=======================================================================
# self.generateButton = Button(self, text="GENERATE", command = self.)
# self.generateButton.pack(side=RIGHT, padx=5, pady=5)
#=======================================================================
def createNewInstance(self):
y = mainApplication()
return y
if __name__ == "__main__":
root = Tk()
x = mainApplication(root).pack(side="top", expand=False)
Tk().mainloop()
You shouldn't create more than one Tk() window in one application with tkinter. Instead tkinter provides a widget called Toplevel which can be useful for this kind of thing.
It creates another window which can exist along side the Tk() window and alongside other Toplevel widgets.
You could use this to create a series of persistent windows with whatever text or widgets you wanted on them at any kind of trigger including a Button.
See my example below for a demonstration:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.top = [] #list to contain the Toplevel widgets
self.entry = Entry(self.root)
self.button = Button(self.root, text="Create window", command=self.command)
self.entry.pack()
self.button.pack()
def command(self): #called when button is pressed
self.top.append(Toplevel(self.root)) #adds new Toplevel to the list
Label(self.top[len(self.top)-1], text=self.entry.get()).pack() #Adds label equal to the entry widget to the new toplevel
root = Tk()
App(root)
root.mainloop()

Open unique secondary window with Tkinter

I'm having a trouble when i open a secondary window. Now I'm just creating a toplevel window with a button and I need to open the same secondary window If i click the button (not generate a new instance).
Which is the better way to generate single secondary window and not generating a new window instance?
I leave the code that I'm actually working on:
import tkinter
class LogWindow():
def __init__(self, parent):
self.parent = parent
self.frame = tkinter.Frame(self.parent)
class MainWindow(tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self, parent)
self.parent = parent
frame = tkinter.Frame(self, borderwidth=1)
frame.pack(fill=tkinter.BOTH, expand=True, padx=5, pady=5)
self.LogButton = tkinter.Button(frame, text="Log Viewer", command= self.openLogWindow)
self.LogButton.grid(sticky=tkinter.E+tkinter.W)
self.pack(fill=tkinter.BOTH,expand=True)
def openLogWindow(self):
self.logWindow = tkinter.Toplevel(self.parent)
self.app = LogWindow(self.logWindow)
def main():
global app, stopRead
root = tkinter.Tk()
root.geometry("300x300")
app = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
Maybe i need to have a single instance of a Toplevel class and call show and close to show or hide the secondary window.
Finally after some tests I've found how to solve that, thanks to the #furas response and some investigation about the Tkinter events with the protocol function.
I've got that working with:
import tkinter
logWindowExists = False
class LogWindow():
def __init__(self, parent):
global logWindowExists, root
logWindowExists = True
self.parent = parent
self.frame = tkinter.Frame(self.parent)
def on_closing(self):
global logWindowExists
logWindowExists = False
self.parent.destroy()
class MainWindow(tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self, parent)
self.parent = parent
frame = tkinter.Frame(self, borderwidth=1)
frame.pack(fill=tkinter.BOTH, expand=True, padx=5, pady=5)
self.LogButton = tkinter.Button(frame, text="Log Viewer", command= self.openLogWindow)
self.LogButton.grid(sticky=tkinter.E+tkinter.W)
self.pack(fill=tkinter.BOTH,expand=True)
def openLogWindow(self):
if not logWindowExists:
self.logWindow = tkinter.Toplevel(self.parent)
self.app = LogWindow(self.logWindow)
self.logWindow.protocol("WM_DELETE_WINDOW", self.app.on_closing)
else:
self.logWindow.deiconify()
def main():
global app, stopRead, root
root = tkinter.Tk()
root.geometry("300x300")
app = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
Using a boolean to know if the window exists or not i can handle when the window it's opened or not and just show the existing window or creating a new one.

Python: Tkinter OK button callback function

I'm working on my very first Python GUI and I'm trying to modify this tkinter example, but I simply cannot figure out how to write a callback function for the OK button that will pass on the entered value to the main program.
#!/usr/bin/python
# -*- coding: utf-8 -*-
from Tkinter import Tk, BOTH, StringVar, IntVar
from ttk import Frame, Button, Style, Label, Entry
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Get Value")
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
valueLabel = Label(self, text="Value: ")
valueLabel.place(x=10, y=10)
value=StringVar(None)
value.set("this is the default value")
valueEntry=Entry(self, textvariable=value)
valueEntry.place(x=70, y=10)
quitButton = Button(self, text="Quit", command=self.quit)
quitButton.place(x=10, y=50)
okButton = Button(self, text="OK", command=self.quit)
okButton.place(x=120, y=50)
def main():
root = Tk()
root.geometry("220x100+300+300")
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
I've read a gazillion of tutorials, but none of them explains this clearly. Theoretically, I should be able to get the selected value with value.get(), but I keep getting error messages no matter where I put it. Also, AFAIK, I should be able to define a default value with value.set(), but this doesn't seem to have an effect, since the text box is empty when I run the program.
What is the easiest way to pass on values to the main python program after root.mainloop() terminates? (The actual dialog box contains several entry boxes for entering string and integer values.)
I.e. I want to be able to use something like:
root = Tk()
root.geometry("220x100+300+300")
app = Example(root)
root.mainloop()
print value
print value2
print value3
How do I define default values for entry boxes?
Change every occurrence of the value variable with self.value. This should fix it and the default value will be displayed.
UPDATE
from Tkinter import Tk, BOTH, StringVar, IntVar
from ttk import Frame, Button, Style, Label, Entry
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def showMe(self):
print(self.value.get())
def initUI(self):
self.parent.title("Get Value")
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
valueLabel = Label(self, text="Value: ")
valueLabel.place(x=10, y=10)
self.value=StringVar(None)
self.value.set("this is the default value")
valueEntry=Entry(self, textvariable=self.value)
valueEntry.place(x=70, y=10)
quitButton = Button(self, text="Quit", command=self.quit)
quitButton.place(x=10, y=50)
okButton = Button(self, text="OK", command=self.showMe)
okButton.place(x=120, y=50)
def main():
root = Tk()
root.geometry("220x100+300+300")
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
Both your quitButton and okButton call the self.quit functions. So no mater what value you enter when you press the OK button you are calling the quit function which has its own problems as well outside the scope of your question.
Try to define value as self.value and make the okButton call a function that does: print self.value.get().

Categories

Resources