PySimpleGUI教程5 - 高级和自定义事件

本文详细介绍了如何在PySimpleGUI中使用自定义事件,如手动触发事件循环,控件交互事件(如输入框实时更新),以及全局键盘监听和高级事件绑定,包括鼠标移动、滚轮事件和长按事件的实例演示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

PySimpleGUI教程5 - 高级和自定义事件

PySimpleGUI中,一般只使用按钮点击触发事件。但有时我们希望监测一些自定义事件,例如用户自己编写的回调事件。又或者,还有一些其他高级事件,例如键盘输入、鼠标移动…该怎么办呢?下面给一些解决方案:

自定义事件

当我们需要手动触发事件循环时,可以使用 windowswrite_event_value 方法(前面已经介绍过):

def write_event_value(self,
                      key: Any,
                      value: Any) -> None

Adds a key & value tuple to the queue that is used by threads to communicate with the window

Params:
key – The key that will be returned as the event when reading the window
value – The value that will be in the values dictionary

我们可以在其他线程触发自定义事件:

import PySimpleGUI as sg
import threading, time

def looping(window):
    called = 0
    while True:
        called += 1
        time.sleep(1)
        window.write_event_value('loop_event', {'called': called})


txt = sg.Text('------', key='test')
layout = [[txt]]
window = sg.Window('test', layout, finalize=True)

threading.Thread(target=looping, args=(window,)).start()

while True:
    event, value = window.read()
    if event == sg.WINDOW_CLOSED: exit()
    print(event, value) # 打印所有触发的事件

可以看到打印结果如下:

loop_event {'loop_event': {'called': 1}}
loop_event {'loop_event': {'called': 2}}
loop_event {'loop_event': {'called': 3}}
loop_event {'loop_event': {'called': 4}}
loop_event {'loop_event': {'called': 5}}
...

控件交互事件

按官方文档,当控件的 enable_eventstrue 时,当该元素被交互时(例如单击,输入一个字符),则立即生成一个事件,导致 window.read() 调用返回。例如,对一个输入框,每当输入的字符更新时,都会触发事件。下面是示例代码:

import PySimpleGUI as sg

inp = sg.Input(enable_events=True, key='input')
layout = [[inp]]
window = sg.Window('test', layout)

while True:
    event, value = window.read()
    if event == sg.WINDOW_CLOSED: exit()
    print('EVENTS: ',event)

这时在每个在输入框里的键盘输入都会被检测到。

全局键盘事件

对于 window 组件可以开启 return_keyboard_events 对键盘进行全局监听。示例:

import PySimpleGUI as sg

button = sg.Button('test', key='test')
layout = [[button]]
window = sg.Window('test', layout, return_keyboard_events=True)

while True:
    event, value = window.read()
    if event == sg.WINDOW_CLOSED: exit()
    print(event)

这样就可以在event获取对应的keycode:

Shift_L:16
Shift_L:16
Escape:27
Control_L:17
1
Q

bind高级事件

然而有一些监听事件是PySimpleGUI做不到的。这时就需要调用组件的bind函数来绑定Tkinter的事件(因为PySimpleGUI是基于Tkinter开发的)。bind函数文档如下:

def bind(self,
        bind_string: str,
        key_modifier: str,
        propagate: bool = True) -> None

Used to add tkinter events to an Element. The tkinter specific data is in the Element’s member variable user_bind_event :param bind_string: The string tkinter expected in its bind function :type bind_string: (str) :param key_modifier: Additional data to be added to the element’s key when event is returned :type key_modifier: (str) :param propagate: If True then tkinter will be told to propagate the event to the element :type propagate: (bool)

Params:
bind_string – The string tkinter expected in its bind function
key_modifier – Additional data to be added to the element’s key when event is returned
propagate – If True then tkinter will be told to propagate the event to the element

这里, bind_string 填待绑定的tkinter事件名(event sequences)。python-course站点给出了一些示例:

EventDescription
<Button>表示鼠标按下的事件。特别的,<Button-1>表示鼠标左键,<Button-2>表示鼠标中键,<Button-3>表示鼠标右键。
<Motion>表示鼠标移动的事件。特别的,鼠标按下时,鼠标的左中右键被表示为 <B1-Motion>, <B2-Motion> and <B3-Motion>。
<ButtonRelease>表示鼠标松开的事件。特别的,<ButtonRelease-1>表示鼠标左键,<ButtonRelease-2>表示鼠标中键,<ButtonRelease-3>表示鼠标右键。
<Enter>表示鼠标移入控件的事件。
<Leave>表示鼠标移出控件的事件。

