tkinker optionmenu not showing chosen result - python-3.x

import tkinter
window = tkinter.Tk()
def abc(event):
ans=0
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=numberss[ans], command=efg)
ans=ans+1
def efg(event=None):
print('yee')
numbers = ['1','2', '3']
number=['4','5','6']
var = tkinter.StringVar(window)
var1 = tkinter.StringVar(window)
omenu = tkinter.OptionMenu(window, var, *numbers, command = abc)
omenu.grid(row=1)
omenu2 = tkinter.OptionMenu(window, var1, *number, command = efg)
omenu2.grid(row=2)
after you have entered the first option menu, it will update the second one. when you enter data into the second one, it runs the command, but doesn't show you what you entered. i do not want to include a button, and i know that the command works and not on the second
i found some code that changed the options of the second menu, however when i ran this, the command wouldn't work as it was changed to tkinter.setit (i would also like to know what is does. i do not currently understand it)
omenu2['menu'].add_command(label=numberss[ans], command=tkinter._setit(var1, number))
this has been taken from a larger piece of code, and has thrown the same error

You should set your StringVar(var1) new value.
def abc(event):
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=number, command=lambda val=number: efg(val))
def efg(val, event=None):
print('yee')
var1.set(val)
You are using for loop so you don't need ans(at least not in this code) since it iterates over items themselves.

Related

Run function when an item is selected from a dropdown

