外面一直在下雨,比较无聊,顺便总结了下Kivy的消息的处理过程。
总的来说,在Kivy里,处理的消息一共有四种:按键消息,鼠标消息,触屏消息,还有自定义消息。下面来看下整个消息的处理流程。
先来看张图:
先来解释下这几个类都是干嘛的:
1、EventDispatcher:看名称就知道这是一个消息分发类,在这个类中通过了以下的主要方法:
register_event_type : 注册一种消息类型
bind :将一个方法绑定到一个消息类型,用于保存方法的是一个list类型。
dispatch :调用相应的已经注册到指定消息类型的方法。
2、WindowBase:Window的基类,定义了Window的一些常用的方法,如add_wdiget,on_draw。
WindowPygame,WindowX11,WindowSDL这三个类是WindowBase的子类,在某一平台上只会初始化一种,并且只有一个实例,主要用于接收消息和处理消息。具体的初始化代码在kivy/core/__init__.py:
#: Instance of a :class:`WindowBase` implementation
Window = core_select_lib('window', (
('egl_rpi', 'window_egl_rpi', 'WindowEglRpi'),
('pygame', 'window_pygame', 'WindowPygame'),
('sdl', 'window_sdl', 'WindowSDL'),
('x11', 'window_x11', 'WindowX11'),
), True)
如果没有特别指定,Kivy将会实例化WindowPygame,作为Kivy的主窗口。所有的画图,创建widget的操作都将在这个主窗口上进行。
再来解释下主要变量和方法。EventDispatcher.__event_stack,这是一个用于保存所有已经注册的消息。添加一个消息到__event_stack有两种方法:
1、一种是通过调用EventDispatcher.register_event_type
2、另一种方法是在子类中声明一个类变量__events__,如在WindowBase中就有关于__events__的声明:
__events__ = ('on_draw', 'on_flip', 'on_rotate', 'on_resize', 'on_close',
'on_motion', 'on_touch_down', 'on_touch_move', 'on_touch_up',
'on_mouse_down', 'on_mouse_move', 'on_mouse_up',
'on_keyboard', 'on_key_down', 'on_key_up', 'on_dropfile')
并在EventDispatcher.__cinit__时把这些事件类型加到__event_stack变量中。
有了事件的类型,接下来要做的事就是通过EventDispatcher.bind添加事件的处理方法:
例如,在input/providers/mouse.py的MouseMotionEventProvider中,就有下面的bind代码:
def start(self):
'''Start the mouse provider'''
if not EventLoop.window:
return
EventLoop.window.bind(
on_mouse_move=self.on_mouse_motion,
on_mouse_down=self.on_mouse_press,
on_mouse_up=self.on_mouse_release)
好,注册了消息类型和消息的处理过程,按下来就要等消息上门了。
前面讲到,Kivy实例化了WindowPygame作为主窗口,并且通过调用App.run最终进入WindowPygame._mainloop这个消息循环方法。接下来看下_mainloop都做了些什么:
def _mainloop(self):
EventLoop.idle()
for event in pygame.event.get():
# kill application (SIG_TERM)
if event.type == pygame.QUIT:
EventLoop.quit = True
self.close()
# mouse move
elif event.type == pygame.MOUSEMOTION:
x, y = event.pos
self.mouse_pos = x, self.system_size[1] - y
# don't dispatch motion if no button are pressed
if event.buttons == (0, 0, 0):
continue
self._mouse_x = x
self._mouse_y = y
self._mouse_meta = self.modifiers
self.dispatch('on_mouse_move', x, y, self.modifiers)
# mouse action
elif event.type in (pygame.MOUSEBUTTONDOWN,
pygame.MOUSEBUTTONUP):
self._pygame_update_modifiers()
x, y = event.pos
btn = 'left'
if event.button == 3:
btn = 'right'
elif event.button == 2:
btn = 'middle'
elif event.button == 4:
btn = 'scrolldown'
elif event.button == 5:
btn = 'scrollup'
elif event.button == 6:
btn = 'scrollright'
elif event.button == 7:
btn = 'scrollleft'
eventname = 'on_mouse_down'
if event.type == pygame.MOUSEBUTTONUP:
eventname = 'on_mouse_up'
self._mouse_x = x
self._mouse_y = y
self._mouse_meta = self.modifiers
self._mouse_btn = btn
self._mouse_down = eventname == 'on_mouse_down'
self.dispatch(eventname, x, y, btn, self.modifiers)
# keyboard action
elif event.type in (pygame.KEYDOWN, pygame.KEYUP):
self._pygame_update_modifiers(event.mod)
# atm, don't handle keyup
if event.type == pygame.KEYUP:
self.dispatch('on_key_up', event.key,
event.scancode)
continue
# don't dispatch more key if down event is accepted
if self.dispatch('on_key_down', event.key,
event.scancode, event.unicode,
self.modifiers):
continue
self.dispatch('on_keyboard', event.key,
event.scancode, event.unicode,
self.modifiers)
# video resize
elif event.type == pygame.VIDEORESIZE:
self._size = event.size
self.update_viewport()
elif event.type == pygame.VIDEOEXPOSE:
self.canvas.ask_update()
# ignored event
elif event.type == pygame.ACTIVEEVENT:
pass
# drop file (pygame patch needed)
elif event.type == pygame.USEREVENT and \
hasattr(pygame, 'USEREVENT_DROPFILE') and \
event.code == pygame.USEREVENT_DROPFILE:
self.dispatch('on_dropfile', event.filename)
'''
# unhandled event !
else:
Logger.debug('WinPygame: Unhandled event %s' % str(event))
在这个消息循环里,通过pygame.event.get()获取消息,消息的类型有:按键,鼠标,这两种,通过dispatch将消息发送到相应的方法中去处理,这里有一个疑问:怎么没有看到Kivy对触屏消息的处理?答案是触屏消息的处理是在函数开始的EventLoop.idle()里进行的。这篇讲的就要是消息的处理机制,所以这里就不再展来。其内容留着以后再讲。