stop ttk.Entry() from reading keyboard - python-3.x

Environment: macOS Catalina, Python 3.7.4, Tcl/Tk 8.6.9, VSC 1.39.1
I have a situation where I am using a bar/qr code scanner to provide a string to a ttk.Entry() method, which then fires off a function.
The reader is seen by the OS as an HID keyboard, so the text from the QR code is received by the ttk.Entry() widget that I give focus to during code execution. I have bound the widget to the key because the scanner sends a cr/lf at the end of the text string, which works as needed.
However, I am running into an issue where if the qr code lingers over the scanner too long it will rescan the qr code and the widget receives the qr code text again, which then causes it to be processed again.
I have tried disabling the ttk.Entry() in the function, deleting the widget contents, and removing focus to no avail. The behavior I'm seeing is occurring even though the widget is disabled and does not have focus, it is still getting input and executing the function again if the scanner rescans the qr code while the function is executing.
In this first example, I simply tried to disable the widget, but that doesn't work. The widget still gets the later scans while in the function.
# test-ttk-entry1.py
import time
import tkinter as tk
from tkinter import StringVar, ttk
root = tk.Tk()
def print_text(event):
global kbEntry
textValue = kbEntry.get()
kbEntry.configure(state="disabled")
time.sleep(2) # Add in a delay to allow for repeat scan
print(textValue)
time.sleep(2) # Add in a delay to allow for repeat scan
kbEntry.configure(state="active")
kbText = StringVar()
kbEntry = ttk.Entry(root, width=10, textvariable=kbText)
kbEntry.bind("<Return>", print_text)
kbEntry.pack()
kbEntry.focus_set()
root.mainloop()
The second attempt was to disable the entry widget and upon making it active again delete the text in the field.
# test-ttk-entry2.py
import time
import tkinter as tk
from tkinter import END, StringVar, ttk
root = tk.Tk()
def print_text(event):
global kbEntry
textValue = kbEntry.get()
kbEntry.delete(0, END)
kbEntry.configure(state="disabled")
time.sleep(2) # Add in a delay to allow for repeat scan
print(textValue)
time.sleep(2) # Add in a delay to allow for repeat scan
kbEntry.configure(state="active")
kbEntry.delete(0, END)
kbText = StringVar()
kbEntry = ttk.Entry(root, width=10, textvariable=kbText)
kbEntry.bind("<Return>", print_text)
kbEntry.pack()
kbEntry.focus_set()
root.mainloop()
And finally, I was reading about taking focus from a widget and giving focus to the root window, so I added that in and it still prints multiple times to the console like there is a keyboard buffer being read by the ttk.Entry() widget. The weird thing is it seems like widgets don't normally respond to any calls to methods when they are disabled, but it appears the ttk.Entry() widget's properties/attributes (excuse me if my OOP terms are not correct) can be manipulated while disabled.
# test-ttk-entry2.py
import time
import tkinter as tk
from tkinter import END, StringVar, ttk
root = tk.Tk()
def print_text(event):
global kbEntry
textValue = kbEntry.get()
kbEntry.delete(0, END)
kbEntry.configure(state="disabled")
root.focus_set()
time.sleep(2) # Add in a delay to allow for repeat scan
print(textValue)
time.sleep(2) # Add in a delay to allow for repeat scan
kbEntry.configure(state="active")
kbEntry.delete(0, END)
kbEntry.focus_set()
kbText = StringVar()
kbEntry = ttk.Entry(root, width=10, textvariable=kbText)
kbEntry.bind("<Return>", print_text)
kbEntry.pack()
kbEntry.focus_set()
root.mainloop()
So how can I prevent the ttk.Entry() widget from accepting any input from the HID/keyboard device while my function is executing?

