Tkinter pack_forget() and destroy() don't work - python

I have a problem with these two functions that don't work as expected.
I want to use a selection form drop-down menù and based on the selection two different set of checkbox are displayed in the root window. To do so, I use this code:
from tkinter import *
import tkinter as tk
def evaluation(form):
form.pack_forget()
form.destroy()
form=Frame(root)
GotPlatform=platform.get()
for elem in PlatformOption[GotPlatform]:
OptionButtonSel[elem]=Checkbutton(form, text=elem)
OptionButtonSel[elem].pack()
form.pack(fill="both", expand = 1)
PlatformOption={'Option1':["option1-1","option1-2"],'Option2':["option2-1","option2-2"]}
OptionButtonSel={}
root = tk.Tk()
f1=Frame(root)
menuBar = tk.Menu(root)
menu1 = tk.Menu(root)
submenu = tk.Menu(root)
platform = StringVar()
submenu.add_radiobutton(label="Option1", value="Option1", variable=platform,command=lambda:evaluation(f1))
submenu.add_radiobutton(label="Option2", value="Option2", variable=platform,command=lambda:evaluation(f1))
menuBar.add_cascade(label="Options", menu=menu1)
menu1.add_cascade(label="Select option", menu=submenu)
root.config(menu=menuBar)
root.mainloop()
The code works but whenever I change the options fron drop-down menù, the checkboxes option are stacked and aren't overwritten as expected.
This puzzles me since I have used this other code and it works as expected:
from tkinter import Tk, Frame, Menu, Label
def show_frame1():
forget_all()
f1.pack(fill="both", expand = 1)
def show_frame2():
forget_all()
f2.pack(fill="both", expand = 1)
def forget_all():
f1.pack_forget()
f2.pack_forget()
root = Tk()
f1 = Frame(root)
f2 = Frame(root)
Label(f1, text="MENU SELECTED 1").pack()
Label(f2, text="MENU SELECTED 2").pack()
menubar=Menu(root)
subMenu=Menu(menubar, tearoff = 0)
menubar.add_cascade(label = 'MENU', menu=subMenu)
subMenu.add_command(label = 'SUBMENU1', command = show_frame1)
subMenu.add_command(label = 'SUBMENU2', command = show_frame2)
root.config(menu = menubar)
root.mainloop()
Aside from addind destroy(), the usage of frame and pack_forget() seems identical to me.
I use Python 3.10.1, IDE Spyder last version, Windows 8.

The root of the problem is that you create a form that never appears on the screen. You create it, but never call pack on it. You then pass this form to the function every time the button is clicked. In other words, you keep passing the original form, not the new one that is recreated.
The best solution is for your main code to call pack, place, or grid on the form, and then in the function you can delete the children of the form without deleting the form itself.
So, add the following shortly after creating f1:
f1.pack(fill="both", expand=True)
Next, modify evaluation to destroy the children rather than the form itself:
def evaluation(form):
for child in form.winfo_children():
child.destroy()
GotPlatform=platform.get()
for elem in PlatformOption[GotPlatform]:
OptionButtonSel[elem]=Checkbutton(form, text=elem)
OptionButtonSel[elem].pack()

It gives the effect that it doesn't work because the widget is invisible to the screen. This topic however is further explained below
pack_forget() not working
Just to prove my theory I did some other research about the topic and
https://www.geeksforgeeks.org/python-forget_pack-and-forget_grid-method-in-tkinter/
explains the concept of pack_forget() perfectly

Related

Trying to make a tkinter menu button that do multiple tasks

I've been trying to make a button in menu bar in the tkinter app and can't seem to figure out how to make this button make multiple tasks. I've tried the following, it forgets the frame correctly but don't execute the carr function.
from tkinter import *
root = Tk()
menu_bar = Menu(root)
file_menu = Menu(menu_bar, tearoff = 0)
file_menu.add_command(label="Carré", command=lambda:[frame.pack_forget(),carr])
menu_bar.add_cascade(label="Séléction", menu=file_menu)
root.config(menu=menu_bar)
root.mainloop()
The proper solution is to create a function for the button.
def do_carr():
frame.pack_forget()
carr()
...
file_menu.add_command(label="Carré", command=do_carr)

