Reputation: 102039
I'm writing a custom widget for kivy(see this question), but I just found out that, for some unknown reason, sometimes the bind
method does not actually bind the callbacks.
In my class I have this code:
def __init__(self, **kwargs):
super(SpinBox, self).__init__(**kwargs)
self._value_label = Label(text=str(self.value))
self.inc_button = TimedButton(text='+')
self.dec_button = TimedButton(text='-')
def inc(inst):
if float(self.value) + float(self.step) <= self.max_value:
self.value += self.step
def dec(inst):
if float(self.value) - float(self.step) >= self.min_value:
self.value -= self.step
self.inc_button.bind(on_press=inc)
self.inc_button.bind(on_time_slice=inc)
self.dec_button.bind(on_press=dec)
self.dec_button.bind(on_time_slice=dec)
# ...
Where TimedButton
is an other custom class. It is a Button
subclass that on on_touch_down
starts a timer, if after a certain amount of time it didn't receive a on_touch_up
it consider the press a long-press and starts to dispatch the on_time_slice
event using Clock.schedule_interval
every some milliseconds.
So, trying to use my custom class like this:
class MyApp(App):
def build(self):
return SpinBox()
MyApp().run()
The value
is not incremented at all.
If I do:
class MyApp(App):
def build(self):
s = SpinBox()
def inc(inst):
s.value += 1
s.inc_button.bind(on_time_slice=inc)
return s
MyApp().run()
The value is incremented at every on_time_slice
event. I don't understand why the bind inside the MyApp
class works, while binding in the SpinBox.__init__
method does not.
What am I doing wrong?
Upvotes: 1
Views: 167
Reputation: 102039
I found a work around. Instead of binding the on_touch_down
and on_touch_up
I tried to bind the state
property implementing an on_state
method in the TimedButton
and it works. I still don't understand why the previous implementation worked when used alone but not in the SpinBox
, except when binding a function inside the App
class directly.
Anyway, since it looks like the TimedButton
class has something to do with it, here's the code of the old implementation:
class TimedButton(Button):
"""A simple ``Button`` subclass that produces an event at regular intervals
when pressed.
This class, when long-pressed, emits an ``on_time_slice`` event every
``time_slice`` milliseconds.
:param long_press_interval: Defines the minimum time required to consider
the press a long-press.
:type long_press_interval: int
:param time_slice: The number of milliseconds of each slice.
:type time_slice: int
"""
def __init__(self, long_press_interval=550, time_slice=225, **kwargs):
super(TimedButton, self).__init__(**kwargs)
self.long_press_interval = long_press_interval
self.time_slice = time_slice
self._touch_start = None
self._touch_uid = None
self._long_press_callback = None
self._slice_callback = None
self.register_event_type('on_time_slice')
self.register_event_type('on_long_press')
def on_touch_down(self, touch):
start_time = time.time()
self._touch_start = start_time
self._touch_uid = touch.uid
def callback(dt):
self._check_long_press(dt)
Clock.schedule_once(callback, self.long_press_interval / 1000.0)
self._long_press_callback = callback
super(TimedButton, self).on_touch_down(touch)
def _check_long_press(self, dt):
delta = dt * 1000
if delta > self.long_press_interval and self._touch_uid is not None:
self.dispatch('on_long_press')
self._long_press_callback = None
def slice_callback(dt):
self.dispatch('on_time_slice')
Clock.schedule_interval(slice_callback, self.time_slice / 1000.0)
self._slice_callback = slice_callback
def on_touch_up(self, touch):
end_time = time.time()
delta = (end_time - (self._touch_start or 0)) * 1000
Clock.unschedule(self._slice_callback)
if (self._long_press_callback is not None and
delta > self.long_press_interval):
self.dispatch('on_long_press')
self._touch_start = self._touch_uid = None
self._long_press_callback = self._slice_callback = None
super(TimedButton, self).on_touch_up(touch)
def on_long_press(self):
pass
def on_time_slice(self):
pass
And here's the new code using state
which works perfectly well:
class TimedButton(Button):
"""A simple ``Button`` subclass that produces an event at regular intervals
when pressed.
This class, when long-pressed, emits an ``on_time_slice`` event every
``time_slice`` milliseconds.
:param long_press_interval: Defines the minimum time required to consider
the press a long-press.
:type long_press_interval: int
:param time_slice: The number of milliseconds of each slice.
:type time_slice: int
"""
def __init__(self, long_press_interval=550, time_slice=225, **kwargs):
super(TimedButton, self).__init__(**kwargs)
self.long_press_interval = long_press_interval
self.time_slice = time_slice
self._touch_start = None
self._long_press_callback = None
self._slice_callback = None
self.register_event_type('on_time_slice')
self.register_event_type('on_long_press')
def on_state(self, instance, value):
if value == 'down':
start_time = time.time()
self._touch_start = start_time
def callback(dt):
self._check_long_press(dt)
Clock.schedule_once(callback, self.long_press_interval / 1000.0)
self._long_press_callback = callback
else:
end_time = time.time()
delta = (end_time - (self._touch_start or 0)) * 1000
Clock.unschedule(self._slice_callback)
if (self._long_press_callback is not None and
delta > self.long_press_interval):
self.dispatch('on_long_press')
self._touch_start = None
self._long_press_callback = self._slice_callback = None
def _check_long_press(self, dt):
delta = dt * 1000
if delta > self.long_press_interval and self.state == 'down':
self.dispatch('on_long_press')
self._long_press_callback = None
def slice_callback(dt):
self.dispatch('on_time_slice')
return self.state == 'down'
Clock.schedule_interval(slice_callback, self.time_slice / 1000.0)
self._slice_callback = slice_callback
def on_long_press(self):
pass
def on_time_slice(self):
pass
Upvotes: 1