外面一直在下雨,比较无聊,顺便总结了下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()里进行的。这篇讲的就要是消息的处理机制,所以这里就不再展来。其内容留着以后再讲。
Kivy A to Z -- Kivy的消息处理机制