How can I detect long mouse clicks with tkinter? - python

I want to detect a long mouse click using tkinter.
How can I do that?
The <Button-1> doesn't help at all.

You can create a binding for both the button press (<ButtonPress-1>) and button release (<ButtonRelease-1>). Save the time that the press happened and then compute the delay when the release happened.
Or, depending on what you mean by "detect", you can schedule a job to run when the button is clicked, and then cancel the job if the button is released before the timeout.

Here's code demonstrating how to "detect" when a long mouse click has occurred (the second thing #Bryan Oakley mentioned). It accomplished the feat by "binding" various callback functions to different tkinter events, including a virtual one I made up named '<<LongClick-1>>'.
It will display a window with a Label in it indicating the mouse's click status. If you click and hold the mouse button down long enough (2 secs), the on_long_click() event handler function will called and change the text on the Label accordingly.
import tkinter as tk
from tkinter.constants import *
from time import perf_counter as cur_time
LONG_CLICK = 2.0 # Seconds.
start_time = None
timer = None
CHECKS_PER_SECOND = 100 # Frequency that a check for a long click is made.
root = tk.Tk()
root.geometry('100x100')
def on_button_down(event):
global start_time, timer
label.config(text='Click detected')
start_time = cur_time()
timing = True
timer = check_time()
def check_time():
global timer
if (cur_time() - start_time) < LONG_CLICK:
delay = 1000 // CHECKS_PER_SECOND # Determine millisecond delay.
timer = root.after(delay, check_time) # Check again after delay.
else:
root.event_generate('<<LongClick-1>>')
root.after_cancel(timer)
timer = None
def on_button_up(event):
global timer
if timer:
root.after_cancel(timer)
timer = None
label.config(text='Waiting')
def on_long_click(event):
label.config(text='Long click detected')
label = tk.Label(root, text='Waiting')
label.pack(fill=BOTH, expand=1)
root.bind('<ButtonPress-1>', on_button_down)
root.bind('<ButtonRelease-1>', on_button_up)
root.bind('<<LongClick-1>>', on_long_click)
root.mainloop()
Although it's not particularly exciting, here's a screenshot of it running:

Related

Python: Can bring window to front but cannot set focus (win32gui.SetForegroundWindow)

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 ;-)

Python tKinter hanging

Why does in the code below button1 hang until the time.sleep(10) has completed.
I can only assume tKinter is waiting for the click event to finish before updating it's paint function.
I want on button1 click the state to change to DISABLED as in the code straight away, not when mainformbutton1press() has finished.
I have put time.sleep(10) to mimic rest of code functions - but the actual programme will be many minutes instead.
EDIT! - sleep is just there to show how tkinter hangs. My real programme has lots more code and no sleep function - and it takes a long time to process data with the hung GUI as mentioned. No more sleep suggestions please :)
import tkinter as tk
from tkinter import ttk
from tkinter.constants import DISABLED, NORMAL
import time
# ==================================================
class App:
def __init__(self, tk, my_w):
self.button1 = tk.Button(my_w, text="START", width=34, command = self.mainformbutton1press)
self.button1.grid(columnspan=3, row=6, column=1,padx=10,pady=20, ipadx=20, ipady=20)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def mainformbutton1press(self):
self.button1.config(text="PLEASE WAIT...")
self.button1['state'] = DISABLED
# DO REST OF PROCESSING
# TO MIMIC THIS:
time.sleep(10)
print("doing...")
# ==================================================
if __name__ == "__main__":
my_w = tk.Tk()
my_w.geometry("430x380")
my_w.resizable(False, False)
app = App(tk, my_w)
my_w.mainloop() # Keep the window open
Tk.mainloop is a sort of while loop. time.sleep() stops the loop for a particular period of time. That makes the window unresponsive. You might use .after function:
class App:
def __init__(self, tk, my_w):
self.my_w=my_w
....
def continue_next(self):
print("Doing")
....
def mainformbutton1press(self):
self.button1.config(text="PLEASE WAIT...")
self.button1['state'] = DISABLED
# DO REST OF PROCESSING
# TO MIMIC THIS:
self.my_w.after(10000,self.continue_next)
The only change you need to make to your code is to insert an update to your button.
The 10 second delay might need to be shortened (10 seconds is a long time to wait)
self.button1.config(text="PLEASE WAIT...")
self.button1['state'] = DISABLED
# INSERT UPDATE HERE
self.button1.update()
# DO REST OF PROCESSING
# TO MIMIC THIS:
time.sleep(1)
print("doing...")

Tkinter: Detecting a window drag event

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()

GUI Button hold down - tkinter

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.

Testing a Tkinter Button object with an if statement

I am working on a program that needs a GUI with buttons to do certain things, as is usually the case when having questions about Buttons, but I have ran into difficulties because while you can activate functions with buttons, you cannot test wether they are currently being pressed with an if statement. I know how to use check buttons and radio buttons, but I have not found anything else remotely useful. I need to be able to tell how long they are pressed, and to do things as they are being pressed that stop when they are released. I need a way of assigning a variable that will be true while you are still holding click down over the button, and false any other time, with a normal button, not one that toggles each time you press.
It's not clear to me what you're having trouble with, so I took the liberty of coding up a little GUI that times how long a button is pressed.
import tkinter as tk
import time
class ButtonTimer:
def __init__(self, root):
self.master = root
self.button = tk.Button(self.master, text="press me") # Notice I haven't assigned the button a command - we're going to bind mouse events instead of using the built in command callback.
self.button.bind('<ButtonPress>', self.press) # call 'press' method when the button is pressed
self.button.bind('<ButtonRelease>', self.release) # call 'release' method when the button is released
self.label = tk.Label(self.master)
self.startTime = time.time()
self.endTime = self.startTime
self.button.grid(row=1, column=1)
self.label.grid(row=2, column=1)
def press(self, *args):
self.startTime = time.time()
def release(self, *args):
self.endTime = time.time()
self.label.config(text="Time pressed: "+str(round(self.endTime - self.startTime, 2))+" seconds")
root = tk.Tk()
b = ButtonTimer(root)
root.mainloop()
Note: I tested this in python 2.7 then changed the import from Tkinter to tkinter. It will probably work in 3.x, but I haven't tested it with that version.

Categories

Resources