tkinter auto switch focus after write in entry - python-3.x

I have got 4 entry fields and I want to switch focus to next field after write one letter. So basically i want to write 4 letters, each one in another field, without touching mouse or Tab button.
from tkinter import *
root = Tk()
r1= StringVar()
r2= StringVar()
r3= StringVar()
r4= StringVar()
e1=Entry(root, textvariable=r1)
e1.pack()
e2=Entry(root, textvariable=r2)
e2.pack()
e3=Entry(root, textvariable=r3)
e3.pack()
e4=Entry(root, textvariable=r4)
e4.pack()
list=[e1,e2,e3,e4]
for i, element in enumerate(list):
lista=[r1,r2,r3,r4]
element.focus()
while lista[i].get() == "":
pass
root.mainloop()
How can I do that?
Thanks for help :D

Your original code hangs before reaching root.mainloop() because the Entry() fields will never be edited. You have to use Tk events, e.g. after packing the Entry fields, you could try this:
liste=[e1,e2,e3,e4]
e1.focus()
curre=0
def nexte(e):
global curre
curre+=1
if (curre<4):
liste[curre].focus()
else:
root.unbind("<Key>")
# we've got input into the last field, now do sth useful here...
root.bind("<Key>",nexte)
root.mainloop()

Try something like:
all_typed = False
e1.focus()
boxes = [e1, e2, e3, e4]
focused = 0
while not all_typed:
if focused > 3:
all_typed = True
elif boxes[focused].get() != "":
focused += 1
boxes[focused].focus()
Not my finest code, but should work. I'm not too familiar with TK so I don't know if this will block the app from running or anything.

Related

Why callback function is bypassed

I am trying to develop a basic calculator and then introduce various functionalities.
I am new to Python but has been working with VB and c# for quite some time.
At present it has an Entry and three labels. I accept input from keyboard in Entry, and display on one label. Entry is hidden.
e.g. 52+56+36+45=189
User enters 52 and presses + sign
at this stage "+" is displayed on one label, 52 gets transferred to second label and Entry, first label becomes blank.
Till this stage it works properly. Now in step two when user is to enter 56 it allows allows all the keys without any validation and it stops printing output.
This means that callback function is bypassed.
Any help is welcome.
import tkinter
from tkinter import *
def callback(input):
if input.isdigit() or "." in input or input == "\b":
print(input)
return True
elif "+" in input :
print(input)
svLabelOpr.set("+")
svLabelDisp.set(svLabel.get())
svLabel.set("")
svTxt.set("")
txt.focus_set()
return False
else:
print(input)
return False
def oddblue(a,b,c):
svLabel.set(svTxt.get())
frm=Tk()
frm.geometry("250x250")
svTxt = StringVar()
svLabel = StringVar()
svLabelOpr = StringVar()
svLabelDisp = StringVar()
svTxt.trace('w',oddblue)
txt=Entry(frm, width=10, textvariable=svTxt)
txt.place(x=20, y=20)
reg=frm.register(callback)
txt.config(validate="key", validatecommand=(reg, '%S'))
lbl=Label(frm,anchor='e',width=15,relief=SUNKEN,textvariable=svLabel)
lbl.place(x=50,y=50)
lblOpr=Label(frm,width=3,relief=SUNKEN,textvariable=svLabelOpr)
lblOpr.place(x=180,y=50)
lblDisp=Label(frm,anchor='e', width=15,relief=SUNKEN,textvariable=svLabelDisp)
lblDisp.place(x=50,y=70)
txt.focus_set()
frm.mainloop()
Adding line:
txt.config(validate="key", validatecommand=(reg, '%S'))
here:
elif "+" in input :
print(input)
svLabelOpr.set("+")
svLabelDisp.set(svLabel.get())
svLabel.set("")
svTxt.set("")
txt.focus_set()
txt.config(validate="key", validatecommand=(reg, '%S')) # <---Here
return False
Maybe it deregisters due to some case. Configuring it again solves it.
Also, I'm not 100% sure, what you want to do. But, in case, if you want to add the values, you can replace this line:
svLabelDisp.set(svLabel.get())
with these:
try:
svLabelDisp.set(int(svLabelDisp.get())+int(svLabel.get()))
except ValueError:
svLabelDisp.set(svLabel.get())

populating one combobox based on another combo box using tkinter python

