mirror of https://github.com/kivy/kivy.git
Merge pull request #3736 from matham/kv-overwrite
Make widget kwargs passing higher priority than kv
This commit is contained in:
commit
1dc1a4a64d
|
@ -14,6 +14,7 @@ cdef class EventDispatcher(ObjectWithUid):
|
|||
cdef dict __properties
|
||||
cdef dict __storage
|
||||
cdef object __weakref__
|
||||
cdef public set _kwargs_applied_init
|
||||
cpdef dict properties(self)
|
||||
|
||||
|
||||
|
|
|
@ -244,12 +244,13 @@ cdef class EventDispatcher(ObjectWithUid):
|
|||
def __init__(self, **kwargs):
|
||||
cdef basestring func, name, key
|
||||
cdef dict properties
|
||||
cdef list prop_args
|
||||
cdef dict prop_args
|
||||
|
||||
# Auto bind on own handler if exist
|
||||
properties = self.properties()
|
||||
prop_args = [
|
||||
(k, kwargs.pop(k)) for k in list(kwargs.keys()) if k in properties]
|
||||
prop_args = {
|
||||
k: kwargs.pop(k) for k in list(kwargs.keys()) if k in properties}
|
||||
self._kwargs_applied_init = set(prop_args.keys()) if prop_args else set()
|
||||
super(EventDispatcher, self).__init__(**kwargs)
|
||||
|
||||
__cls__ = self.__class__
|
||||
|
@ -268,7 +269,7 @@ cdef class EventDispatcher(ObjectWithUid):
|
|||
self.fbind(func[3:], getattr(self, func))
|
||||
|
||||
# Apply the existing arguments to our widget
|
||||
for key, value in prop_args:
|
||||
for key, value in prop_args.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
def register_event_type(self, basestring event_type):
|
||||
|
|
72
kivy/lang.py
72
kivy/lang.py
|
@ -685,6 +685,46 @@ and not by any previous styles that may have set `state_image`.
|
|||
is reached. This means that initially, previous rules could be used to set
|
||||
the property.
|
||||
|
||||
Order of kwargs and KV rule application
|
||||
---------------------------------------
|
||||
|
||||
Properties can be initialized in KV as well as in python. For example, in KV:
|
||||
|
||||
.. code-block:: kv
|
||||
|
||||
<MyRule@Widget>:
|
||||
text: 'Hello'
|
||||
ramp: 45.
|
||||
order: self.x + 10
|
||||
|
||||
Then `MyRule()` would initialize all three kivy properties to
|
||||
the given KV values. Separately in python, if the properties already exist as
|
||||
kivy properties one can do for example `MyRule(line='Bye', side=55)`.
|
||||
|
||||
However, what will be the final values of the properties when
|
||||
`MyRule(text='Bye', order=55)` is executed? The quick rule is that python
|
||||
initialization is stronger than KV initialization only for constant rules.
|
||||
|
||||
Specifically, the `kwargs` provided to the python initializer are always
|
||||
applied first. So in the above example, `text` is set to
|
||||
`'Bye'` and `order` is set to `55`. Then, all the KV rules are applied, except
|
||||
those constant rules that overwrite a python initializer provided value.
|
||||
|
||||
That is, the KV rules that do not creates bindings such as `text: 'Hello'`
|
||||
and `ramp: 45.`, if a value for that property has been provided in python, then
|
||||
that rule will not be applied.
|
||||
|
||||
So in the `MyRule(text='Bye', order=55)` example, `text` will be `'Bye'`,
|
||||
`ramp` will be `45.`, and `order`, which creates a binding, will first be set
|
||||
to `55`, but then when KV rules are applied will end up being whatever
|
||||
`self.x + 10` is.
|
||||
|
||||
.. versionchanged:: 1.9.1
|
||||
|
||||
Before, KV rules always overwrote the python values, now, python values
|
||||
are not overwritten by constant rules.
|
||||
|
||||
|
||||
Lang Directives
|
||||
---------------
|
||||
|
||||
|
@ -1638,7 +1678,8 @@ def create_handler(iself, element, key, value, rule, idmap, delayed=False):
|
|||
idmap = copy(idmap)
|
||||
idmap.update(global_idmap)
|
||||
idmap['self'] = iself.proxy_ref
|
||||
handler_append = _handlers[iself.uid][key].append
|
||||
bound_list = _handlers[iself.uid][key]
|
||||
handler_append = bound_list.append
|
||||
|
||||
# we need a hash for when delayed, so we don't execute duplicate canvas
|
||||
# callbacks from the same handler during a sync op
|
||||
|
@ -1698,7 +1739,7 @@ def create_handler(iself, element, key, value, rule, idmap, delayed=False):
|
|||
handler_append(bound)
|
||||
|
||||
try:
|
||||
return eval(value, idmap)
|
||||
return eval(value, idmap), bound_list
|
||||
except Exception as e:
|
||||
tb = sys.exc_info()[2]
|
||||
raise BuilderException(rule.ctx, rule.line,
|
||||
|
@ -1912,8 +1953,13 @@ class BuilderBase(object):
|
|||
self._apply_rule(widget, rule, rule, template_ctx=proxy_ctx)
|
||||
return widget
|
||||
|
||||
def apply(self, widget):
|
||||
def apply(self, widget, ignored_consts=set()):
|
||||
'''Search all the rules that match the widget and apply them.
|
||||
|
||||
`ignored_consts` is a set or list type whose elements are property
|
||||
names for which constant KV rules (i.e. those that don't create
|
||||
bindings) of that widget will not be applied. This allows e.g. skipping
|
||||
constant rules that overwrite a value initialized in python.
|
||||
'''
|
||||
rules = self.match(widget)
|
||||
if __debug__:
|
||||
|
@ -1921,12 +1967,13 @@ class BuilderBase(object):
|
|||
if not rules:
|
||||
return
|
||||
for rule in rules:
|
||||
self._apply_rule(widget, rule, rule)
|
||||
self._apply_rule(widget, rule, rule, ignored_consts=ignored_consts)
|
||||
|
||||
def _clear_matchcache(self):
|
||||
BuilderBase._match_cache = {}
|
||||
|
||||
def _apply_rule(self, widget, rule, rootrule, template_ctx=None):
|
||||
def _apply_rule(self, widget, rule, rootrule, template_ctx=None,
|
||||
ignored_consts=set()):
|
||||
# widget: the current instantiated widget
|
||||
# rule: the current rule
|
||||
# rootrule: the current root rule (for children of a rule)
|
||||
|
@ -2063,9 +2110,16 @@ class BuilderBase(object):
|
|||
key = rule.name
|
||||
value = rule.co_value
|
||||
if type(value) is CodeType:
|
||||
value = create_handler(widget_set, widget_set, key,
|
||||
value, rule, rctx['ids'])
|
||||
setattr(widget_set, key, value)
|
||||
value, bound = create_handler(
|
||||
widget_set, widget_set, key, value, rule,
|
||||
rctx['ids'])
|
||||
# if there's a rule
|
||||
if bound or key not in ignored_consts:
|
||||
setattr(widget_set, key, value)
|
||||
else:
|
||||
if key not in ignored_consts:
|
||||
setattr(widget_set, key, value)
|
||||
|
||||
except Exception as e:
|
||||
if rule is not None:
|
||||
tb = sys.exc_info()[2]
|
||||
|
@ -2261,7 +2315,7 @@ class BuilderBase(object):
|
|||
key = prule.name
|
||||
value = prule.co_value
|
||||
if type(value) is CodeType:
|
||||
value = create_handler(
|
||||
value, _ = create_handler(
|
||||
widget, instr.proxy_ref,
|
||||
key, value, prule, idmap, True)
|
||||
setattr(instr, key, value)
|
||||
|
|
|
@ -226,3 +226,27 @@ class LangTestCase(unittest.TestCase):
|
|||
self.assertTrue('on_press' in wid.binded_func)
|
||||
wid.binded_func['on_press']()
|
||||
self.assertEquals(wid.a, 1)
|
||||
|
||||
def test_kv_python_init(self):
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
class MyObject(object):
|
||||
value = 55
|
||||
|
||||
class MyWidget(Widget):
|
||||
cheese = MyObject()
|
||||
|
||||
Builder.load_string('''
|
||||
<MyWidget>:
|
||||
x: 55
|
||||
y: self.width + 10
|
||||
height: self.cheese.value
|
||||
width: 44
|
||||
''')
|
||||
|
||||
w = MyWidget(x=22, height=12, y=999)
|
||||
self.assertEqual(w.x, 22)
|
||||
self.assertEqual(w.width, 44)
|
||||
self.assertEqual(w.y, 44 + 10)
|
||||
self.assertEqual(w.height, 12)
|
||||
|
|
|
@ -317,13 +317,7 @@ class Widget(WidgetBase):
|
|||
|
||||
# Apply all the styles.
|
||||
if not no_builder:
|
||||
#current_root = Builder.idmap.get('root')
|
||||
#Builder.idmap['root'] = self
|
||||
Builder.apply(self)
|
||||
#if current_root is not None:
|
||||
# Builder.idmap['root'] = current_root
|
||||
#else:
|
||||
# Builder.idmap.pop('root')
|
||||
Builder.apply(self, ignored_consts=self._kwargs_applied_init)
|
||||
|
||||
# Bind all the events.
|
||||
if on_args:
|
||||
|
|
Loading…
Reference in New Issue