Destroy an OptionMenu in Python - python

I seem to be hitting a dead end here. I have done a decent amount of research online and have not been able to reach a solution.
My issue is, i have an "optionmenu" (#1) in my GUI, when a certain option is chosen, a new "optionmenu" (#2) is created. The user can then make his choice in #2. Based on his choice in #2, entry widgets appear and are destroyed as the option is changed. My problem is here, when optionmenu #2 is displayed and the user decides to change optionmenu#1, i am able to destroy all the entry widgets from the #1 and #2 optionmenu; however, i am still left with the optionmenu#2 in the background.
I was only able to find online solutions for
Entry & Label
However, i was unable to find any solution for
OptionMenu
Any ideas on how to destroy the option menu? A snippet of the code is below, as it currently behaves as stated above.
from Tkinter import *
neper_tessellation_type={'hard-core':'1','centroid':'3','weight':'0'}
neper_weight_type={'dirac':'1','gaussian':'2','flat':'2','bernoulli':'3'}
class Application(Frame):
def __init__(self, master):
Frame.__init__(self, master)
#Setting up widgets onLoad
self.grid()
self.create_widgets()
# Tessellations Type Option Builder
def tessellation_type(self, req_dim):
global tess_container
global labels_container
global weight_container
for wid in tess_container:
wid.destroy() ## Destroy the OPTIONMENU1 ENTRY CONTAINER fields
for label in labels_container:
label.destroy() ## Supposed to destroy the OptionMenu2 ITSELF, but does not do as requried.
for lid in weight_container:
lid.destroy() ## Destroy the OPTIONMENU2 ENTRY CONTAINER fields
weight_container = []
labels_container = []
tess_container = []
for type, req_dim in neper_tessellation_type.iteritems():
self.s = StringVar()
choice = self.tess_type.get()
if type == self.tess_type.get() and choice != 'weight':
u = int(req_dim)
elif choice == 'weight': ## OPTIONMENU 2 - When weight is chosen a new drop down menu is made and the function command moves to weighttype
weight_dropdown = OptionMenu(self, self.s, *neper_weight_type, command=self.weight_type).grid(row=13, column=2)
u = 0
for b in range(u):
c = Entry(self)
c.grid(row=13, column=2 + b)
tess_container.append(c) # Append widget to container list
def weight_type(self, req_dim1):
global weight_container
for lid in weight_container:
lid.destroy()
weight_container = []
for type1, req_dim1 in neper_weight_type.iteritems():
if type1 == self.s.get():
u1 = int(req_dim1)
for bf in range(u1):
t = Entry(self)
t.grid(row=13, column=3 + bf)
weight_container.append(t) # Append widget to container list
# *** MAIN FRAMES ***
def create_widgets(self):
## OPTIONMENU 1
Label(self, text="Voronoi Type").grid(row=13, column=0)
self.tess_type = StringVar()
tess_type_dropdown = OptionMenu(self, self.tess_type, *neper_tessellation_type, command=self.tessellation_type).grid(row=13, column=1)
## Reset for containers of choice
tess_container = []
labels_container = []
weight_container = []
root = Tk()
app = Application(root)
root.mainloop()
Question amended after Bryan clarification.

Thanks to #BryanOakley hint of the variable and widget not being called on. By placing the .grid on a new line and calling on the widget and appending it seemed to resolve the issue.
...
elif choice == 'weight':
self.weight_dropdown = OptionMenu(self.tessframe, self.s, *neper_weight_type, command=self.weight_type)
self.weight_dropdown.grid(row=13, column=2)
labels_container.append(self.weight_dropdown)
u = 0
...

Related

Python Tkinter - Name of objects created by function

I am creating a set of buttons via this function:
from tkinter import *
from random import randint
window = Tk()
window.title("Test")
window.geometry('200x200')
color = ["red","blue","green","yellow","black","purple","orange"]
RandInt = 0
j = 0
h = 0
def ButtonDef(xvar = 0,yvar = 0):
btn = Button(command =lambda:[RandomColor()])
btn.grid()
btn.place(x = xvar*50, y = yvar*50, width = 50, height = 50)
def RandomColor():
RandInt = randint (0,6)
btn.configure(bg = color[RandInt])
while j in range (4):
i = 0
j += 1
while i in range (4):
ButtonDef(i,h)
i += 1
if i == 4:
h += 1
window.mainloop()
However, my RandomColor() function is changing only the color of the very button i have pressed - that is fun too, but i wonder how i can make it randomly change the color of all buttons. When being created by a function, i would have guessed that all buttons that were created are named "btn" since thats the only name i have given them.
How could i address all (or one specific) buttons, out of a group of created-by-function buttons?
Or to put it simply, what name do all of those buttons have? Do they share the name "btn"? Are they assigned a hidden ID?
The reason behind your problem:
The problem is that when this line is executed: btn = Button(command =lambda:[RandomColor()]) by the end of the loop, you get a reference only to the last button which is created. You loose reference to other buttons.
Solution:
You can overcome this problem if you rely on winfo_children().
You have to do 2 steps to fix your issue:
First of all, change: btn = Button(command =lambda:[RandomColor()]) to btn = Button(window, command=lambda:[RandomColor()]). That simply means we attach each created button to a parent widget called window.
Then all that you need to change is RandomColor() function body as follows:
def RandomColor():
for child in window.winfo_children():
child.configure(bg=color[randint(0,6)])
Demo:
This solves your problem but your code is not clean. You can ask for suggestions to improve your code on Code Review website.
EDIT:
Here is a solution for the scenario you described in the comment.
Note that I had to create code from scratch, cleaner one (I know you started only today, so I am not blaming you). In this code, I keep reference for every button I create:
import tkinter as tk
import random
class ChangeBottomRightColor(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, self.master)
self.__colors = ["red","blue","green","yellow","black","purple","orange"]
self.configure_gui()
self.create_widgets()
def configure_gui(self):
pass
def create_widgets(self):
self.create_buttons()
def create_buttons(self):
self.buttons = {}
c = 0
for i in range(4):
for j in range(4):
self.buttons['button{}'.format(c)] = tk.Button(self.master)
self.buttons['button{}'.format(c)].grid(row=i, column=j)
self.buttons['button{}'.format(c)].config(width=3, height=3)
self.buttons['button{}'.format(c)].config(command=self.change_bottom_right_button_color)
c += 1
def get_random_color(self):
return random.choice(self.__colors)
def change_bottom_right_button_color(self):
self.buttons['button{}'.format(15)].config(bg=self.get_random_color())
if __name__ == '__main__':
root = tk.Tk()
main_app = ChangeBottomRightColor(root)
root.mainloop()
Demo:
Let try
btn = []
for i in range(16):
btn.append(Button(window))
it will create an array of button. So you can access by btn[i].configure(command=lambda:[RandomColor()]) or something else.

Get values in tkinter entry under same name

I currently have a function which creates 2 entry boxes when a button is clicked. The values of these entry boxes need to be entered into a database. The problem with this is that every time the button is clicked the entry boxes have the same name as the ones before. This means that if the button is clicked twice then when it is being submitted into the database only the values in the last set of boxes are entered in. This is the code:
def new(self):
global ExerciseCount
ExerciseCount = ExerciseCount + 1
print (ExerciseCount)
for num in range(ExerciseCount):
self.Exercises = Entry(self.FrameExercise, bg = "PaleTurquoise1", font =("Arial","16"), width = 20)
self.Exercises.grid(row=2+ExerciseCount, column=1)
global WeightCount
WeightCount = WeightCount + 1
print (WeightCount)
for num in range(WeightCount):
self.Weights = Entry(self.FrameExercise, bg = "PaleTurquoise1", font =("Arial","16"), width = 4)
self.Weights.grid(row=2+WeightCount, column=2)
def Update(self):
global MemberID
connection = sqlite3.connect(r"F:\TESTING\Program\Accounts.db")
cursor = connection.cursor()
Exercise = self.Exercises.get()
Weight = self.Weights.get()
ID = self.ent_MemberID.get()
cursor.execute("INSERT INTO Exercises (Exercise, Weight, ID) VALUES (?,?,?)",
(Exercise, Weight, ID,))
connection.commit()
When the button is clicked only the last set of entry boxes are submitted to the database. This could be down to me using .get() to retrieve the values from the entry, however I do not know any alternatives to this as I am still only a student.
I have also tried to iterate the entry boxes by assigning a number to them however it appears with an error saying that a function cannot be assigned.
If there is any way to get the values in every entry box I would appreciate it.
Try storing the text boxes in an array when you create them. Then use the array to access them. Here is an example:
import Tkinter as tk
class MyApplication(tk.Frame):
arr = []
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
def addTextBox(self,event):
newtext = tk.Entry(self)
self.arr.append(newtext)
newtext.pack()
def getValues(self,event):
for i in range(len(self.arr)):
print self.arr[i].get()
def createWidgets(self):
self.btnAdd = tk.Button(self)
self.btnAdd["text"] = "Add"
self.btnAdd.bind("<Button-1>", self.addTextBox)
self.btnAdd.pack()
self.btnGet = tk.Button(self)
self.btnGet["text"] = "Get"
self.btnGet.bind("<Button-1>", self.getValues)
self.btnGet.pack()
# main
root = tk.Tk()
root.minsize(width=325, height=325)
root.maxsize(width=325, height=325)
app = MyApplication(master=root)
app.mainloop()

Tkinter destroy button create by a loop

I want to create a reminder to remind me of the time I have chosen in the combobox by pressing the confirmed button. I put the time in a label and also create a delete button that can delete the label and button itself in the same row by a loop. It works if there's only one label, but if I increased the number of it it can only destroy the last label and button.
below is my code:
class final:
def __init__(self,app):
self.savelist=[]
self.time= StringVar()
self.timecombo = ttk.Combobox(app,textvariable=self.time)
self.timecombo.grid(row=0,column=1)
self.timecombo.config(value =('1:00','2:00','3:00','4:00','5:00','6:00','7:00','8:00','9:00','10:00','11:00','12:00'))
self.button1=Button(app,text='confirmed',command=self.save)
self.button1.grid(row=3,column=2)
***def save(self):
savetext = self.time.get()
self.savelist.append(savetext)
self.deletebutton_list = []
self.savelabel_list = []
for i in range(len(self.savelist)):
savelabel = Label(app, text=self.savelist[i])
savelabel.grid(row=4 + i, column=0)
self.savelabel_list.append((savelabel))
deletebutton = Button(app, text='delete' , command=functools.partial(self.deletelabel,idx=i))
deletebutton.grid(row=4 + i, column=1)
self.deletebutton_list.append(deletebutton)
def deletelabel(self, idx):
self.savelabel_list[idx].destroy()
self.deletebutton_list[idx].destroy()
self.savelist.remove(self.savelist[idx])
self.savelabel_list.remove(self.savelabel_list[idx])
self.deletebutton_list.remove(self.deletebutton_list[idx])***
app = Tk()
a = final(app)
app.title('things to do')
app.geometry("500x300+200+200")
app.mainloop()
I believed that there must be something wrong in the loop or the function deletelabel but I still can't fix it.
self.savelabel_list.remove(self.savelabel_list[idx])
Do not change the list. If you delete label/button #1, then label #2 becomes #1, and so when you press the button to delete label #2, it deletes label #3 because everything has moved up. Also, note that every time you call "save()" it creates a new set of widgets that overlay the old widgets, which will eventually slow down the computer. Create and grid the new time label only. Keep track of the row with a self.next_row variable (or whatever) and increment it by one each time.
This is a question that points out how usable classes are. Create a new class, with label and a close button, for each reminder.
from tkinter import *
from tkinter import ttk
class NewButton:
def __init__(self, master, label_text, this_row):
## put everything in a new frame so destroying
## one frame will destroy everything in it
self.fr=Frame(master)
self.fr.grid(row=this_row, column=1)
Label(self.fr, text=label_text).grid(row=0, column=1)
Button(self.fr, text="Close This",
command=self.fr.destroy).grid(row=0, column=2)
class Final:
def __init__(self,app):
self.app=app
self.this_row=4
self.time_var= StringVar()
self.timecombo = ttk.Combobox(app,textvariable=self.time_var)
self.timecombo.grid(row=0,column=1)
self.button1=Button(app,text='confirmed',command=self.save)
self.button1.grid(row=3,column=2)
def save(self):
save_text = self.time_var.get()
self.this_row += 1
next_button=NewButton(self.app, save_text, self.this_row)
self.time_var.set("")
app = Tk()
a = Final(app)
app.title('things to do')
app.geometry("500x300+200+200")
app.mainloop()

python tkinter append list

I am an amateur python programer with 2 months of experience. I am trying to write a GUI to-do list through tkinter. The actual placement of the buttons are not important. I can play around with those after. I need some help with displaying the appended item to the list. In the program, it updates well on the digit, but it won't print onto the list. I double checked it on the console and it says "tkinter.StringVar object at 0x102fa4048" but didn't update the actual list. What I need help is how can I update the list Main_Q on my the label column? Much appreciate some direction and coding help. Thanks.
Main_Q =["read","clean dishes", "wash car"]
from tkinter import*
root=Tk(className="total tasks in the Q")
#formula
def update():
global Main_Q
a=len(Main_Q)
num.set(a)
def add2list():
Main_Q.append(name)
a=len(Main_Q)
num.set(a)
print (Main_Q)
#output
num=StringVar()
y=Label(root, textvariable=num).grid(row=0, column=1)
#input
name=StringVar()
b=Entry(root, textvariable=name).grid(row=7,column=0)
#buttons
z=Button(root, text="update", command=update).grid(row=7, column=2)
add2list=Button(root,text="add", command=add2list).grid(row=7,
column=1)
r = 0
for c in Main_Q:
Label(text=c, relief=RIDGE,width=15).grid(row=r,column=0)
r = r + 1
root.mainloop()
Your problem is that your for loop which build up your labels doesnt get called after each time you have entered a new "task". To easily fix this you can move this loop into your update function.
If you want to prevent of looping through widget everytime you can create a new list with all widgets which already have been created:
createdWidgets = []
widgetsQueue = []
In your update function you than have to iterate through the widgetsQueue (widgetsQueue.pop() for instance), create the widgets and append the widget to the createdWidgetes list.
def update():
global Main_Q
r = 0
for c in Main_Q:
Label(text=c, relief=RIDGE,width=15).grid(row=r,column=0)
r += 1 # shorthand for r = r + 1
Some addition notes:
for the entry it is easier to seperate the definition and placement:
b = Entry(root)
b.grid(row=7,column=0)
because than Entry() returns its instance and you can use it to get the text:
b.get()
if you go shopping do you throw everything into one bag ?
from tkinter import *
does axactly that(in this case the globals() variable would be the bag).If you want to read more about that Importing Python Modules. To prevent that and shorten the amount of letters to type:
import tkinter as t # or tk
root = t.Tk()
*But for sure, if you just want a small program its okay.
Design:
To resolve your problem, you need to design this simple solution:
retrieve the text of the Tkinter.Entry widget using get() method.
add the text you got in 1 to Main_Q using append() method.
bind the button that updates on click both Main_Q and your GUI using command method.
create a new Tkinter.Label widget, set its text to the value you got in 1 and increment its corresponding row in the GUI.
I prefer to organize your code within a class that contains a constructor where Main_Q is initialized so that we call initialize_user_interface() to initialize the GUI with its three elements:
def __init__(self, parent):
Tkinter.Frame.__init__(self, parent)
self.parent = parent
self.Main_Q = ["read", "clean dishes", "wash car"]
self.r = 0 # position of the row of each label
self.initialize_user_interface()
The method initialize_user_interface() does what its name says. We mainly bind the function update_gui() that inserts a new label with the text set to what the user types in Tkinter.Entry widget using command = self.update_gui
ef initialize_user_interface(self):
self.parent.title("Update GUI")
self.parent.grid_rowconfigure(0, weight = 1)
self.parent.grid_columnconfigure(0, weight = 1)
for e in self.Main_Q:
Tkinter.Label(self.parent, anchor = Tkinter.W, text = e).grid(row = self.r, sticky = Tkinter.W)
self.r+=1
self.entry_text = Tkinter.Entry(self.parent)
self.entry_text.grid(row = 0, column = 1)
self.button_update = Tkinter.Button(self.parent, text = "Update", command = self.update_gui).grid(row = 1, column = 1, sticky = Tkinter.E)
Finally, nothing is simpler than update_gui() function:
def update_gui(self):
self.r+=1 # increment the row reserved to the new label
self.Main_Q.append(self.entry_text.get())
Tkinter.Label(self.parent, anchor = Tkinter.W, text = self.entry_text.get()).grid(row = self.r, sticky = Tkinter.W)
Programming the application:
Here is the full program:
'''
Created on Mar 11, 2016
#author: Bill BEGUERADJ
'''
import Tkinter
class Begueradj(Tkinter.Frame):
def __init__(self, parent):
Tkinter.Frame.__init__(self, parent)
self.parent = parent
self.main_queue = ["read", "clean dishes", "wash car"]
self.r = 0
self.initialize_user_interface()
def initialize_user_interface(self):
self.parent.title("Update GUI")
self.parent.grid_rowconfigure(0, weight = 1)
self.parent.grid_columnconfigure(0, weight = 1)
for e in self.main_queue:
Tkinter.Label(self.parent, anchor = Tkinter.W, text = e).grid(row = self.r, sticky = Tkinter.W)
self.r+=1
self.entry_text = Tkinter.Entry(self.parent)
self.entry_text.grid(row = 0, column = 1)
self.button_update = Tkinter.Button(self.parent, text = "Update", command = self.update_gui).grid(row = 1, column = 1, sticky = Tkinter.E)
def update_gui(self):
self.r+=1
self.main_queue.append(self.entry_text.get())
Tkinter.Label(self.parent, anchor = Tkinter.W, text = self.entry_text.get()).grid(row = self.r, sticky = Tkinter.W)
def main():
root = Tkinter.Tk()
b = Begueradj(root)
root.mainloop()
if __name__ == "__main__":
main()
Demo:
Here is a screenshot of the running program:
Note:
I coded the previous program using Python 2.7, so if you want to test it, please change Tkinter to tkinter. Everything else remains the same.

bring window to top level

I want to present several questions, one after another. The first question is shown as I like, with the cursor set in the entry field. Then I destroy the window and call the function again to create a new window. This time the window is not shown in the front and therefore I first have to click on the screen in order to have the cursor set to the entry field. Also the escape key does not work until I click on the screen to bring the window to the top. I'd be very happy for your help!
Thank you in advance!
Here's my code:
from Tkinter import *
def text_input_restricted(fn,question, nr_letters, limit, len_min, len_max,keys, justify):
class MyApp():
def validate(root, S):
return all(c in keys for c in S)
def __init__(self, q= None):
#save response after "next"-button has been clicked
def okClicked():
lines = e.get()
if len_min < len(lines) < len_max:
lines = unicode(lines).encode('utf-8')
datFile = open(fn, "a")
datFile.write(" '%s'"%(lines))
datFile.close()
self.root.destroy()
self.root = Tk()
vcmd = (self.root.register(self.validate), '%S')
#quit if escape-key has been pressed
self.root.bind('<Escape>', lambda q: quit())
#colors
color = '#%02x%02x%02x' % (200, 200, 200)
self.root.configure(bg=color)
#set window size to screen size
RWidth=MAXX
RHeight=MAXY
self.root.geometry(("%dx%d")%(RWidth,RHeight))
#remove buttons (cross, minimize, maximize)
self.root.overrideredirect(1)
#remove title
self.root.title("")
#item
labelWidget = Label(self.root,text=question, font=("Arial", int(0.02*MAXX)), bd=5, bg=color, justify="center")
labelWidget.place(x=0, y=RHeight/40,width=RWidth)
#"next"-button
ok_width = RWidth/15
ok_height = RWidth/15
okWidget = Button(self.root, text= "next", command = okClicked, font=("Arial",int(0.015*MAXX)), bd=5, justify="center")
okWidget.place(x=RWidth/2-ok_width/2,y=13*RHeight/40, width=ok_width,height=ok_height)
def callback(sv):
c = sv.get()[0:limit]
sv.set(c)
sv = StringVar()
width=nr_letters * int(0.02*MAXX)*1.3
sv.trace("w", lambda name, index, mode, sv=sv: callback(sv))
e = Entry(self.root, textvariable=sv,font=("Arial", int(0.02*MAXX)),justify=justify,validate="key", validatecommand=vcmd)
e.place(x=RWidth/2-width/2, y=9*RHeight/40, width=width)
#show cursor
e.focus_set()
self.root.mainloop()
MyApp()
MAXX=1366
MAXY=768
fn = "D:/test.dat"
text_input_restricted(fn = fn, question=u"f for female, m for male", nr_letters=1, limit =1, len_min =0, len_max=2, keys = 'fm', justify="center")
text_input_restricted(fn = fn, question="How old are you?", nr_letters=2,limit=2, len_min = 1, len_max = 3, keys = '1234567890',justify="center")
In Tk you use the raise command to bring a window to the front of the Z-order. However, raise is a keyword in Python so this has been renamed to lift. Provided your application is still the foreground application you can call the lift() method on a toplevel widget. If the application is not the foreground application then this will raise the window but only above other windows from the same application. On Windows this causes the taskbar icon for your application to start flashing.
You might do better to destroy the contents of the toplevel and replace them. Or even better - create a number of frames holding each 'page' of your application and toggle the visibility of each frame by packing and pack_forgetting (or grid and grid forget). This will avoid loosing the focus completely - you can just set the focus onto the first widget of each frame as you make it visible.

Categories