在这里查看完整一点的列表:https://python-course.eu/tkinter/events-and-binds-in-tkinter.php

最完整的tkinter事件列表可参见:https://manpages.debian.org/bullseye/tk8.6-doc/bind.3tk.en.html

key_modifier 用来填写给event返回的附加值。例如,一个按钮控件的 keybtnbind 函数里的 key_modifier_move,那么 bind 事件触发时,在事件循环里获取的event值就是 btn_move

下面是一个完整的bind示例:

import PySimpleGUI as sg

button = sg.Button('TEST', key='test')  # 必须设置 key
layout = [[button]]
window = sg.Window('test', layout, finalize=True)  # 必须设置 finalize

button.bind('<Motion>', '_move')  # 绑定鼠标移动的事件

while True:
    event, value = window.read()
    if event == sg.WINDOW_CLOSED: exit()
    print('EVENTS: ', event)

这里给按钮绑定了 <Motion> 事件,当鼠标在按钮上移动时,就会触发事件。因为 key_modifier_move ,所以事件名为 test_move 。打印结果如下:

EVENTS:  test_move
EVENTS:  test_move
EVENTS:  test_move
EVENTS:  test_move
...

这里要注意的是,由于bind属于对组件的动态操作,所以调用时必须确保窗体构建完毕,因此需要给window 加上 finalize

也可以参考官方仓库里的issue:https://github.com/PySimpleGUI/PySimpleGUI/issues/2639

获取bind事件值

可惜,可以看出,在上面的示例中,我们在事件循环中只能获取事件名,但不能获取事件值。什么是事件值?一般来说,当一个事件触发时,我们还希望获得事件的具体情况。例如,当鼠标在控件上移动时,我们更希望知道鼠标的位置——不然只知道鼠标在移动有什么用?像这种事件的具体信息都会打包成一个事件值。

这时,我们可以使用控件的 user_bind_event 属性。当用户自定义的 bind 事件触发时,user_bind_event 会更新为本次事件的事件值。这里的事件值对象是tkinter里的 Event 类构造出的对象。其文档如下:

class Event:
    """Container for the properties of an event.

    Instances of this type are generated if one of the following events occurs:

    KeyPress, KeyRelease - for keyboard events
    ButtonPress, ButtonRelease, Motion, Enter, Leave, MouseWheel - for mouse events
    Visibility, Unmap, Map, Expose, FocusIn, FocusOut, Circulate,
    Colormap, Gravity, Reparent, Property, Destroy, Activate,
    Deactivate - for window events.

    If a callback function for one of these events is registered
    using bind, bind_all, bind_class, or tag_bind, the callback is
    called with an Event as first argument. It will have the
    following attributes (in braces are the event types for which
    the attribute is valid):

        serial - serial number of event
    num - mouse button pressed (ButtonPress, ButtonRelease)
    focus - whether the window has the focus (Enter, Leave)
    height - height of the exposed window (Configure, Expose)
    width - width of the exposed window (Configure, Expose)
    keycode - keycode of the pressed key (KeyPress, KeyRelease)
    state - state of the event as a number (ButtonPress, ButtonRelease,
                            Enter, KeyPress, KeyRelease,
                            Leave, Motion)
    state - state as a string (Visibility)
    time - when the event occurred
    x - x-position of the mouse
    y - y-position of the mouse
    x_root - x-position of the mouse on the screen
             (ButtonPress, ButtonRelease, KeyPress, KeyRelease, Motion)
    y_root - y-position of the mouse on the screen
             (ButtonPress, ButtonRelease, KeyPress, KeyRelease, Motion)
    char - pressed character (KeyPress, KeyRelease)
    send_event - see X/Windows documentation
    keysym - keysym of the event as a string (KeyPress, KeyRelease)
    keysym_num - keysym of the event as a number (KeyPress, KeyRelease)
    type - type of the event as a number
    widget - widget in which the event occurred
    delta - delta of wheel movement (MouseWheel)
    """

从文档中可以看出,可以通过 user_bind_event.xuser_bind_event.y 来获取鼠标当时的位置。看如下示例:

import PySimpleGUI as sg

button = sg.Button('test', key='test')  # 必须设置 key
layout = [[button]]
window = sg.Window('test', layout, finalize=True)  # 必须设置 finalize

button.bind('<Motion>', '_move')  # 绑定鼠标移动的事件

while True:
    event, value = window.read()
    if event == sg.WINDOW_CLOSED: exit()
    if event == 'test_move':
        print('Mouse at:', button.user_bind_event.x, button.user_bind_event.y)