I have a dropdown in tkinter, that i have populated with some items.
OPTIONS = [0,1,2,3,4,5,6,7]
clicked = tk.StringVar()
clicked.set(OPTIONS[0]) # default value
drop = tk.OptionMenu(frame2, clicked, *OPTIONS)
drop.place(relx = 0.65, rely=0.25, relwidth=0.08, relheight=0.6)
However, when a user selects a value, i want other things to happen as well.
Like returning the value to a global variable, or making the state of a button normal, so it's visible again.
How can i run a function, when an item is selected, or when a different item is selected?
EDIT:
Following the suggestions of TheLizzard, i changed my code to this:
# this function is triggered, when a value is selected from the dropdown
def dropdown_selection():
global dropdown_value
dropdown_value = clicked.get()
print("You changed the selection. The new selection is %s." % dropdown_value)
button_single['state'] = 'normal'
OPTIONS = list(range(8))
clicked = tk.StringVar(master=frame2)
clicked.set(OPTIONS[0])
clicked.trace("w", dropdown_selection)
drop = tk.OptionMenu(frame2, clicked, *OPTIONS)
drop.place(relx = 0.65, rely=0.25, relwidth=0.08, relheight=0.6)
However, i get this error:
TypeError: dropdown_selection() takes 0 positional arguments but 3 were given
Try this:
import tkinter as tk
def changed(*args):
print("You changed the selection. The new selection is %s." % clicked.get())
root = tk.Tk()
OPTIONS = list(range(8))
clicked = tk.StringVar(master=root) # Always pass the `master` keyword argument
clicked.set(OPTIONS[0]) # default value
clicked.trace("w", changed)
drop = tk.OptionMenu(root, clicked, *OPTIONS)
drop.pack()
root.mainloop()
In tkinter you can add callbacks to variables like StringVar using <tkinter variable>.trace(mode, callback) for more info read this.
Also always pass in the master keyword argument to all tkinter widgets/variables. You did this with the OptionMenu (it's the first argument usually). But you didn't do it for the StringVar. If you always pass the master keyword argument, you can save yourself a headache.
Edit:
When tkinter calls the callback when the variable is changed it passes some arguments (I don't think they are useful) so make sure that the callback accepts them. Instead of having def callback() use def callback(*args).

Tkinter - How to trace expanding list of variables

What I am trying to do track when any values in a list of StringVar change, even when the list is expanding. Any additions to the list before the trace statement will result in the callback. But any additions afterward, such as when pressing a button, will not cause any callback.
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
add = tk.Button(frame,text='add Entry',command='buttonpressed')
add.grid(row=0)
add.bind('<Button-1>',add_entry)
for i in range(2):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
for i in L:
i.trace('w',lambda *arg:print('Modified'))
root.mainloop()
Modifying the first two Entry's prints out Modified, but any Entry's after the trace is run, such as the ones produced when a button is pressed, will not.
How do I make it so that trace method will run the callback for the entire list of variables even if the list is expanded?
Simple suggestion, change your add_entry function to something like this:
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[len(L)-1].trace('w',lambda *arg:print('Modified'))
Extra suggestions:
This add = tk.Button(frame,text='add Entry',command='buttonpressed') is assigning a string to command option, means it will try to execute that string when button is clicked(which will do nothing). Instead, you can assign your function add_entry to command option and it will call that function when button is clicked and you can avoid binding Mouse Button1 click to your Button(Note: No need to use argument event in function when using like this). Read more here
Python supports negative indexing of List, so you can call L[-1] to retrieve the last element in the list instead of calling L[len(L)-1]).
Once you change your add_entry function as suggested, you can reduce your code to
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry():
global L
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[-1].trace('w',lambda *arg:print('Modified'))
add = tk.Button(frame,text='add Entry',command=add_entry)
add.grid(row=0)
for i in range(2):
add_entry()
root.mainloop()

Tkinter Entry validatation

I am writing a program in Python 3.6 using Tkinter where a customer has multiple(11) entry fields. I want these entry fields to only accept integers and also be able to define the maximum amount of characters.
I already have a function that does this. But this function only works for one entry field. I have tried entering variables with calling the function so it changes another entry field for example. I was not able to do this.
This is the function I have that works with 1 entry field.
def limitMuntgeld(self, *args):
value = self.invoerM.get()
if len(value) > 5:
self.invoerM.set(value[:5])
if value.lower() in "abcdefghijklmnopqrstuvwxyz-=[];/":
self.invoerM.set(value[:0])
This is the example entry field code that works with the function
self.invoerMuntgeld = Entry(self, font=('Arial', 14), textvariable=self.invoerM)
This is combined with a trace on the entry field posted below.
self.invoerM = StringVar()
self.invoerM.trace('w', self.limitMuntgeld)
I have also tried it with vcmd and validatecommand. However, no good results.
My endresult would be one function working with all entry fields. If anyone has any suggestions, I am all ears!
The proper way to do entry validation is with the validatecommand option rather than using trace. With the validation feature built into the widget you don't need a reference to the widget itself (though you can use it if you want).
When the validatecommand is run, you can have it pass in what the new value will be if the input is valid. You only need to check this value and then return True or False, without having to know which widget it applies to.
For example:
import tkinter as tk
def validate_input(new_value):
valid = new_value .isdigit() and len(new_value) <= 5
return valid
root = tk.Tk()
validate = root.register(validate_input)
for i in range(10):
entry = tk.Entry(root, validate="key", validatecommand=(validate, "%P"))
entry.pack(side="top", fill="x")
root.mainloop()
For information about what %P represents, and what else can be used as arguments to the command, see this question: Interactively validating Entry widget content in tkinter

Python 3.5.1 Tkinter // Entry widget/Frame management issue

So I want my program to be able to reset the password. The problem is that everything works just fine with the first reset process, but if you wanted to reset the password one more time it gets wicked and I can't find a mistake or solution. What the program does the first time and what it should do:
When button3 is pressed show the first frame where you have to enter you current pw. If the enter key on the keyboard is pressed and the pw is right show the second frame. There, set a new pw and by hitting enter show the third frame. Type in the same pw you used in the second frame, the pw set frame. If both pw are the same go back to the title screen.
Second+ time and the PROBLEM: button3 is pressed, it shows the first frame, but the cursor is not in the entry field like it is the first time (entry.focus_set() is in the code), BUT the cursor is in the third entry field (of the last, the pw confirm, frame) without it even being .pack()ed or .raised or anything beyond. If I click the entry of the first frame (where I actually would be at this point) and confirm it shows the second frame like it is supposed to be, but the cursor now is in the entry of the first frame which is not even active anymore because it just .pack_forget(). If I proceed to the third frame the cursor is in the entry of the second frame.
from tkinter import *
root = Tk()
pw_actual = ""
pw_first = ""
def ssc_title():
sc_pw.pack_forget()
sc_pw_second.pack_forget()
sc_title.pack()
def ssc_pw_change():
sc_title.pack_forget()
sc_pw_testing.pack()
def ssc_pw_first():
sc_pw_testing.pack_forget()
sc_pw_first.pack()
def ssc_pw_second():
sc_pw_first.pack_forget()
sc_pw_second.pack()
#-----------------------------------
def ev_pw_testing(event):
pw_proof = en_pw_testing.get()
if pw_proof == pw_actual:
en_pw_testing.delete(0, END)
ssc_pw_first()
else:
en_pw_testing.delete(0, END)
def ev_pw_first(event):
global pw_first
pw_first = en_pw_first.get()
if pw_first == "":
errormessage("Please enter a password")
else:
en_pw_first.delete(0, END)
ssc_pw_second()
def ev_pw_second(event):
global pw_first
pw_second = en_pw_second.get()
if pw_first == pw_second:
write(pw_second)
en_pw_second.delete(0, END)
ssc_title()
else:
errormessage("Passwords don't match")
en_pw_second.delete(0, END)
#------------------
sc_pw_testing = Frame(root, bg=bgclr)
lab_pw_testing = Label(sc_pw_testing, text="Please enter the password", bg=bgclr, fg=fontclr, font=font_12).pack(ipady=10)
en_pw_testing = Entry(sc_pw_testing, show="X")
en_pw_testing.pack()
en_pw_testing.focus_set()
en_pw_testing.bind("<Return>", ev_pw_testing)
sc_pw_first = Frame(root, bg=bgclr)
lab_pw_first = Label(sc_pw_first, text="Set new password", bg=bgclr, fg=fontclr, font=font_12).pack(ipady=10)
en_pw_first = Entry(sc_pw_first, show="X")
en_pw_first.pack()
en_pw_first.focus_set()
en_pw_first.bind("<Return>", ev_pw_first)
sc_pw_second = Frame(root, bg=bgclr)
lab_pw_second = Label(sc_pw_second, text="Confirm password", bg=bgclr, fg=fontclr, font=font_12).pack(ipady=10)
en_pw_second = Entry(sc_pw_second, show="X")
en_pw_second.pack()
en_pw_second.focus_set()
en_pw_second.bind("<Return>", ev_pw_second)
sc_title = Frame(root, bg=bgclr)
bttn3 = Button(sc_title, text="Password reset", bg=bgclr, fg=fontclr, font=font_12, relief=FLAT, command=ssc_pw_change).pack()
#------------
root.mainloop()
(Here I didn't include irrelevant functions etc like the errormessage toplevel funtion which justs create a window or the pickle stuff, because then it'll be too long and the issue does not seem to be laying there.)
Like I was saying - the first time changing the password works just fine. Every other time after the first time the same problem occures every time. The only way to get it to work again is to restart the program. I was trying following solutions:
resetting the global variables before entering the reset process again
resetting the global variables after switching the frame and being used
putting each frame (or page if you will) into a function
putting each frame (or page) into a class following the Switch between two frames in tkinter tutorial
leaving the frame/page switcing functions out and put the .pack()s and .pack_forget()s right in the pw checking (event) functions
instead of packing using a grid
using ("", lambda event: function())
Each solution worked just like the code right now, no changes at all, and the problem stays just as well.
I mean I could work around it, like disable the button so you can only reset the pw one time each session, but there is no reason why it should be like that, or even omit the entry.focus_set(), so you have to click the entry field no matter what and the problem never has a chance to occur (which is seriously annoying to click each entry field before entering characters each time).
I ran out of ideas what I could try to do differently. I feel like missing the forest for the trees. Come on guys, gimme a kick in my butt, what am I missing out here!?

Label keeps on appearing

SO I am using Python 3.4 and tkinter.
And when I call a function again n again which contains a label, the label keeps on appearing in window but previous label doesn't go away?
How can I remove any printed label from GUI window as soon as function is called and then display new one?
Here is the code:-
#def prestart():
#here I check if number of match is okay, if not, user is redirected to setting else, I call start()
def start():
#CPU Choice
cpu_choice = Label(historyframe, text = "CPU Choosed: {}".format(dict['cpu_choice']))
#Played Match
#played_num_of_match = Label(scoreframe, text = "Number of Matches Played: {}".format(int(dict['match_played'])))
#Display Status
status_disp = Label(scoreframe, text = "Current Status: {}".format(dict['status']))
if(int(dict['match_played']) < int(dict['num_of_match'])):
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
elif(int(dict['match_played']) == int(dict['num_of_match'])):
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
cp = dict['cpu_point']
up = dict['user_point']
result(cp, up)
cpu_choice.pack(fill = X)
scoreframe.grid(row = 2, column = 0)
This function just updates the display!
def send_value(x):
#Here I run logic of game and change value of key in dictionary and call start() at end of change.
Now, the choice buttons are not in any definition as they don't need to be called again n again. I just make playframe disappear n appear!
Here is the code for them:-
#Display Question
question = Label(playframe, text = "Rock? Paper? Scissor?")
#Rock
rock = Button(playframe, text = "Rock!", command = lambda: send_value("ROCK"))
#Paper
paper = Button(playframe, text = "Paper!", command = lambda: send_value("PAPER"))
#Scissor
scissor = Button(playframe, text = "Scissor!", command = lambda: send_value("SCISSOR"))
So when user clicks Rock/Paper/Scissor, I just change key value in dictionary! But if I keep the label outside function, it doesn't get auto updated!
Everything else is working perfectly. I'll kind of now start to make code cleaner.
Try something like this instead of creating a new label every time:
import Tkinter as tk
class Window():
def __init__(self, root):
self.frame = tk.Frame(root)
self.frame.pack()
self.i = 0
self.labelVar = tk.StringVar()
self.labelVar.set("This is the first text: %d" %self.i)
self.label = tk.Label(self.frame, text = self.labelVar.get(), textvariable = self.labelVar)
self.label.pack(side = tk.LEFT)
self.button = tk.Button(self.frame, text = "Update", command = self.updateLabel)
self.button.pack(side = tk.RIGHT)
def updateLabel(self):
self.i += 1
self.labelVar.set("This is new text: %d" %self.i)
root = tk.Tk()
window = Window(root)
root.mainloop()
Important points:
1) A class is used, as it is much easier to pass values around when all Tkinter objects and variables are member variables, accessible from all of your GUI functions.
2) updateLabel does not create a new Label. It simply updates the StringVar() object to hold new text every time you call the function. This is accomplished with the textvariable = self.labelVar keyword when creating my Label widget.
PS: This is done in Python 2.5 so for this code to work for you, change Tkinter to tkinter
EDIT 06/19/2015:
If you want to implement something similar to what I have with your code, without using a class, you'll need to pass around references to your variables.
1) Change start:
Your Labels cpu_choice, status_disp, etc. should be created outside of the function; likely in the same location as question, rock, paper, scissors, etc. You will also pack them outside of the function as well. Same with all the calls to .grid inside of start; you shouldn't need to call pack or grid more than once: right when you create the widget.
The following lines:
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
Can be done outside of the function as well; you execute these 3 statements under both the if and the elif conditions. This means they aren't really conditional statements; they are done regardless of the validity of the condition.
2) Create a StringVar for both cpu_choice & status_disp & edit the Labels as follows (remember, outside of the function):
cpu_choice_text = StringVar()
cpu_choice_text.set("Set this to whatever is shown at the start of the game")
cpu_choice = Label(historyframe, text = cpu_choice_text.get(), textvariable = cpu_choice_text)
cpu_choice.pack(fill = X)
# And do this same thing for status_disp
3) When you call start, you will now pass it cpu_choice_text & status_disp_text (or whatever they are called). Instead of trying to change the text field of the Label frame, you may now use a set call on the StringVar which is connected to the Label & the Label will automatically update. Example:
def start(cpu_choice_text, status_disp_text):
cpu_choice.set(text = "CPU Choice: {}".format(dict['cpu_choice']))
...
Alternatively, wrap it all in a class and make it much easier for yourself by using self on every Tkinter variable & widget. In this way you won't need to pass variables to your functions, just access member variables directly as I have with self.i, self.labelVar in my example.
Each time you call start you create new labels and use grid to place them in the same spot as the old labels. The best solution is to only create the labels once, but if you insist on creating new labels each time start is called, you need to delete the old labels first.
You can use the destroy() method of a label to destroy it, though for that to work you must keep a global reference of the label.

Resources