Python Tkinter: Call function after long press spacebar for 1 second - python

I would like to create a function for my GUI using key-press events. My goal is to allow a function to be called if user presses the spacebar for more than 1 second, and abort the function if key releases within this 1 second.
How do I do this?
Feel free to edit my example:
from Tkinter import Tk, Frame
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.parent.geometry('%dx%d+%d+%d' % (800, 300, 0, 0))
self.parent.resizable(0, 0)
self.pack(expand = True)
self.parent.bind('<Control-s>', self.printer)
def printer(self, event = None):
print "Hello World"
def main():
root = Tk()
Application(root)
root.mainloop()
if __name__ == '__main__':
main()
Python 2.7, Linux
Reference: http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm

This is either going to be really easy, or really hard. Which it is depends on several factors. Conceptually, the solution is simple:
on the press of the space key, use after to schedule a job to run in the future
on release of the key, cancel the job.
Where it gets hard is that some systems, when you keep a key pressed, will continue to auto-repeat either the keypress (so you'll get a stream of presses in a row, without a release) or a pair of presses and releases (you'll get a steady stream of press/release events). This might be done at the keyboard hardware level, or it might be done by the OS.

I know that this is an old question, but I was able to implement a solution with a bit of trial-and-error, and thought I'd post it here in case it helps anybody else. (Note that I've only tested this with Python 3.6 and Windows, but I've got a similar solution working with long mouse button presses on Linux, so I'm assuming this transfers).
from tkinter import Tk, Frame
class Application(Frame):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.parent.geometry('%dx%d+%d+%d' % (800, 300, 0, 0))
self.parent.resizable(0, 0)
self.pack(expand = True)
self._short_press = None
self.parent.bind('<KeyPress-space>', self.on_press_space)
self.parent.bind('<KeyRelease-space>', self.on_release_space)
# set timer for long press
def on_press_space(self, event):
if self._short_press is None: # only set timer if a key press is not ongoing
self._short_press = True
self._do_space_longpress = self.after(1000, self.do_space_longpress)
# if it was a short press, cancel event. If it was a long press, event already happened
def on_release_space(self, event):
if self._short_press:
self.cancel_do_space_longpress()
# do long press action
def do_space_longpress(self):
self.cancel_do_space_longpress() # cancel any outstanding timers, if they exist
print('long press')
# cancels long press events
def cancel_do_space_longpress(self):
self._short_press = None
if self._do_space_longpress:
self.parent.after_cancel(self._do_space_longpress)
def main():
root = Tk()
Application(root)
root.mainloop()
if __name__ == '__main__':
main()

Related

How to have python initiate a command the moment you push a button in the GUI

