How to destroy comboboxes if they are available in python gui? - python

I have a function, which based on the count generates the comboboxes. I want to destroy any combobox which is available already whenever my count variable changes. I used winfo_exists to do this...but it throws an attribute error every time. Please help me with this.
Here is the code of that function:
def create(event):
count = combo.current()
print ("count")
print(count)
for i in range(1,count+2):
if (create_combo[i].winfo_exists()):
create_combo[i].destroy()
for i in range (1,count+2):
create = tk.StringVar()
create_combo[i]= ttk.Combobox(new_window_2,width = 15,textvariable = create, values = sheets)
#create_combo.set("Sheet " + str(i))
create_combo[i].grid(column = i, row =4, padx=10,pady=10)

To delete the widgets which are created in loop, can be deleted by using the method available in this link
Python Tkinter :removing widgets that were created using a for loop
This worked for me... I dont understand why winfo_exists didn't work.
Anyway Thanks!!
list_of_owner_widgets = []
def create(event):
count = combo.current()
print(count)
for widget in list_of_owner_widgets:
widget.destroy()
for i in range (1,count+2):
create = tk.StringVar()
create_combo[i]= ttk.Combobox(new_window_2,width = 15,textvariable = create, values = sheets)
list_of_owner_widgets.append(create_combo[i])
create_combo[i].grid(column = i, row =4, padx=10,pady=10)

If you wish to destroy a Python widget, be it a Checkbox in your case, you use the following code.
It is a lot easier to remove and show widgets using the .grid method!
Your code:
create_combo[i].destroy()
I assume (as I can see further down the code file) that you used the grid method. In which case I would simply change the code to:
create_combo[i].grid_forget()
Hope This Helps!

From your post:
for i in range(1,count+2):
if (create_combo[i].winfo_exists()):
create_combo[i].destroy()
And the error:
AttributeError: 'str' object has no attribute 'winfo_exists'
I can infer that:Your create_combo must be a list full of string(Instead of Combobox widget).
You could add print(create_combo) before the first for loop to check the value in create_combo.It must be a list full of string.
And it seems that your problem is not here,you should check the way how you create the create_combo.
lets assume create_combo = ['a','b','c']. So I am creating three comboboxes create_combo[0...2]. So name of the comboboxes(widgets) is a, b, c.
No,you couldn't.
If really want to get a list of comboboxes you create,you should use:
create_combo = []
for i in range(3):
t = ttk.Combobox(xxxxx)
t.grid(xxxxxx)
create_combo.append(t) # append it to your create_combo
And then,you could use:
for i in create_combo:
if i.winfo_exists(): # i should be a widget,not string
xxxxxxx # your job

Related

How to get state of each checkbox generated through a loop, as soon as it is checked/unchecked in tkinter?

I'm trying add/remove an item (text of checkbox) to/from a list whenever a checkbox is checked/unchecked in tkinter.
My idea was to add a command to the checkbutton, like:
cb = Checkbutton(master,...,command=some_fun)
but I cannot think of a way to define the function. I was thinking the function should contain the widget attribute cget('text'), but the problem is I have many checkboxes made with the help of a loop.
I guess the question is: how can I reference the checkbox whose state got changed and is therefore calling the function some_fun?
The way I generated the checkboxes is:
cb_identities = []
for i in range(cb_max_num):
cb = Checkbutton(frame_data,bg="white")
cb_identities.append(cb)
And then I'm dynamically changing them depending on some radiobuttons:
def fun_chck(): #shows or hides checkbuttons based on radiobutton input
data = read_data(rb_var.get())
for i in range(cb_max_num):
cbname = (cb_identities[i])
if len(data)-1 < i:
cbname.grid_forget()
else:
cbname.config(text=data[i]) #I would place some_fun here, which gets text option of checked box
cbname.grid(row=i,column=1,sticky=W)
Update! I managed with the following code for anyone interested:
cb_var_init = [0] * cb_max_num #create the initial list of inactive checkbuttons, all 0
input_params=[] #list which needs to be populated/depopulated based on checkbutton state
def get_data(data): #populates a list with parameter from checked checkbuttons,
global cb_var_init
cb_var_list = list(map(lambda var: var.get(),list(cb_var.values())))
for i in range(len(data)):
if cb_var_list[i] > cb_var_init[i]:
input_params.append(data[i])
elif cb_var_list[i] < cb_var_init[i]:
input_params.remove(data[i])
cb_var_init = cb_var_list
return(input_params)
cb_var is a dictionary of IntVars, and data is a list of checkbuttons' names.
As for the command on each checkbutton, I used cbname.config(text=data[i],command=lambda: get_data(data)) as suggested in another topic for functions with arguments.
Now each time I check a checkbutton, I immediately get a list of parameters which should show in the next Frame, which is dynamically updated.

