GUI Button hold down - tkinter - python

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.

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

Running a looping function and killing it when required Tkinter

I am using tkinter to build my application for testing something. I have a start button which starts a function myFunc() which is defined something like this:
def myFunc():
for i in range(20):
# do something which takes 0.5sec to execute
And I have another button which should stop this function when pressed, even if its not fully executed.
There are 2 problems here. First, the loop in myFunc() takes 0.5*20=10seconds to execute, which will freeze my application for that long. I can use threading to overcome this but the second problem is if I use someThread.join() as the command for the stop button, it will still wait until the function is executed. Also, although the GUI is not frozen while myFunc() is running, it hangs when I press stop button (because now its waiting until the function completes). So I need to kill the thread and stop executing that function AS SOON AS the stop button is pressed. This is a necessity.
I read everywhere on the internet that its not recommended to kill a thread. How do I do this elegantly? I dont want any problems or freezing. I just want the function to stop executing myFunc() the instant I press the stop button.
Full source code:
import tkinter as tk
from tkinter import ttk
import time
from threading import Thread
def myFunc():
for i in range(20):
time.sleep(0.5)
print('DONE')
def start():
someThread.start()
def stop():
someThread.join()
root.quit()
root = tk.Tk()
root.title('My app')
someThread = Thread(target=myFunc)
ttk.Button(root, text='Start', command=start).pack()
ttk.Button(root, text='Stop', command=stop).pack()
root.mainloop()
We would need to define a variable such as stopped which will tell the code if to stop the for loop or not. I have also added print('hi') and print('hi') to check if def stop() is triggered or not and if the for loop stopped or not.
Code
import tkinter as tk
from tkinter import ttk
import time
from threading import Thread
def myFunc():
global stopped
for i in range(20):
print('hi')
if stopped == True: #checks if stopped is True or False
break
time.sleep(.5)
print('DONE')
def start():
someThread.start()
def stop():
global stopped
stopped = True #makes stopped True which will stop the for loop/thread
print('triggered')
root = tk.Tk()
root.title('My app')
someThread = Thread(target=myFunc)
ttk.Button(root, text='Start', command=start).pack()
ttk.Button(root, text='Stop', command=stop).pack()
stopped = False #stopped is false so the for loop can run
root.mainloop()
hi and triggered
hi
hi
hi
triggered #pressed stop
hi
DONE

TKinter how to stop the mainloop while its running and return to "idle" state

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

Tkinter not waiting for user input inside functions

I am trying to make a program that when conditions are met, goes back to the beginning and waits. But instead of waiting for the user to push a button, it continues through the code.
I am using python 3.7.4 and Windows 10.
I assume this problem occurs because tkinter doesn't wait for user input in this situation, and instead continues through the code.
My code:
from tkinter import *
from tkinter.ttk import *
def start():
print("Start")
# Removes all widgets without destroying root
for widget in root.winfo_children():
widget.destroy()
button_1 = Button(root, text="Begin", command=begin).pack()
button_2 = Button(root, text="Do something else", command=something).pack()
# I want the program to wait here for the user to click a button
def begin():
print("\nDoing stuff")
if True:
start()
print("This should not be printed")
def something():
pass
root = Tk()
root.geometry("300x300")
btn1 = Button(root, text = "Start", command = start)
btn1.pack()
root.mainloop()
This outputs:
Start
Doing stuff
Start
This should not be printed
I want this to output:
Start
Doing stuff
Start
And then wait for the user to select a button.
If you want a function to wait for a user action, you need to explicitly tell it to wait.
Tkinter has three functions for that. One is wait_window, which will wait for a window to be destroyed. One is wait_visibility, which will wait for the visibility of a window to change. The third one is wait_variable, which waits for a specific tkinter variable to be set.
While tkinter is waiting, it's able to service other events.
In your case, the solution might look something like this:
var = BooleanVar(value=False)
def do_something():
something()
var.set(True)
button_2 = Button(root, text="Do something else", command=do_something).pack()
print("waiting...")
root.wait_variable(var)
print("done waiting.")
When you modify your code to include the above snippet, you'll notice that "waiting..." will be printed on stdout, and then nothing else will be printed until you click on the "Do something else" button and something returns, allowing the variable to be modified.

How do you close a python - tkinter window, when the window object is encapsulated?

Ok, so this is from a larger project that I am working on, so I apologize if it looks messy.
The issue is that when I click the 'Exit Program' Button on the GUI, the window remains active.
I know that the button is working as when I hit the 'x' on the top right corner the window; the program closes, so the run variable has been set back to 0, which stops the code from looping.
My question is how do I get the window to be closed automatically when the exit button is clicked, because the root.destroy() method isn't doing it.
#imports
from tkinter import *
import random, pickle, shelve
#global vars
run = 0
class Window(Frame):
#the class that manages the UI window
def __init__(self, master, screen_type = 0):
"""Initilize the frame"""
super(Window, self).__init__(master)
self.grid()
if screen_type == 1:
self.log_in_screen()
def log_in_screen(self):
#Program Exit Button
self.exit = Button(self, text = " Exit Program ", command = self.end)
self.exit.grid(row = 3, column = 0, columnspan = 2, sticky = W)
def end(self):
global run, root
run = 0
root.destroy()
#Main Loop
def main():
global run, root
run = 1
while run != 0:
root = Tk()
root.title("Budget Manager - 0.6.1")
root.geometry("400x120")
screen = Window(root, screen_type = run)
root.mainloop()
store = shelve.open("store.dat", "c")
main()
store.close()
My question is how do I get the window to be closed automatically when
the exit button is clicked, because the root.destroy() method isn't
doing it.
The answer is: call destroy() on the root window. You say it isn't working, but the code you posted seems to work, and what destroy() is documented to do is exactly what you describe you want to have happen: it will destroy the window. Your code creates new toplevel windows in a loop, so maybe it only appears to not work since the old window id destroyed and the new window is created in the blink of an eye.
It seems like what you're really asking is "how can I make clicking on the "x" do the same as clicking on the "Exit program" button?". If that is the case, the answer is very straight-forward, even with your unconventional code that creates root windows in a loop.
To get the "x" button on the window frame to call a function instead of destroying the window, use the wm_protocol method with the "WM_DELETE_WINDOW" constant and the function you want it to call.
For example:
while run != 0:
root = Tk()
...
screen = Window(root, screen_type = run)
root.wm_protocol("WM_DELETE_WINDOW", screen.end)
...
root.mainloop()
you could do something like the below. I've used it in my own projects etcand it works.
Mywin =tkinter.Tk()
def exit():
Mywin.quit()
# etc.

Categories

Resources