I researched this topic and could not find exactly what I needed to get the results I needed. I am fairly new to Python and new to Tkinter, so any help would be greatly appreciated.
I am using Python code through the Raspberry Pi to control a Arduino Uno, which will ultimately control servos to control a remote control car.
I am using the GUI through Raspberry Pi and using Tkinter commands such as button to interact through the GUI. The buttons are four, which are 'Up', 'Down' ,'left' and 'right'.
Right now I am using the Serial Monitor through the Sketch Arduino program to debug the code.
Although the code is working and is sending a signal to the Arduino, it is not working as I would have hoped. Right now it is initiating the command after you let go of the button and only for a moment.
What I need: For the signal to be sent to the Arduino the moment the button is pressed, and continue to send the signal as long as the button is being pressed and cut off as soon as the button is released.
Here is the code:
from Tkinter import *
import serial
running = True
ser = serial.Serial('/dev/ttyUSB0')
class Application(Frame):
"""Defining the remote control buttons"""
def __init__(self,master):
"""Initialize the frame"""
Frame.__init__(self,master)
self.grid() # How many times has the user clicked the button
self.create_widgets()
def create_widgets(self):
"""Creates four buttons that move the servos"""
#Create the 'up' button
self.bttn1 = ButtonPress(self)
self.bttn1["text"] = "Up"
self.bttn1["command"] = self.up
self.bttn1.grid()
#Create the 'down' button
self.bttn2 = Button(self)
self.bttn2.grid()
self.bttn2["text"] = "Down"
self.bttn2["command"] = self.down
#Create the 'left' button
self.bttn3 = Button(self)
self.bttn3.grid()
self.bttn3["text"] = "Left"
self.bttn3["command"] = self.left
#create the 'right' button
self.bttn4 = Button(self)
self.bttn4.grid()
self.bttn4["text"] = "Right"
self.bttn4["command"] = self.right
#Tells python to send the data to Arduino via serial
def right(self):
ser.write('3')
def left(self):
ser.write('4')
def up(self):
ser.write('1')
def down(self):
ser.write('2')
#Main
root = Tk()
root.title("Remote control")
root.geometry("250x250")
app = Application(root)
root.mainloop()
Buttons in tkinter by default don't fire until you release the mouse button. However, you can set bindings on both a button press and a button release. You can make bindings that set a flag on press and unset it on release, and then create a function that sends data while the flag is set.
There are several ways to implement this. One way is to have a function that runs periodically, and sends the key if the button is pressed and doesn't if it's unset. Another way is to only have it run while the key is pressed. For simplicity's sake, I'll show the first method.
In this example I'll use the flag not only to determine if data should be sent, but also to define what data is to be sent. That is by no means a requirement, it just cuts down a little on how much code you have to write.
def send_data(self):
if self.char is not None:
ser.write(self.char)
# run again in 100ms. Here is where you control how fast
# to send data. The first parameter to after is a number
# of milliseconds to wait before calling the function
self.job = self.after(100, self.send_data)
The above code will run approximately every 100 milliseconds, sending whatever character is set (or nothing at all). At any time you can cancel this auto-running task with self.after_cancel(self.job).
The buttons need to simply set or unset self.char. While it's unusual to use bindings rather than the command attribute for buttons, in this case that's exactly what you want.
Since the only difference between the buttons is what they send, all of the buttons can call the same function, passing in the character to be sent:
self.button1 = Button(self, text="Up")
self.button1.bind("<ButtonPress-1>", lambda: self.set_char('1'))
self.button1.bind("<ButtonRelease-1>", lambda: self.set_char(None))
def set_char(self, char):
self.char = char
Finally, you need to make sure you call send_data exactly once, and it will then run forever (or until cancelled with after_cancel)
class Application(Frame):
"""Defining the remote control buttons"""
def __init__(self,master):
...
self.char = None
self.send_data()

Gtk widget shows up with delay

Using python3 and gi.repository, I want to have a Gtk.HeaderBar with a Gtk.Button that is replaced by a Gtk.Spinner as soon as you click on it. After a calculation the button should appear again.
Here is an example of how I think it should work but the Gtk.Spinner only shows up after the calculation (in this example sleep) for a very short time. How can I achieve that the spinner shows up for the whole calculation (or sleep)?
from gi.repository import Gtk
import time
class window:
def __init__(self):
self.w = Gtk.Window()
self.button = Gtk.Button('x')
self.button.connect('clicked', self.on_button_clicked)
self.spinner = Gtk.Spinner()
self.hb = Gtk.HeaderBar()
self.hb.props.show_close_button = True
self.hb.pack_start(self.button)
self.w.set_titlebar(self.hb)
self.w.connect('delete-event', Gtk.main_quit)
self.w.show_all()
def on_button_clicked(self, widget):
self.button.hide()
self.hb.pack_start(self.spinner)
self.spinner.show()
self.spinner.start()
time.sleep(5)
self.spinner.stop()
self.hb.remove(self.spinner)
self.button.show()
if __name__ == '__main__':
w = window()
Gtk.main()
GTK+ is an event driven system where the mainloop should be left free to update the UI and everything that takes time (like reading from a file, making a network connection, long calculations) should happen asynchronously.
In your case this would look something like this:
def on_button_clicked(self, widget):
self.button.hide()
self.spinner.show()
self.spinner.start()
GLib.timeout_add_seconds (5, self.processing_finished)
def processing_finished(self):
self.spinner.stop()
self.spinner.hide()
self.button.show()
Note that I removed the pack and remove calls: do those in __init__(). You'll want from gi.repository import GLib in there as well.
This way the main loop is free to update the UI as often as it wants. If you really want to use a blocking call like sleep(), then you'll need to do that in another thread, but my suggestion is to use libraries that are asychronous like that timeout_add_seconds() call.
The problem is time.sleep(): it is a blocking function.
def on_button_clicked(self, widget):
self.button.hide()
self.hb.pack_start(self.spinner)
self.spinner.show()
self.spinner.start()
t = time.time()
while time.time() - t < 5:
Gtk.main_iteration()
self.spinner.stop()
self.hb.remove(self.spinner)
self.button.show()
I think that's what you expect.
Edit: You may put a time.sleep(.1) inside while loop, for cpu saving, but don't forget Gtk.main_iteration(): that is the function that exit from while loop to main loop (show spinner, progress bar and so on).

