gpt4 book ai didi

python - 在 tkinter 中中断嵌入式 pygame 会跳过 KEYUP 事件并认为该键仍被按下

转载 作者:行者123 更新时间:2023-12-03 19:42:34 24 4
gpt4 key购买 nike

在这段代码中,我按住 'shift' 将屏幕变为绿色。如果在按住 'shift' 时 pygame 焦点被中断,它会跳过 KEYUP 事件,并且 pygame 继续认为正在按住 'shift'。模拟 KEYUP 事件不起作用。我发现的唯一解决方法是手动按下和释放“shift”,但我不希望用户必须这样做。

为了演示,运行代码并按住“Shift”,然后按住“Enter”打开一个对话框。然后松开“Shift”,然后退出对话框。即使没有按住“Shift”键,绿屏也会保留。

如果在将 'embedding_pygame_and_showing_the_bug' 设置为 False 后再次运行代码,您将看到 KEYUP 事件没有被跳过。

import tkinter as tk
import pygame
import os
from tkinter.simpledialog import askstring

root = tk.Tk()
root.geometry("200x100")

embedding_pygame_and_showing_the_bug = True
if embedding_pygame_and_showing_the_bug:
embed_frame = tk.Frame(root)
embed_frame.pack(fill='both', expand=True)
os.environ['SDL_WINDOWID'] = str(embed_frame.winfo_id())
os.environ['SDL_VIDEODRIVER'] = 'windib'

pygame.init()
screen = pygame.display.set_mode((200, 100))
pygame.event.set_blocked([pygame.MOUSEMOTION, pygame.ACTIVEEVENT])
while True:
root.update()
for event in pygame.event.get():
print(event)
screen.fill((255, 255, 255))
if pygame.key.get_mods() & pygame.KMOD_SHIFT:
screen.fill((50, 205, 50))

if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN:
askstring(' ', ' ', parent=root)
# simulating a KEYUP does not convince pygame think that shift is not being pressed
pygame.event.post(pygame.event.Event(pygame.KEYUP, {'key': 304, 'mod': 0, 'scancode': 42, 'window': None}))
pygame.event.pump()

pygame.display.flip()

最佳答案

解释

Pygame 的窗口

您已经注意到的是 KEYUP每当 pygame 的窗口失去焦点时就会发送事件。这样做的原因是 pygame 的关键事件在很大程度上是 SDL 关键事件的包装器(SDL 是一个用 C 编写的库,可以对许多不同的组件进行低级访问,其中一个是图形硬件),如果您查看 SDL_KeyboardEvent 's data fields ,可以看到有一个数据字段叫做windowID :


windowID data 字段负责保存它从中获取键盘输入的窗口的窗口 ID - 仅当窗口处于焦点时,否则窗口没有键盘输入的信息。作为保护措施,SDL 的窗口会自动发送一个人工 KEYUP每当在 windowID 中指定的窗口时发生事件失去焦点(这反过来使 pygame 也发送 KEYUP 事件)。另外需要注意的是,操作系统发送了多个 KEYDOWN每次按住键时都会向窗口发送事件,但 SDL 会自动忽略每个 KEYDOWN除了第一个事件。

Tkinter 的窗口

Tkinter 的窗口与任何其他窗口一样,也从操作系统获取键盘输入——但原始输入。这就是为什么当您按住某个键一段时间时,tkinter 的窗口会显示所有 KeyPress它从操作系统获取的事件。当 tkinter 的窗口失去焦点时,它不会发送任何 KeyRelease事件,因为它没有从操作系统获得任何信息(与 SDL 的窗口不同,其中 KEYUP 事件是人为生成的)。

Tkinter 窗口内的 Pygame

当 pygame 使用 tkinter 的窗口时,它无法捕获操作系统的键盘输入——只能捕获来自 tkinter 的键盘事件。这样做的原因是以下代码行:

os.environ['SDL_WINDOWID'] = str(embed_frame.winfo_id())
SDL_KeyboardEvent现在使用 embed_framewindowID .这意味着 pygame 捕获的所有键盘事件都将来自 tkinter。这就是为什么 tkinter 中的 pygame 没有额外的 KEYUP当 tkinter 的窗口失去焦点时的事件,因为它有 KEYUP当它使用自己的窗口时的事件。

解决方案

除非您愿意编辑和编译 pygame、tkinter 或 SDL,否则无法使用 pygame 的默认事件队列来解决此问题。但是您仍然可以使用自定义事件处理程序或编写自己的事件处理程序并解决此问题。

对于这个例子,只要有一个键监听器就足够了(解决方案使用 pynput ):

import tkinter as tk
import pygame
import os
from tkinter.simpledialog import askstring
from pynput import keyboard


on_enter = False


def on_press(key):
# Uses global 'on_enter' variable
global on_enter

# If key's name is a single letter ('a', '1', etc.) then 'key.char' is used,
# otherwise ('shift', 'enter', etc.) 'key.name' is used
try:
key_ = key.char
except AttributeError:
key_ = key.name

# When 'shift' is down - set color to dark* green
if key_ == 'shift':
screen.fill((50, 205, 50))
# When 'enter' is down - set color to white, run 'askstring'
elif key_ == 'enter':
screen.fill((255, 255, 255))
on_enter = True

def on_release(key):
# If key's name is a single letter ('a', '1', etc.) then 'key.char' is used,
# otherwise ('shift', 'enter', etc.) 'key.name' is used
try:
key_ = key.char
except AttributeError:
key_ = key.name

# When 'shift' is up - set color to white
if key_ == 'shift':
screen.fill((255, 255, 255))


root = tk.Tk()
root.geometry("200x100")

embedding_pygame_and_showing_the_feature = True
if embedding_pygame_and_showing_the_feature:
embed_frame = tk.Frame(root)
embed_frame.pack(fill='both', expand=True)
os.environ['SDL_WINDOWID'] = str(embed_frame.winfo_id())
os.environ['SDL_VIDEODRIVER'] = 'windib'

pygame.init()
screen = pygame.display.set_mode((200, 100))
screen.fill((255, 255, 255))

# Runs 'on_press' when it detects key down,
# runs 'on_release' when it detects key up
listener = keyboard.Listener(on_press=on_press, on_release=on_release)
listener.start()

while True:
# on_enter == True only when 'enter' is down
if on_enter:
# This display flip is necessary as keyboard.Listener runs
# in a separate thread - 'on_enter' can change at any point
# in the while-loop
pygame.display.flip()

askstring(' ', ' ', parent=root)
on_enter = False

root.update()
pygame.display.flip()

关于python - 在 tkinter 中中断嵌入式 pygame 会跳过 KEYUP 事件并认为该键仍被按下,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61105230/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com