from tkinter import *
from tkinter.ttk import Combobox
v1=[]
root = Tk()
root.geometry('500x500')
frame1=Frame(root,bg='#80c1ff',bd=5)
frame1.place(relx=0.5,rely=0.1,relwidth=0.75,relheight=0.1,anchor='n')
lower_frame=Frame(root,bg='#80c1ff',bd=10)
lower_frame.place(relx=0.5,rely=0.25,relwidth=0.75,relheight=0.6,anchor='n')
v=[]
def maincombo():
Types=["MA","MM","MI","SYS","IN"]
combo1=Combobox(frame1,values=Types)
combo1.place(relx=0.05,rely=0.25)
combo2=Combobox(frame1,values=v)
combo2.bind('<<ComboboxSelected>>', combofill)
combo2.place(relx=0.45,rely=0.25)
def combofill():
if combo1.get()=="MA":
v=[1,2,3,45]
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
if combo1.get()=="MM":
v=[5,6,7,8,9]
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
maincombo()
root.mainloop()
I want to populate the one combobox based on selection of other combobox I,e types.But failed to do so with simple functions.
Looking at you code, most of what you need is already there. The changes I have made are as follows:
Bound to combo1 rather than combo2 (as combo1 is the one you want to monitor)
Set combo1 and combo2 as global variables (so they can be used in the combofill method)
Set the combofill method to accept the event arg (it would raise a TypeError otherwise)
Use the .config method on combo2 rather than creating a new one each time
Set combo2 to be empty when neither "MA" or "MM" are selected
Here is my implementation of that:
from tkinter import *
from tkinter.ttk import Combobox
v1=[]
root = Tk()
root.geometry('500x500')
frame1=Frame(root,bg='#80c1ff',bd=5)
frame1.place(relx=0.5,rely=0.1,relwidth=0.75,relheight=0.1,anchor='n')
lower_frame=Frame(root,bg='#80c1ff',bd=10)
lower_frame.place(relx=0.5,rely=0.25,relwidth=0.75,relheight=0.6,anchor='n')
v=[]
def maincombo():
global combo1, combo2
Types=["MA","MM","MI","SYS","IN"]
combo1=Combobox(frame1,values=Types)
combo1.place(relx=0.05,rely=0.25)
combo1.bind('<<ComboboxSelected>>', combofill)
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
def combofill(event):
if combo1.get()=="MA":
v=[1,2,3,45]
elif combo1.get()=="MM":
v=[5,6,7,8,9]
else:
v=[]
combo2.config(values=v)
maincombo()
root.mainloop()
A couple other ideas for potential future consideration:
I would recommend using the grid manager rather than the place manager as it will stop widgets overlapping, etc. (on my system, combo2 slightly covers combo1)
Use a dictionary rather than if ... v=... elif ... v= ... and then use the get method so you can give the default argument. For example:
v={"MA": [1,2,3,45],
"MM": [5,6,7,8,9]}. \
get(combo1.get(), [])
EDIT:
Responding to the question in the comments, the following is my implementation of how to make a "toggle combobox" using comma-separated values as requested.
As the combobox has already overwritten the value of the text area when our <<ComboboxSelected>> binding is called, I had to add a text variable trace so we could keep track of the previous value of the text area (and therefore append the new value, etc.). I am pretty sure that explanation is completely inadequate so: if in doubt, look at the code!
from tkinter import *
from tkinter.ttk import Combobox
root = Tk()
def log_last():
global last, cur
last = cur
cur = tx.get()
def append_tx(event):
if last:
v = last.split(",")
else:
v = []
v = list(filter(None, v))
if cur in v:
v.remove(cur)
else:
v.append(cur)
tx.set(",".join(v))
combo.selection_clear()
combo.icursor("end")
last, cur = "", ""
tx = StringVar()
combo = Combobox(root, textvariable=tx, values=list(range(10)))
combo.pack()
combo.bind("<<ComboboxSelected>>", append_tx)
tx.trace("w", lambda a, b, c: log_last())
root.mainloop()

How do I return the input of a tkinter entry box generated by a loop to a list for later use