Pause a python tkinter script indefinetly

I have a game in Tkinter in which I want to implement a pause option. I want to bind the key p to stop the script. I tried using time.sleep, but I want to pause the game until the user presses u. I have tried:
def pause(self, event):
self.paused = True
while self.paused:
time.sleep(0.000001)
def unpause(self, event):
self.paused = False
However, this crashes the program and doesn't un-pause.
What is going wrong and how can I fix it?
while creates a loop which makes the GUI loop unresponsive to anything--including KeyPress bindings. Calling just time.sleep(9999999) in the pause method would do the same thing. I'm not sure how the rest of your program is structured, but you should look into the after() method for an easy way to add start and stop features. Here's a simple example:
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.text = Text(self)
self.text.pack()
self._unpause(None) # start the method
self.bind_all('<p>', self._pause)
self.bind_all('<u>', self._unpause)
def _unpause(self, event):
'''this method is the running portion. after its
called, it continues to call itself with the after
method until it is cancelled'''
self.text.insert(END, 'hello ')
self.loop = self.after(100, self._unpause, None)
def _pause(self, event):
'''if the loop is running, use the after_cancel
method to end it'''
if self.loop is not None:
self.after_cancel(self.loop)
root = Tk()
App(root).pack()
mainloop()

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.

python GTK: Dialog with information that app is closing

I have a problem
My application on close has to logout from web application. It's take some time. I want to inform user about it with " logging out" information
class Belt(gtk.Window):
def __init__(self):
super(Belt, self).__init__()
self.connect("destroy", self.destroy)
def destroy(self, widget, data=None):
if self.isLogged:
md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ico, gtk.BUTTONS_NONE, txt)
md.showall()
self.send('users/logout.json', {}, False, False)
gtk.main_quit()
def main(self):
if self.iniError is False:
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
if __name__ == "__main__":
app = Belt()
app.main()
When I try to show dialog in destroy method only window does appear, without icon and text.
I want to, that this dialog have no confirm button, just the information, and dialog have to be destroy with all app.
Any ideas?
Sorry for my poor English
Basically, GTK has to have the chance to work through the event queue all the time. If some other processing takes a long time and the event queue is not processed in the meantime, your application will become unresponsive. This is usually not what you want, because it may result in your windows not being updated, remaining grey, having strange artefacts, or other kinds of visible glitches. It may even cause your window system to grey the window out and offer to kill the presumably frozen application.
The solutution is to make sure the event queue is being processed. There are two primary ways to do this. If the part that takes long consists of many incremental steps, you can periodically process the queue yourself:
def this_takes_really_long():
for _ in range(10000):
do_some_more_work()
while gtk.events_pending():
gtk.main_iteration()
In the general case, you'll have to resort to some kind of asynchronous processing. The typical way is to put the blocking part into its own thread, and then signal back to the main thread (which sits in the main loop) via idle callbacks. In your code, it might look something like this:
from threading import Thread
import gtk, gobject
class Belt(gtk.Window):
def __init__(self):
super(Belt, self).__init__()
self.connect("destroy", self.destroy)
self.show_all()
self.isLogged = True
self.iniError = False
def destroy(self, widget, data=None):
if self.isLogged:
md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 0, gtk.BUTTONS_NONE, "Text")
md.show_all()
Thread(target=self._this_takes_very_long).start()
def main(self):
if self.iniError is False:
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
def _this_takes_very_long(self):
self.send('users/logout.json', {}, False, False)
gobject.idle_add(gtk.main_quit)
if __name__ == "__main__":
app = Belt()
app.main()

Categories

Resources