Tkinter widget keeps its state after being destroyed and re-built - python

My code destroys some widgets then builds new widgets then in the end of the application a button asks if I want to get to the beginning. This button will call class constructor which re-initialize every variable and start re-drawing the same old widgets. The problem is that the widgets even if they are new keep their latest values before destruction.
def mapping(self):
sort_frame = Frame(self.top_frame)
sort_frame.grid(row=0,column=1)
sort = False
Checkbutton(sort_frame, text="Sort: ", variable=sort, onvalue=True, offvalue=False,command=lambda fr=sort_option_frame, nx = next_button : self.enable_sort(fr,nx)).pack(side=TOP)
next_button = Button(self.bottom_frame, text='Next',borderwidth=1, command=self.output_select)
next_button.pack( side = RIGHT)
def output_select(self):
for widget in self.top_frame.winfo_children():
widget.destroy()
for widget in self.bottom_frame.winfo_children():
widget.destroy()
#new widgets drawing
Button(self.bottom_frame, text='New file',borderwidth=1, command=self.restart).pack( side = TOP)
#This UI resets the application for a new cycle
def restart(self):
for widget in self.top_frame.winfo_children():
widget.destroy()
for widget in self.bottom_frame.winfo_children():
widget.destroy()
self.__init__(self.root)
In this code for example, the Checkbutton on mapping will keep its latest values when mapping is called back in the new cycle.
I want the checkbutton to be new as if it's the first time it was created.
Thanks for your help

You can't use normal variables for the variable attribute. They need to be an instance of StringVar, IntVar, DoubleVar, or BooleanVar.

Related

Want to change color of 100 buttons on hover in tkinter

import tkinter as tk
def on_enter(e):
year_btn.config(background="orange",foreground="white")
def on_leave(e):
year_btn.config(background="white", foreground="black")
window = tk.Tk()
yearnumber=1
for i in range(10):
window.rowconfigure(i,weight=1,minsize=40)
window.columnconfigure(i,weight=1,minsize=40)
for j in range(10):
frame = tk.Frame(padx=5,pady=5)
frame.grid(row=i,column=j,sticky="nsew")
year_btn = tk.Button(text=f"{yearnumber}", master=frame, activebackground="red", activeforeground="white")
year_btn.pack(padx=1, pady=1,fill="both",expand="true")
#year_btn.grid(sticky="nsew")
yearnumber+=1
year_btn.bind('<Enter>', on_enter)
year_btn.bind('<Leave>',on_leave)
window.mainloop()
So, I created hundred buttons over here and wanted them to change color when the mouse hovers over them, I did this as per the internet to create events and bind them with the buttons.
My problem is I created hundred buttons using for-loop, so I added the binding code in the loop. The result of this was that if I hover the mouse over any Button only the 100th hover changes color. I also placed the hovering code outside the loop but that does nothing
How do I change color of button over hover for each button in this case.
Thank you
The event object that is passed to the bound function has a reference to the widget that received the event, under the attribute widget. You can use that to change the attribute of the button.
def on_enter(e):
e.widget.config(background="orange",foreground="white")
#^^^^^^^
def on_leave(e):
e.widget.config(background="white", foreground="black")
#^^^^^^^

tkinter: Unbind not working, even workarounds don't work

I bring up here a problem, that's been there for ages, but is obviously still not solved and older workarounds don't work on my Python 3.7.2 (64-bit on Win10).
I have this code:
import tkinter as tk
import tkinter.simpledialog
# message box to enter a value where to set the scale to
class EnterValueBox(tk.simpledialog.Dialog):
def body(self, master):
self.e = tk.Entry(self, width=10)
self.e.pack(pady=5)
return self.e # initial focus
def apply(self):
print(self.e.get())
# callback to open message box
def enterValue(event):
EnterValueBox(root, title="Enter Value 0..100")
# create window with scale widget
root = tk.Tk()
scale = tk.Scale(root, orient=tk.HORIZONTAL, from_=0, to=100)
scale.pack()
# unbind any button-3 events
scale.unbind("<ButtonPress-3>")
scale.unbind("<ButtonRelease-3>")
scale.unbind("<Button-3>")
# bind button-3 press event to open message box
scale.bind("<ButtonPress-3>", enterValue)
tk.mainloop()
It creates a window with a single scale widget. I want to bind ButtonPress-3 to open a little dialog to directly enter a new value. The code only prints that value to the shell, but the example shows, that the unbind is not working, because after printing the value, the dialog box is closed (when the user clicks OK) and then the default binding is executed, which sets the slider, where the user clicked in the trough of the slider widget.
I tried the workaround from Deleting and changing a tkinter event binding with a PatchedScale widget (instead of the PatchedCanvas shown there), but that didn't make any difference.
Any help would be greatly appreciated!
The default bindings are not on the widget, they are on the widget class. Calling unbind on a widget for which there is no widget-specific binding won't have any effect.
If you don't want the default binding to run after your widget-specific binding, the normal technique is to have your bound function return the string break.
def enterValue(event):
EnterValueBox(root, title="Enter Value 0..100")
return "break"