Tkinter: Opening a URL via Dropdown. Need to specify Item from List

Another one of these puzzles where Im sure I got the right idea but I cant seem to implement it.
Im trying to make a dropdown that instantly opens the URL to whatever column 1 of the list says but I'm not sure if I maybe need a dictionary and call it from that, do I need to define the list by columns...
So I figured out how to open up URLS from a TreeView but now Im figuring out how to open the URL from a Dropdown. I want the {spec} to be populated by only the first item from the list (Pepperoni) in this case.
QUICKJUMP_1 = [
["--", "--"],
["Pepperoni", "Pizza"],
]
def openGoogle():
_spec_ = QUICKJUMP_1
url = f'https://google.com/{_spec_}'
webbrowser.open(url)
quickjump = StringVar()
quickjump.set(QUICKJUMP_1[0])
spec = ttk.Combobox(root, width=30, textvariable=quickjump, state="")
spec['values'] = [item[1] for item in QUICKJUMP_1]
spec.place(x=300, y=400)
spec.current(0)
spec.bind("<<ComboboxSelected>>", openGoogle)
EDIT:
I updated the code as it was figured out how to make it actually open up the URL but as it stands right now all it currently does is just opens up the first part of the list whatever part of the list the "spec = QUICKJUMP" points it to i.e [1] = item 2.
So I need someway to tell it to pick from the drop box and only the first part.
Since the code given is not a working code, I will write down an example and you can hopefully figure it out from it:
from tkinter import *
from tkinter import ttk
import webbrowser
root = Tk()
base_url = 'www.google.com/{}'
options = ['--','Peperoni Pizza']
def open_url(e): # e.widget is the widget that triggers the event, here, combobox
if e.widget.get() != options[0]: # If the value is not '--'
value = e.widget.get()
just_the_flavour = value.split(' ')[0] # This will split the text 'Peperoni Pizza' into two items in a list
# It does this where there is ' ', ie, a blank space, so the list will be ['Peperoni','Pizza']
# Then we take just the first item from it use [0]
url = base_url.format(just_the_flavour)
webbrowser.open(url)
combobox = ttk.Combobox(root,values=options)
combobox.pack()
combobox.current(0) # Set the first value to the first in the list
combobox.bind('<<ComboboxSelected>>',open_url) # Bind to the function
root.mainloop()
I have explained it on the comments to understand it on the go.

How to know in what row is a label on a grid in Tkinter?

I have a table (grid) I'm creating, constituted of labels.
These labels are showing elements I'm adding to a list, therefore, when I add a new obj to the list, the table will grow, showing more labels.
My intent is that I can click a label and have it print the row of the table that that label is in.
import tkinter as tk
phrasesdb = []
def debug(event):
#this is where I'd have it print the row
#but how do I get it?
#for example, it the label I pressed was in the row 2, it'd print 2
print( ??? )
#Add obj to list
def addline():
##This creates new obj with values from the input fields, and inserts it in the list
newobj = {"text": newtext.get()} #This is getting the text from an Entery
phrasesdb.append(newobj)
##This shows new obj on the table
newesttext = tk.Label(tableframe, text=newobj["text"])
newesttext.grid(row=len(phrasesdb), column=1, sticky=tk.W)
newesttext.bind("<Double-Button-1>", debug)
I'm already able to show them in the table, and to have it recognize I'm pressing the correct label (tested with a simple print("yup, this is it") ), but I'm not being able to figure out how to access the row of the label I'm clicking.
I'm kinda new to python and especially tkinter, so sorry if this is a really easy question, but I'm not finding the answer anywhere else...
You can use the grid_info method which will return a dictionary of the item's grid attributes.
def debug(event):
widget = event.widget
info = widget.grid_info()
row = info['row']
If I understand your problem correctly using .grid_info()['row'] on the label you already received after clicking should return the result you need.

