Reputation: 1217
I'm using a spinbox to control the size of fonts in real time, for a zooming effect. A spinbox widget can generate a lot of events. As long as you hold down a direction key on the keyboard or click and hold down one the direction widget arrow icons, events get generated by the spinbox.
The problem is I'm getting to many events and this is making the zoom effect hang. I have setup a demo showing this using two different spinboxes, a tk.Spinbox and a ttk.Spinbox. With th tk.Spinbox you can limit the rate using "repeatdelay and repeatinterval" well this does work it only works when you click one of the arrow buttons in the spinbox. If you press the up or down keys though the "repeatdelay and repeatinterval" has no effect. As for the ttk.Spinbox it doesn't except the parameters "repeatdelay and repeatinterval" so it has no effect on it. How can I limit the repeat rate for both type of spinboxes?
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.rowconfigure(990, weight=1)
self.columnconfigure(0, weight=1)
self.title('Timed Events Demo')
self.geometry('420x200+20+20')
tk_spn = tk.Spinbox(
self,
value=0,
from_=0, to=1000,
repeatdelay=500,
repeatinterval=500,
values=list(range(0, 1000))
)
tk_spn.grid(row=0, pady=5)
tk_spn = ttk.Spinbox(
self,
from_=0, to=1000,
value=0,
values=list(range(0, 1000))
)
tk_spn.grid(row=1, pady=5)
self.cnt = 0
def test(e):
print(self.cnt, e)
tk_spn.bind('<<Increment>>', test)
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()
Upvotes: 1
Views: 673
Reputation: 16169
I used similar solutions for both kinds of spinboxes but the implementation is a bit different since they don't use the same events. The idea is
to create a Spinbox class with an _increment_lock
attribute which is set to True
when the Spinbox is incremented and after a delay is set back to False
. Then, the events that increment the spinbox are bound to a method that checks _increment_lock
before actually performing the increment. The principle is the same for the decrement.
For the tk.Spinbox
, I used the bindings to the <Up>
and <Down>
arrows to implement the above solution while I used the bindings to <<Increment>>
and <<Decrement>>
.
Here is the code:
import tkinter as tk
import tkinter.ttk as ttk
class MySpinbox(tk.Spinbox):
def __init__(self, master=None, delay=500, **kwargs):
kwargs.setdefault('repeatdelay', delay)
kwargs.setdefault('repeatinterval', delay)
tk.Spinbox.__init__(self, master, **kwargs)
self.delay = delay # repeatdelay in ms
self.bind('<Up>', self._on_increment)
self.bind('<Down>', self._on_decrement)
self._increment_lock = False
self._decrement_lock = False
def _unlock_increment(self):
self._increment_lock = False
def _on_increment(self, event):
if self._increment_lock:
return "break" # stop the increment
else:
self._increment_lock = True
self.after(self.delay, self._unlock_increment)
def _unlock_decrement(self):
self._decrement_lock = False
def _on_decrement(self, event):
if self._decrement_lock:
return "break" # stop the increment
else:
self._decrement_lock = True
self.after(self.delay, self._unlock_decrement)
class MyTtkSpinbox(ttk.Spinbox):
def __init__(self, master=None, delay=500, **kwargs):
ttk.Spinbox.__init__(self, master, **kwargs)
self.delay = delay # repeatdelay in ms
self.bind('<<Increment>>', self._on_increment)
self.bind('<<Decrement>>', self._on_decrement)
self._increment_lock = False
self._decrement_lock = False
def _unlock_increment(self):
self._increment_lock = False
def _on_increment(self, event):
if self._increment_lock:
return "break" # stop the increment
else:
# generate a virtual event corresponding to when the spinbox
# is actually incremented
self.event_generate('<<ActualIncrement>>')
self._increment_lock = True
self.after(self.delay, self._unlock_increment)
def _unlock_decrement(self):
self._decrement_lock = False
def _on_decrement(self, event):
if self._decrement_lock:
return "break" # stop the increment
else:
# generate a virtual event corresponding to when the spinbox
# is actually decremented
self.event_generate('<<ActualDecrement>>')
self._decrement_lock = True
self.after(self.delay, self._unlock_decrement)
class App(tk.Tk):
def __init__(self):
super().__init__()
self.rowconfigure(990, weight=1)
self.columnconfigure(0, weight=1)
self.title('Timed Events Demo')
self.geometry('420x200+20+20')
tk_spn1 = MySpinbox(self, value=0, values=list(range(0, 1000)))
tk_spn1.grid(row=0, pady=5)
tk_spn2 = MyTtkSpinbox(self, from_=0, to=1000)
tk_spn2.grid(row=1, pady=5)
def test(e):
print(e)
tk_spn2.bind('<<ActualIncrement>>', test)
if __name__ == '__main__':
app = App()
app.mainloop()
Upvotes: 1