码迷,mamicode.com
首页 > 其他好文 > 详细

Kivy A to Z -- Kivy之Properties

时间:2014-07-27 11:29:12      阅读:326      评论:0      收藏:0      [点我收藏+]

标签:python   kivy   

在像VB.netC#这样的语言中,都有Property的概念,可以通过get获取属性的值,也可以通过set来设置一个属性的值,Kivy也提供了类似的功能。在Kivy里,提供了以下的属性类:

StringProperty

NumericProperty

BoundedNumericProperty

ObjectProperty

DictProperty

ListProperty

OptionProperty

AliasProperty

BooleanProperty

ReferenceListProperty

 

看名字应该就能对这些属性有一个初步的印象。

 

先来看下Property是怎样使用的。

 

import kivy

kivy.require(‘1.8.0‘)

 

from kivy.app import App

from kivy.properties import BooleanProperty

from kivy.uix.button import Button

from kivy.uix.boxlayout import BoxLayout

 

class MyButton(Button):

    focus = BooleanProperty(False)

    def __init__(self,**kwargs):

        super(MyButton,self).__init__(**kwargs)

    def set_focus(self):

        self.focus = True

    def on_focus(self,instance,focused):

        print ‘++++++++++++++on_focus:‘,focused

 

class BoxLayoutTest(BoxLayout):

    def __init__(self,**kargs):

        super(BoxLayoutTest,self).__init__(**kargs)

        self.btn = MyButton(text=‘press me‘,on_press=self.on_press)

        self.add_widget(self.btn)

 

    def on_press(self,control):

        self.btn.set_focus()

 

class MyApp(App):

    def build(self):

        return BoxLayoutTest()

    def on_pause(self):

        return True

 

if __name__ == ‘__main__‘:

    MyApp().run()

 

 

运行这个例子,连续按下Button,将会得到下面的输出:

++++++++++++++on_focus:True

 

并且只会在第一次按下Button的时候输出。

 

再来分析下代码:

1、在MyButton里定义了一个为BooleanProperty的类变量:

focus = BooleanProperty(False)

2、然后定义了on_focus方法。

3、在BoxLayoutTeston_press方法中调用了MyButton.set_focus,给MyButton.focus赋值为True,这将会触发MyButton.on_focus方法

 

如果对Python没有比较深入的了解,可能会产生这样的疑问:怎么给一个变量赋值能够触发一个方法呢。其实这里的focus就是类似VB.net,C#里的属性了。为了解析这个现象,我们

 

来看下下面的例子:

Test.py

 

class Prop(object):
    def __init__(self):
        self._name = ''
    def __set__(self, obj, val):
        print '__set__'
        self._name = val
    def __get__(self, obj, objtype):
        print '__get__'
        if obj is None:
            return None
        return self._name
 
class Widget(object):
    p = Prop()
    
w = Widget()
w.p = 'abc'
print '--------------------------'
print w.p


 

运行这个例子将会得到下面的输出:

__set__
--------------------------
__get__
abc


 

从这个例子来看,Python是完全支持类似C#的属性的,可以把实现一个“属性类”。但是,这里要注意下:

1、属性类必须继承自object,否则将不会触发__get__,__set__

2、属性类必须实现__get__和__set__方法。

 

了解了属性的应用和基本的实现原理,接下来深入分析下Kivy里的属性是怎么实现的,以解开上面的疑问:怎么给一个变量赋值就能够触发一个已经定义好的方法呢?

 

我们找到kivy/properties.pyx,代码是用Cython写的,所有的Property都在这个文件中实现。我们重点关注Property类:这是所有Property的基类。

这里,先了解二个事实:

1、首先,Property都是在Widget中使用的。

2、第二,在Cython里,自定义的类都是默认继承自object的。

 

接下来,先看下_event.py中的EventDispatcher.__cinit__方法的实现:

        cdef dict cp = cache_properties

...

        if __cls__ not in cp: #查找类的Property是否已经在缓存里了

            attrs_found = cp[__cls__] = {}

            attrs = dir(__cls__)

            for k in attrs:

                uattr = getattr(__cls__, k, None)

                if not isinstance(uattr, Property):

                    continue

                if k == ‘touch_down‘ or k == ‘touch_move‘ or k == ‘touch_up‘:

                    raise Exception(‘The property <%s> have a forbidden name‘ % k)

                attrs_found[k] = uattr

        else:

            attrs_found = cp[__cls__]

 

        # First loop, link all the properties storage to our instance

        for k in attrs_found:

            attr = attrs_found[k]

            attr.link(self, k)

 

        # Second loop, resolve all the reference

        for k in attrs_found:

            attr = attrs_found[k]

            attr.link_deps(self, k)

 

        self.__properties = attrs_found

 