Since I am unable to find a way to temporarily disable the .Entry() widget programmatically to keep it from reading successive inputs, I have come up with this solution:
# test-ttk-entry2.py
import time
import tkinter as tk
from tkinter import END, StringVar, ttk
root = tk.Tk()
tempStr = "" # Global temporary string variable to trip repeated scans
def print_text(event):
global tempStr
textValue = kbEntry.get()
if tempStr == textValue:
kbEntry.delete(0, END)
print ("Duplicate scan")
return
tempStr = textValue
kbEntry.delete(0, END)
print(textValue)
time.sleep(3) # Simulate the time it takes for the function to complete
kbEntry = ttk.Entry(root, width=10)
kbEntry.bind("<Return>", print_text)
kbEntry.grid(row=0, column=0, padx=10, pady=10)
kbEntry.focus_set()
root.mainloop()

Related

Changing order of execution in python

I have recently started programming and written a fairly simple program. But stuck at a point. My program does a very simple thing that when you click a button it will say a line and shows that text on the screen. But it is speaking the text first and then displays it on the screen but I want its reverse, i.e it should first display it on the screen and then speak the text.
from tkinter import *
import pyttsx3
root = Tk()
def speak():
engine = pyttsx3.init()
say = "I am speaking."
text.insert("0.0", say)
engine.say(say)
engine.runAndWait()
text = Text(root)
text.pack()
btn = Button(root, text="speak", command=speak)
btn.pack()
root.mainloop()
Thanks in advance.
It is because engine.runAndWait() blocks the application, and so text box cannot be updated by tkinter mainloop until the speaking completed.
You can call text.update_idletasks() to force tkinter to update the text box before the speaking:
engine = pyttsx3.init() # initialize only once
def speak():
say = "I am speaking."
text.insert("0.0", say)
text.update_idletasks() # force tkinter update
engine.say(say)
engine.runAndWait()
You could delay the call to engine.say
from tkinter import *
import pyttsx3
def speak(sentence):
engine.say(sentence)
engine.runAndWait()
def display_and_speak():
sentence = "I am speaking."
text.insert("1.0", sentence)
root.after(1000, speak, sentence) # call to speak delayed 1 second
engine = pyttsx3.init()
root = Tk()
text = Text(root)
text.pack()
btn = Button(root, text="speak", command=display_and_speak)
btn.pack()
root.mainloop()

hiding tkinter window using withdraw vs wm_withdraw

What is the difference between withdraw and wm_withdraw?
import time
import tkinter as tk
def hide():
root.withdraw()
time.sleep(2)
root.deiconify()
root = tk.Tk()
tk.Button(root, text = 'hide', command = hide).pack()
root.mainloop()
When the 'hide' button is clicked, the window is hidden. It disappears from the panel (taskbar), and is not visible in the task view (simultaneous view of all open windows) for 2 seconds.
import time
import tkinter as tk
def hide():
root.wm_withdraw()
time.sleep(2)
root.deiconify()
root = tk.Tk()
tk.Button(root, text = 'hide', command = hide).pack()
root.mainloop()
Same code, but wm_withdraw instead of withdraw. Again, clicking the 'hide' button makes the both the taskbar entry and the window itself invisible for 2 seconds.
Is there any difference at all between these two? Which one should I use? Further, should I use deiconify or wm_deiconify? All four combinations (withdraw, deiconify; wm_withdraw, deiconify; withdraw, wm_deiconify; wm_withdraw, wm_deiconify) seem to do the exact same thing. Is there any application where they will do different things?
There's no difference between them - they both (withdraw and deiconify) just shortucts for wm_ counterparts.
The same applies to all functions, that interact with Window manager under Wm class.
There is no difference between withdraw and wm_withdraw. I can not specify why this was done, but here is the source of tkinter in which we have line withdraw = wm_withdraw (which makes it clear that both calls end up at the same method):
def wm_withdraw(self):
"""Withdraw this widget from the screen such that it is unmapped
and forgotten by the window manager. Re-draw it with wm_deiconify."""
return self.tk.call('wm', 'withdraw', self._w)
withdraw = wm_withdraw

Enter button doesn't trigger

