Tkinter - How would I go about resetting list of objects? - python

Yesterday I asked this question Creating elements by loop Tkinter to find out how to dynamically create some bullet points. Now I'm looking to add a clear button so when pressed, will reset the entire form. I have tried setting the list back to [] but it didn't work.
edit - So basically when I press reset I'd like it to look exactly like it did when the form was loaded.

The buttons are removed with the destroy method:
for button in self.button:
button.destroy()
import Tkinter as tk
class ButtonBlock(object):
def __init__(self, master):
self.master = master
self.button = []
self.button_val = tk.IntVar()
entry = tk.Entry()
entry.grid(row=0, column=0)
entry.bind('<Return>', self.onEnter)
entry.focus()
clear_button = tk.Button(master, text='Clear', command=self.onClear)
clear_button.grid(row=0, column=1)
def onClear(self):
for button in self.button:
button.destroy()
def onEnter(self, event):
entry = event.widget
num = int(entry.get())
self.onClear()
for i in range(1, num+1):
self.button.append(tk.Radiobutton(
self.master, text=str(i), variable=self.button_val, value=i,
command=self.onSelect))
self.button[-1].grid(sticky='WENS', row=i, column=0, padx=1, pady=1)
def onSelect(self):
print(self.button_val.get())
if __name__ == '__main__':
root = tk.Tk()
ButtonBlock(root)
root.mainloop()

Setting the list back (i.e. using self.button = []) just clears the data stored in the button variable. That action alone is not connected to the user interface (UI). You have to explicitly remove the widget objects which were created (by the onEnter method).
So the clearing feature you are looking for should be feasible by extending the answer from your previous question. Add an onClear method to the ButtonBlock class so that when your "Clear" control (i.e. using a button widget) is selected its callback function calls ButtonBlock.onClear(), similar to how your Entry widget invokes the onEnter method.
EDIT: See unutbu's answer to this question. When selected, the clear_button control calls ButtonBlock.onClear(). The for loop in onClear gets a reference to each button ojbect from the button list and calls the object's destroy method, which removes it from the UI.

Related

how to change the color of PanedWindow upon hovering over it, for multiple PanedWindow in tkinter?

I am trying to make PanedWindow change color when I hover mouse over it in tkinter.
now this works for a single iteration.
but when i try to do it for multiple panedwindows it only changes color of the last window.
import tkinter as tk
root = tk.Tk()
for i in range(10):
m1 = tk.PanedWindow(root, bd=4, relief="flat", bg="blue")
m1.pack()
def on_enter(e):
m1.config(background='OrangeRed3', relief="flat")
def on_leave(e):
m1.config(background='SystemButtonFace', relief="flat")
# Create a Button
button1 = tk.Button(m1, text=f"{i}")
button1.pack(pady=20)
# Bind the Enter and Leave Events to the Button
m1.bind('<Enter>', on_enter)
m1.bind('<Leave>', on_leave)
m1.add(button1)
tk.mainloop()
Since at each iteration of the loop all variables are overwritten, the functions are bound to the last created element. It is necessary to pass the desired element to the function. It is even better to collect everything created in dictionaries, so that in the future you can easily change them.
import tkinter as tk
from functools import partial
ms = {}
btns = {}
root = tk.Tk()
def on_enter(m, e):
m.config(background='OrangeRed3', relief="flat")
def on_leave(m, e):
m.config(background='SystemButtonFace', relief="flat")
for i in range(10):
ms[i] = tk.PanedWindow(root, bd=4, relief="flat", bg="blue")
ms[i].pack()
# Create a Button
btns[i] = tk.Button(ms[i], text=f"{i}")
btns[i].pack(pady=20)
# Bind the Enter and Leave Events to the Button
ms[i].bind('<Enter>', partial(on_enter, ms[i]))
ms[i].bind('<Leave>', partial(on_leave, ms[i]))
ms[i].add(btns[i])
tk.mainloop()

How do I destroy specific button in the loop when clicked?

