Get values in tkinter entry under same name - python

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()

Related

python - binding to a tkinter widget is called twice if set() is called on a StringVar()

I have an entry, a listbox(dropdown) and another listbox. Whenever more than 3 characters are typed inside the entry. A completion list is looked up and inserted to the dropdown and the dropdown is shown. If an item is selected from the dropdown. It's value should fill the entry and the entry should get the focus again and the cursor should go to the end of the entry. And then, when Enter key is pressed the value of the entry should be inserted to the other listbox.
I've developed a code for that with much help from this utility and the code works perfectly fine. Except, I realized that whenever I select an option from the dropdown the corresponding method is called twice(I get two prints in the console from the same thing). But if I select the first option of the dropdown, it's called once(which is what should have actually happened in the other case) but the focus does not go to the entry (which is a problem).
Here is my code:
from tkinter import *
class Autocomplete(Frame, object):
def __init__(self, width, height, entries, *args, **kwargs):
super(Autocomplete, self).__init__(*args, **kwargs)
self._entries = entries
self.listbox_height = height
self.entry_width = width
self.text = StringVar()
self.entry = Entry(
self,
textvariable=self.text,
width=self.entry_width
)
self.frame = Frame(self)
self.listbox = Listbox(
self.frame,
height=self.listbox_height,
width=self.entry_width
)
self.dropdown = Listbox(
self.frame,
height=self.listbox_height,
width=self.entry_width,
background="#cfeff9"
)
def build(self):
self.text.trace("w", lambda name, index, mode, text=self.text: self._update_autocomplete())
self.entry.bind("<Return>", lambda event,: self._add_course())
self.entry.focus_set()
self.entry.pack()
self.frame.pack()
self.listbox.grid(column=0, row=0, sticky=N)
self.dropdown.bind("<<ListboxSelect>>", self._select_entry)
self.dropdown.grid(column=0, row=0, sticky=N)
self.dropdown.grid_forget()
return self
def _update_autocomplete(self):
self.dropdown["height"] = self.listbox_height
self.dropdown.delete(0, END)
text = self.text.get()
if len(text) < 3:
self.dropdown.grid_forget()
return
else:
for entry in self._entries:
if text.lower() in entry.strip().lower():
self.dropdown.insert(END, entry)
listbox_size = self.dropdown.size()
if not listbox_size:
self.dropdown.insert(END, "No results found for '{}'")
self.dropdown["height"] = 1
else:
if listbox_size <= self.dropdown["height"]:
self.dropdown["height"] = listbox_size
self.dropdown.grid(column=0, row=0, sticky=N)
def _select_entry(self, event):
widget = event.widget
value = widget.get(int(widget.curselection()[0]))
print(value)
self.text.set(value)
self.entry.focus_set()
self.entry.icursor(END)
def _add_course(self):
self.listbox.insert(END, self.text.get())
So what am I missing here?
By the way any general improvement to the code will also be much appreciated.
And here is how I call it:
from tkinter import *
from autocomplete import Autocomplete
from main import *
courses = load_courses_from_file("courses.txt")
root = Tk()
autocomplete_frame = Autocomplete(
60,
10,
list(set(course.name + ", " + course.instructor for course in courses))
).build().pack()
mainloop()
The selection of the listbox changes when you click on the item -- this is the default behavior of the listbox. This causes the entry widget value to change, which triggers a call to _update_autocomplete. That function deletes everything in the listbox, causing the selection to change again.

Load information into tkinter by checking Entry box

Is there any way to get a tkinter widget to update after an input into an Entry widget is completed?
https://i.stack.imgur.com/egSX6.png
The original Elo program was done with a Form in Access. When the player entries are filled, the Label/Entries denoted by the $ would search through the database and display information.
Is there some way of having the Label update while the GUI is running? A trigger for it could be when character count in the Entry field is 3 characters. I don't know how/if it's possible to make a Label/Entry update after the GUI is already running.
Edit:
def update_winner():
cursor = conn.cursor()
winner = winner_id.get()
school = school_name.get()
temp = school+winner
if len(temp) == 5:
cursor.execute("SELECT Rating FROM KIDS WHERE LocalID = ?", temp)
rating=cursor.fetchval()
cursor.execute("SELECT FirstName FROM KIDS WHERE LocalID = ?", temp)
name=cursor.fetchval()
winner_name.set(name)
loser_id.trace("w",update_loser)
winner_id.trace("w",update_winner)
ratings.mainloop()
When I run the code like this, as soon as I enter text into the winner_id box I get this error: TypeError: update_winner() takes 0 positional arguments but 3 were given
You can associate an instance of StringVar to the entry widget and then put a trace on the variable. The trace will be called whenever the variable value changes, and the value changes whenever the user types into the entry widget.
In the function that is called, you can change the value that is displayed in a label with the configure method.
Here's a brief example. In this example, when you type into the entry widget, the label will be updated to display what is entered.
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.v1 = tk.StringVar()
self.e1 = tk.Entry(self, textvariable=self.v1)
self.l1 = tk.Label(self)
self.e1.pack(side="top", fill="x")
self.l1.pack(side="top", fill="x")
self.v1.trace("w", self.on_change)
def on_change(self, *args):
self.l1.configure(text="You entered: '%s'" % self.v1.get())
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
You can of course do anything you want in the variable trace, such as look up values in a database.

Destroy an OptionMenu in 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
...

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