I'm building a simulator program on python 3 Tkinter window. The progress is now disrupt due to a bug in the input program I build myself.
The broken code that I'm working on right now:
from tkinter import *
root = Tk()
prompt = ' Press any key '
label1 = Label(root, text=prompt, bg='black', fg='green')
label1.grid(row=0, column=0)
prompt2=''
def key(event):
global prompt2
if event.char == event.keysym:
prompt2 = prompt2 + event.char
elif len(event.char) == 1:
prompt2 = prompt2 + event.char
else:
pass
label1.config(text=prompt2)
root.bind_all('<Key>', key)
root.update()
root.mainloop()
So, what I want to do (and trying to do) is:
Build a function that allow the user to use BackSpace and stop them from using Enter (as I don't want them to move to a new line).
Set a variable to True, give the program a signal for when to start (taking input) and when to stop.
But I can't seem to do this. Any suggestions for 1 or 2? Please tell me in the comment section.
Thanks in advance!
Why are you comparing event.char to event.keysym? That will always be True for printable characters, and always be False for character sequences and special characters (like ⇦ and ↲).
How about this (untested)?
import string
import tkinter as tk
# See side note 1
def key_event(label, event):
label_text = label.cget("text")
if event.keysym == "backspace":
label.config(text=label_text[:-1])
elif event.keysym == "return":
# Disallow 'return'
pass
elif event.char in string.ascii_lowercase:
# Character is an ASCII letter
label.config(text=label_text + event.char)
def main():
root = tk.Tk()
prompt = " Press any key "
label = tk.Label(root, text=prompt, bg="black", fg="green")
label.grid(row=0, column=0)
label.bind("<Key>", lambda e: key_event(label, e))
# See side note 2
label.focus()
# Give keyboard focus
root.mainloop()
Side note 1: avoid using wildcard imports (from ... import *). Instead, alias tkinter 'as' tk:
import tkinter as tk
root = tk.Tk()
...
Side note 2: avoid using global variables, prefer using a class (provided you know how to use them properly, see below section for an example), or for really small pieces of code, share variables using lambdas like I did.
import string
import tkinter as tk
class EditableLabel(tk.Label):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bind("<Key>", self._key_event)
def _key_event(self, event):
keysym, char = event.keysym, event.char
text = self.cget("text")
if keysym == "backspace":
self.config(text=text[:-1])
elif keysym == "return":
pass
elif char in string.ascii_lowercase + string.digits:
self.config(text=text + char)
class MyApplication(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._add_and_draw_widgets()
def _add_and_draw_widgets(self):
for i in range(3):
EditableLabel(self, text="Label #{}".format(i)).grid(column=0, row=i)
def main():
root = tk.Tk()
app = MyApplication(root)
app.grid(column=0, row=0)
root.mainloop()
if __name__ == "__main__":
main()
Related
from tkinter import *
root=Tk()
root.geometry("400x300")
def e_input():
print(e1.get())
l1=Label(root,text="Enter here:")
l1.place(x=30,y=40)
e1=Entry(root,bd=2,width=25)
e1.place(x=100,y=40)
b1=Button(root,text="Enter",command=e_input)
b1.place(x=250,y=40)
root.mainloop()
In this code, the user manually has to enter the slashes after every 2 characters.
help me out to make that thing by default,
Image attached as to how I needed it to be...
enter image description here
In my example we expand the Entry widget to handle your date format. The validatecommand makes sure that we are entering numbers and that the text matches the format of the regular expression. The key bind handles inserting slashes in the proper position.
import tkinter as tk, re
class DateEntry(tk.Entry):
def __init__(self, master, **kwargs):
tk.Entry.__init__(self, master, **kwargs)
vcmd = self.register(self.validate)
self.bind('<Key>', self.format)
self.configure(validate="all", validatecommand=(vcmd, '%P'))
self.valid = re.compile('^\d{0,2}(\\\\\d{0,2}(\\\\\d{0,4})?)?$', re.I)
def validate(self, text):
if ''.join(text.split('\\')).isnumeric():
return not self.valid.match(text) is None
return False
def format(self, event):
if event.keysym != 'BackSpace':
i = self.index('insert')
if i in [2, 5]:
if self.get()[i:i+1] != '\\':
self.insert(i, '\\')
class Main(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
DateEntry(self, width=10).grid(row=0, column=0)
if __name__ == "__main__":
root = Main()
root.geometry('800x600')
root.title("Date Entry Example")
root.mainloop()
Problem Description
I have an application in Tkinter that uses a Listbox that displays search results. When I press command + down arrow key, I am putting the focus from the search field to the first item in the Listbox. This is exactly how I want the behaviour but instead for just the down arrow.
However, I am already binding the down arrow to this Listbox by self.bind("<Down>", self.moveDown). I can not understand why command + down works while simply down (to which I literally bind'ed it to) does not. Specifically the result of pressing the down arrow is the following
While pressing command + down gives the intended result:
How can I let down behave just like command + down, and what is the reason why command is required at all?
Code snippets
def matches(fieldValue, acListEntry):
pattern = re.compile(re.escape(fieldValue) + '.*', re.IGNORECASE)
return re.match(pattern, acListEntry)
root = Tk()
img = ImageTk.PhotoImage(Image.open('imgs/giphy.gif'))
panel = Label(root, image=img)
panel.grid(row=1, column=0)
entry = AutocompleteEntry(autocompleteList, panel, root, matchesFunction=matches)
entry.grid(row=0, column=0)
root.mainloop()
With AutocompleteEntry being:
class AutocompleteEntry(Tkinter.Entry):
def __init__(self, autocompleteList, df, panel, rdi, *args, **kwargs):
self.df = df
self.product_row_lookup = {key:value for value, key in enumerate(autocompleteList)}
temp = df.columns.insert(0, 'Product_omschrijving')
temp = temp.insert(1, 'grams')
self.result_list = pd.DataFrame(columns=temp)
self.panel = panel
self.rdi = rdi
# self.bind('<Down>', self.handle_keyrelease)
# Listbox length
if 'listboxLength' in kwargs:
self.listboxLength = kwargs['listboxLength']
del kwargs['listboxLength']
else:
self.listboxLength = 8
# Custom matches function
if 'matchesFunction' in kwargs:
self.matchesFunction = kwargs['matchesFunction']
del kwargs['matchesFunction']
else:
def matches(fieldValue, acListEntry):
pattern = re.compile('.*' + re.escape(fieldValue) + '.*', re.IGNORECASE)
return re.match(pattern, acListEntry)
self.matchesFunction = matches
Entry.__init__(self, *args, **kwargs)
self.focus()
self.autocompleteList = autocompleteList
self.var = self["textvariable"]
if self.var == '':
self.var = self["textvariable"] = StringVar()
self.var.trace('w', self.changed)
self.bind("<Right>", self.selection)
self.bind("<Up>", self.moveUp)
self.bind("<Down>", self.moveDown)
self.bind("<Return>", self.selection)
self.listboxUp = False
self._digits = re.compile('\d')
def changed(self, name, index, mode):
if self.var.get() == '':
if self.listboxUp:
self.listbox.destroy()
self.listboxUp = False
else:
words = self.comparison()
if words:
if not self.listboxUp:
self.listbox = Listbox(width=self["width"], height=self.listboxLength)
self.listbox.bind("<Button-1>", self.selection)
self.listbox.bind("<Right>", self.selection)
self.listbox.bind("<Down>", self.moveDown)
self.listbox.bind("<Tab>", self.selection)
self.listbox.place(x=self.winfo_x(), y=self.winfo_y() + self.winfo_height())
self.listboxUp = True
self.listbox.delete(0, END)
for w in words:
self.listbox.insert(END, w)
else:
if self.listboxUp:
self.listbox.destroy()
self.listboxUp = False
else:
string = self.get()
if '.' in string:
write_to_file(self, string)
def contains_digits(self, d):
return bool(self._digits.search(d))
def selection(self, event):
if self.listboxUp:
string = self.listbox.get(ACTIVE)
self.var.set(string + ' ')
self.listbox.destroy()
self.listboxUp = False
self.icursor(END)
def moveDown(self, event):
self.focus()
if self.listboxUp:
if self.listbox.curselection() == ():
index = '0'
print "ok"
else:
index = self.listbox.curselection()[0]
print "blah"
if index != END:
self.listbox.selection_clear(first=index)
print "noo"
if index != '0':
index = str(int(index) + 1)
self.listbox.see(index) # Scroll!
self.listbox.selection_set(first=index)
self.listbox.activate(index)
else:
print "not up"
def comparison(self):
return [w for w in self.autocompleteList if self.matchesFunction(self.var.get(), w)]
Both command+down and down should produce the same output excepted that down also types question mark onto the entry which made the last letter typed is the question mark box.
This is because pressing command, your computer checks the option menu to see if there's a shortcut with that key, if there isn't any, it will not do anything. While tkinter registered the down button as being pressed, so the event was triggered.
In contrast, With out pressing command, the Entry first displays the value of "down", which there isn't any, then executes the event binding, what you can do is, in the event, remove the last letter of the Entry. You can do so by self.delete(len(self.get())-1) in your event. Or add a return 'break' at the end of your event to prevent it from being typed.
Unfortunately, it's really hard to understand your real problem because you've posted too much unrelated code and not enough related code. It seems to me that what you're trying to accomplish is to let the user press the down or up arrow while an entry has focus, and have that cause the selection in a listbox to move down or up. Further, it seems that part of the problem is that you're seeing characters in the entry widget that you do not want to see when you press down or up.
If that is the problem, the solution is fairly simple. All you need to do is have your binding return the string "break" to prevent the default binding from being processed. It is the default binding that is inserting the character.
Here is an example. Run the example, and press up and down to move the selection of the listbox. I've left out all of the code related to autocomplete so you can focus on how the event binding works.
import Tkinter as tk
class Example(object):
def __init__(self):
self.root = tk.Tk()
self.entry = tk.Entry(self.root)
self.listbox = tk.Listbox(self.root, exportselection=False)
for i in range(30):
self.listbox.insert("end", "Item #%s" % i)
self.entry.pack(side="top", fill="x")
self.listbox.pack(side="top", fill="both", expand=True)
self.entry.bind("<Down>", self.handle_updown)
self.entry.bind("<Up>", self.handle_updown)
def start(self):
self.root.mainloop()
def handle_updown(self, event):
delta = -1 if event.keysym == "Up" else 1
curselection = self.listbox.curselection()
if len(curselection) == 0:
index = 0
else:
index = max(int(curselection[0]) + delta, 0)
self.listbox.selection_clear(0, "end")
self.listbox.selection_set(index, index)
return "break"
if __name__ == "__main__":
Example().start()
For a fairly thorough explanation of what happens when an event is triggered, see this answer: https://stackoverflow.com/a/11542200/7432
Again, leaving aside the autocomplete requirement, I came up with a solution that uses that standard available commands and events for Listbox and Scrollbar. <<ListboxSelect>> lets you capture changes in selection from any of the lists and align the others. In addition, the Scrollbar and Listbox callbacks are directed to a routing function that passes things on to all of the listboxes.
# updownmultilistbox.py
# 7/24/2020
#
# incorporates vsb to propagate scrolling across lists
#
import tkinter as tk
class Example(object):
def __init__(self):
self.root = tk.Tk()
self.listOfListboxes = []
# self.active_lb = None
self.vsb = tk.Scrollbar(orient='vertical', command=self.OnVsb)
self.vsb.pack(side='right', fill='y')
self.lb1 = tk.Listbox(self.root, exportselection=0,
selectmode= tk.SINGLE, yscrollcommand=self.vsb_set)
self.lb2 = tk.Listbox(self.root, exportselection=0,
selectmode=tk.SINGLE, yscrollcommand=self.vsb_set)
self.lb3 = tk.Listbox(self.root, exportselection=0,
selectmode=tk.SINGLE, yscrollcommand=self.vsb_set)
self.listOfListboxes.append(self.lb1)
self.listOfListboxes.append(self.lb2)
self.listOfListboxes.append(self.lb3)
for i in range(30):
self.lb1.insert("end", "lb1 Item #%s" % i)
self.lb2.insert("end", "lb2 Item #%s" % i)
self.lb3.insert("end", "lb3 Item #%s" % i)
self.lb1.pack(side="left", fill="both", expand=True)
self.lb2.pack(side="left", fill="both", expand=True)
self.lb3.pack(side="left", fill="both", expand=True)
for lb in self.listOfListboxes:
lb.bind('<<ListboxSelect>>', self.handle_select)
for lb in self.listOfListboxes:
lb.selection_set(0)
lb.activate(0)
self.listOfListboxes[0].focus_force()
def start(self):
self.root.title('updownmultilistbox')
self.root.mainloop()
def OnVsb(self, *args):
for lb in self.listOfListboxes:
lb.yview(*args)
def vsb_set(self, *args):
print ('vsb_set args: ', *args)
self.vsb.set(*args)
for lb in self.listOfListboxes:
lb.yview_moveto(args[0])
def handle_select(self, event):
# set evey list to the same selection
print ('select handler: ', event, event.widget.curselection())
# self.active_lb = event.widget
for lb in self.listOfListboxes:
if lb != event.widget:
lb.selection_clear(0, 'end') # have to avoid this for the current widget
lb.selection_set(event.widget.curselection())
lb.activate(event.widget.curselection())
if __name__ == "__main__":
Example().start()
I am trying to keybind the enter key to "=".
With the code I have now, I get an error when i put in two numbers and press enter, the error is:
Exception in Tkinter callback
Traceback (most recent call last):
line 1550, in __call__
return self.func(*args)
line 68, in <lambda>
root.bind('<Return>', lambda x: Calculator.calcu('='))
TypeError: calcu() missing 1 required positional argument: 'entry_num'
The fact that the random window pops up at the start is just evidence that it's not working I guess. I believe that if the root window problem gets fixed, it could fix the key bind problem.
I get that error as I press enter, there is also a random window that opens up as I run the code as well if that helps anyone helping trying to solve the problem, please help me I'm really stuck.
THE OVERALL PROBLEM IS:
I don't know how to key bind the enter key on my keyboard to "=" and make the entry get entered by doing so.
This is what I get when i run my code(picture)
from __future__ import division
from functools import partial
from math import *
import tkinter as tk
from tkinter import *
root=Tk()
class Calculator(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.buttons_layout()
self.title("Thomas's calc")
textb = Entry(root)
def calcu(self,entry_num):
if entry_num == "=":
try:
total=eval(self.textb.get())
self.textb.insert(tk.END," = "+str(total))
except:
self.textb.insert(tk.END, " - Invalid Calculation - ")
elif entry_num == "del":
self.txt=self.textb.get()[:-1]
self.textb.delete(0,tk.END)
self.textb.insert(0,self.txt)
elif entry_num == "C":
self.textb.delete(0,END)
elif entry_num == "i":
infob= Toplevel(self.textb.insert(tk.END, ""))
infob = Label(infob, text="Thomas, Calculator").pack()
else:
if "=" in self.textb.get():
self.textb.delete(tk.END)
self.textb.insert(tk.END, entry_num)
def buttons_layout(self):
self.textb = tk.Entry(root, width=66, fg="white", bg="black", state="normal") #text color = fg // background colour bg // sets the entry box specs
self.textb.grid(row=0, column=0, columnspan=5)
buttona="groove"
ycol = 0
xrow = 1
button_list = ["1","2","3","+","C",
"4","5","6","-","del",
"7","8","9","/","",
"0",".","i","*","="]
for button in button_list:
calc1=partial(self.calcu, button)
tk.Button(root,text=button,height=4,command=calc1,bg="aquamarine", fg="red",width=10,state="normal",relief=buttona).grid(
row=xrow, column=ycol)
ycol= ycol + 1
if ycol > 4:
ycol=0
xrow= xrow + 3
class key_binding():
root.bind('<Return>', lambda x: Calculator.calcu('='))
end=Calculator()
end.mainloop()
root.mainloop()
THE KEYBIND IS AT THE BOTTOM OF MY CODE BY THE WAY
This should work. I've also got rid of the second window that wasn't needed
from __future__ import division
from functools import partial
from math import *
import tkinter as tk
from tkinter import *
#root=Tk()
class Calculator(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.buttons_layout()
self.title("Thomas's calc")
textb = Entry(self)
self.bind('<Return>', lambda x: self.calcu('='))
def calcu(self,entry_num):
if entry_num == "=":
try:
total=eval(self.textb.get())
self.textb.insert(tk.END," = "+str(total))
except:
self.textb.insert(tk.END, " - Invalid Calculation - ")
elif entry_num == "del":
self.txt=self.textb.get()[:-1]
self.textb.delete(0,tk.END)
self.textb.insert(0,self.txt)
elif entry_num == "C":
self.textb.delete(0,END)
elif entry_num == "i":
infob= Toplevel(self.textb.insert(tk.END, ""))
infob = Label(infob, text="Thomas, Calculator").pack()
else:
if "=" in self.textb.get():
self.textb.delete(tk.END)
self.textb.insert(tk.END, entry_num)
def buttons_layout(self):
self.textb = tk.Entry(self, width=66, fg="white", bg="black", state="normal") #text color = fg // background colour bg // sets the entry box specs
self.textb.grid(row=0, column=0, columnspan=5)
buttona="groove"
ycol = 0
xrow = 1
button_list = ["1","2","3","+","C",
"4","5","6","-","del",
"7","8","9","/","",
"0",".","i","*","="]
for button in button_list:
calc1=partial(self.calcu, button)
tk.Button(self,text=button,height=4,command=calc1,bg="aquamarine", fg="red",width=10,state="normal",relief=buttona).grid(
row=xrow, column=ycol)
ycol= ycol + 1
if ycol > 4:
ycol=0
xrow= xrow + 3
#class key_binding():
# root.bind('<Return>', lambda x: Calculator.calcu('='))
end=Calculator()
end.mainloop()
#root.mainloop()
I want to make a keyboard quick operation command with Tkinter.The keyboard event will invoke a function:when I press 'b',executive function 'buy',and when I press 's',executive function 'sell'.But there is a entry in my GUI.When I input a number in this entry,I'll press 'b' to invoke function 'buy' or press 's' to invoke function 'sell'.Of course the entry will display 'b' or 's'.I want to invoke function when I press 's' or 'b' and the entry will just distinguish and display numbers.How can I achieve this purpose ?
Here is my code:
# -*- coding: utf-8 -*-
from Tkinter import *
import tkFont
import datetime
class TradeSystem(object):
"""docstring for TradeSystem"""
def __init__(self):
self.root = Tk()
self.root.geometry('465x180')
self.root.resizable(width=True, height=False)
Label(self.root, text = 'Volume',font = tkFont.Font(size=15, weight='bold')).grid(row=0, column=0)
self.e1_str = StringVar()
self.e1 = Entry(self.root,bg = '#D2E48C',width = 10,textvariable = self.e1_str)
self.e1.grid(row = 1, column = 0)
self.v = IntVar()
self.Cb = Checkbutton(self.root,variable = self.v,text = 'Keyboard active',onvalue = 1,offvalue = 0,command = self.Keyeve)
self.Cb.grid(row = 3,column = 0)
self.currenttime = StringVar()
Label(self.root,textvariable = self.currenttime).grid(row=4, column=0,sticky = NW)
self.t_loop()
self.root.mainloop()
def buy(self,event):
print 'This is buy function.'
def sell(self,event):
print 'This is sell function.'
def rb(self):
self.root.bind('<KeyPress-b>',self.buy)
self.root.bind('<KeyPress-s>',self.sell)
def Keyeve(self):
if self.v.get():
self.rb()
else:
self.root.unbind('<KeyPress-b>')
self.root.unbind('<KeyPress-s>')
def t_loop(self):
self.currenttime.set(datetime.datetime.now().strftime("%Y-%m-%d,%H:%M:%S"))
self.root.after(1000,self.t_loop)
if __name__ == '__main__':
TradeSystem()
I input some numbers in entry self.e1,and when the keyboard active is 'on',I press 'b' to invoke function 'buy',like:
And fucntion 'buy' worked.
I just want the entry to distinguish numbers and when I press 'b' after I complete input numbers function 'buy' is invoked immediately.How can I achieve that?
Separate the text input from command hotkeys by using a modifier key, like Ctrl:
self.root.bind('<Control-b>',self.buy)
self.root.bind('<Control-s>',self.sell)
self.root.bind('<Control-B>',self.buy)
self.root.bind('<Control-S>',self.sell)
Note that the above has bound both the uppercase and lowercase keys, so that it still works if Caps Lock is on.
I am trying to code a login window using Tkinter but I'm not able to hide the password text in asterisk format. This means the password entry is plain text, which has to be avoided. Any idea how to do it?
A quick google search yielded this
widget = Entry(parent, show="*", width=15)
where widget is the text field, parent is the parent widget (a window, a frame, whatever), show is the character to echo (that is the character shown in the Entry) and width is the widget's width.
If you don't want to create a brand new Entry widget, you can do this:
myEntry.config(show="*");
To make it back to normal again, do this:
myEntry.config(show="");
I discovered this by examining the previous answer, and using the help function in the Python interpreter (e.g. help(tkinter.Entry) after importing (from scanning the documentation there). I admit I just guessed to figure out how to make it normal again.
widget_name = Entry(parent,show="*")
You can also use a bullet symbol:
bullet = "\u2022" #specifies bullet character
widget_name = Entry(parent,show=bullet)#shows the character bullet
Here's a small, extremely simple demo app hiding and fetching the password using Tkinter.
#Python 3.4 (For 2.7 change tkinter to Tkinter)
from tkinter import *
def show():
p = password.get() #get password from entry
print(p)
app = Tk()
password = StringVar() #Password variable
passEntry = Entry(app, textvariable=password, show='*')
submit = Button(app, text='Show Console',command=show)
passEntry.pack()
submit.pack()
app.mainloop()
Hope that helps!
I was looking for this possibility myself. But the immediate "hiding" of the entry did not satisfy me. The solution I found in the modification of a tk.Entry, whereby the delayed hiding of the input is possible:
Basically the input with delay is deleted and replaced
def hide(index: int, lchar: int):
i = self.index(INSERT)
for j in range(lchar):
self._delete(index + j, index + 1 + j)
self._insert(index + j, self.show)
self.icursor(i)
and the keystrokes are written into a separate variable.
def _char(self, event) -> str:
def del_mkey():
i = self.index(INSERT)
self._delete(i - 1, i)
if event.keysym in ('Delete', 'BackSpace'):
return ""
elif event.keysym == "Multi_key" and len(event.char) == 2: # windows stuff
if event.char[0] == event.char[1]:
self.after(10, del_mkey)
return event.char[0]
return event.char
elif event.char != '\\' and '\\' in f"{event.char=}":
return ""
elif event.num in (1, 2, 3):
return ""
elif event.state in self._states:
return event.char
return ""
Look for PassEntry.py if this method suits you.