I am trying to create a 10*10 board of buttons, which when clicked, the clicked button is destroyed and only that one. However, I don't know how to specify which button has been clicked.
from tkinter import *
root = Tk()
root.title("Board")
def buttonClick():
button.destroy()
for i in range(10):
for j in range(10):
button = Button(root, text="", padx=20, pady=10, command=buttonClick)
button.grid(row=i+1, column=j+1)
root.mainloop()
You have to create function which gets widget/button as argument and uses it with destroy()
def buttonClick(widget):
widget.destroy()
And first you have to create Button without command=
button = tk.Button(root, text="", padx=20, pady=10)
and later you can use this button as argument in command=.
button["command"] = lambda widget=button:buttonClick(widget)
It needs to use lambda to assign function with argument.
Because you create many buttons in loop so it also needs to use widget=button in lambda to create unique variable with value from button for every command. If you don't use it then all commands will use reference to the same (last) button - and click on every button will destroy only last button.
Full working code
import tkinter as tk # PEP8: `import *` is not preferred
# --- functions ---
def buttonClick(widget):
widget.destroy()
# --- main ---
root = tk.Tk()
root.title("Board")
for i in range(10):
for j in range(10):
button = tk.Button(root, text="x", padx=20, pady=10)
button["command"] = lambda widget=button:buttonClick(widget)
button.grid(row=i+1, column=j+1)
root.mainloop()
PEP 8 -- Style Guide for Python Code
This can be easily accomplished with a custom class if you're alright with that:
from tkinter import Button, Tk
root = Tk()
root.title("Board")
class Self_Destruct_Button(Button):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.configure(command=self.button_click)
def button_click(self):
self.destroy()
for i in range(10):
for j in range(10):
button = Self_Destruct_Button(root, text="", padx=20, pady=10)
button.grid(row=i + 1, column=j + 1)
root.mainloop()
So the custom class just assigns the command to a button_click method, which destroys the button.
As a side note, I also removed the wildcard import as that's not best practice.
Let me know if this works for you

Bind Tkinter Combobox to Entry change?

I have a ttk.Combobox that my users can select from a dropdown list of options, or manually type something in. I have it bound to Return, so that if my user presses return after making a change it will update, but if my user clicks in the box and accidentally types something else in, it will cause an error down the road. To be clear, I already have an event bound to a new selection, as well as pressing return.
I am asking if it is possible to check if the box value has been changed when focus leaves the box, and if so, then call a function? When I tried a FocusOut bind, everytime I click on one of the dropdowns it calls my function and doesn't let me select anything from the dropdown, so that isn't working.
selection.bind('<Return>', lambda event, entry=selection, row=row: update(
updated_entry=entry.get(), row=row, entry=entry))
selection.bind('<<ComboboxSelected>>', lambda event, entry=selection, row=row: update(
updated_entry=entry.get(), row=row, entry=entry))
edit: Here is a sample code. The way this is written, if the user selects an item from the dropdown, it updates the label. If the users types something in and presses Return, it updates the label. But if the user types something in, and clicks on the other dropdown, it does not update the label.
import tkinter as tk
from tkinter import ttk
def update(updated_entry, row, entry):
label = tk.Text(root, height=1, width=10)
label.insert(tk.END, updated_entry)
label.grid(row=row, column=2)
return 'break'
def gui(root):
root.geometry('300x150')
root.config(background='snow3')
for row in range(2):
options = ['test', 'test1', 'test2']
selection = tk.ttk.Combobox(root, value=options)
selection.bind('<Return>', lambda event, entry=selection, row=row: update(
updated_entry=entry.get(), row=row, entry=entry))
selection.bind('<<ComboboxSelected>>', lambda event, entry=selection, row=row: update(
updated_entry=entry.get(), row=row, entry=entry))
selection.grid(row=row, column=1)
label = tk.Text(root, height=1, width=10)
label.grid(row=row, column=2)
if __name__ == '__main__':
root = tk.Tk()
gui(root)
tk.mainloop()
ttk.Comboboxes are a subclass of Entry widgets, which means that you can add validation to them in the same manner as you would to their base class. Namely by using the validate= and validatecommand= options Entrys support.
The reason to do this is because "validation" will allow the contents of the associated Combobox to be checked when it loses focus—i.e. your stated goal. This should work fine in conjunction with the bound event-handling you already have. The following code, which is similar to your minimal reproducible example, illustrates how do to something like that.
Note: This approach would also allow doing some real validation of the values the user has entered to prevent problems later on if they're invalid.
import tkinter as tk
from tkinter import ttk
def update(updated_entry, entry):
''' Combobox change Callback. '''
entry.delete('1.0', tk.END)
entry.insert(tk.END, updated_entry)
def gui(root):
root.geometry('300x150')
root.config(background='snow3')
for row in range(2):
text = tk.Text(root, height=1, width=10) # Widget to be updated.
text.grid(row=row, column=2)
def check_okay(new_value, text=text):
update(new_value, text)
return True # Note: accepts anything.
combobox = ttk.Combobox(root, value=('test', 'test1', 'test2'),
validate='focusout',
validatecommand=(root.register(check_okay), '%P'))
combobox.grid(row=row, column=1)
combobox.bind('<Return>', lambda event, entry=combobox, text=text:
update(entry.get(), entry=text))
combobox.bind('<<ComboboxSelected>>', lambda event, entry=combobox, text=text:
update(entry.get(), entry=text))
if __name__ == '__main__':
root = tk.Tk()
gui(root)
tk.mainloop()

