You can create bindings for the <ButtonPress> and <ButtonRelease> events independently.
A good starting point for learning about events and bindings is here: http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
Here's a working example:
import Tkinter as tk
import time
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.button = tk.Button(self, text="Press me!")
self.text = tk.Text(self, width=40, height=6)
self.vsb = tk.Scrollbar(self, command=self.text.yview)
self.text.configure(yscrollcommand=self.vsb.set)
self.button.pack(side="top")
self.vsb.pack(side="right", fill="y")
self.text.pack(side="bottom", fill="x")
self.button.bind("<ButtonPress>", self.on_press)
self.button.bind("<ButtonRelease>", self.on_release)
def on_press(self, event):
self.log("button was pressed")
def on_release(self, event):
self.log("button was released")
def log(self, message):
now = time.strftime("%I:%M:%S", time.localtime())
self.text.insert("end", now + " " + message.strip() + "\n")
self.text.see("end")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Answer from Bryan Oakley on Stack Overflowpython - Tkinter interpreting the long press of the button as many press and release events - Stack Overflow
Python Tkinter Button Release event movement with pressed button problem - Stack Overflow
python - Tkinter KeyPress and KeyRelease events - Stack Overflow
python - Tkinter, when button pressed (not released), do command - Stack Overflow
Videos
Ok some more research found this helpful post which shows this is occuring because of X's autorepeat behaviour. You can disable this by using
os.system('xset r off')
and then reset it using "on" at the end of your script. The problem is this is global behaviour - not just my script - which isn't great so I'm hoping someone can come up with a better way.
Well, this is a bit late now, but I have a solution that works. It's not great, but it does not require os.system overwriting system settings, which is nice.
Basically, I make a class that records the timing of key presses. I say that a key is down when it has been pressed in the last short amount of time (here, .1ms). To get a press, it is easy enough: if the key is not registered as pressed, trigger the event. For releases, the logic is harder: if there is a suspected release event, set a timer for a short time (here, .1s) and then check to make sure the key is not down.
Once you have validated a press or release, call the on_key_press or on_key_release methods in your code. As for those, just implement them the way you originally wanted them
I know this is not perfect, but I hope it helps!!
Here is the code:
Where you are initializing keypress events:
key_tracker = KeyTracker()
window.bind_all('<KeyPress>', key_tracker.report_key_press)
window.bind_all('<KeyRelease>', key_tracker.report_key_release)
key_tracker.track('space')
Here is my custom KeyTracker class:
class KeyTracker():
key = ''
last_press_time = 0
last_release_time = 0
def track(self, key):
self.key = key
def is_pressed(self):
return time.time() - self.last_press_time < .1
def report_key_press(self, event):
if event.keysym == self.key:
if not self.is_pressed():
on_key_press(event)
self.last_press_time = time.time()
def report_key_release(self, event):
if event.keysym == self.key:
timer = threading.Timer(.1, self.report_key_release_callback, args=[event])
timer.start()
def report_key_release_callback(self, event):
if not self.is_pressed():
on_key_release(event)
self.last_release_time = time.time()
Hello,
So I have a (relatively) slow callback function which needs to be called only when the UI has absolutely settled on a set of values, while still being a fairly reactive UI (meaning, I'd rather not have an "Apply" button).... The function runs fast enough, as long as it's not called several times in a row.
I would like to be able to run the callback whenever the following happens:
A slider (aka Scale widget) has changed and stopped moving
Double-clicking on the slider to reset to a default value
Of course, I got double-click working, by binding <Double-Button-1> (which actually runs a separate callback, to look up the default value & adjust the widget, before running the normal callback).
Then, without thinking much, I tried binding <ButtonRelease-1> to handle any other change.
This naturally results in double-clicks triggering two button releases, and thus, 2 extra calls to my callback.
My own digging lead to trying the `.trace` method on my Tk Variables, but that just made it far worse, since it tracks changes in real time.
So with that, the only question I know how to ask with my current knowledge of Tkinter & UI design in general, is in the title. However, more accurately, in what way am I approaching this problem wrong?
Thanks for reading!!
p.s. Here's a first effort of boiling down some of my code (it obviously will not run on its own, but should convey every part of my current approach/problem...and Note: I'm using a modular toolbar, so it adds to the complexity of reading it a bit...sorry):
def slider_changed(*args, **kwargs):
"""Called on button release; duplicated by double-click, but should only run when
UI has settled.
"""
update_img(**get_params())
def slider_reset(event):
"""Called on double click...Resets slider to default, then runs slider_changed()
"""
defval = toolbar_def[event.widget.cget("label")][3]
# Not sure why I did this, instead of just calling them directly
tkroot.after(1, lambda: event.widget.set(defval))
tkroot.after(10, lambda: slider_changed(event))
toolbar = tk.Frame(tkroot, background = root_bg)
toolbar.pack()
toolbar_def = {
# Label : 0min , 1max , 2res , 3def, 4param , 5type
"Offset X" : (-imgsize, imgsize, 1 , 0 , "offsx" , tk.IntVar ),
"Offset Y" : (-imgsize, imgsize, 1 , 0 , "offsy" , tk.IntVar ),
"Scale X" : (-1 , 1 , 0.001, 0.01, "sclx" , tk.DoubleVar ),
"Scale Y" : (-1 , 1 , 0.001, 0.01, "scly" , tk.DoubleVar ),
"Octaves" : ( 1 , 10 , 1 , 4 , "octaves" , tk.IntVar ),
"Persistence" : (-0.99 , 1 , 0.01 , 0.5 , "persistence", tk.DoubleVar ),
"Lacunarity" : (-10 , 10 , 0.1 , 2 , "lacunarity" , tk.DoubleVar ),
"Threshold" : ( 0 , 255 , 1 , 128 , "threshold" , tk.IntVar ),
"Seed" : (-262144 , 262144 , 1 , 0 , "seed" , tk.IntVar ),
"Tile" : (0 , 1 , 1 , 1 , "tile" , tk.BooleanVar)
}
for label, data in toolbar_def.items():
frame = tk.Frame(toolbar)
tkvar = data[5](value = data[3])
# Tried using tkvar.trace() here but it tracks every change close to real time,
# which is much worse
slider = tk.Scale(
frame,
label = label,
from_ = data[0],
to = data[1],
resolution = data[2],
orient = "horizontal",
length = 512,
variable = tkvar
)
slider.bind("<Double-Button-1>", slider_reset) # No good - triggers 2 releases
slider.bind("<ButtonRelease-1>", slider_changed)
slider.pack(side = "left", ipady = 21)
frame.pack()So you can set the relief of the button using its config, this makes it look like it is pressed.
def save(self):
self.button0.config(relief=SUNKEN)
# if you also want to disable it do:
# self.button0.config(state=tk.DISABLED)
#...
def stop(self):
self.button0.config(relief=RAISED)
# if it was disabled above, then here do:
# self.button0.config(state=tk.ACTIVE)
#...
EDIT
This doesn't work on Mac OSx apparently. This link shows how in should look: http://www.tutorialspoint.com/python/tk_relief.htm
If Tkinter.Button doesn't allow to configure its relief property on your system then you could try ttk.Button-based code instead:
try:
import Tkinter as tk
import ttk
except ImportError: # Python 3
import tkinter as tk
import tkinter.ttk as ttk
SUNKABLE_BUTTON = 'SunkableButton.TButton'
root = tk.Tk()
root.geometry("400x300")
style = ttk.Style()
def start():
button.state(['pressed', 'disabled'])
style.configure(SUNKABLE_BUTTON, relief=tk.SUNKEN, foreground='green')
def stop():
button.state(['!pressed', '!disabled'])
style.configure(SUNKABLE_BUTTON, relief=tk.RAISED, foreground='red')
button = ttk.Button(root, text ="Start", command=start, style=SUNKABLE_BUTTON)
button.pack(fill=tk.BOTH, expand=True)
ttk.Button(root, text="Stop", command=stop).pack(fill=tk.BOTH, expand=True)
root.mainloop()
You already had your event function. Just correct your code to:
"""Create Submit Button"""
self.submitButton = Button(master, command=self.buttonClick, text="Submit")
self.submitButton.grid()
def buttonClick(self):
""" handle button click event and output text from entry area"""
print('hello') # do here whatever you want
This is the same as in @Freak's answer except for the buttonClick() method is now outside the class __init__ method. The advantage is that in this way you can call the action programmatically. This is the conventional way in OOP-coded GUI's.
You should specify a handler, or a function, that is called when you click the Button. You can do this my assigning the name (not calling the function) of the function to the property command of your Button.
For example:
self.submitButton = Button(self.buttonClick, text="Submit", command=buttonClick)
Note the absence of () when assigning buttonClick as the command property of self.submitButton.
Note that you don't need the second parameter called event in your handler/function buttonClick().