Python tkinter When Toplevel object is destroyed, With toplevel binding for <Destroy> the bound command executes multiple times

In the following code, When the toplevel window is destroyed, the command in the
bind statement is executed multiple times. Probably once for each child widget within the Top Level. When I change the toplevel to a Frame, the bind command only executes once.
In the example, quit() or raise SystemExit are deferred until the command finishes its looping. Why is this happening?
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showinfo
class PlainFrame(tk.Frame):
def __init__(self,parent):
super().__init__(parent)
self.butts = []
for i in range(20) :
# button
self.butts.append(ttk.Button(self, text=f'Click Me {i}'))
self.butts[i]['command'] = self.button_clicked
self.butts[i].grid(column=0,row=i)
self.pack()
def button_clicked(self):
showinfo(title='Information',
message='Hello, Tkinter!')
class MainFrame(tk.Toplevel):
#class MainFrame(tk.Frame):
def __init__(self, container,*args,**kwargs):
super().__init__(container,kwargs)
options = {'padx': 5, 'pady': 5}
# label
self.label = ttk.Label(self, text='Hello, Tkinter!')
self.label.pack(**options)
self.quit_button = ttk.Button(self,text='Quit',
command = self._quit)
self.quit_button.pack()
self.frame = PlainFrame(self)
# add when frame
#self.pack()
def button_clicked(self):
showinfo(title='Information',
message='Hello, Tkinter!')
def _quit(self):
self.destroy()
class App(tk.Tk):
def __init__(self):
super().__init__()
# configure the root window
self.title('My Awesome App')
self.geometry('600x100')
def quitting(self,event):
print ('just passing through')
quit()
raise SystemExit
if __name__ == "__main__":
app = App()
frame = MainFrame(app)
# app.withdraw()
frame.bind('<Destroy>',app.quitting)
app.mainloop()
With toplevel binding for the bound command executes multiple times
Yes, this is how tkinter is designed to work.
When you bind to something, you don't bind to a widget. Rather, you bind to a binding tag. Every widget has a set of binding tags. When a widget receives an event, tkinter will check each of its binding tags to see if there's a function bound to it for the given event.
So, what are the widget binding tags? Every widget gets the binding tag "all". Each widget also gets a binding tag named after the widget itself. It gets a third binding tag that is the name of the widget class (eg: "Button", "Label", etc). The fourth tag -- the one causing you trouble -- is the name of the window that contains the widget. The order goes from most to least specific: widget, widget class, window, "all".
You can see this by printing out the binding tags for a widget. Consider the following code:
import tkinter as tk
root = tk.Tk()
toplevel = tk.Toplevel(root)
label = tk.Label(toplevel)
print(f"binding tags for label: {label.bindtags()}")
When run, the above code produces this output:
binding tags for label: ('.!toplevel.!label', 'Label', '.!toplevel', 'all')
The first string, .!toplevel.!label is the internal name of the label widget. Label is the widget class, .!toplevel is the name of the toplevel widget, and then there's the string all.
What happens when you click on the label? First, tkinter will check to see if there is a binding for the button click on the tag .!toplevel.!label. It will have one if you bound to the widget itself. Next, it will check to see if there is a binding on the widget class. Widgets like scrollbars and buttons will have a binding on the widget class, but label won't. Next, tkinter will see if there's a binding on the window itself for the event. And finally, it will see if there is a binding on the special tag all.
You can alter the bindtags for a widget by passing the list of binding tags to the bindtags command. For example, if you want every widget to have a bind tag for the frame, you could set the bindtags to include the frame, and then every widget will respond to an event that is bound to the frame.
You can use the same technique to remove bindings as well. For example, if you wanted to remove all default bindings from a Text widget, you could remove Text from its list of bind tags. Once you do that, the widget will not respond to any key presses or key releases.
The canonical description of how binding tags works is in the bindtags man page for tcl/tk.

