I am working on a wordle clone as a project to get familiar with python and tkinter. I have made a 6x5 grid of entry boxes that all accept one letter. I am trying to make it that each box will automatically convert that letter to uppercase, but I am having issues with that. Only the very last entry will be uppercase.
def validate(P):
if len(P) == 0:
# empty Entry is ok
return True
elif len(P) == 1 and not P.isdigit():
# Entry with 1 digit is ok
return True
else:
# Anything else, reject it
return False
# sets v to upper
def caps(event):
v.set(v.get().upper())
vcmd = (window.register(validate), '%P')
canvas1 = tk.Canvas(window, width = 400, height = 300)
borderColor = Frame(window, background="#A3F299")
#2d array of entrys to create the 6x5 grid
entries = [[]]
inner = []
# Will loop to create 6 rows, 5 columns
# v - user input
# inner - row of entries
# entries - entire grid of entries
# validate ensures that only 1 character is entered in the box
for x in range(6):
for i in range(5):
v = StringVar()
inner.insert(i, Entry(window, validate="key", width = 2, validatecommand=vcmd, font=("Helvetica, 35"), justify='center',
textvariable= v, bg='white'))
inner[i].bind("<KeyRelease>", caps)
entries.insert(x, inner)
entries[x][i].grid(row= x, column= i)
I have sort of deduced that it is because v is being set as text for each entry and it will make it uppercase if a key is released; however, because it continues to loop through this loop, it keeps setting v to a different entry and that would be the only one to be set to uppercase. e.g:
If I only loop through it for one row the output will be [a, a, a, a, A] if I loop through it a second time it will be [a, a, a, a, a], [a, a, a, a, A]
I have also tried making v a double array and setting each index to text variable and calling it in caps; however, it would only work for one row and it was very redundant because I would have to call v.set(v.get.... 5 different times, it would not work if I tried to loop through it.
CodePudding user response:
You can pass your widget name with %W into your validation part.
vcmd = (window.register(validate), '%P', '%W')
Now let's come to validation part. There is a little problem in here. You cannot change whatever comes into validation actually. It changes only when you return True. So as you may not execute anything after returned we have a problem. Well, the solution is you can use .after method. Simply by entering time in milliseconds, you can run a function and change value right after validation returned True. If you pass time a bit more like 1000, it will be capitalized after 1 second.
def validate(P,nameofwidget):
if len(P) == 0:
# empty Entry is ok
return True
elif len(P) == 1 and not P.isdigit():
entry = window.nametowidget(nameofwidget)
def makeBigger(entry):
text = entry.get()
print("run:",text)
if(not text.isupper()):
entry.delete(0,"end")
entry.insert(0,text.upper())
entry.after(1,makeBigger,entry)
# Entry with 1 digit is ok
return True
else:
# Anything else, reject it
return False
So if you choose small values, people not even realize its value changed after they typed. However passing too small values can cause makeBigger function run even before entry value is updated so careful about that.
CodePudding user response:
As v
will be the reference to the tkinter variable created in the last iteration of the nested for loop after the for loop, so caps()
will always modify the last entry box.
You can pass v
as an argument of caps()
instead using lambda
and default value of optional argument:
...
def caps(v):
v.set(v.get().upper())
...
entries = []
for x in range(6):
inner = []
for i in range(5):
v = StringVar()
inner.append(Entry(window, validate="key", width=2, validatecommand=vcmd,
font=("Helvetica, 35"), justify='center',
textvariable=v, bg='white'))
inner[i].bind("<KeyRelease>", lambda e, v=v: caps(v)) # pass v to caps()
inner[i].grid(row=x, column=i)
entries.append(inner)
...
Also if you want only letter can be input in those entry boxes, use P.isalpha()
instead of not P.isdigit()
inside validate()
.