Finding it difficult to update a listbox dynamically within a tikinter window

I am creating a simple tkinter list and populating it with a list of numbers. then I am deleting a part of the list and want that change to reflect in the listbox as soon as I run the delete function.
I used the update_idletasks() in the function but it does not update the list. Can some one assist me to resolve this issue?
from tkinter import *
main = Tk()
b1 = Button(main,text='delete section',command=lambda:func())
b1.pack()
l1 = Listbox(main)
l1.pack()
mylist =[]
for i in range(0,100):
mylist.append(i)
for k in mylist:
l1.insert(0,k)
def func():
del mylist[50:100]
l1.update_idletasks()
main.mainloop()
update_idletasks(self)
Enter event loop until all idle callbacks have been called. This will update the display of windows but not process events caused by the user.
update_idletasks is not what you're wanting to use if you just want to update the Listbox with the new list.
There are a few ways you can do this. You can delete all items from index 50 to 100
l1.delete(50, 100)
Or you can delete every item from the list and add them again
del mylist[50:100]
li.delete(0, END)
for k in mylist:
l1.insert(0, k)
Also, if you wish to use the first option, deleting from index 50 to 100. You'll need to change your current insert line from l1.insert(0, k) to l1.insert(END, k) this way it adds to the list in a ascending order.
I'm mentioning this because it appears you want to keep 0, 1, .. , 48, 49. But if you want them in descending order then use l1.delete(0, 49) instead.

Forgetting a widget created by a forloop tkinter

How would I go about forgetting a widget that was created by a for loop?
For example, I have this code here:
for getSong in a.findAll('name'):
z += 1
x += 1
if z == 11:
break
else:
text = ''.join(getSong.findAll(text=True))
data = text.strip()
songLab = Label(frame1, text=data)
Afterwards, the user presses a Button and it updates the widget, like so:
def update():
try:
openArtist.pack_forget()
artistLab.pack_forget()
songLab.pack_forget()
getScrobbledTracksArtist()
except NameError:
getScrobbledTracksArtist()
The other widgets get removed by the one created in the for loop does not.
Example here:
Before updating widgets
After updating widgets
As you can see, only one line of the widget was removed.
Edit
I tried doing the duplicate but it is not working for me.
I made a list and made sure the labels were being added to the list, they are.
labels = []
for getSong in a.findAll('name'):
z += 1
x += 1
if z == 11:
break
else:
text = ''.join(getSong.findAll(text=True))
data = text.strip()
songLab = Label(frame1, text=data)
labels.append(songLab)
songLab.pack()
Then after a Button is pressed, the widgets update.
def update1():
try:
openArtist.pack_forget()
artistLab.pack_forget()
labels[0].destroy()
print labels
getScrobbledTracksArtist()
except NameError:
getScrobbledTracksArtist()
The labels are still in the list and are not being destroyed.
Like any other widget, you simply need to keep a reference to it. For example, you could append each widget to a list, then iterate over the list to remove the widgets.
Let's take a look at this code:
def update1():
try:
openArtist.pack_forget()
artistLab.pack_forget()
labels[0].destroy()
print labels
getScrobbledTracksArtist()
except NameError:
getScrobbledTracksArtist()
you are only ever deleting the first label in the list, and then you are failing to remove it from the list. What you need to do instead is to loop over the list, destroying each widget. Then you can reinitialize the list:
for label in labels:
label.destroy()
labels = []
However, you have another problem in that it appears that labels may be a local variable. You will need to declare it as global so that the two different functions will be able to access and modify the list.
None of this is related to tkinter, this is simply how all python objects work.

Categories