You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pyglet中事件无法中和及窗口异常问题咨询

Pyglet Widget事件处理问题修复

问题1:叠加Widget重复接收事件

当两个PushButton叠加时,点击上层按钮会触发两个按钮的on_press和on_release回调。虽然文档说明返回True可终止事件传递,但这仅对窗口事件处理器链生效,PushButton内部的事件分发逻辑不会因回调返回True而停止传递事件给其他Widget。

解决思路

重写PushButton的鼠标事件处理方法,让按钮在完成自身事件处理后返回True,直接终止事件在窗口处理器链中的传递,避免下层Widget接收到事件。

问题2:按钮移出窗口松开触发异常

点击按钮不松开,移出窗口后再松开时,鼠标坐标超出窗口范围,PushButton内部的区域判断逻辑会触发异常,同时_pressed状态无法正确重置。

解决思路

重写PushButton的on_mouse_release方法,优先重置按钮的按压状态,再处理后续逻辑,避免坐标异常导致的错误;同时完善鼠标移动事件的处理,确保hover状态更新逻辑稳定。


修改后的完整代码

from __future__ import annotations

import pyglet
from pyglet.graphics import Batch
from pyglet.window import Window
from pyglet.gui.widgets import PushButton
from pyglet.shapes import Rectangle

from PIL import Image, ImageDraw


# 重写PushButton,修复事件传递和移出窗口异常问题
class FixedPushButton(PushButton):
    def on_mouse_press(self, x, y, buttons, modifiers):
        if self._check_hit(x, y):
            self._pressed = True
            self._update_image()
            if self.on_press:
                self.on_press(self)
            # 返回True阻止事件传递给其他Widget
            return True
        return False

    def on_mouse_release(self, x, y, buttons, modifiers):
        # 先重置按压状态,避免坐标异常导致逻辑出错
        if self._pressed:
            self._pressed = False
            self._update_image()
            if self.on_release:
                self.on_release(self)
            # 返回True阻止事件传递
            return True
        return False

    def on_mouse_motion(self, x, y, dx, dy):
        # 修复hover状态的异常处理
        hit = self._check_hit(x, y)
        if hit != self._hover:
            self._hover = hit
            self._update_image()
            if self.on_hover:
                self.on_hover(self, hit)
        return self._hover


class TestsEventsKlass():
    def __init__(self, width, height):
        self.init_display(width, height)
        self.setup_ressources()
        self.setup_widgets()
        self.setup_handlers()

    def init_display(self, width, height):
        self.width = width
        self.height = height

        self.batch = Batch()
        self.display = pyglet.display.get_display()
        self.screen = self.display.get_default_screen()

        template = pyglet.gl.Config(double_buffer=True, alpha_size=8, depth_size=0, stencil_size=0)
        try:
            self.config = self.screen.get_best_config(template)
        except pyglet.window.NoSuchConfigException as Err:
            print(f"Configuration désirée non supportée...[{Err}]")
            raise

        context = self.config.create_context(None)

        self.main_window = Window(width, height, config=self.config, style=Window.WINDOW_STYLE_OVERLAY)
        self.main_window.set_caption("B & Ev")

    def setup_ressources(self):
        self.back_color = (0, 0, 0, 0)

        w = round(self.main_window.width * 0.4)
        h = round(self.main_window.height * 0.3)

        # UNPRESSED
        temp_image = Image.new("RGBA", (w, h), self.back_color)
        draw = ImageDraw.Draw(temp_image)
        draw.rounded_rectangle(xy=(0, 0, w, h), radius=27.0, fill=(64, 64, 64, 255), outline=(32, 32, 32, 255), width=7, corners=(True, True, True, True))
        raw_image = temp_image.tobytes()
        self.unpressed_img = pyglet.image.ImageData(temp_image.width, temp_image.height, 'RGBA', raw_image, pitch=-temp_image.width * 4)

        # PRESSED
        temp_image = Image.new("RGBA", (w, h), self.back_color)
        draw = ImageDraw.Draw(temp_image)
        draw.rounded_rectangle(xy=(0, 0, w, h), radius=27.0, fill=(32, 32, 32, 255), outline=(64, 64, 64, 255), width=4, corners=(True, True, True, True))
        raw_image = temp_image.tobytes()
        self.pressed_img = pyglet.image.ImageData(temp_image.width, temp_image.height, 'RGBA', raw_image, pitch=-temp_image.width * 4)

        # HOVER
        temp_image = Image.new("RGBA", (w, h), self.back_color)
        draw = ImageDraw.Draw(temp_image)
        draw.rounded_rectangle(xy=(0, 0, w, h), radius=27.0, fill=(32, 64, 32, 255), outline=(32, 64, 32, 255), width=2, corners=(True, True, True, True))
        raw_image = temp_image.tobytes()
        self.hover_img = pyglet.image.ImageData(temp_image.width, temp_image.height, 'RGBA', raw_image, pitch=-temp_image.width * 4)

    def setup_widgets(self):
        self.background = Rectangle(0, 0, self.main_window.width, self.main_window.height,
                                    (0, 0, 255, 32),
                                    batch=self.batch)

        w = self.unpressed_img.width
        h = self.unpressed_img.height
        x = round((self.main_window.width - w) / 2)
        y = round((self.main_window.height - (h * 2)) / 3)
        # 使用修复后的FixedPushButton
        self.button1 = FixedPushButton(x, self.main_window.height - (y * 4), self.unpressed_img, self.pressed_img, self.hover_img, self.batch)
        self.button2 = FixedPushButton(x, y * 2, self.unpressed_img, self.pressed_img, self.hover_img, self.batch)

    def setup_handlers(self):
        self.main_window.on_draw = self.window_on_draw

        self.button1_handlers = Button1_Events()
        self.button1.push_handlers(self.button1_handlers)

        self.button2_handlers = Button2_Events()
        self.button2.push_handlers(self.button2_handlers)

        self.main_window.push_handlers(self.button2)
        self.main_window.push_handlers(self.button1)

    def window_on_draw(self):
        self.main_window.clear()
        self.batch.draw()

    def run(self):
        pyglet.app.run()


class Button1_Events():
    def on_press(self, widget: PushButton):
        print(f"{self.__class__.__name__}.on_press")
        widget._sprite.opacity = 27
        return True

    def on_release(self, widget: PushButton):
        print(f"{self.__class__.__name__}.on_release")
        widget._sprite.opacity = 255
        return True


class Button2_Events():
    def on_press(self, widget: PushButton):
        print(f"{self.__class__.__name__}.on_press")
        widget._sprite.opacity = 27
        return True

    def on_release(self, widget: PushButton):
        print(f"{self.__class__.__name__}.on_release")
        widget._sprite.opacity = 255
        return True


if __name__ == "__main__":
    try:
        ww = wh = 500
        app = TestsEventsKlass(ww, wh)
        app.run()
    except Exception as Err:
        print(Err)
        raise

修复说明

  • 问题1解决:通过FixedPushButton重写鼠标事件方法,处理完自身事件后返回True,直接终止事件传递,下层Widget不会再收到同一事件。
  • 问题2解决:在on_mouse_release中优先重置按压状态,避免坐标异常导致的逻辑错误;同时优化on_mouse_motion逻辑,确保hover状态更新稳定。

内容的提问来源于stack exchange,提问作者izd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 05:07:10