Reputation: 64854
I'm using the slider to update my visualization, but the command updateValue is sent everytime I move the slider thumb, even for intermediate values.
Instead I want to trigger it only when I release the mouse button and the interaction is complete.
self.slider = tk.Scale(self.leftFrame, from_=0, to=256, orient=tk.HORIZONTAL, command=updateValue)
How can I trigger the function only once, when the interaction is ended ?
Upvotes: 15
Views: 31370
Reputation: 3599
debounced tk scale with label and value
import tkinter as tk
from tkinter import ttk
class tk_scale_debounced(ttk.Frame):
"""
scale with after_change event
aka: debounced scale
example:
def after_change(key, value):
print(key, value)
root = tk.Tk()
s = tk_scale_debounced(root, "some label", after_change, "x", from_=-10, to=10)
s.pack()
"""
# current value
value = None
# debounce timer for keyboard input
_change_key_timer = None
def __init__(self, parent, label, after_change, key=None, **scale_kwargs):
"""
example scale_kwargs:
from_=-10, to=10, orient='horizontal'
"""
super().__init__(parent)
self._after_change = after_change
self._label = label
self._key = key or label
self.value = tk.DoubleVar()
self.columnconfigure(0, weight=2)
self.columnconfigure(1, weight=1)
self.columnconfigure(2, weight=100)
# label
self._scale_label = ttk.Label(self, text=self._label)
self._scale_label.grid(column=0, row=0, sticky='w')
# value
self._value_label = ttk.Label(self, text=self._format_value())
self._value_label.grid(column=1, row=0, sticky='w')
# scale
self._scale = ttk.Scale(self, command=self._scale_change_live, variable=self.value, **scale_kwargs)
self._scale.grid(column=2, row=0, columnspan=4, sticky='we')
# mouse
self._scale.bind("<ButtonRelease-1>", self._scale_change_done)
# keyboard
self._scale.bind("<KeyRelease>", self._scale_change_key)
def _format_value(self):
return '{: .2f}'.format(self.value.get())
def _scale_change_live(self, event):
self._value_label.configure(text=self._format_value())
def _scale_change_done(self, event=None):
self._after_change(self._key, self.value.get())
def _scale_change_key(self, event):
if self._change_key_timer:
self.after_cancel(self._change_key_timer)
t = 1000
self._change_key_timer = self.after(t, self._scale_change_done)
Upvotes: 0
Reputation: 723
This is quite an ancient question now, but in case anyone stumbles upon this particular problem just use the bind() function and the "ButtonRelease-1" event like so:
import Tkinter as tk
class App:
def __init__(self):
self.root = tk.Tk()
self.slider = tk.Scale(self.root, from_=0, to=256,
orient="horizontal")
self.slider.bind("<ButtonRelease-1>", self.updateValue)
self.slider.pack()
self.root.mainloop()
def updateValue(self, event):
print self.slider.get()
app=App()
Hope this helps anyone!
Upvotes: 58
Reputation: 386325
You can't.
What you can do instead is have your command delay any real work for a short period of time using 'after'. Each time your command is called, cancel any pending work and reschedule the work. Depending on what your actual requirements are, a half second delay might be sufficient.
Another choice is to not use the built-in command feature and instead use custom bindings. This can be a lot of work to get exactly right, but if you really need fine grained control you can do it. Don't forget that one can interact with the widget using the keyboard in addition to the mouse.
Here's a short example showing how to schedule the work to be done in half a second:
import Tkinter as tk
#create window & frames
class App:
def __init__(self):
self.root = tk.Tk()
self._job = None
self.slider = tk.Scale(self.root, from_=0, to=256,
orient="horizontal",
command=self.updateValue)
self.slider.pack()
self.root.mainloop()
def updateValue(self, event):
if self._job:
self.root.after_cancel(self._job)
self._job = self.root.after(500, self._do_something)
def _do_something(self):
self._job = None
print "new value:", self.slider.get()
app=App()
Upvotes: 9