sorry for what I assume is a noob question. this seems like it should be so simple. I am trying to create a list from an entry box generated by a loop.
I have two lists, one list "myLabelList" that has info such as "job Name, Project name" etc. and one empty list "myEntryLists" to capture the info from the entry.
The problem is when i print(myEntryList) it seems to display info about the entry rather than the input itself. I have a workaround but that's exactly what it is.
sorry if i have formatted this badly, its my first post.
from tkinter import *
root = Tk()
root.title("Job Information Entry")
root.geometry("400x150")
topFrame = Frame(root)
bottomFrame = Frame(root)
topFrame.grid(row=0, column=0)
bottomFrame.grid(row=1, column=0)
myLabelList = ["Enter Job Number", "Enter Project Name", "Enter Job Name", "Enter Drawing Number"]
myEntryList = []
lst = []
# this is where i seem to be having problems
def ok_button_click():
for entry in myEntryList: # this is my workaround
lst.append(entry.get()) # this is my workaround
print(myEntryList) # this is what im getting
print(lst) # this is what i want to print()
x = 0
for i in myLabelList:
myLabel = Label(topFrame, text=i)
myEntry = Entry(topFrame, width=50)
myLabel.grid(row=x, sticky=E)
myEntry.grid(row=x, column=1)
x = x + 1
myEntryList.append(myEntry)
# bottomFrame
okButton = Button(bottomFrame, text="OK", command=ok_button_click)
cancelButton = Button(bottomFrame, text="Cancel")
okButton.grid(row=0, column=0)
cancelButton.grid(row=0, column=1)
root.mainloop()
In this line you insert an Entry widget to myEntryList:
myEntryList.append(myEntry)
When you're trying to print it, it gives you a printable representation of the the Entry widgets, saved in the list.
If you want to get the input itself, just print the lst list.
EDIT:
Entry is an object. In OOP (Object-Oriented Programming), you define classes, which represent an object. Each object has properties and methods. You can read more about OOP here .
Entry widget is an example of an object. It has constructor that creates an instance of that class (Entry()), propeties like 'bg', 'fg' and methods like get().
When you insert to myEntryList an Entry widget, you insert the whole object to the list, and not the input it holds. In order to get the input, you need to use the get() method on the Entry widget.
I hope everything is clear now :)

Change the label's text everytime a button is pressed

I'm doing a PYTHON table using FOR, in TKINTER, and I would like every time a new number is placed in ENTRY, the label changes to the new table. For example, a number will be placed in the ENTRY and the person will click the TAB button, when it is clicked, the table will appear, but if the person wants another number, and click again, the new table will go down from the previous one. My solution was to create a button that erases the previous table, but when the button is pressed, only the last multiplication is deleted. I would like to know how I click the tabuada button, and the previous one erases the new one without using another button .Get the code and a photo below, Thanks. Obs.: First photo shows reset button working, but it just erase the last multiplication,second photo shows the whole multiplication.
from tkinter import *
import tkinter as tk
win=tk.Tk()
win.title('Table')
lb=Label(win,text='Type a number:',font='Helvetica 12 bold')
lb.pack()
e=Entry(win)
e.pack()
def click():
global c
c=e.get()
print('requested number ',c)
for b in (range(0, 11)):
global lb2
lb2=Label(text='{} x {} = {} '.format(c, b, int(b)*int(c)))
lb2.pack()
def reset():
lb2['text'] = ' '
bt1=Button(win,text='GO',bg='lightblue',command=click)
bt1.pack()
bt2=Button(win,text='RESET',bg='lightblue',command=reset)
bt2.pack()
win.mainloop()
erasing:
whole multiplication:
Here are some fixes for your code; it was not entirely clear what you exactly meant by: "the new table will go down from the previous one.", so I went with having the new table replacing the previous one.
c was not defined in your code and that threw an exception.
I placed the construction of the label inside a function make_label that is called from the main block, and from click(), to rebuild it when a new number is requested.
reset was missing a call to pack on the label, to update the text displayed.
I think that should help you get started in the right direction; let me know if something is unclear.
edit:
I modified reset so the label is destroyed and re-created from view, thus removing the growth in size of the window.
from tkinter import *
import tkinter as tk
win=tk.Tk()
win.title('Table')
lb=Label(win,text='Type a number:',font='Helvetica 12 bold')
lb.grid(row=0, column=0)
lb2 = Label(text='')
e=Entry(win)
e.grid(row=1, column=0)
c = 2
def click():
global c
c = e.get()
print('requested number ', c)
reset()
make_label(c)
def make_label(c):
global lb2
txt = []
for b in (range(0, 11)):
txt.append('{} x {} = {} '.format(c, b, int(b)*int(c)))
text = '\n'.join(txt)
lb2 = Label(text=text)
lb2.grid(row=4, column=0)
def reset():
global lb2
lb2.destroy()
lb2 = Label()
lb2.grid(row=4, column=0)
make_label(c)
bt1=Button(win,text='GO',bg='lightblue',command=click)
bt1.grid(row=2, column=0)
bt2=Button(win,text='RESET',bg='lightblue',command=reset)
bt2.grid(row=3, column=0)
win.mainloop()

