tkinter how delete both checkbox and label if checked box - python

Im trying to check a checkbox and delete both 'checked box' and 'label' with upper button. They are both created in a loop from the same list.
I've tried to build a dictionary with 'buttons' as key and 'result_of_checkboxes' as value and destroy key if value is different from ''. It doesnot work. If buttons are keys, why can't I destroy them? What is the correct approach?
Thanks in advance!
from tkinter import *
root = Tk()
root.geometry('400x400')
def destroy_button():
for key, value in dict_check_and_buttons:
if value != '' and current_var != '':
key.destroy()
current_box.destroy()
my_friends = ['Donald', 'Daisy', 'Uncle Scrooge']
button1= tk.Button(root, text = "delete both if cbox is checked", command = destroy_button).pack()
#-----ild checkbuttons and store results in checkbutton_result list
checkbutton_result = []
for index, friend in enumerate(my_friends):
current_var = tk.StringVar()
current_box = tk.Checkbutton(root, text= friend,
variable = current_var,
onvalue = friend, offvalue = ""
)
checkbutton_result.append(current_var) #append on and off results
current_box.pack()
#-----build buttons and store them in buttons_list
buttons_list = []
for index, friend in enumerate(my_friends):
buttons= tk.Button(root, text = friend).pack()
buttons_list.append(buttons)
#-----build a dict with list to say "if onvalue != '' destroy button"
dict_check_and_buttons = dict(zip(buttons_list, checkbutton_result))
root.mainloop()
the error is:
Exception in Tkinter callback
Traceback (most recent call last):
File "c:\python\python38\lib\tkinter\__init__.py", line 1892, in __call__
return self.func(*args)
File "<ipython-input-18-954d3a090f2c>", line 7, in destroy_button
for key, value in dict_check_and_buttons:
TypeError: cannot unpack non-iterable NoneType object

There are following issues in your code:
all items in buttons_list are None due to the following line:
buttons= tk.Button(root, text = friend).pack() # buttons is the result of pack()
The line should be split into two lines:
buttons = tk.Button(root, text=friend)
buttons.pack()
you cannot get a (key, value) pair by iterating a dictionary:
for key, value in dict_check_and_buttons: # dict_check_and_buttons is a dictionary
...
Instead you should iterate on the result of dict_check_and_buttons.items():
for key, value in dict_check_and_buttons.items():
...
you need to call get() on a tk.StringVar():
for key, value in dict_check_and_buttons.items():
if value.get() != '': # use value.get()
key.destroy()
If you need to destroy the checkbutton as well, you need to save the checkbutton to checkbutton_result along with its associated variable:
checkbutton_result = []
for index, friend in enumerate(my_friends):
current_var = tk.StringVar()
current_box = tk.Checkbutton(root, text= friend,
variable = current_var,
onvalue = friend, offvalue = ""
)
checkbutton_result.append((current_box, current_var)) # save both checkbutton and its associated variable
current_box.pack()
Then destroy the checkbutton inside destroy_button():
def destroy_button():
for btn, (cb, cbvar) in dict_check_and_buttons.items():
if cbvar.get() != '':
btn.destroy()
cb.destroy()

With your answer I rebuild the code. It is perfect now thanks to you.
import tkinter as tk
root = tk.Tk()
root.geometry('400x400')
'''
to get if checkbox is checked to use results to delete checkbox and label
'''
def destroy_button_box():
for button_names, (ckbox, on_off) in dict_buttons_box_var.items():
if on_off.get() != 0:
button_names.destroy()
ckbox.destroy()
my_friends = ['Donald', 'Daisy', 'Uncle Scrooge']
button1 = tk.Button(root, text="delete label if box is checked", command=destroy_button_box).pack()
checkbox_variable_list = []
box_list = []
for index, friend in enumerate(my_friends):
# current_var = tk.IntVar()
current_var = tk.IntVar()
# current_var = tk.StringVar()
current_box = tk.Checkbutton(root, text=friend,
variable=current_var,
onvalue=1, offvalue=0
)
current_box.pack()
# append checkbox and 'on' or 'off' results
checkbox_variable_list.append((current_box, current_var))
# -----build buttons and store them in buttons_list
buttons_list = []
for index, friend in enumerate(my_friends):
button_names = tk.Button(root, text=friend)
button_names.pack()
# append buttons in loop
buttons_list.append(button_names)
# -----build a dict with lists to use in destroy_box_button function:
dict_buttons_box_var = dict(zip(buttons_list, checkbox_variable_list))
root.mainloop()

Related

How can I set the text of a ttk.Entry?

