[Python] Textual 事件系统全貌

505 阅读3分钟

on_<event> 事件处理机制

官方预置的事件 (示例):

from textual.app import App
from textual.widget import Widget

# on_click, on_mount 是我们从父类继承来的事件.

class InputText(Widget):
    async def on_click(self) -> None:
        ...

class MainApp(App):
    async def on_mount(self) -> None:
        ...

如何自定义事件:

想要凭空创建一个自定义的事件是非常困难的, 如果你在考虑做这种事, 比如实现一个 "快速三击鼠标" 事件, 这篇文章无法给出答案, 你可能要阅读 textual 的源代码来获得启示.

通常来说, 当我们讨论自定义事件时, 并非指凭空创造一个别人从来没想过的事件, 而是基于已有的 (官方内置的) 事件来 扩展和延伸.

举个例子来说, 官方的 Placeholder 组件已经提供了 on_click, on_hover 事件, 我们要做的自定义, 其实是指写一个自定义类, 继承 Placeholder, 然后覆写这些事件.

再次强调, 凭空创建一个自定义事件是非常困难的, 也是 (我个人认为) 空中楼阁般的需求, 这种需求是在脱离实际应用的情况下钻牛角尖想出来的.

同样的道理, 如果你想要脱离 Widget 来 "自定义" 一个全新的组件, 也几乎是不可能的. 因为任何你想要在不继承 Widget 的情况下写出来的纯 python class, 随着思考的加深, 细节的打磨, 最终会发现自己不过是把官方的 Widget 自己从头到尾 "创造" 了一遍. 这些话说得有点过分, 但大部分人并不如 textual 的作者本人对 Widget 更精通, 也很难凭空去创建一个不依赖 Widget 的自定义组件.

action, handle, on, watch 区别

action

from textual.widget import Widget

class MyWidget(Widget):

    def __init__(self):
        super().__init__()
        self.bind('q', 'quit')
    
    async def action_quit(self):
        ...

handle

from textual.event import Event
from textual.widget import Widget

class MyWidget(Widget):

    def __init__(self):
        super().__init__()
        self.emit(MyEvent(self, 1, 2, 3))
    
    async def handle_my_event(self, a: int, b: int, c: int):
        ...

class MyEvent(Event):
    ...

on

from textual.app import App
from textual.widget import Widget

# on_click, on_mount 是我们从父类继承来的事件.

class InputText(Widget):
    async def on_click(self) -> None:
        ...

class MainApp(App):
    async def on_mount(self) -> None:
        ...

watch

from textual.widget import Widget

class MyWidget(Widget):

    def __init__(self):
        super().__init__()
        self.my_prop = Reactive(False)
    
    async def watch_my_prop(self, val: bool):
        ...

参考:

为什么 Textual Event 要设计为自动触发父类方法

假设 View A 包含了 View B. 当我们的鼠标在 B 上滑动时, 我们希望无论是 A 还是 B, 都能在其类内部处理各自的鼠标事件.

textual 设计为自动触发, 其原本目的并不是为了自动触发父类, 给大家造成困扰; 而是为了支持在多组件组合/继承/嵌套时都能 独立 处理好各自内部的事件, 特别是在复杂的应用设计中, 包含了大量的组合, 交叠, 关联的组件, 在不能彻底掌握组件先后响应顺序的情况下, 如果 B 截获了事件就吃干抹净, 其他组件都抓瞎了, 所以事件必须天然地具有扩散能力, 就像春风吹拂万物一样.

不过, 这并不是说我们无法拦截某个事件. 只要我们想拦截, 那么就调用 event.prevent_default().