Kivy A to Z -- Kivy之Properties

在像VB.net,C#这样的语言中,都有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、在BoxLayoutTest的on_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

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

时间: 2024-12-07 17:18:27

Kivy A to Z -- Kivy之Properties的相关文章

Kivy A to Z -- Kivy的消息处理机制

外面一直在下雨,比较无聊,顺便总结了下Kivy的消息的处理过程. 总的来说,在Kivy里,处理的消息一共有四种:按键消息,鼠标消息,触屏消息,还有自定义消息.下面来看下整个消息的处理流程. 先来看张图: 先来解释下这几个类都是干嘛的: 1.EventDispatcher:看名称就知道这是一个消息分发类,在这个类中通过了以下的主要方法: register_event_type : 注册一种消息类型 bind                :将一个方法绑定到一个消息类型,用于保存方法的是一个list

Kivy A to Z -- Kivy 示例演示自带名单

所有的样品已经在Android 4.04 手机正常进行 1.demo/kivycatalog 这个例子说明了如何使用主控件,例如Layout,Button,MediaPlayer,Progress Bar等等 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSTJDYnVz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" > 2.demo/Pic

Kivy A to Z -- 如何实现焦点切换效果

Kivy是面向触屏设备的,对键盘,遥控器等输入设备的处理比较弱,但是有时候我们又需要实现对按键的处理,如通过方向键切换焦点,这篇文章来讨论下如何去实现. 在看下面的代码之前,最好是对Kivy的UI系统有一个基本的了解. 按照惯例,我们先上代码,然后再对代码进行解释: focustest.py import kivy kivy.require('1.8.0') from kivy.app import App from kivy.properties import StringProperty,B

Kivy a to Z -- 一个简单的通过adb同步Android系统文件的工具

来兴趣时写了些Kivy的代码,调试却总感觉不是很方便.直接打包到public.mp3的方式太繁锁,用文件共享的软件又发现没有一个好用的, 用samba filesharing本来也只是慢,但是更新的版本之后就一直提示说wifi没有tethering,意思是wifi热点没有打开,但是打开了还是提示没有tethering. 找了个叫什么卓*力的文件管理器,下载了samba插件后输入用户名和密码死活不对,被搞得实在恼火,花了点时间写了个通过adb同步安卓文件的工具,用着也挺爽的. 事件为什么总是要搞得

Kivy A to Z -- 如何从Python创建一个基于Binder的Service及如何从Java访问Python创建的Service

<Kivy A to Z -- 如何从python代码中直接访问Android的Service> 一文中讲到了如何从python访问java的service,这一篇再来讲下如何创建一个基于Binder的Python Service以及如何从Java代码中访问这个Python创建的Service. 先来看代码,再作下解释: 接<Kivy A to Z -- 如何从python代码中直接访问Android的Service>一文,我们在相关的文件中增加代码: binder_wrap.cp

Kivy A to Z -- 如何从python代码中直接访问Android的Service

在Kivy中,通过pyjnius扩展可以间接调用Java代码,而pyjnius利用的是Java的反射机制.但是在Python对象和Java对象中转来转去总让人感觉到十分别扭.好在android提供了binder这个进程间通信的功能,Java中的Service也是基于Binder的C++代码封装来实现进程间通信的,这也为从Python代码中绕开pyjnius直接访问Java代码提供了可能,既然Java的Service是基于C++的封装来实现的,也同样可以在Python中封装同样的C++代码,这篇文

kivy安装

>>> os.system('pip install kivy')Collecting kivy Downloading Kivy-1.9.1-cp27-none-win_amd64.whl (7.6MB)Collecting Kivy-Garden>=0.1.4 (from kivy) Downloading kivy-garden-0.1.4.tar.gzRequirement already satisfied: requests in d:\workspace\py_dem

Kivy 自定义控件之(一)

lableSlider1.kv文件 <LabelSlider>: orientation:'vertical' BoxLayout: MyLabelSlider: name: 'Slider1' onValue: root.onMySlider id:mySlider Label: text:str(mySlider.ids.slider.value) BoxLayout: orientation:'vertical' MyLabelSlider: name: 'slider2' MyLabe

在Windows上按照Kivy

Installation Now that python is installed, open the Command line and make sure python is available by typing python --version. Then, do the following to install. Ensure you have the latest pip and wheel: python -m pip install --upgrade pip wheel setu