Tkinter: creating an arbitrary number of buttons/widgets

So, I've got a list with entries that look like this:
Option1 Placeholder1 2 Placeholder2 0
Option2 Placeholder1 4
Option3 Placeholder1 2 Placeholder2 -2 Placeholder3 6
I have a listbox of the Options and a button that creates a new window with the values for the selected Option. What I want to do is to create n number of buttons when this new window is created, where n is the number of values of the selected Options (i.e. 2, 1 and 3 for Options 1 through 3, respectively). I want it to look something like this:
Option1
Placeholder1 [button1 containing value=2]
Placeholder2 [button2 containing value=0]
... which is of course quite simple if I just assign a button for the maximum number of n that I know will be present, but I'm wondering if there's a way to do it more arbitrarily. Obviously the same problem applies to the arbitrary number of Labels I would need to use for the value names (the 'PlaceholderX's) as well.
I've been trying to do some reading on this type of thing, variable variables, etc., and it seems it's a very big NO-NO most (if not all) of the time. Some advocate the use of dictionaries, but I don't really get how that's supposed to work (i.e. naming variables from entries/values in a dict).
Is this something that can (and should) be done, or am I better off just creating all the buttons manually?
[EDIT: added code]
from tkinter import *
import csv
root = Tk()
root.wm_title("RP")
listFrame = Frame(root, bd=5)
listFrame.grid(row=1, column=2)
listbox1 = Listbox(listFrame)
listbox1.insert(1, "Option1")
listbox1.insert(2, "Option2")
listbox1.insert(3, "Option3")
listbox1.pack()
infoFrame = Frame(root, bd=5)
infoFrame.grid(row=1, column=3)
info_message = Message(infoFrame, width=300)
info_message.pack()
# Read stats from file
stat_file = open('DiceTest.csv', 'rU')
all_stats = list(csv.reader(stat_file, delimiter=';'))
def list_selection(event):
# gets selection and info/stats for info_message
index = int(listbox1.curselection()[0])
stats = all_stats[index]
infotext = str(stats[0]) # just the name
for n in range(int((len(stats)-2)/2)): # rest of the stats
infotext += ('\n' + str(stats[n*2 + 2]) + '\t' + str(stats[n*2 + 3]))
info_message.config(text=infotext)
listbox1.bind('<ButtonRelease-1>', list_selection)
def load():
top = Toplevel()
top.geometry('300x100')
index = int(listbox1.curselection()[0])
stats = all_stats[index]
# some way to create arbitrary buttons/labels here (?)
load_button = Button(root, text='Load', command=load)
load_button.grid(row=2, column=2)
root.mainloop()
Oh, and every button should have the same command/function, which reduces whatever value currently is in the button by 2.
Figured it out! Creating the widgets dynamically with a dictionary worked just fine, but calling the correct widget on the various button presses was more difficult. This is what I had:
buttons = dict()
for k in range(len(info)):
buttons[k] = Button(top, text=info[k], command=lambda: my_function(buttons[k]))
... which would work, but all button presses would call the function with the last created button as the target. All that was needed was a few extra characters in the command part of the buttons:
buttons = dict()
for k in range(len(info)):
buttons[k] = Button(top, text=info[k], command=lambda a=k: my_function(buttons[a]))
... which I assume works because it somehow stores the value of k inside a rather than taking the last known value of k, i.e. equivalent to the last created button. Is this correct?
You can store Buttons in a list:
from tkinter import *
master = Tk()
buttons = []
n = 10
for i in range(n):
button = Button(master, text = str(i))
button.pack()
buttons.append(button)
master.mainloop()

Resources