Tkinter scale gets stuck each time a function is called

I have a problem where the tkinter Scale widget seems to get stuck whenever I run a seemingly big function.
This is the code:
from tkinter import Tk, Button, Frame, Scale
root = Tk()
slider = Scale(root, orient='horizontal')
slider.pack()
frame = Frame(root)
frame.pack()
num = 0
def buttons():
for widget in frame.winfo_children():
widget.destroy()
for i in range(50):
Button(frame, text='Button' + str(i)).pack()
def basic():
global num
slider.set(num)
num += 1
print(num)
if num <= 100:
slider.after(100, basic)
if __name__ == '__main__':
buttons()
basic()
root.bind('<space>', lambda x: buttons())
root.mainloop()
What I want my program to do is update the slider normally even when I press 'Space' (meaning calling the buttons() function)
If you watch closely each time you press Space the slider will get stuck a little.
Since I'm using the slider for an Mp3 player in order to show time elapsed, this loss of time is extremely important for example for audio files of 10 or so seconds since the slider falls behind a lot making it seem as if it's working wrong \
I'd also like to point out that destroying the buttons and then repacking them is necessary for me.
I suspect that this happens because the program has to go over the buttons() function something that takes time since it's creating 50 buttons. Or am I mistaken?
Can I avoid that lag?
PS: As I mentioned in my comment:
I normally have a button that renames a (button) which is a song and in order for them to alphabetically ordered after renaming i need to recall the function that draws them. If I only configure tha name of the button (and not redraw them), it will stay in place and not move down or up depending on its name, while on the actual directory the order will change leading to inappropriate behavior such as playing the same song
Here are some images for better understanding:
Thanks in advance!
Look at this code:
import tkinter as tk
def config_buttons():
# Get the `text` of the first button
starting_value = int(buttons[0].cget("text")) + 1
# Iterate over all of the buttons
for i, button in enumerate(buttons, start=starting_value):
# Change the button's `text` and `command` atributes
button.config(text=i, command=lambda i=i:print("Clicked %i"%i))
root = tk.Tk()
buttons = []
add_button = tk.Button(root, text="+1 on all buttons", command=config_buttons)
add_button.pack()
for i in range(50):
button = tk.Button(root, text=i, command=lambda i=i:print("Clicked %i"%i))
button.pack()
buttons.append(button)
root.mainloop()
When the add_button buttons is pressed, I iterate over all of the buttons and change their text and command attributes. As I am not creating new buttons, the function runs very fast.
You can implement something similar in your code. Basically, avoid creating new buttons and just update the ones you already have on the screen.

Can you make a button create itself?

So I want to make a button, that when I click on it, it gets deleted and another one appears, and this can be done an infinite amount of times
a=0
button1 = [1,2,3,4,5,6]
def ButtonClick():
global a
#Destroys the first button
button1[a].destroy()
a+a+1
#Making new button, that should do the same as the old button
button1[a] = tk.Button(text='hello',command=ButtonClick)
canvas1.create_window(250+a*50, 140, window=button1[a])
button1[a] = tk.Button(text='hello',command=ButtonClick)
canvas1.create_window(100, 140, window=button1[a])
As you can see the new button is also using command=ButtonClick so when I press on the created button it should do the same as the old one, but it doesn't and I am not sure why because when I change the command on the new button it says error so it is somehow refereeing to the def ButtonClick. But nothing happens when I press the new button. Can anyone help me out?
In order to destroy and rebuild a button, you will have to make it global. Here is a working example of a button that can be destroyed by its own function, only to be created again:
def ButtonClick():
global root
root.b.destroy()
root.b = Button(root,text="Hello",command=ButtonClick)
root.c.create_window(250+root.button1[root.bnumber]*50,140,window=root.b)
if root.bnumber<len(root.button1)-1:root.bnumber+=1
global root
root = Tk()
root.geometry('800x600')
root.resizable(0, 0)
root.c = Canvas(root,bg="#ffffff",width=800,height=600)
root.c.grid(row=0,column=0,sticky=W+S+E+N)
root.b = Button(root,text="Hello",command=ButtonClick)
root.c.create_window(100,140,window=root.b)
root.button1 = [1,2,3,4,5]
root.bnumber = 0
I made your button1 list and the current index (a) attributes of the tkinter widget, so it will be accessible as long as a function has access to the widget.
For this example, however, it would be easier to just move the window within the tkinter Canvas instead of removing and re-creating it.

Categories