在这种情况下使用 TKinter 捕获密钥
Capture keys with TKinter with this scenario
我想要捕获所有击键,或者将击键与按钮相关联。目前,除了用户点击按钮外,该游戏中没有其他用户输入。我想为每个按钮分配一个键盘字母。我也在玩 pynput,但由于该程序已经在使用 TKinter,看来我应该能够利用它的功能来完成它。
我可以在主游戏 class 中有一个 on_press 方法,然后为每个键调用适当的函数(与用户单击该键相同),或者也许有更好的方法.
我见过的大多数示例都处理从 tkinter class 创建的对象,但在这种情况下,它已从我的主程序中删除了几个级别。
这是我从 GitHub 获得的游戏,并根据我的喜好进行了调整。所以我试图在结构上尽可能少地改变它。
在Graphics.py中,我看到这段代码:
class GraphWin(tk.Canvas):
def __init__(self, title="Graphics Window", width=200, height=200):
master = tk.Toplevel(_root)
master.protocol("WM_DELETE_WINDOW", self.close)
tk.Canvas.__init__(self, master, width=width, height=height)
self.master.title(title)
self.pack()
master.resizable(0,0)
self.foreground = "black"
self.items = []
self.mouseX = None
self.mouseY = None
self.bind("<Button-1>", self._onClick) #original code
self.height = height
self.width = width
self._mouseCallback = None
self.trans = None
def _onClick(self, e):
self.mouseX = e.x
self.mouseY = e.y
if self._mouseCallback:
self._mouseCallback(Point(e.x, e.y))
主程序基本上是这样的:
def main():
# first number is width, second is height
screenWidth = 800
screenHeight = 500
mainWindow = GraphWin("Game", screenWidth, screenHeight)
game = game(mainWindow)
mainWindow.bind('h', game.on_press()) #<---- I added this
#listener = keyboard.Listener(on_press=game.on_press, on_release=game.on_release)
#listener.start()
game.go()
#listener.join()
mainWindow.close()
if __name__ == '__main__':
main()
我在游戏中添加了一个测试功能class,目前没有触发。
def on_press(self):
#print("key=" + str(key))
print( "on_press")
#if key == keyboard.KeyCode(char='h'):
# self.hit()
按钮设置如下:
def __init__( self, win ):
# First set up screen
self.win = win
win.setBackground("dark green")
xmin = 0.0
xmax = 160.0
ymax = 220.0
win.setCoords( 0.0, 0.0, xmax, ymax )
self.engine = MouseTrap( win )
然后……
self.play_button = Button( win, Point(bs*8.5,by), bw, bh, 'Play')
self.play_button.setRun( self.play )
self.engine.registerButton( self.play_button )
最后,按钮代码在 guiengine.py
class Button:
"""A button is a labeled rectangle in a window.
It is activated or deactivated with the activate()
and deactivate() methods. The clicked(p) method
returns true if the button is active and p is inside it."""
def __init__(self, win, center, width, height, label):
""" Creates a rectangular button, eg:
qb = Button(myWin, Point(30,25), 20, 10, 'Quit') """
self.runDef = False
self.setUp( win, center, width, height, label )
def setUp(self, win, center, width, height, label):
""" set most of the Button data - not in init to make easier
for child class methods inheriting from Button.
If called from child class with own run(), set self.runDef"""
w,h = width/2.0, height/2.0
x,y = center.getX(), center.getY()
self.xmax, self.xmin = x+w, x-w
self.ymax, self.ymin = y+h, y-h
p1 = Point(self.xmin, self.ymin)
p2 = Point(self.xmax, self.ymax)
self.rect = Rectangle(p1,p2)
self.rect.setFill('lightgray')
self.rect.draw(win)
self.label = Text(center, label)
self.label.draw(win)
self.deactivate()
def clicked(self, p):
"Returns true if button active and p is inside"
return self.active and \
self.xmin <= p.getX() <= self.xmax and \
self.ymin <= p.getY() <= self.ymax
def getLabel(self):
"Returns the label string of this button."
return self.label.getText()
def activate(self):
"Sets this button to 'active'."
self.label.setFill('black')
self.rect.setWidth(2)
self.active = True
def deactivate(self):
"Sets this button to 'inactive'."
self.label.setFill('darkgrey')
self.rect.setWidth(1)
self.active = False
def setRun( self, function ):
"set a function to be the mouse click event handler"
self.runDef = True
self.runfunction = function
def run( self ):
"""The default event handler. It either runs the handler function
set in setRun() or it raises an exception."""
if self.runDef:
return self.runfunction()
else:
#Neal change for Python3
#raise RuntimeError, 'Button run() method not defined'
raise RuntimeError ('Button run() method not defined')
return False # exit program on error
请求额外代码:
class Rectangle(_BBox):
def __init__(self, p1, p2):
_BBox.__init__(self, p1, p2)
def _draw(self, canvas, options):
p1 = self.p1
p2 = self.p2
x1,y1 = canvas.toScreen(p1.x,p1.y)
x2,y2 = canvas.toScreen(p2.x,p2.y)
return canvas.create_rectangle(x1,y1,x2,y2,options)
def clone(self):
other = Rectangle(self.p1, self.p2)
other.config = self.config
return other
class Point(GraphicsObject):
def __init__(self, x, y):
GraphicsObject.__init__(self, ["outline", "fill"])
self.setFill = self.setOutline
self.x = x
self.y = y
def _draw(self, canvas, options):
x,y = canvas.toScreen(self.x,self.y)
return canvas.create_rectangle(x,y,x+1,y+1,options)
def _move(self, dx, dy):
self.x = self.x + dx
self.y = self.y + dy
def clone(self):
other = Point(self.x,self.y)
other.config = self.config
return other
def getX(self): return self.x
def getY(self): return self.y
更新
我在评论中的一些注释:
它正在使用这个:http://mcsp.wartburg.edu/zelle/python/graphics.py John Zelle 的 graphic.py.
http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf - 请参阅 class _BBox(GraphicsObject):了解常用方法。
我看到 class GraphWin 有一个任意键 - 它在其中捕获键。但是我如何将它恢复到我的主程序中,尤其是作为一个在用户输入时立即触发的事件?
我是否需要编写自己的侦听器 - 另请参阅 …。 post 有一个等待键的 while 循环。我不确定我会在哪里放置这样一个 while 循环,以及如何触发 "Game" class 来触发事件。我需要编写自己的侦听器吗?
每当您想将鼠标和键盘输入与小部件结合使用时,我强烈建议您使用内置的 .bind()
方法。 .bind()
可以有两个值:
- 输入类型
- 回调函数名称
一个例子:
entry_name.bind("<Return>", function_name_here)
只要按下键盘上的 Return
或 Enter
键,就会调用 function_name_here()
函数。这个方法几乎适用于所有的 tk widgets。您还可以说明组合键和鼠标控制。(Ctrl-K、左键单击等)。如果你想将多个击键绑定到某个回调函数,那么只需将两者绑定为相同的回调函数,如下所示:
entry_name.bind("<Return>", function_name_here)
entry_name.bind("<MouseButton-1>", function_name_here)
这将允许您在按下 Return
或单击鼠标左键时调用该函数。
on_press()
命令未触发的原因是 .bind()
调用绑定到 Canvas
的实例。这意味着 canvas 小部件必须具有焦点才能注册按键。
使用 bind_all
代替 bind
。
解决此问题的替代方法:
使用 mainWindow.bind_all("h", hit)
- 将字母 h 直接绑定到“hit”按钮处理函数(只需确保 hit 函数具有如下签名:
def hit(self, event='')
使用mainWindow.bind_all("h", game.on_press)
- 将按键绑定到整个应用程序
使用 root.bind("h", game.on_press)
- 将按键绑定到根 window(也许 toplevel
在这里更准确,具体取决于是否有多个 windows)
与捕获任何键相关,这里有一些关于使用 "<Key>"
事件说明符执行此操作的示例:https://tkinterexamples.com/events/keyboard
我想要捕获所有击键,或者将击键与按钮相关联。目前,除了用户点击按钮外,该游戏中没有其他用户输入。我想为每个按钮分配一个键盘字母。我也在玩 pynput,但由于该程序已经在使用 TKinter,看来我应该能够利用它的功能来完成它。
我可以在主游戏 class 中有一个 on_press 方法,然后为每个键调用适当的函数(与用户单击该键相同),或者也许有更好的方法.
我见过的大多数示例都处理从 tkinter class 创建的对象,但在这种情况下,它已从我的主程序中删除了几个级别。
这是我从 GitHub 获得的游戏,并根据我的喜好进行了调整。所以我试图在结构上尽可能少地改变它。
在Graphics.py中,我看到这段代码:
class GraphWin(tk.Canvas):
def __init__(self, title="Graphics Window", width=200, height=200):
master = tk.Toplevel(_root)
master.protocol("WM_DELETE_WINDOW", self.close)
tk.Canvas.__init__(self, master, width=width, height=height)
self.master.title(title)
self.pack()
master.resizable(0,0)
self.foreground = "black"
self.items = []
self.mouseX = None
self.mouseY = None
self.bind("<Button-1>", self._onClick) #original code
self.height = height
self.width = width
self._mouseCallback = None
self.trans = None
def _onClick(self, e):
self.mouseX = e.x
self.mouseY = e.y
if self._mouseCallback:
self._mouseCallback(Point(e.x, e.y))
主程序基本上是这样的:
def main():
# first number is width, second is height
screenWidth = 800
screenHeight = 500
mainWindow = GraphWin("Game", screenWidth, screenHeight)
game = game(mainWindow)
mainWindow.bind('h', game.on_press()) #<---- I added this
#listener = keyboard.Listener(on_press=game.on_press, on_release=game.on_release)
#listener.start()
game.go()
#listener.join()
mainWindow.close()
if __name__ == '__main__':
main()
我在游戏中添加了一个测试功能class,目前没有触发。
def on_press(self):
#print("key=" + str(key))
print( "on_press")
#if key == keyboard.KeyCode(char='h'):
# self.hit()
按钮设置如下:
def __init__( self, win ):
# First set up screen
self.win = win
win.setBackground("dark green")
xmin = 0.0
xmax = 160.0
ymax = 220.0
win.setCoords( 0.0, 0.0, xmax, ymax )
self.engine = MouseTrap( win )
然后……
self.play_button = Button( win, Point(bs*8.5,by), bw, bh, 'Play')
self.play_button.setRun( self.play )
self.engine.registerButton( self.play_button )
最后,按钮代码在 guiengine.py
class Button:
"""A button is a labeled rectangle in a window.
It is activated or deactivated with the activate()
and deactivate() methods. The clicked(p) method
returns true if the button is active and p is inside it."""
def __init__(self, win, center, width, height, label):
""" Creates a rectangular button, eg:
qb = Button(myWin, Point(30,25), 20, 10, 'Quit') """
self.runDef = False
self.setUp( win, center, width, height, label )
def setUp(self, win, center, width, height, label):
""" set most of the Button data - not in init to make easier
for child class methods inheriting from Button.
If called from child class with own run(), set self.runDef"""
w,h = width/2.0, height/2.0
x,y = center.getX(), center.getY()
self.xmax, self.xmin = x+w, x-w
self.ymax, self.ymin = y+h, y-h
p1 = Point(self.xmin, self.ymin)
p2 = Point(self.xmax, self.ymax)
self.rect = Rectangle(p1,p2)
self.rect.setFill('lightgray')
self.rect.draw(win)
self.label = Text(center, label)
self.label.draw(win)
self.deactivate()
def clicked(self, p):
"Returns true if button active and p is inside"
return self.active and \
self.xmin <= p.getX() <= self.xmax and \
self.ymin <= p.getY() <= self.ymax
def getLabel(self):
"Returns the label string of this button."
return self.label.getText()
def activate(self):
"Sets this button to 'active'."
self.label.setFill('black')
self.rect.setWidth(2)
self.active = True
def deactivate(self):
"Sets this button to 'inactive'."
self.label.setFill('darkgrey')
self.rect.setWidth(1)
self.active = False
def setRun( self, function ):
"set a function to be the mouse click event handler"
self.runDef = True
self.runfunction = function
def run( self ):
"""The default event handler. It either runs the handler function
set in setRun() or it raises an exception."""
if self.runDef:
return self.runfunction()
else:
#Neal change for Python3
#raise RuntimeError, 'Button run() method not defined'
raise RuntimeError ('Button run() method not defined')
return False # exit program on error
请求额外代码:
class Rectangle(_BBox):
def __init__(self, p1, p2):
_BBox.__init__(self, p1, p2)
def _draw(self, canvas, options):
p1 = self.p1
p2 = self.p2
x1,y1 = canvas.toScreen(p1.x,p1.y)
x2,y2 = canvas.toScreen(p2.x,p2.y)
return canvas.create_rectangle(x1,y1,x2,y2,options)
def clone(self):
other = Rectangle(self.p1, self.p2)
other.config = self.config
return other
class Point(GraphicsObject):
def __init__(self, x, y):
GraphicsObject.__init__(self, ["outline", "fill"])
self.setFill = self.setOutline
self.x = x
self.y = y
def _draw(self, canvas, options):
x,y = canvas.toScreen(self.x,self.y)
return canvas.create_rectangle(x,y,x+1,y+1,options)
def _move(self, dx, dy):
self.x = self.x + dx
self.y = self.y + dy
def clone(self):
other = Point(self.x,self.y)
other.config = self.config
return other
def getX(self): return self.x
def getY(self): return self.y
更新
我在评论中的一些注释: 它正在使用这个:http://mcsp.wartburg.edu/zelle/python/graphics.py John Zelle 的 graphic.py.
http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf - 请参阅 class _BBox(GraphicsObject):了解常用方法。
我看到 class GraphWin 有一个任意键 - 它在其中捕获键。但是我如何将它恢复到我的主程序中,尤其是作为一个在用户输入时立即触发的事件?
我是否需要编写自己的侦听器 - 另请参阅
每当您想将鼠标和键盘输入与小部件结合使用时,我强烈建议您使用内置的 .bind()
方法。 .bind()
可以有两个值:
- 输入类型
- 回调函数名称
一个例子:
entry_name.bind("<Return>", function_name_here)
只要按下键盘上的 Return
或 Enter
键,就会调用 function_name_here()
函数。这个方法几乎适用于所有的 tk widgets。您还可以说明组合键和鼠标控制。(Ctrl-K、左键单击等)。如果你想将多个击键绑定到某个回调函数,那么只需将两者绑定为相同的回调函数,如下所示:
entry_name.bind("<Return>", function_name_here)
entry_name.bind("<MouseButton-1>", function_name_here)
这将允许您在按下 Return
或单击鼠标左键时调用该函数。
on_press()
命令未触发的原因是 .bind()
调用绑定到 Canvas
的实例。这意味着 canvas 小部件必须具有焦点才能注册按键。
使用 bind_all
代替 bind
。
解决此问题的替代方法:
使用
mainWindow.bind_all("h", hit)
- 将字母 h 直接绑定到“hit”按钮处理函数(只需确保 hit 函数具有如下签名:def hit(self, event='')
使用
mainWindow.bind_all("h", game.on_press)
- 将按键绑定到整个应用程序使用
root.bind("h", game.on_press)
- 将按键绑定到根 window(也许toplevel
在这里更准确,具体取决于是否有多个 windows)
与捕获任何键相关,这里有一些关于使用 "<Key>"
事件说明符执行此操作的示例:https://tkinterexamples.com/events/keyboard