It appears that I cannot set the text of a ttk.Entry with a root of ttk.Label during an event:
import tkinter
from tkinter import ttk
def modify_label_text(event):
entry = event.widget
newvalue = entry.get()
label = entry.master
label.configure(text=newvalue)
entry.unbind("<Return>")
entry.pack_forget()
label.focus()
def create_entry(event):
label = event.widget
oldvalue = label.cget("text")
newvalue = tkinter.StringVar()
entry = ttk.Entry(label, textvariable=newvalue)
'''
entry = tkinter.Entry(label, textvariable=newvalue)
'''
entry.pack()
entry.delete(0, "end")
entry.insert(0, oldvalue)
entry.bind("<Return>", modify_label_text)
entry.focus()
root = tkinter.Tk()
clickme = ttk.Label(root, width=16)
clickme.pack()
clickme.bind("<Button-1>", create_entry)
clickme.focus()
root.mainloop()
When I click the empty label and enter a new value, the value is reflected in the label. However, if I click the label again to "edit" the value, the entry field is empty again.
Furthermore, if I use tkinter.Entry rather than ttk.Entry, it appears to work.
Why is the text of the entry only set when using tkinter.Entry? How can I fix this to work with ttk.Entry?
This may not an answer, but a bit too large for a comment. Also could someone elaborate this behavior in a different enviorment. I ran that code with:
Mashine:
Windows 10 Home; x64-base,Intel(R) Core(TM) i3-2120 # 3.30GHz, 3300 MHz, 2Cores
with
Python 3.7.2 and tkinter 8.6
After some research I couldnt find a reason for this behavior. Neither in the docs, nor in the sometimes tricky style_map or in the default bindings.
Can I assume that you create the labels with the entries in a function/loop? You may want to reuse the entry, because for some reason the exact same code works if you adress the entry by winfo_children(). And even if I call .update_idletasks() or just for experience the evil .update() it dosent work for no reason.
import tkinter
from tkinter import ttk
def modify_label_text(event):
entry = event.widget
newvalue = entry.get()
label = entry.master
label.configure(text=newvalue)
entry.unbind("<Return>")
entry.pack_forget()
label.focus()
def create_entry(event):
label = event.widget
oldvalue = label.cget("text")
newvalue = tkinter.StringVar()
if len(label.winfo_children()) == 0:
print('new')
entry = ttk.Entry(label, textvariable=newvalue)
'''
entry = tkinter.Entry(label, textvariable=newvalue)
'''
e = label.winfo_children()[0]
e.pack()
e.delete(0, "end")
e.insert(0, oldvalue+' works')
e.bind("<Return>", modify_label_text)
e.focus()
root = tkinter.Tk()
clickme = ttk.Label(root, width=16)
clickme.pack()
clickme.bind("<Button-1>", create_entry)
clickme.focus()
root.mainloop()
Another solution could be:
def create_entry(event):
label = event.widget
oldvalue = label.cget("text")
newvalue = tkinter.StringVar()
entry = ttk.Entry(label, textvariable=newvalue)
'''
entry = tkinter.Entry(label, textvariable=newvalue)
'''
root.after(100,after_time,entry,oldvalue)
def after_time(entry,oldvalue):
entry.pack()
entry.delete(0, "end")
entry.insert(0, oldvalue)
entry.bind("<Return>", modify_label_text)
entry.focus()
I suggest you create an instance of Entry outside the function and simply pack() and pack_forget() in the function. pack_forget() will not delete your widget it will only hide it from the layout. To delete the widget you might have to use widget.destroy().
import tkinter
from tkinter import ttk
def modify_label_text(event):
newvalue = entry.get()
clickme.configure(text=newvalue)
entry.pack_forget()
clickme.focus()
def create_entry(event):
label = event.widget
entry.pack()
entry.focus()
root = tkinter.Tk()
newvalue = tkinter.StringVar()
clickme = ttk.Label(root, width=16)
clickme.pack()
clickme.bind("<Button-1>", create_entry)
clickme.focus()
entry = ttk.Entry(clickme, textvariable=newvalue)
entry.bind("<Return>", modify_label_text)
root.mainloop()
Edit(writing my comment as answer)
I suspect that it has to do something with the textvariable being set. Simply removing it or changing the newvalue to global scope seems to work. Also, since you don't seem to be using .set or .get methods you can simply remove it if you want.

ttk.treeview selection_set to the item with specific id

