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()
Related
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)
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:
I have a tkinter application where user have many tasks to do. Most of the tasks call themselves recursively with tkinter after() method since every call I must check some pin states and other things, I figured that calling the function recursively every second will be fine. When the requirmenet is met ( some ping is toggled as required by the task), the task will complete and the function will go to mainloop "idle" state where I wait again for the user input.
Everything works fine except that I would like to implement one very important feature. Whenever user starts a task, I must have a emergency reset button, if pressed would restart the mainloop and immediately return to the "idle" state without the need to complete task. However, when the mainloop is running, I am not not sure how can I interrupt the task and stop it. I have made a simple "test" program to show exactly what I mean:
import tkinter as tk
from tkinter import Button,Entry,Canvas,Label,ttk
class Application(tk.Frame):
def __init__(self,master=None):
self.master = master
self.button_counter = 0
def Create_canvas(self,canvas_width,canvas_height):
global canvas#described as global because used outside class
canvas = tk.Canvas(master,bg='papaya whip',width=canvas_width,height=canvas_height)
def Application_Intro(self):
print("starting new app")
restart_program_button = tk.Button(canvas, text="Restart_program",font='Helvetica 12 bold', width=20, height=2,
command =self.Restart)
start_program_button = tk.Button(canvas, text="Start_program",font='Helvetica 12 bold', width=20, height=2,
command =self.Start_program)
increment_button = tk.Button(canvas, text="increment_button",font='Helvetica 12 bold', width=20, height=2,
command =self.increment_button)
canvas.create_text(960,20,text="MY PROGRAM",font='Helvetica 16 bold')
canvas.create_window(710,300,window = restart_program_button)
canvas.create_window(710,500,window = start_program_button)
canvas.create_window(710,100,window = increment_button)
canvas.pack()
master.mainloop()
def increment_button(self):
print("BUTTON INCREMENTED")
self.button_counter = self.button_counter +1 # increment button pressed everytime is pressed
def Start_program(self):
print("Program started")
if(self.button_counter > 1):
print("Task complete")
self.button_counter = 0
return
else:
master.after(1000,self.Start_program)
def Restart(self):# IF TASK STARTED SET RESTART =1, IF NOT restart devices and refresh app
print("HERE I WANT TO INTERRUPT START PROGRAM AND RETURN TO IDLE STATE")
print("REFRESH GUI ELEMENTS, DESTROY ANY WIDGETS IF CREATED")
print("RESET THE GLOBAL VARIABLE VALUES")
#master.mainloop()
#WHAT TO DO IN THIS FUNCTION TO GO BACK TO INITIAL MAINLOOP STATE??
return
master = tk.Tk()
app = Application(master=master)
app.Create_canvas(1920,1080)
app.Application_Intro()
The program above will create a simple tkinter program with 3 buttons.
Button at the top will increment button counter variable
Button in the middle is supposed to interrupt whatever task is running and return to "idle"state immediately
Button at the bottom will start a task. A task involves a person pressing Button at the top 2 times to increment button counter >1. After button incremented the counter 2 times, i return out of the recursive function which returns me back to mainloop "idle" state.
Now, as I mentioned, I must implement a technique where a user can return to "idle" state of the mainloop even if he didint press button to increment counter. It may be due to whatever emergency reason but I must be able to stop the current task and cancel it!
Can someone suggest me how should I do that since I could not find a neat way to do that.. It is also very important for me that this "reset" function will work for any possible task.
For example in reset function I could just set the button counter to any values > 1, and then the next call to task would trigger a reset, but this may not be the case for other tasks I will have that will not have any button counter involved.
Appreciate any help and suggestions
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.
Is it possible to automatically activate the main window of a tkinter app? I am using Yosemite on a Mac. When the window comes up, the title bar is grayed out, and I have to click on the window before it will respond to events. The Tk manual says that event generate, "Generates a window event and arranges for it to be processed just as if it had come from the window system." I tried generating a <Button-1> event, but it had no effect. The manual goes on to say, "Certain events, such as key events, require that the window has focus to receive the event properly." I tried focus_force, but it didn't work either.
Is it possible to do what I want? Is this a Mac peculiarity? In the code below, the text changes as the mouse cursor enters and leaves the label, but the app is unresponsive until you click on the window.
import tkinter as tk
root = tk.Tk()
def visit(event):
kilroy['text'] = 'Kilroy was here.'
def gone(event):
kilroy['text'] = 'Kilroy has left the building'
def startup():
root.focus_force()
root.event_generate('<Button-1>')
frame = tk.Frame(root, width=500,height=100)
kilroy = tk.Label(frame, text="Kilroy hasn't been here.", width = 50)
kilroy.grid(row=0,column=0)
frame.grid(row=0,column=0)
kilroy.grid_propagate(0)
frame.grid_propagate(0)
kilroy.bind('<Enter>', visit)
kilroy.bind('<Leave>', gone)
root.after(100,startup)
root.mainloop()