将按键发送到嵌入式 Pygame
Posted
技术标签:
【中文标题】将按键发送到嵌入式 Pygame【英文标题】:Dispatching keypresses to embedded Pygame 【发布时间】:2018-02-13 16:06:58 【问题描述】:我一直在努力创建一些我可以在将来使用的代码,以便在 tkinter 窗口中嵌入一个 pygame 窗口,以便使用 tkinter 菜单和按钮。我目前在处理按键时遇到了一些问题。我希望所有按键都由 pygame 而不是 tkinter 处理,这样如果 pygame 元素被设为全屏(因此意味着不使用 tkinter),那么 tkinter 键绑定将被忽略。
我的问题是,当窗口最初打开时(或在它被单击并再次打开后),只有 tkinter 正在注册键绑定。一旦用户点击 pygame 窗口,只有 pygame 注册键绑定。我的问题是如何检测 tkinter 或 pygame 是否检测到按键,以及如何使 pygame 在检测到按键时检测到按键而不是 tkinter?
我的代码在下面(对不起,它很长)
import pygame, os, _tkinter, sys
try:
import Tkinter as tk
BOTH,LEFT,RIGHT,TOP,BOTTOM,X,Y = tk.BOTH,tk.LEFT,tk.RIGHT,tk.TOP,tk.BOTTOM,tk.X,tk.Y
two = True
except ImportError:
import tkinter as tk
from tkinter.constants import *
two = False
from pygame.locals import *
class PygameWindow(tk.Frame):
""" Object for creating a pygame window embedded within a tkinter window.
Please note: Because pygame only supports a single window, if more than one
instance of this class is created then updating the screen on one will update
all of the windows.
"""
def __init__(self, pygame_size, pygame_side, master=None, **kwargs):
"""
Parameters:
pygame_size - tuple - The initial size of the pygame screen
pygame_side - string - A direction to pack the pygame window to
master - The window's master (often a tk.Tk() instance
pygame_minsize - tuple - The minimum size of the pygame window.
If none is specified no restrictions are placed
pygame_maxsize - tuple - The maximum size of the pygame window.
If none is specified no restrictions are placed.
Note: This includes the pygame screen even when fullscreen.
tkwin - string - A direction to pack a tkinter frame to designed to be
used to contain widgets to interact with the pygame window.
If none is specified the frame is not included.
fullscreen - boolean - Whether fullscreen should be allowed
menu - boolean - Whether a menu bar should be included.
the menu bar contains a File menu with quit option and an Options
menu with fullscreen option (If enabled)
"""
# I have decided to use a global variable here because pygame only supports a single screen,
# this should limit confusion if multiple instances of the class are created because each
# instance will always contain the same screen. A global variable should hopefully make this
# clearer than screen being a seperate attribute for each instance
global screen
self.master = master
self.fullscreen = tk.BooleanVar(value=False)
if two:
tk.Frame.__init__(self,master)
else:
super().__init__(self,master)
self.pack(fill=BOTH,expand=1)
if 'pygame_minsize' in kwargs:
w,h = kwargs['pygame_minsize']
master.minsize(w,h)
del kwargs['pygame_minsize']
w,h = pygame_size
self.embed = tk.Frame(self, width = w, height = h)
self.embed.pack(side=pygame_side,fill=BOTH,expand=1)
if 'tkwin' in kwargs:
if kwargs['tkwin'] != None:
self.tk_frame = tk.Frame(self,bg='purple')
if kwargs['tkwin'] in [TOP,BOTTOM]:
self.tk_frame.pack(side=kwargs['tkwin'],fill=X)
elif kwargs['tkwin'] in [LEFT,RIGHT]:
self.tk_frame.pack(side=kwargs['tkwin'],fill=Y)
else:
raise ValueError('Invalid value for tkwin: "%r"' %kwargs['tkwin'])
del kwargs['tkwin']
if 'fullscreen' in kwargs:
if kwargs['fullscreen']:
self.fs_okay = True
else:
self.fs_okay = False
else:
self.fs_okay = False
os.environ['SDL_WINDOWID'] = str(self.embed.winfo_id())
if sys.platform == "win32":
os.environ['SDL_VIDEODRIVER'] = 'windib'
pygame.display.init()
if 'pygame_maxsize' in kwargs:
w,h = kwargs['pygame_maxsize']
self.pygame_maxsize = (w,h)
screen = pygame.display.set_mode((w,h),RESIZABLE)
del kwargs['pygame_maxsize']
else:
screen = pygame.display.set_mode((0,0),RESIZABLE)
self.pygame_maxsize = (0,0)
screen.fill((255,255,255))
if 'menu' in kwargs:
if kwargs['menu']:
self.menubar = tk.Menu(self.master)
self.master.config(menu=self.menubar)
self.filemenu = tk.Menu(self.menubar,tearoff=0)
self.filemenu.add_command(label='Quit',command=self.close,accelerator='Ctrl+Q')
self.menubar.add_cascade(label='File',menu=self.filemenu)
self.optionmenu = tk.Menu(self.menubar,tearoff=0)
if self.fs_okay:
self.optionmenu.add_checkbutton(label='Fullscreen',command=self.updatefs,variable=self.fullscreen,accelerator='F11')
self.menubar.add_cascade(label='Options',menu=self.optionmenu)
def update(self):
""" Update the both the contents of the pygame screen and
the tkinter window. This should be called every frame.
"""
pressed = pygame.key.get_pressed()
if self.fullscreen.get():
if pressed[K_ESCAPE] or pressed[K_F11] or not pygame.display.get_active():
self.togglefs()
else:
if pressed[K_q] and (pressed[K_LCTRL] or pressed[K_RCTRL]):
self.close()
for event in pygame.event.get(KEYDOWN):
if event.key == K_F11:
self.togglefs()
pygame.event.post(event)
pygame.event.pump()
pygame.display.flip()
self.master.update()
def close(self,*args):
""" Closes the open window."""
self.master.destroy()
def togglefs(self,*args):
"""Toggles the self.fullscreen variable and then calls
the updatefs function.
"""
self.fullscreen.set(not self.fullscreen.get())
self.updatefs()
def updatefs(self):
"""Updates whether the window is fullscreen mode or not
dependent on the value of the fullscreen attribute.
"""
if not self.fs_okay:
self.fullscreen.set(False)
global screen
tmp = screen.convert()
cursor = pygame.mouse.get_cursor()
flags = screen.get_flags()
bits = screen.get_bitsize()
if self.fullscreen.get():
pygame.display.quit()
del os.environ['SDL_WINDOWID']
if sys.platform == "win32":
del os.environ['SDL_VIDEODRIVER']
pygame.display.init()
screen = pygame.display.set_mode(self.pygame_maxsize,FULLSCREEN|(flags&~RESIZABLE),bits)
else:
pygame.display.quit()
os.environ['SDL_WINDOWID'] = str(self.embed.winfo_id())
if sys.platform == "win32":
os.environ['SDL_VIDEODRIVER'] = 'windib'
pygame.display.init()
screen = pygame.display.set_mode(self.pygame_maxsize,RESIZABLE|(flags&~FULLSCREEN),bits)
screen.blit(tmp,(0,0))
pygame.mouse.set_cursor(*cursor)
class TestWindow(PygameWindow):
def __init__(self, pygame_size, pygame_side, master=None, **kwargs):
if two:
PygameWindow.__init__(self,pygame_size, pygame_side, master=master, **kwargs)
else:
super().__init__(self,pygame_size, pygame_side, master=master, **kwargs)
self.drawn = False
self.button1 = tk.Button(self.tk_frame,text = 'Draw', command=self.draw)
self.button1.pack(side=LEFT)
screen.fill((255,255,255))
pygame.display.flip()
def draw(self):
if not self.drawn:
pygame.draw.circle(screen, (0,255,175), (250,250), 125)
else:
screen.fill((255,255,255))
self.drawn = not self.drawn
if __name__ == '__main__':
root = tk.Tk()
window = TestWindow((500,500),LEFT,root,pygame_minsize=(500,500),tkwin=LEFT,menu=True,fullscreen=True)
while True:
try:
window.update()
except _tkinter.TclError:
break
quit()
【问题讨论】:
效果很好,谢谢! 对不起。我没有声望!有机会我会做的 我明白了,没关系。玩得开心,不要犹豫,问其他有趣的问题! 【参考方案1】:如果没有直接的解决方案(我不知道),您可以制作一个处理程序,将 tkinter 中检测到的按键传递给 pygame。
这个想法是把键绑定到一个dispatch_event_to_pygame
函数,该函数将创建一个对应的pygame.event.Event
对象,并通过pygame.event.post
函数将后者注入到pygame的事件循环中。
首先,我定义了一个字典,它建立了我想要从 tkinter 发送到 pygame 的键和 pygame 中的相应符号之间的对应关系:
tkinter_to_pygame =
'Down': pygame.K_DOWN,
'Up': pygame.K_UP,
'Left': pygame.K_LEFT,
'Right': pygame.K_RIGHT
然后,我定义了一个 dispatch_event_to_pygame
函数,它接受一个 tkinter 事件,创建一个相应的 pygame 事件,然后发布它:
def dispatch_event_to_pygame(tkEvent):
if tkEvent.keysym in tkinter_to_pygame:
pgEvent = pygame.event.Event(pygame.KEYDOWN,
'key': tkinter_to_pygame[tkEvent.keysym])
pygame.event.post(pgEvent)
最后,我在根窗口小部件上绑定了我想要分发给 pygame 的所有键:
for key in tkinter_to_pygame:
root.bind("<>".format(key), dispatch_event_to_pygame)
键名参考:
tkinter
pygame
【讨论】:
你可以在pygame.event.Event
参数中使用pygame.KEYDOWN
鼠标事件吗?我有同样的问题,此外,我还想在我的 pygame(嵌入在 tkinter 中)中点击鼠标。以上是关于将按键发送到嵌入式 Pygame的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法使用 Capybara 将按键发送到 Webkit?