Deselecting from a listbox in Tkinter

I'm just wondering how I can deselect from a list box in thinter. Whenever I click on something in a list box, it gets highlighted and it gets underlined, but when I click off of the screen towards the side, the list box selection stays highlighted. Even when I click a button, the selection still stays underlined. For ex: in the example code below, I can't click off of the list box selection after clicking on one of them.
from tkinter import *
def Add():
listbox.insert(END, textVar.get())
root = Tk()
textVar = StringVar()
entry = Entry(root, textvariable = textVar)
add = Button(root, text="add", command = Add)
frame = Frame(root, height=100, width=100, bg="blue")
listbox = Listbox(root, height=5)
add.grid(row=0, column=0, sticky=W)
entry.grid(row=0, column=1, sticky=E)
listbox.grid(row=1, column=0)
frame.grid(row=1, column=1)
root.mainloop()
Yes, that's the normal behavior of the listbox. If you want to change that you could call the clear function every time the listbox left focus:
listbox.bind('<FocusOut>', lambda e: listbox.selection_clear(0, END))
Use the selectmode parameter on the Listbox widget.
You can click the selected item again and it will clear the selection.
See the effbot link:
http://effbot.org/tkinterbook/listbox.htm
listbox = Listbox(root, height=5, selectmode=MULTIPLE)
I have managed to create the functionality needed within the Listbox widget so that when a user clicks either back on the same item in the Listbox or elsewhere on screen the currently selected item is deselected. The solution came out to be quite simple.
Firsly I created a binding so that when the left mouse button is pressed anywhere on the window a function to deselect the list box is executed.
root.bind('<ButtonPress-1>', deselect_item)
I then created a variable to store the value of the last listbox item to be selected and initialised its value to None
previous_selected = None
Then I defined the function to deselect the listbox as follows. Firsly the new item (what item the user has just clicked on) is selected and compared to the previously selected item. If this is true then the user has clicked on an already highlighted item in the listbox and so the listbox's selection is cleared, removing the selected item. Finally, the function updates the previously selected box to the current selected box.
def deselect_item(event):
if listbox.curselection() == previous_selected:
listbox.selection_clear(0, tkinter.END)
previous_selected = listbox.curselection()
A full working example of this (in python 3.8.0) is shown below:
import tkinter
class App(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
self.previous_selected = None
self.listbox = tkinter.Listbox(self)
self.bind('<ButtonPress-1>', self.deselect_item)
self.listbox.insert(tkinter.END, 'Apple')
self.listbox.insert(tkinter.END, 'Orange')
self.listbox.insert(tkinter.END, 'Pear')
self.listbox.pack()
def deselect_item(self, event):
if self.listbox.curselection() == self.previous_selected:
self.listbox.selection_clear(0, tkinter.END)
self.previous_selected = self.listbox.curselection()
app = App()
app.mainloop()

How to delete Tkinter widgets from a window?

I have a list of tkinter widgets that I want to change dynamically.
How to delete the widgets from the window?
You can call pack_forget to remove a widget (if you use pack to add it to the window).
Example:
from tkinter import *
root = Tk()
b = Button(root, text="Delete me", command=lambda: b.pack_forget())
b.pack()
root.mainloop()
If you use pack_forget, you can later show the widget again calling pack again. If you want to permanently delete it, call destroy on the widget (then you won't be able to re-add it).
If you use the grid method, you can use grid_forget or grid_remove to hide the widget.
One way you can do it, is to get the slaves list from the frame that needs to be cleared and destroy or "hide" them according to your needs. To get a clear frame you can do it like this:
from tkinter import *
root = Tk()
def clear():
list = root.grid_slaves()
for l in list:
l.destroy()
Label(root,text='Hello World!').grid(row=0)
Button(root,text='Clear',command=clear).grid(row=1)
root.mainloop()
You should call grid_slaves(), pack_slaves() or slaves() depending on the method you used to add the widget to the frame.
You simply use the destroy() method to delete the specified widgets like this:
lbl = tk.Label(....)
btn = tk.Button(....., command=lambda: lbl.destroy())
Using this you can completely destroy the specific widgets.
You say that you have a list of widgets to change dynamically. Do you want to reuse and reconfigure existing widgets, or create all new widgets and delete the old ones? It affects the answer.
If you want to reuse the existing widgets, just reconfigure them. Or, if you just want to hide some of them temporarily, use the corresponding "forget" method to hide them. If you mapped them with pack() calls, you would hide with pack_forget() (or just forget()) calls. Accordingly, grid_forget() to hide gridded widgets, and place_forget() for placed widgets.
If you do not intend to reuse the widgets, you can destroy them with a straight destroy() call, like widget.destroy(), to free up resources.
clear_btm=Button(master,text="Clear") #this button will delete the widgets
clear_btm["command"] = lambda one = button1, two = text1, three = entry1: clear(one,two,three) #pass the widgets
clear_btm.pack()
def clear(*widgets):
for widget in widgets:
widget.destroy() #finally we are deleting the widgets.
Today I learn some simple and good click event handling using tkinter gui library in python3, which I would like to share inside this thread.
from tkinter import *
cnt = 0
def MsgClick(event):
children = root.winfo_children()
for child in children:
# print("type of widget is : " + str(type(child)))
if str(type(child)) == "<class 'tkinter.Message'>":
# print("Here Message widget will destroy")
child.destroy()
return
def MsgMotion(event):
print("Mouse position: (%s %s)" % (event.x, event.y))
return
def ButtonClick(event):
global cnt, msg
cnt += 1
msg = Message(root, text="you just clicked the button..." + str(cnt) + "...time...")
msg.config(bg='lightgreen', font=('times', 24, 'italic'))
msg.bind("<Button-1>", MsgClick)
msg.bind("<Motion>", MsgMotion)
msg.pack()
#print(type(msg)) tkinter.Message
def ButtonDoubleClick(event):
import sys; sys.exit()
root = Tk()
root.title("My First GUI App in Python")
root.minsize(width=300, height=300)
root.maxsize(width=400, height=350)
button = Button(
root, text="Click Me!", width=40, height=3
)
button.pack()
button.bind("<Button-1>", ButtonClick)
button.bind("<Double-1>", ButtonDoubleClick)
root.mainloop()
Hope it will help someone...
You can use forget method on the widget
from tkinter import *
root = Tk()
b = Button(root, text="Delete me", command=b.forget)
b.pack()
b['command'] = b.forget
root.mainloop()
I found that when the widget is part of a function and the grid_remove is part of another function it does not remove the label. In this example...
def somefunction(self):
Label(self, text=" ").grid(row = 0, column = 0)
self.text_ent = Entry(self)
self.text_ent.grid(row = 1, column = 0)
def someotherfunction(self):
somefunction.text_ent.grid_remove()
...there is no valid way of removing the Label.
The only solution I could find is to give the label a name and make it global:
def somefunction(self):
global label
label = Label(self, text=" ")
label.grid(row = 0, column = 0)
self.text_ent = Entry(self)
self.text_ent.grid(row = 1, column = 0)
def someotherfunction(self):
global label
somefunction.text_ent.grid_remove()
label.grid_remove()
When I ran into this problem there was a class involved, one function being in the class and one not, so I'm not sure the global label lines are really needed in the above.

Categories