Tkinter toplevel window is not defined

I wonder if someone could tell me if its possible to update toplevel windows using external functions. I've replicated my issue below what I need to do is update the Toplevel(master) using the function updatelabel(). I have used similar external function to update items in root which works like a dream. However, with the top level window I always get the
NameError: name 'newWindow' is not defined
The only work around I found was to kill the newWindow using newWindow.destroy() on each load but this method makes the screen pop up and then close again which doesn't look pretty. Any help most welcome thanks.
from tkinter import *
from tkinter.ttk import *
master = Tk()
master.geometry("200x200")
def updatelabel():
Label(newWindow,
text="I changed").pack()
def openNewWindow():
# Toplevel object which will
# be treated as a new window
newWindow = Toplevel(master)
# sets the title of the
# Toplevel widget
newWindow.title("New Window")
# sets the geometry of toplevel
newWindow.geometry("200x200")
# A Label widget to show in toplevel
Label(newWindow,
text="I want to change").pack()
button1 = Button(newWindow,
text="Click me to change label", command=updatelabel).pack()
btn = Button(master,
text="open a new window",
command=openNewWindow)
btn.pack(pady=10)
mainloop()
Your “newWindow” is defined in your “openNewWindow” function and so it basically only exists in there, you could probably fix this by either defining “newWindow” outside of the function, or by using it as an argument(just add it to the brackets and give it a name in the function itself’s brackets) calling “updateLabel”
I think this should work, though I haven’t worked with tkinter in a bit so don’t blame me if it doesn’t
from tkinter import *
from tkinter.ttk import *
master = Tk()
master.geometry("200x200")
def updatelabel(newWindow):
Label(newWindow,
text="I changed").pack()
def openNewWindow():
# Toplevel object which will
# be treated as a new window
newWindow = Toplevel(master)
# sets the title of the
# Toplevel widget
newWindow.title("New Window")
# sets the geometry of toplevel
newWindow.geometry("200x200")
# A Label widget to show in toplevel
Label(newWindow,
text="I want to change").pack()
button1 = Button(newWindow,
text="Click me to change label", command= lambda: updatelabel(newWindow)).pack()
btn = Button(master,
text="open a new window",
command=openNewWindow)
btn.pack(pady=10)
mainloop()

How to disable the movement of the Tkinter Window without removing the title bar

I have been creating an application for taking the test. So, for that, I have to do two things.
First, disable the drag of the Tkinter window and don't let the user focus on other windows rather than my application window. This means I wanted to make my application such that, No other application can be used while my application is in use.
Try this:
import tkinter as tk
class FocusedWindow(tk.Tk):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Force it to be unminimisable
super().overrideredirect(True)
# Force it to always be on the top
super().attributes("-topmost", True)
# Even if the user unfoceses it, focus it
super().bind("<FocusOut>", lambda event: self.focus_force())
# Take over the whole screen
width = super().winfo_screenwidth()
height = super().winfo_screenheight()
super().geometry("%ix%i+0+0" % (width, height))
root = FocusedWindow()
# You can use it as if it is a normal `tk.Tk()`
button = tk.Button(root, text="Exit", command=root.destroy)
button.pack()
root.mainloop()
That removed the title bar but you can always create your own one by using tkinter.Labels and tkinter.Buttons. I tried making it work with the title bar but I can't refocus the window for some reason.
One way to do this is by the following, another could be to overwrite the .geometry() method of tkinter.
In the following code I simply had get the position by using winfo_rootx and winfo_rooty. After this you can force the window by calling the geometry method via binding the event every time the window is configured.
import tkinter as tk
def get_pos():
global x,y
x = root.winfo_rootx()
y = root.winfo_rooty()
def fix_pos():
root.bind('<Configure>', stay_at)
def stay_at(event):
root.geometry('+%s+%s' % (x,y))
root = tk.Tk()
button1 = tk.Button(root, text='get_pos', command=get_pos)
button2 = tk.Button(root, text='fix_pos', command=fix_pos)
button1.pack()
button2.pack()
root.mainloop()