鼠标在 button 上移动时, test_move 事件被触发,同时 buttonuser_bind_event 也会更新。此时便能获取鼠标的位置。输出如下:

Mouse at: 10 1
Mouse at: 11 1
Mouse at: 12 1
Mouse at: 13 1
Mouse at: 14 2
Mouse at: 15 3
...

例1 - 滚轮事件

我们用两个例子来说明如何自定义事件。第一个例子是一个文本控件,当鼠标滚轮在文本上上滚动时,文本显示UP,反之显示DOWN。

初始布局如下:

import PySimpleGUI as sg

txt = sg.Text('------', key='test')
layout = [[txt]]
window = sg.Window('test', layout, finalize=True)

通过查询上面的文档,我们可以知道滚轮事件的 bind_string<MouseWheel> ,绑定即可:

txt.bind('<MouseWheel>', '_wheel')

接下来编写事件循环,并获取事件值对象,打印一下试试:

while True:
    event, value = window.read()
    if event == sg.WINDOW_CLOSED: exit()
    if event == 'test_wheel':
        bind_event = txt.user_bind_event
        print(bind_event)

可以看到 bind_event 结构如下:

<MouseWheel event send_event=True state=Mod1 delta=-120 x=19 y=9>

不难看出,使用 delta 属性可以获知滚轮的位移。正数就是向上滚,反之就是向下。因此,加入判断即可:

if bind_event.delta > 0:
    txt.update('UP')
else:
    txt.update('DOWN')

下面是完整代码:

import PySimpleGUI as sg

txt = sg.Text('------', key='test')
layout = [[txt]]
window = sg.Window('test', layout, finalize=True)

txt.bind('<MouseWheel>', '_wheel')

while True:
    event, value = window.read()
    if event == sg.WINDOW_CLOSED: exit()
    if event == 'test_wheel':
        bind_event = txt.user_bind_event
        if bind_event.delta > 0:
            txt.update('UP')
        else:
            txt.update('DOWN')

运行效果正常:

例2 - 长按事件

接下来,我们为按钮编写一个长按事件,当按住按钮时,事件就一直被触发。GUI如下:

import PySimpleGUI as sg
import threading, time

button = sg.Button('test', key='test')  # 必须设置 key
layout = [[button]]
window = sg.Window('test', layout, finalize=True)  # 必须设置 finalize

虽然Tkinter里没有长按事件的定义,但我们可以监控鼠标按下和松开的事件。这里用到的 bind_string<Button-1><ButtonRelease-1> 。由于按钮的默认事件就是ButtonRelease,所以我们不用bind这个事件:

# 绑定按钮按下的事件
button.bind('<Button-1>', '-down')
# button按钮松开的事件就是原生事件,无需绑定,其他元素需要
# button.bind('<ButtonRelease-1>', '-up')

通过这两个事件的触发,我们定义一个flag来表示按钮是否在按下状态。如果是,就反复触发按钮按下的事件。我们用额外的线程来实现:

test_pressed_flag = False

def trigger_callback(window):
    while True:
        time.sleep(0.1)
        if test_pressed_flag:
            window.write_event_value('test-pressed', {})

threading.Thread(target=trigger_callback, args=(window,)).start()

接着在事件循环中更新这个flag:

while True:
    event, value = window.read()
    if event == sg.WINDOW_CLOSED: exit()
    if event == 'test-down':  # mouse down
        test_pressed_flag = True
    if event == 'test':  # mouse up
        test_pressed_flag = False
    print(event)

现在 test-pressed 事件就可以被正常识别了!下面是打印结果:

test-down
test-pressed
test-pressed
test-pressed
test-pressed
test

完整代码:

import PySimpleGUI as sg
import threading, time

button = sg.Button('test', key='test')  # 必须设置 key
layout = [[button]]
window = sg.Window('test', layout, finalize=True)  # 必须设置 finalize

# 绑定按钮按下的事件
button.bind('<Button-1>', '-down')
# button按钮松开的事件就是原生事件,无需绑定,其他元素需要
# button.bind('<ButtonRelease-1>', '-up')

test_pressed_flag = False

def trigger_callback(window):
    while True:
        time.sleep(0.1)
        if test_pressed_flag:
            window.write_event_value('test-pressed', {})

threading.Thread(target=trigger_callback, args=(window,)).start()

while True:
    event, value = window.read()
    if event == sg.WINDOW_CLOSED: exit()
    if event == 'test-down':  # mouse down
        test_pressed_flag = True
    if event == 'test':  # mouse up
        test_pressed_flag = False
    print(event)
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值