A mixin and a property wrapper class are a nice approach, thank you!
I have modified your implementation:
- Properties with descriptor were not managed by your version, so I have added
property
here:
if isinstance(prop_value, (anvil.CustomComponentProperty, property)):
- A burst of property changes would trigger a burst of redraws, so I have added debouncing
- Debouncing at runtime doesn’t hurt, so I have removed the
in_designer
test. If you really need to usein_designer
for other reasons, you can still do it inside theredraw
method - We may want to skip redraw for certain properties, so I have added the
skip_properties
property to the mixin - I don’t like to setup the event listener, so I have replaced the event with a simpler function call
All of the above is overkill for the test custom component I linked to, but it can be useful for more complex custom component with long rendering time.
The code is in the previous clone link, and it’s here:
# AutoRedrawCustomComponent.py
import anvil
class _PropertyWrapper:
"""
Descriptor that wraps a CustomComponentProperty, and defers redrawing via a debounced Timer
"""
def __init__(self, component_property, prop_name):
self.property = component_property
self.prop_name = prop_name
def __get__(self, obj, obj_type):
return self.property.__get__(obj, obj_type)
def __set__(self, obj, value):
old_value = self.property.__get__(obj, type(obj))
self.property.__set__(obj, value)
if value != old_value and self.prop_name not in obj.__class__.skip_properties:
obj.schedule_redraw()
class AutoRedrawCustomComponent:
"""
1. Subclass this mixin alongside the _Template class
2. At the class level of the custom component list which properties should cause a redraw:
AutoRedrawCustomComponent.skip_properties = {'prop1', 'prop2'}
AutoRedrawCustomComponent.delay = 0.5
3. Implement a `redraw()` method on the custom component (called after the 0.1s debounce)
The custom component can call self.redraw() for an immediate redraw, for example inside
mouse event handlers, or can call self.schedule_redraw() for a debounced redraw.
"""
skip_properties = set()
delay = 0.1
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for prop_name in dir(cls):
prop_value = getattr(cls, prop_name)
if isinstance(prop_value, (anvil.CustomComponentProperty, property)):
setattr(cls, prop_name, _PropertyWrapper(prop_value, prop_name))
def schedule_redraw(self):
if not hasattr(self, "_debounce_timer"):
t = anvil.Timer()
t.interval = 0
t.set_event_handler("tick", self._on_debounce_timer_tick)
self._debounce_timer = t
self.add_component(t)
self._debounce_timer.interval = max(self.delay, 0.001)
def _on_debounce_timer_tick(self, **event_args):
self._debounce_timer.interval = 0
if callable(getattr(self, "redraw", None)):
self.redraw()
# in the custom component
from ..AutoRedrawCustomComponent import AutoRedrawCustomComponent
[...]
class MyCustomComponent(AutoRedrawCustomComponent, MyCustomComponentTemplate):
skip_properties = {'tooltip', 'auto_save'}
delay = 0.3
def __init__(self, **properties):
[...]
def redraw(self):
[...]