I want to open a dialog box (that captures an input string from the user) when the user clicks a keyboard button. I'm using "tkinter" for the dialog box, and "pynput" for capturing keystrokes.
The dialog box works ok, if I'm placing the "simpledialog" command on the main code section. I fail to find a way to make it work only when I'm clicking a button (F2 in the following example):
import time # For the delay function
from pynput import keyboard # For catching keyboard strokes
import tkinter
from tkinter import simpledialog
# Execute functions based on the clicked key
def on_press(key):
if key == keyboard.Key.f2:
USER_INP = simpledialog.askstring(title="Test", prompt="What's your Name?:")
ROOT = tkinter.Tk()
ROOT.withdraw() # Hides tkinter's default canvas
# Assigning event to function
listener = keyboard.Listener(on_press=on_press)
# initiating listener
listener.start()
while 1 < 2:
time.sleep(.01)
Can anyone help?
Thanks
Ok, in the meantime, I've solved it by doing two things:
Moving the "simpledialog" back to the main code section (inside a while loop)
Using a global variable to indicate whether I should open the dialog box.
import time # For the delay function
from pynput import keyboard # For catching keyboard strokes
import tkinter
from tkinter import simpledialog
openBox = False
# Execute functions based on the clicked key
def on_press(key):
global openBox
if key == keyboard.Key.f2:
openBox = True
ROOT = tkinter.Tk()
ROOT.withdraw() # Hides tkinter's default canvas
# Assigning event to function
listener = keyboard.Listener(on_press=on_press)
# initiating listener
listener.start()
while 1 < 2:
if openBox:
USER_INP = simpledialog.askstring(title="Test", prompt="What's your Name?:")
print(USER_INP)
openBox = False
time.sleep(.01)
Related
My program pops up a window every time the user presses F2 (in any application).
I'm using pynput to capture the F2 button (works ok)
I'm using tkinter to create the popup window (works ok)
I'm using win32gui.SetForegroundWindow(windowHandel) to bring the tkinter window to the front and set the focus. And there is the problem.
If the python windows is selected when I press F2, everything works ok, and the tkinter window both moves to front and gets focus.
BUT - if any other window is selected when I press F2, the tkinter window does moves to the front, but it is not selected (i.e. focused).
Here is the relevant section from the code (find full code below):
while not windowFound and counter < MAX_TRIES_TO_FIND_THE_FLIPPER_WINDOW:
try:
windowHandel = win32gui.FindWindow(None, windowName)
win32gui.SetForegroundWindow(windowHandel)
except:
windowFound = False
else:
print("Success, Window found on the " + str(counter + 1) + " tries")
windowFound = True
After looking for an answer for a while, I found someone saying that this can be solved by using win32process. So I tried adding:
windowHandelID, _ = win32process.GetWindowThreadProcessId(windowHandel)
win32process.AttachThreadInput(win32api.GetCurrentThreadId(), windowHandelID, True)
win32gui.SetFocus(windowHandel)
Yet, it resulted in the same behavior.
Here below is the full (simplified, without exit conditions) code.
Try pressing F2 while pythong is focused.
And then try pressing F2 while any other window (e.g. notepad) is focused.
You'll see that in one case you can just start writing and the tkinter window will receive the input while in the other case, you'll still have to click the window.
I'd appreciate any help or suggestions.
import pyautogui # For keyboard shortcuts and moving the cursor and selecting the window
import time # For the delay function
from pynput import keyboard # For catching keyboard strokes
import tkinter # GUI
import threading # For Threading
import win32gui # For Setting Focus on the Flipper Window
import win32process
import win32api
# Resetting Variables / Settings
start_flipping_text_sequence = False
ContinueThreads = True
SearchForFlipperWindow = False
window_name = "tk"
MAX_TRIES_TO_FIND_THE_FLIPPER_WINDOW = 10
# This function runs in a separate thread
def selectFlipperWindow(windowName):
# Since the thread runs constantly, it will only start looking for the flipper window when this variable is True
global SearchForFlipperWindow
# How many loops should the program go through before it gives up on finding the window
global MAX_TRIES_TO_FIND_THE_FLIPPER_WINDOW
# While program was not ended
while True:
# This is False, unless F2 is pressed
if SearchForFlipperWindow:
# Did the program find the flipper window
windowFound = False
counter = 0
while not windowFound and counter < MAX_TRIES_TO_FIND_THE_FLIPPER_WINDOW:
try:
windowHandel = win32gui.FindWindow(None, windowName)
win32gui.SetForegroundWindow(windowHandel)
except:
windowFound = False
else:
print("Success, Window found on the " + str(counter + 1) + " tries")
windowHandelID, _ = win32process.GetWindowThreadProcessId(windowHandel)
win32process.AttachThreadInput(win32api.GetCurrentThreadId(), windowHandelID, True)
win32gui.SetFocus(windowHandel)
windowFound = True
counter += 1
time.sleep(0.1)
SearchForFlipperWindow = False
time.sleep(0.1)
# Execute functions based on the clicked key
def on_press(key):
global start_flipping_text_sequence
# If the user pressed the F2 key
if key == keyboard.Key.f2:
start_flipping_text_sequence = True
def okButton():
root.destroy()
def enter(event):
okButton()
# Assigning event to function
listener = keyboard.Listener(on_press=on_press)
# initiating listener
listener.start()
# Start a thread for searching for the flipper window
selectWindowThread = threading.Thread(target=selectFlipperWindow, args=(window_name,))
selectWindowThread.start()
while 1 == 1:
time.sleep(.05)
if start_flipping_text_sequence:
SearchForFlipperWindow = True
root = tkinter.Tk()
tk_window_input = tkinter.Entry(root, width=100)
tk_window_input.pack(padx=20)
tk_window_input.focus()
# Binds the OK button to the okButton function above
tk_window_ok = tkinter.Button(root, width=20, text="OK", command=okButton)
tk_window_ok.pack(pady=20)
# Binds the "Enter" keyboard key to the "enter" event above
tk_window_input.bind('<Return>', enter)
# the main looper of the tkinter window
# runs until root.destroy() to executed above
root.mainloop()
start_flipping_text_sequence = False
```
What you see is an intentional restriction in Windows. The restriction is described by Raymond Chen in article Foreground activation permission is like love: You can’t steal it, it has to be given to you. Remarks section of the SetForegroundWindow documentation gives more technical details about the restriction.
There are ways to be exempt from the restriction. One good way to do so is described by Raymond Chen in article Pressing a registered hotkey gives you the foreground activation love.
The following code shows one more, strange way to bypass the restriction:
kbd.press(keyboard.Key.alt)
try:
win32gui.SetForegroundWindow(windowHandel)
finally:
kbd.release(keyboard.Key.alt)
where kbd was created like this:
from pynput.keyboard import Controller
kbd = Controller()
Here is an explanation why this workaround works: link.
A good way to get rid of this workaround may be to require a user to press Alt-F2 in order to switch to your application.
Good luck with coding ;-)
I have written a Tkinter and I hope to have a Listener to monitor the Keyboard Input by Users. But when I use mainloop() to start the Tkinter, the Listener cannot work together with it, and will start until I quit Tkinter.
I have tried to add this Listener in Tkinter sub-unit, but it does not work as the same.
def initialization():
print("Starting...")
print("Start listener...")
with mouse.Listener(on_click=onMouseClick) as listener:
listener.join()
if __name__ == "__main__" :
root = tk.Tk()
root.geometry('800x80')
root.resizable(height=True, width=True)
root.overrideredirect(False)
root.title('vENC Console')
OneBtn = Button(root, command=initialization, text="One Button", width='30')
root.mainloop()
How can I let them work together? Do I need to use multi-thread?
Like practically all GUI toolkits, Tkinter is event driven. That means that Tkinter should handle mouse and keyboard events.
Widgets can register that they're interested in event by adding bindings for certain events. Tkinter depends on the flow of events to work.
Essentially, the mainloop waits for events to occur (keyboard, mouse, time-outs) and then calls any registered callbacks.
Adding another event handler separate from Tkinter will clash. On top of that, Tkinter is not thread-safe. If you must use threads you should make sure that only the main thread uses Tkinter functions and methods.
Basically, Tkinter and a Listener cannot work together.
So I would propose to you to use bind_all instead.
By using the bind_all method (read about it here) you can register a binding on the application level instead of for specific widgets.
You can write it this way
listener = mouse.Listener(on_click=onMouseClick)
listener.start() # start thread
root.mainloop()
listener.stop() # stop thread
listener.join() # wait till thread really ends its job
And don't use return False in onMouseClick because it ends listener.
tkinter has own methods to get keys and mouse events so maybe you should use them. Listener can be useful if you have to catch event when tkinter's window is minimized and it doesn't get events from system.
EDIT:
import tkinter as tk
from pynput import mouse
def onMouseClick(*args):
print(args)
root = tk.Tk()
listener = mouse.Listener(on_click=onMouseClick)
listener.start() # start thread
root.mainloop()
listener.stop() # stop thread
listener.join() # wait till thread really ends its job
EDIT:
import tkinter as tk
from pynput import mouse
def onMouseClick(*args):
print(args)
def initialization():
global listener
print("Starting...")
print("Start listener...")
listener = mouse.Listener(on_click=onMouseClick)
listener.start() # start thread
if __name__ == "__main__" :
listener = None
root = tk.Tk()
btn = tk.Button(root, command=initialization, text="One Button")
btn.pack()
root.mainloop()
# stop listener if it was created
if listener: # if listener is not None:
print("Stop listener...")
listener.stop() # stop thread
listener.join() # wait till thread really ends its job
EDIT: example with button which stop listener
import tkinter as tk
from pynput import mouse
def onMouseClick(*args):
print(args)
def on_start():
global listener
if not listener:
print("Start listener...")
listener = mouse.Listener(on_click=onMouseClick)
listener.start() # start thread
else:
print("listener already running")
def on_stop():
global listener
if listener:
print("Stop listener...")
listener.stop() # stop thread
listener.join() # wait till thread really ends its job
listener = None # to inform that listener doesn't exist
else:
print("listener not running")
if __name__ == "__main__" :
print("Starting...")
listener = None # to keep listener
root = tk.Tk()
btn = tk.Button(root, command=on_start, text="Star Mouse Listener")
btn.pack()
btn = tk.Button(root, command=on_stop, text="Stop Mouse Listener")
btn.pack()
root.mainloop()
# stop listener if it was created
if listener: # if listener is not None:
print("Stop listener...")
listener.stop() # stop thread
listener.join() # wait till thread really ends its job
I am making a tkinter code that uses button widget but when I press the button, it stays pushed until the function which is executed on button press is not completed. I want the button to be released immediately and execute the function.
Here is a code that shows a good example of the happening:
from tkinter import *
import time
root = Tk()
root.geometry('100x100+100+100') # size/position of root
def callback(): # this function will run on button press
print('Firing in 3')
time.sleep(3) # wait for 3 seconds
def main(): #function 'main'
b = Button(root, text="ᖴIᖇE", width=10,height=2, command=callback)# setting the button
b["background"] = 'red' #button color will be red
b["activebackground"] = 'yellow' #button color will be yellow for the time when the button will not be released
b.place(x=25,y=25) #placing the button
main() # using function 'main'
mainloop()
GUI programs are typically driven in a single thread, which is controlled by the "main loop" of the graphic toolkit in use. That is: a program usually set up the application, and pass the control to the toolkit, which runs a tight loop that answers all users (and network, file, etc...) events, and the only user code ever to run again are the callbacks coded during the setup phase.
At the same time, when your code is running in during a callback, it holds controls - which means the toolkit won't be able to answer to any events while your function does not return.
What has to be done is to write code that cooperates with the GUI toolkit - that is, create events that generate further callbacks, if you need things spaced in time. In the case of tkinter, this is achieved with the method .after of a widget: after that many milliseconds, the callable passed will be run. time.sleep, on the other hand, stops the single thread there, and the event loop does not run.
In your example, you can simply write:
from tkinter import *
import time
root = Tk()
root.geometry('100x100+100+100') # size/position of root
def callback(): # this function will run on button press
print('Firing in 3')
root.after(3000, realcallback)
def realcallback():
print('Firing now!')
def main(): #function 'main'
b = Button(root, text="ᖴIᖇE", width=10,height=2, command=callback)# setting the button
b["background"] = 'red' #button color will be red
b["activebackground"] = 'yellow' #button color will be yellow for the time when the button will not be released
b.place(x=25,y=25) #placing the button
main() # using function 'main'
mainloop()
You can use threading inside the function you're being blocked while pressed. This is my edit on yours:
from tkinter import *
import time
import threading
root = Tk()
root.geometry('100x100+100+100') # size/position of root
def callback(): # this function will run on button press
def callback2():
print('Firing in 3')
time.sleep(3) # wait for 3 seconds
threading.Thread(target=callback2).start()
def realcallback():
print('Firing now')
def main(): # function 'main'
b = Button(
root,
text="Fire",
width=10,
height=2,
command=callback
) # setting the button
b["background"] = 'red' # button color will be red
# button color will be yellow for the time when the button will not be released
b["activebackground"] = 'yellow'
b.place(x=25, y=25) # placing the button
main() # using function 'main'
mainloop()
I would like to add:
from tkinter import *
import time
root = Tk()
root.geometry('100x100+100+100') # size/position of root
def callback(): # this function will run on button press
root.update() #<- this works for me..
print('Firing in 3')
root.after(3000, realcallback)
def realcallback():
print('Firing now!')
def main(): #function 'main'
b = Button(root, text="ᖴIᖇE", width=10,height=2, command=callback)# setting the button
b["background"] = 'red' #button color will be red
b["activebackground"] = 'yellow' #button color will be yellow for the time when the button will not be released
b.place(x=25,y=25) #placing the button
main() # using function 'main'
mainloop()
I have an animation running in my window, which I would like to pause whenever the user drags the window, to ensure a smooth interaction.
I have tried the following:
root.bind("<ButtonPress-1>", lambda e: start_stop_animation(False))
root.bind("<B1-Motion>", lambda e: start_stop_animation(False))
root.bind("<ButtonRelease-1>", lambda e: start_stop_animation(self._is_running))
It seems that these calls do not bind to the title bar at all.
I would like to do this without removing the title bar using root.overrideredirect(True), unless there is a simple way of replacing it with a similar title bar capable of capturing these events.
Window dragging is captured by the <Configure> event, which is also triggered by window resizing.
To execute different actions at the beginning of the dragging, during the dragging and at end, you can use the after method:
Each time a <Configure> event happens, you schedule a call to your stop_drag function with a given delay, but you cancel this call each time another <Configure> event happens before the end of the delay.
import tkinter as tk
root = tk.Tk()
drag_id = ''
def dragging(event):
global drag_id
if event.widget is root: # do nothing if the event is triggered by one of root's children
if drag_id == '':
# action on drag start
print('start drag')
else:
# cancel scheduled call to stop_drag
root.after_cancel(drag_id)
print('dragging')
# schedule stop_drag
drag_id = root.after(100, stop_drag)
def stop_drag():
global drag_id
print('stop drag')
# reset drag_id to be able to detect the start of next dragging
drag_id = ''
root.bind('<Configure>', dragging)
root.mainloop()
I'm trying to do a GUI in python to control my robotic car. My question is how I do a function that determine a hold down button. I want to move the car when the button is pressed and held down and stop the car when the button is released.
from Tkinter import *
hold_down = False
root = Tk()
def button_hold(event):
hold_down=true
while hold_down== True:
print('test statement')
hold_down = root.bind('<ButtonRelease-1>',stop_motor)
def stop_motor(event):
hold_down= False
print('button released')
button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<Button-1>',button_forward)
root.mainloop()
I'm trying to simulate what I found in this answer
I try to do it in a while loop with a boolean. When the user presses the button the boolean changes to True and code enters the while loop. When user releases the button the boolean changes to False and code exits from loop but in this code the boolean stay always true no matter if I released the button or not.
Edit: I want a function to be called until a condition occurs.The function to be called is hold_down() and the condition to check is the button is released.
Update: I found a way to make it work.
Set a flag when the button is pressed, unset the flag when the button is released. There's no need for a loop since you're already running a loop (mainloop)
from Tkinter import *
running = False
root = Tk()
def start_motor(event):
global running
running = True
print("starting motor...")
def stop_motor(event):
global running
print("stopping motor...")
running = False
button = Button(root, text ="forward")
button.pack(side=LEFT)
button.bind('<ButtonPress-1>',start_motor)
button.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()
Assuming that you actually want to do something while the key is pressed, you can set up an animation loop using after. For example, to call a print statement once a second while the button is pressed you can add a function that does the print statement and then arranges for itself to be called one second later. The stop button merely needs to cancel any pending job.
Here's an example. The main difference to the original code is the addition of a move function. I also added a second button to show how the same function can be used to go forward or backward.
from Tkinter import *
running = False
root = Tk()
jobid = None
def start_motor(direction):
print("starting motor...(%s)" % direction)
move(direction)
def stop_motor():
global jobid
root.after_cancel(jobid)
print("stopping motor...")
def move(direction):
global jobid
print("Moving (%s)" % direction)
jobid = root.after(1000, move, direction)
for direction in ("forward", "backward"):
button = Button(root, text=direction)
button.pack(side=LEFT)
button.bind('<ButtonPress-1>', lambda event, direction=direction: start_motor(direction))
button.bind('<ButtonRelease-1>', lambda event: stop_motor())
root.mainloop()
You might want to try the repeatinterval option. The way it works is a button will continually fire as long as the user holds it down. The repeatinterval parameter essentially lets the program know how often it should fire the button if so. Here is a link to the explanation:
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/button.html
Search in-page for "repeatinterval".
Another name for this parameter is repeatdelay.
Building on Bryan Oakley's answer of using flags to simulate a press and hold button. The problem is that you can't have any while loops in your tkinter application to say while running move car forward.
Which is why I suggest using threads. This way you can have a while loop running in the background checking if the car should be moving foward.
from threading import Thread
from Tkinter import *
running = False
root = Tk()
def start_motor(event):
global running
print("starting motor...")
running = True
def stop_motor(event):
global running
running = False
print("stopping motor...")
def move_forward():
while True: # Thread will run infinitely in the background
if running:
print("Car is moving forward...\n")
button = Button(root, text ="forward")
button.pack(side=LEFT)
button.bind('<ButtonPress-1>',start_motor)
button.bind('<ButtonRelease-1>',stop_motor)
# Create and start the new thread
t = Thread(target = move_forward, args = ())
t.start()
root.mainloop()
Try this...
from Tkinter import *
root = Tk()
global hold_down
def button_hold(event):
hold_down = True
while hold_down:
print('test statement')
def stop_motor(event):
hold_down = False
print('button released')
button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<Button-1>',button_hold)
root.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()
# Danny Try the following code:
def stop_motor(event):
print('button released')
return False
This answer run print 'test statement' one time. The while loop run one time when the button is pressed.
# Bryan Oakley Set a flag when the button is pressed, unset the flag when the button is released. There's no need for a loop since you're already running a loop (mainloop)
from Tkinter import *
running = False
root = Tk()
def start_motor(event):
global running
running = True
print("starting motor...")
def stop_motor(event):
global running
print("stopping motor...")
running = False
button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<ButtonPress-1>',start_motor)
root.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()
This answer above stays in a infinite loop when the button is pressed.
# Joseph FarahYou might want to try the repeatinterval option. The way it works is a button will continually fire as long as the user holds it down. The repeatinterval parameter essentially lets the program know how often it should fire the button if so. Here is a link to the explanation:
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/button.html
Search in-page for "repeatinterval".
Another name for this parameter is repeatdelay.
I set the repeat interval in the parameter option for the button widget but it doesn't repeat the command.
Thanks for all the answer . Still looking to solve this problem.