OptionMenu widget not updating when StringVar is changed until hovered

I need a menu that can respond to items being clicked by running code then switch the text back to a default text.
Currently, my implementation works but the default text is only displayed when the cursor hovers over the menu after clicking.
I have searched but I could not find anything related to this problem, although maybe that is because I am unsure as to what exactly is causing this.
Here is the code to reproduce this behaviour:
from tkinter import *
root = Tk()
default_text = 'select an item'
def thing_selected(self, *args):
#other stuff happens here
var.set(default_text)
var = StringVar(root)
var.set(default_text)
var.trace('w', thing_selected)
menu = OptionMenu(root, var, *['Pizza','Lasagne','Fries','Fish'])
menu.pack()
root.mainloop()
Here is a gif representing the outcome:
I would expect the text at the top to be updated instantaneously, but it only updates when the cursor has hovered over the widget
I am looking for some way to trigger a hover event on the widget or I am open to suggestions for any other methods of accomplishing this.
You could take a different route and use the command attribute of the OptionMenu:
import tkinter as tk
root = tk.Tk()
default_text = 'select an item'
def thing_selected(selected):
#other stuff happens here
print(var.get())
var.set(default_text)
print(var.get())
var = tk.StringVar()
var.set(default_text)
options = ['Pizza','Lasagne','Fries','Fish']
menu = tk.OptionMenu(root, var, *options, command = thing_selected)
menu.pack()
root.mainloop()

Tkinter Button Function Control by MessageBox

The code below shows part of my program and the issue im facing.
def checkAnswer():
mainAnswer = answer01.get()
if len(mainAnswer) == 0:
messagebox.showwarning(message='Please answer the question!')
return
if int(mainAnswer) != answer:
messagebox.showwarning(message='Incorrect! The correct answer is: ' + str(answer))
else:
nxtquest.config(state=NORMAL)
messagebox.showinfo(message='Correct! :)')question01 = Label(easy)
question01.grid(row=2, column=0)
answer01 = Entry(easy)
answer01.grid(row=3, column=2)
answer01.bind('<Return>', func=lambda e:checkAnswer())
start = Button(easy, text = "Start!", command=ask, bg='green', fg='white')
start.grid(row=3, column=3)
nxtquest = Button(easy, text='Next Question', command=ask)
nxtquest.grid(row=5, column=2)
checkbut = Button(easy, text='Check', command=checkAnswer)
checkbut.grid(row=4, column=2)
#check button and answer01 enabled after start pressed
launch = 1
if launch == 1:
answer01.config(state=DISABLED)
checkbut.config(state=DISABLED)
nxtquest.config(state=DISABLED)
The issue which im struggling here is that whenever i run the program everything is okay. When the window is displayed checkbut, nxtquest and label answer01 are greyed out (disabled).
The start button enables only checkbut and answer01 and then is destroyed. (So far so good)
So nxtquest will enable once the input is correct as seen in the
else:
nxtquest.config(state=NORMAL)
But when I reach another question the nxtquest button is already enabled, this is the problem!
How could I make it so the button will enable itself only after the warning message box is displayed?
Could I ask for some help with this and possibly suggestions if you see any rookie mistakes ?
Whilst I don't know of any way you could do this with a messagebox widget (although I'm sure there's an event you could use as the trigger) you can most certainly do this by substituting the messagebox with a Toplevel widget and using .protocol("WM_DELETE_WINDOW", callback()) on the widget.
This would mean that whenever the Toplevel widget was "closed" we would actually be overwriting the action taken when the event was raised and would manually handle the closing of the widget as well as whatever else we wanted it to do.
This would look something like the below:
from tkinter import *
root = Tk()
button = Button(root, text="Ok", state="disabled")
button.pack()
top = Toplevel(root)
def close():
top.destroy()
button.configure(state="active")
top.protocol("WM_DELETE_WINDOW", close)
root.mainloop()
If you close the Toplevel widget you will see that the button is now active instead. This would equally work if we added a Button to the Toplevel widget which called the function close().

Categories

Resources