首先,查找类的Property是否已经在缓存里了,如果是,直接取缓存,否则查找类的所有Property,保存到attrs_found

接下来很重要的一步:调用attr.link(self, k)将属性绑定到类的实例。

 for k in attrs_found:

            attr = attrs_found[k]

            attr.link(self, k)

 

按下来看下Property.link的实现:

    cpdef link(self, EventDispatcher obj, str name):

        cdef PropertyStorage d = PropertyStorage()

        self._name = name

        obj.__storage[name] = d

        self.init_storage(obj, d)

 

    cdef init_storage(self, EventDispatcher obj, PropertyStorage storage):

        storage.value = self.convert(obj, self.defaultvalue)

        storage.observers = []

 

这里创建了一个PropertyStorage 对象,并对其进行初始化,PropertyStorage 用于保存Property的值以及Property触发时要调用的方法。这里的name即是Property的变量名称,是一个字符串类型,比如上面的例子中的focus。

接下来把这个PropertyStorage 对象保存到EventDispatcher 实例的dict类型的__storage中。PropertyStorage.observers用于保存当值改变是要调用的方法,因为我们看到这个类定义了两个方法:

    def __set__(self, EventDispatcher obj, val):

        self.set(obj, val)

    def __get__(self, EventDispatcher obj, objtype):

        if obj is None:

            return self

        return self.get(obj)

这样,在对Property赋值时将会调用Property.set方法:

cpdef set(self, EventDispatcher obj, value):

        ‘‘‘Set a new value for the property.

        ‘‘‘

        cdef PropertyStorage ps = obj.__storage[self._name]

        value = self.convert(obj, value)

        realvalue = ps.value

        if self.compare_value(realvalue, value):

            return False

 

        try:

            self.check(obj, value)

        except ValueError as e:

            if self.errorvalue_set == 1:

                value = self.errorvalue

                self.check(obj, value)

            elif self.errorhandler is not None:

                value = self.errorhandler(value)

                self.check(obj, value)

            else:

                raise e

 

        ps.value = value

        self.dispatch(obj)

        return True

 

1、第一行代码取出保存在__storage中的PropertyStorage,也就是在link时创建的:

cdef PropertyStorage ps = obj.__storage[self._name]

 

2、接下来这个方法通过调用compare_value检测新值是否与之前的相同,如果相同,直接返回。

3、然后,将值保存到PropertyStorage中:ps.value = value

4、最后,调用self.dispatch,这将会调用保存在PropertyStorage.observers中的所有方法。

 

 

到这里,我们已经把整个流程梳理了一遍,但是我们还是没有看到on_focus是怎么被调用的,在init_storage时,storage.observers = []

而on_focus方法其实是通过Property.bind方法来添加到observers 中去的,请看下面的bind的实现:

    cpdef bind(self, EventDispatcher obj, observer):

        ‘‘‘Add a new observer to be called only when the value is changed.

        ‘‘‘

        cdef PropertyStorage ps = obj.__storage[self._name]

        if observer not in ps.observers:

            ps.observers.append(observer)

 

但是我们并没有看到在哪里有调用Property.bind方法。那么bind方法是在什么地方调用的呢?

我们来到EventDispatcher.__init__这个类的初始化函数:

 

   __cls__ = self.__class__

        if __cls__ not in cache_events_handlers:

            event_handlers = []

            for func in dir(self):

                if func[:3] != ‘on_‘:

                    continue

                name = func[3:]

                if name in properties:

                    event_handlers.append(func)

            cache_events_handlers[__cls__] = event_handlers

        else:

            event_handlers = cache_events_handlers[__cls__]

        for func in event_handlers:

            self.bind(**{func[3:]: getattr(self, func)})

这个函数将会查找所有的以on_开头的方法,并对其调用bind方法:
            self.bind(**{func[3:]: getattr(self, func)})

再来看下bind的实现:

        cdef Property prop

        for key, value in kwargs.iteritems():

            if key[:3] == ‘on_‘:

                if key not in self.__event_stack:

                    continue

                # convert the handler to a weak method

                handler = WeakMethod(value)

                self.__event_stack[key].append(handler)

            else:

                prop = self.__properties[key]

                prop.bind(self, value)

 

看下红字部分,这里就是调用Property.bind的代码了。

 

OK,that’s all

 

全文字,可能不便于理解,但是整个流程是讲清楚了,有机会在用图来总结下。

Kivy A to Z -- Kivy之Properties

标签:python   kivy   

原文地址:http://blog.csdn.net/i2cbus/article/details/38151349

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!