i am trying to open a google search window using python and the enter button trigger doesn't work.
so if you press the enter button it should open the window but it doesn't.
i am also using tkinter for gui.
help me :( thanks
(i am using win 10)
import tkinter as tk
def keyup():
opener()
def opener():
import webbrowser
text = name.get().strip()
query=str(text)
webbrowser.open("www.google.com/search?rlz=1C1CHZL_koUS766US766&ei=rDxSWtPFOeKt0gK4_YioDg&q="+query)
def clear():
name.delete(0, 'end')
root = tk.Tk()
ler=tk.Label(root, text = "what do you want to search?",font=("Helvetica", 40))
ler.pack()
name = tk.Entry(root,width=100)
name.pack()
widget=tk.Button(root, text = 'search', command =opener,width=30,height=3)
widget.pack()
widget.bind('<Enter>', opener)
wider=tk.Button(root, text = 'clear', command =clear,width=10,height=3)
wider.pack()
root.mainloop()
how do i fix this?
You have few mistakes
for key "Enter" is event <Return>. You can also assign <Return> to root and it works even when button is not selected/focused.
root.bind('<Return>', opener)
or assign to Entry
name.bind('<Return>', opener)
and "Enter" will run opener only when Entry is focused.
command= executes function without arguments but bind() executes function with one argument so you have to define function with argument which has default value and then it will work with both.
def opener(event=None):
use http:// in url because you can use file:// to open local file. On Linux link without http:// is treated as local file.
Smaller mistakes: put all import at top to make code more readable, Entry returns string so you don't need str()
EDIT: added name.bind('<Return>', opener)
import tkinter as tk
import webbrowser
def opener(event=None):
text = name.get().strip()
webbrowser.open("http://www.google.com/search?q="+text)
def clear():
name.delete(0, 'end')
root = tk.Tk()
ler=tk.Label(root, text="what do you want to search?", font=("Helvetica", 40))
ler.pack()
name = tk.Entry(root,width=100)
name.pack()
name.bind('<Return>', opener) # added
widget=tk.Button(root, text='search', command=opener, width=30, height=3)
widget.pack()
widget.bind('<Return>', opener)
wider = tk.Button(root, text='clear', command=clear, width=10, height=3)
wider.pack()
root.mainloop()
With Tk, the event binding <Enter is used to describe the cursor moving over the space occupied by the widget (entering the space). The binding you wish to use is <Return>, which is mapped to the return (enter) key on the keyboard.
I think it should be <Return> instead of <Enter>. Also, the indentations seem incorrect.

tkinter how to open 2 or more root windows in different locations on the screen

I have written a task reminder application in python and tkinter. It schedules the reminders using the Task Scheduler. The reminders are displayed by a small GUI program in a certain location on the screen. My problem is that the reminders overlap each other. When multiple reminders appear, how can I make them appear in distinct positions? Please note that I am referring to a separate invocation of the GUI program for each reminder.
The situation is similar to opening, say, multiple copies of the calculator program. They open in distinct locations on the screen. How does it happen?
The program that creates the reminders is as follows -
from tkinter import *
import shelve
import sys
def showTask(parent, key):
parent.title('Reminder')
parent.geometry('300x100-0-40')
parent.rowconfigure(0, weight=1)
parent.columnconfigure(0, weight=1)
shelfFile = shelve.open('C:\\Users\\hp\\pypgms\\data\\tasks')
message = shelfFile[key]['name']
shelfFile.close()
Label(parent, text=message).grid(padx=20, pady=20, sticky=NW)
btn = Button(parent, text='Ok', command=parent.quit)
btn.grid(pady=5)
btn.bind('<Return>', lambda e: parent.quit())
key = sys.argv[1]
root = Tk()
showTask(root, key)
root.mainloop()
Tkinter windows have a convenient method called geometry() to define their size and position on the screen following this format:
root.geometry('250x150+300+300') # width=250, height=150, position=(300,300)
See below how you can use it to open reminder windows next to one another:
import Tkinter as tk
class MainApp():
def __init__(self, root):
self.root = root
self.reminderWindows = []
self.button = tk.Button(self.root, text="New reminder",
command=self.open_new_reminder)
self.button.pack()
def open_new_reminder(self):
reminder = tk.Toplevel(self.root)
self.reminderWindows.append(reminder)
windowNumber = len(self.reminderWindows)
reminder.geometry("100x120+{}+200".format(str(150*windowNumber)))
if __name__ == "__main__":
app = tk.Tk()
MainApp(app)
app.mainloop()

Tkinter - Changing label text via another function

I know that it's often best practice to write Tkinter GUI code using object-oriented programming (OOP), but I'm trying to keep things simple because I'm new to Python.
I have written the following code to create a simple GUI:
#!/usr/bin/python3
from tkinter import *
from tkinter import ttk
def ChangeLabelText():
MyLabel.config(text = 'You pressed the button!')
def main():
Root = Tk()
MyLabel = ttk.Label(Root, text = 'The button has not been pressed.')
MyLabel.pack()
MyButton = ttk.Button(Root, text = 'Press Me', command = ChangeLabelText)
MyButton.pack()
Root.mainloop()
if __name__ == "__main__": main()
The GUI looks like this.
I thought the text in the GUI (MyLabel) would change to "You pressed the button!" when the button is clicked, but I get the following error when I click the button:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\elsey\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1549, in __call__
return self.func(*args)
File "C:/Users/elsey/Documents/question code.py", line 6, in ChangeLabelText
MyLabel.config(text = 'You pressed the button!')
NameError: name 'MyLabel' is not defined
What am I doing wrong? Any guidance would be appreciated.
MyLabel is local to main() so the way you can not access it that way from ChangeLabelText().
If you do not want to change the design of your program, then you will need to change the definition of ChangeLabelText() like what follows:
def ChangeLabelText(m):
m.config(text = 'You pressed the button!')
And withing main() you will need to pass MyLabel as an argument to ChangeLabelText().
But again, you will have a problem if you code this command = ChangeLabelText(MyLabel) when you declare and define MyButton because the program will execute directly the body of ChangeLabelText() at the start and you will not have the desired result.
To resolve this later problem, you will have to use (and may be read about) lambda
Full program
So your program becomes:
#!/usr/bin/python3
from tkinter import *
from tkinter import ttk
def ChangeLabelText(m):
m.config(text = 'You pressed the button!')
def main():
Root = Tk()
MyLabel = ttk.Label(Root, text = 'The button has not been pressed.')
MyLabel.pack()
MyButton = ttk.Button(Root, text = 'Press Me', command = lambda: ChangeLabelText(MyLabel))
MyButton.pack()
Root.mainloop()
if __name__ == "__main__":
main()
Demo
Before clicking:
After clicking:
Are your sure you don't want to do it as a class (i think it makes the code a bit more clean as your project grows)? Here is a way to accomplish what you'e looking for:
#!/usr/bin/python3
from tkinter import *
from tkinter import ttk
class myWindow:
def __init__(self, master):
self.MyLabel = ttk.Label(root, text = 'The button has not been pressed.')
self.MyLabel.pack()
self.MyButton = ttk.Button(root, text = 'Press Me', command = self.ChangeLabelText)
self.MyButton.pack()
def ChangeLabelText(self, event=None):
self.MyLabel.config(text = 'You pressed the button!')
if __name__ == "__main__":
root = Tk()
mainWindow = myWindow(root)
root.mainloop()
In a Mac, is looks like this before pressing the button:
And when you press it:
But basically, in order to be able to change the text in a Label or a button, you need to ensure it has an active reference. In this case, we are doing it by creating the window as a class and referencing the widgets in the form self. widget_name = widget().
but I'm trying to keep things simple because I'm new to Python Hopefully this helps in understanding that classes are the simple way, otherwise you have to jump through hoops and manually keep track of many variables. Also, the Python Style Guide suggests that CamelCase is used for class names and lower_case_with_underlines for variables and functions. https://www.python.org/dev/peps/pep-0008/
from tkinter import *
from tkinter import ttk
class ChangeLabel():
def __init__(self):
root = Tk()
self.my_label = ttk.Label(root, text = 'The button has not been pressed.')
self.my_label.pack()
## not necessary to keep a reference to this button
## because it is not referenced anywhere else
ttk.Button(root, text = 'Press Me',
command = self.change_label_text).pack()
root.mainloop()
def change_label_text(self):
self.my_label.config(text = 'You pressed the button!')
if __name__ == "__main__":
CL=ChangeLabel()

Resources