I want to edit an item in treeview by another Toplevel window and after editing want to refresh / reload items into list from database and set focus to the edited item.
The problem I am facing is to SET FOCUS TO THE EDITED ITEM IN TREEVIEW. Any help will be appreciated.
Here is the minimal sample code.
import tkinter as tk
from tkinter import ttk
class _tree(tk.Frame):
def __init__(self, *args):
tk.Frame.__init__(self, *args)
self.tree = ttk.Treeview(self, columns = ("id", "name"))
self.tree.heading("#0", text = "s.n")
self.tree.heading("#1", text = "id")
self.tree.heading("#2", text = "name")
self.tree.pack()
_items = [[52,"orange"],[61,"manggo"],[1437,"apple"]] # item with id 61 needs to be changed
sn = 1
for r in (_items):
self.tree.insert("", "end", text = str(sn), values = (r[0], r[1]))
sn += 1
self.tree.bind("<Double-Button-1>", self._item)
def _item(self, event):
global item_values
global item_id
global item_name
idx = self.tree.selection()
item_values = self.tree.item(idx)
print("item_values : %s" % item_values)
item_id = self.tree.set(idx, "#1")
item_name = self.tree.set(idx, "#2")
edit_item(self)
class edit_item(tk.Toplevel):
def __init__(self, master, *args):
tk.Toplevel.__init__(self, master)
self.master = master
global item_values
global item_name
lbl1 = tk.Label(self, text = "item name")
self.ent1 = tk.Entry(self)
btn1 = tk.Button(self, text = "update", command = self.update_item)
lbl1.place(x=0, y=10)
self.ent1.place(x=90, y=10)
btn1.place(x=90, y=100)
self.ent1.insert(0, item_name)
def update_item(self):
for i in self.master.tree.get_children():
self.master.tree.delete(i)
new_data = [[52,"orange"],[61,"mango"],[1437,"apple"]] # item with id 61 has been changed
sn = 1
for r in (new_data):
self.master.tree.insert("", "end", text = str(sn), values = (r[0], r[1]))
sn += 1
# Need to set focus on item with id 61
idx = self.master.tree.get_children(item_values['values'][0]) # HERE NEED HELP
self.master.tree.focus_set()
self.master.tree.selection_set(idx)
self.master.tree.focus(idx)
self.destroy()
def main():
root = tk.Tk()
app = _tree()
app.pack()
root.mainloop()
if __name__ == "__main__":
main()
`
I am receiving the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "test_tree.py", line 55, in update_item
idx = self.master.tree.get_children(item_values['values'][0]) # HERE NEED HELP
File "/usr/lib/python3.8/tkinter/ttk.py", line 1225, in get_children
self.tk.call(self._w, "children", item or '') or ())
_tkinter.TclError: Item 61 not found
You don't need to delete the existing values in order to update the value. You can simply use the set() method to update your treeview.
syntax:
tree.set(iid, column=None, value=None)
If you specify only iid in set method it will return items as dict.
Here is a better way to do the same.
from tkinter import ttk
import tkinter as tk
titles={'Id': [1,2,3,4,5, 6, 7, 8, 9], 'Names':['Tom', 'Rob', 'Tim', 'Jim', 'Kim', 'Kim', 'Kim', 'Kim', 'Kim']}
def update(selected_index_iid, changed):
index = treev.index(selected_index_iid)# or just index = treev.index(treev.selection())
treev.set(selected_index_iid, 1, changed) # updating the tree
titles['Names'][index] = changed #updating the dictionary
print(titles)
def clicked(event):
global titles
top = tk.Toplevel(window)
label = tk.Label(top, text='Update: ')
label.pack()
entry = tk.Entry(top)
entry.insert(0, treev.set(treev.selection())['1']) #if you only specify the iid 'set' will return dict of items, ['1'] is to select 1st column
entry.pack()
button= tk.Button(top, text='Update', command=lambda :update(treev.selection(), entry.get()))
button.pack()
window = tk.Tk()
treev = ttk.Treeview(window, selectmode ='browse')
treev.bind('<Double-Button-1>', clicked)
treev.pack(side='left',expand=True, fill='both')
verscrlbar = ttk.Scrollbar(window,
orient ="vertical",
command = treev.yview)
verscrlbar.pack(side ='right', fill ='y')
treev.configure(yscrollcommand = verscrlbar.set)
treev["columns"] = list(x for x in range(len(list(titles.keys()))))
treev['show'] = 'headings'
for x, y in enumerate(titles.keys()):
treev.column(x, minwidth=20, stretch=True, anchor='c')
treev.heading(x, text=y)
for args in zip(*list(titles.values())):
treev.insert("", 'end', values =args)
window.mainloop()

Using python tkinter library, how to write a function that checks if a textbox is checked?

I'm trying to see what checkbox are checked and write a if/else function to do stuff. The number of check boxes depends on the number of topics I parse into the program and create a checkbox for each item.
I added:
chk_state = IntVar()
But this is only good if you are using one checkbox.
I am using a list to generate all my checkboxes:
Which generates these variables for each checkbox:
'chk0', 'chk1', 'chk2', 'chk3', 'chk4', 'chk5', 'chk6', 'chk7', 'chk8', 'chk9', 'chk10', 'chk11', 'chk12', 'chk13', 'chk14', 'chk15', 'chk16', 'chk17', 'chk18', 'chk19', 'chk20', 'chk21', 'chk22', 'chk23', 'chk24']
from tkinter import *
from tkinter import messagebox
from reader import openit
import sys
data = openit(sys.argv[1])
window = Tk()
#set size of window
window.geometry('850x400')
window.title("Chose Your ROS Topic" )
lbl = Label(window, text="Topics", font=("Arial Bold", 25))
lbl.grid(column=0,row=0)
#check checkbox value boolean
chk_state = IntVar()
#chk_state.set(0) #uncheck
#chk_state.set(1) #checked
#Looping through list and adding each one as a checked button
counter = 0
selected_list = []
for x in data.split(","):
# global selected_list
#print(x)
#print(counter)
name = "chk" + str(counter)
# appends each checkbox name to a list
selected_list.append(name)
name = Checkbutton(window, text='%s' % x , font=(15), onvalue=1, offvalue=0, variable=chk_state)
if counter == 0:
name.grid(column=1, row=1)
#print('only for counter 0')
else:
name.grid(column=1, row=1+counter -1)
#print("the rest")
counter += 1
#After selecting all the topics you want to graph
def topics_selected():
#messagebox.showinfo('Topics picked', 'graphing all your checked topics')
#for topics in
if chk_state.get():
print("some checked topics")
else:
print("Nothing is checked")
# Adding input tkinter textbox
txt = Entry(window,width=10)
txt.grid(column=1,row=0)
# Function that changes buttond
def inputcheck():
res = "Topics picked " + txt.get()
lbl.configure(text = res)
def clicked():
lbl.configure(text="Parser was clicked, checking rosbag")
# Adding button widget
btn = Button(window, text="ROS topic parser", bg="orange", fg="black", command=topics_selected)
btn.grid(column=2, row=1)
#Create a checkbox
#chk = Checkbutton(window, text='Choose')
#chk.grid(column=0, row=4)
window.mainloop()
if __name__ == "__main__":
pass
#print(data)
I want to be able to add whatever was selected to a list and push that list to another function.
You need to create a tkinter variable for each Checkbutton. Right now all you Checkbutton shares the same variable chk_state.
To make this work, simply move your definition of IntVar inside the for loop:
...
selected_list = {} #use a dict to store name/IntVar pair; can use a list also
for num, x in enumerate(data.split(",")): #use enumerate to get a num count
chk_state = IntVar() #create a new IntVar in each iteration
selected_list[num] = chk_state #save name/IntVar pair
name = Checkbutton(window, text='%s' % x , font=(15), onvalue=1, offvalue=0, variable=chk_state)
if num == 0:
name.grid(column=1, row=1)
else:
name.grid(column=1, row=1+num-1)
#After selecting all the topics you want to graph
def topics_selected():
if any(s.get() for s in selected_list.values()): #check if any checkbutton is checked
print ("some checked topics")
print ([s.get() for s in selected_list.values()])
else:
print("Nothing is checked")
You could use tk.BooleanVar for each of your check boxes, and set their values inside a for loop.
Keeping the variables in a list allows you to pass the selection to a function.
import tkinter as tk
DEFAULTS = [True, False, False, False]
def display_selected_options(options, values):
for option, value in zip(options, values):
print(f'{option}: {value.get()}')
def create_option_selectors(frame, options, option_variables) -> None:
for idx, option in enumerate(options):
option_variables[idx].set(DEFAULTS[idx])
tk.Checkbutton(frame,
variable=option_variables[idx],
onvalue=True,
offvalue=False,
text=option,
).pack(side=tk.LEFT)
root = tk.Tk()
options = ['chk0', 'chk1', 'chk2', 'chk3']
option_variables = [tk.BooleanVar(root) for _ in range(len(options))]
frame = tk.Frame(root)
create_option_selectors(frame, options, option_variables)
frame.pack()
display_options = tk.Button(root, text='validate', command=lambda options=options,
values=option_variables: display_selected_options(options, values))
display_options.pack()
root.mainloop()
Alternatively, you could use a dictionary to store the option -> value pairs:

Using tkinter in function to return sublist from list input

I am trying to define a callable function which should take a large list of items as input and through interaction with user return a sublist of checked out items.
I'm quite new to tkinter, but I have searched around stack after similar problems and used this to compose the following code. I know using global variables is a bad idea, and it actually does not work in this case. I've tried to append all the variables of the checked out boxes to a global lst and then tried to return this list, but does not work. Is it possible to make a new function to export the list or use widget?
items = ['screw driver', 'belt', 'nut','hammer','tape','ducked tape','drill']
def choose_items(item_list):
import tkinter as tk
class App(object):
def __init__(self):
def click(event):
button.config(bg='green')
root = tk.Tk()
root.title('Choose items')
text = tk.Text(root, cursor='arrow')
vsb = tk.Scrollbar(root, command=text.yview)
button = tk.Button(root, text='Add items', command=addtolist)
button2 = tk.Button(root, text='Quit', command=root.destroy)
text.configure(yscrollcommand=vsb.set)
button.pack(side='top')
button2.pack(side='bottom')
button.bind('<Button-1>',click)
vsb.pack(side='right',fill='y')
text.pack(side='left', fill='both', expand=True)
global varList
varList = []
for i in range(len(items)):
var = tk.StringVar()
cb = tk.Checkbutton(text, text=item_list[i], variable=var,
onvalue=item_list[i], offvalue='')
varList.append(var)
text.window_create('end', window=cb)
text.insert('end','\n')
text.configure(state='disabled')
root.mainloop()
def addtolist():
global lst
lst = []
for i in varList:
if i.get() != ' ':
lst.append(i.get())
if __name__=='__main__':
App()
return lst
choose_items(items)
I expect the output to be the items that the user has checked of but get the following error message:
NameError: name 'lst' is not defined
You should use return within your addtolist function. Also addtolist should be a class method instead of a separate function since you are looping over the items in varList, which also should be made a class attribute instead of declaring global.
class App(object):
def __init__(self):
...
self.varList = []
for i in range(5):
var = tk.StringVar()
cb = tk.Checkbutton(text, text=f"Item {i}", variable=var,
onvalue=i, offvalue='')
self.varList.append(var)
text.window_create('end', window=cb)
text.insert('end', '\n')
...
def addtolist(self):
lst = []
for i in self.varList:
if i.get() != ' ':
lst.append(i.get())
return lst

Display labels using loop on button press

So I'm reading contents from the database and I have appended necessary elements into lists. I am trying to display each elements in the list in a label. For example, I have a list from the database:
test = ['Name' , 'Age', 'Location']
I want to display the label with text- Name (i.e test[0]) on button press. Likewise, when I press the button again I want the label to display, Age (i.e. test[1]) and so on upto the length of the list. What I tried:
from tkinter import *
import sqlite3
# connecting to the database
conn = sqlite3.connect('database.db')
c = conn.cursor()
# empty lists to later append elements into
all_ids = []
all_names = []
all_time = []
# execute sql
sql = ('SELECT * FROM appointments')
res = c.execute(sql)
for r in res:
idd = r[0]
name = r[1]
time = r[6]
all_ids.append(idd)
all_names.append(name)
all_time.append(time)
# variable that determines the index of elements on button press (updates)
x = 0
class Application:
def __init__(self, master):
self.master = master
self.heading = Label(master, text="Appointments", font=('arial 60 bold'), fg='steelblue')
self.heading.place(x=300, y=0)
self.btn = Button(master, text="Next Patient", width=20, height=2, command=self.func)
self.btn.place(x=500, y=600)
self.display = Label(self.master, text="", font=('arial 200 bold'))
self.display.place(x=500, y=80)
self.nd = Label(self.master, text="", font=('arial 80 bold'))
self.nd.place(x=200, y=400)
def func(self):
global x
for i in all_ids:
self.display.config(text=str(all_ids[x]))
self.nd.config(text=str(all_names[x]))
x += 1
root = Tk()
v = Application(root)
root.geometry("1366x768+0+0")
root.resizable(False, False)
root.mainloop()
When I press the button, last element is displayed and when I press again, I get list index out of range. Can anyone please tell me what I'm doing wrong? Thanks
Output is the label of last element in the list and error on clicking again:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.5/tkinter/__init__.py", line 1553, in __call__
return self.func(*args)
File "test.py", line 45, in func
self.display.config(text=str(all_ids[x]))
IndexError: list index out of range
for loop in func seems unnecessary since you are saying you want to show one of your labels on each press. Remove it.
Also you can move x inside of your class if you are only using it in there.
def __init__(self, master):
...
self.x = 0
def func(self):
self.display.config(text=str(all_ids[self.x]))
self.nd.config(text=str(all_names[self.x]))
self.x += 1

Categories

Resources