如何在tkinter中编程实现两个同时的按键事件,通过按键事件字典对画布项目进行对角移动?
下面是一个用来在画布上移动方块的代码。它可以捕捉到你按下的箭头键事件,然后让方块向上、向下、向左和向右移动。如果你同时按下两个箭头键(比如上和左),方块不会对角移动,而是只会朝其中一个方向移动。
我该如何修改这个代码,让方块能够平滑地对角移动呢?谢谢你的帮助。
from tkinter import *
x = 10
y = 10
a = 100
b = 100
direction = None
def move():
global x_vel
global y_vel
global direction
if direction is not None:
canvas1.move(rect, x_vel,y_vel)
window.after(33,move)
def on_keypress(event):
global direction
global x_vel
global y_vel
direction, x_vel, y_vel = dir_vel[event.keysym]
def on_keyrelease(event):
global direction
direction = None
dir_vel = {
"Left": ("left", -5, 0),
"Right": ('right', 5, 0),
"Down": ('down', 0, 5),
"Up": ('up', 0, -5),}
window = Tk()
window.geometry("400x200")
move()
#canvas and drawing
canvas1=Canvas(window, height = 200, width = 400)
canvas1.grid(row=0, column=0, sticky=W)
coord = [x, y, a, b]
rect = canvas1.create_rectangle(*coord, outline="#fb0", fill="#fb0")
#capturing keyboard inputs and assigning to function
window.bind_all('<KeyPress>', on_keypress)
window.bind_all('<KeyRelease>', on_keyrelease)
2 个回答
我在玩同时按键的事情,发现它们之间的时间差最多只有10毫秒。所以如果你检查两个按钮是否在20毫秒内被按下,就可以认为它们是同时按下的。不过,我相信还有更好的解决办法。
如何编程处理...事件
编程部分: Tkinter可以自己生成用户界面事件,而不需要在用户界面“前面”发生外部刺激。所以,“如何编程一个事件”的部分可以通过这种方法来完成:
self.event_generate( <eventNameId>, **args ) # fire STIMULUS without User-interaction
# # triggers <eventNameId>
# # **args allow to set <keyword>=<value>
# # pairs for Event-fields,
# # that are passed to anEventHANDLER
# # via an-<Event>-object ...
# # ref below ( not the system-assigned ones, sure )
同时性的问题:
原则上,Tkinter/Python代码是按顺序执行的。没有简单的方法可以在同一时刻触发两个事件。简单来说,你的代码必须以某种方式模拟或检测几乎同时发生的事件,因为它本质上是一个顺序处理器。
正如Bryan Oakley在其他帖子中所解释的,用户界面的检测要考虑到,按住ArrowUp和/或ArrowLeft实际上可能会导致自动生成的用户界面事件序列,这些是你无法控制的(就像以前的BIOS键盘重复速率设置,负责在键盘检测到按键被按住时自动重复按键...这些情况并没有结束...)
如何读取输入刺激
Tkinter有一套强大的MVC控制器方法来处理(既包括自然检测到的用户界面事件,也包括通过.event_generate()
人工“注入”的事件)。这对于任务的其他方面非常重要:
# eventInstanceMethods() bear many details about click/key/time/.widget()
# <event>.char on-{ <KeyPress> | <KeyRelease> }
# .keysym on-{ <KeyPress> | <KeyRelease> }
# .keysym_num on-{ <KeyPress> | <KeyRelease> }
# .num on-{ <Mouse-1> | <Mouse-2> | ... } ? 4,5 == <MouseWheel>
# .height on-{ <Configure> }
# .width on-{ <Configure> }
# .serial <-- system-assigned Integer
# .time <-- system-assigned Integer ( .inc each msec )
# .widget <-- system-assigned <widget>-instance
# .x <-- system-assigned <Event>-in-<widget>-mouse-location.x
# .y <-- system-assigned <Event>-in-<widget>-mouse-location.y
# .x_root <-- system-assigned <Event>-on-<Screen>-mouse-location.x
# .y_root <-- system-assigned <Event>-on-<Screen>-mouse-location.y
为了检测这些事件,Tkinter配备了以下方法:
# |<<_aNamedEVENT_>>|<<______________________________aHANDLER>>|
# |or | |
# |<<_VirtualEVENT>>| |
# | | |
.bind( "<KeyPress-Left>", self.__doWidgetBoundTaskSpecificHANDLER )
.bind_class( "Button", "<KeyPress-Left>", self.__doClass_BoundTaskSpecificHANDLER )
.bind_all( "<KeyPress-Left>", self.__doApplicBoundTaskSpecificHANDLER )
如何通过字典编程移动
这是一个全新的问题,如果限制在MVC模型部分使用字典,那就可以开始了。在上面提到的问题之后,你的有限状态自动机(FSA)用于方向(不仅基于{ <KeyPress>
| <KeyRelease>
}这一对盲状态转换的触发器,还基于按键的序列,结合时间域的处理,以及扩展的单键和双键按下状态语法{ nil, Left, Up, Right, Down, Left&&
Up, Left&&
Dn, Right&&
Up, Right&&
Dn }及其处理)会变得有点复杂,但对于第一个原型,你可以简单地更改字典的赋值规则,开始时可以用这样的方式:
def on_keypress( event ): # keeping the Globals-style,
global direction # however shall be rather
global x_vel # implemented in a Class-based
global y_vel # manner
direction = dir_vel[event.keysym][0] # ref. remark on more complex FSA
x_vel += dir_vel[event.keysym][1]
y_vel += dir_vel[event.keysym][2]
def on_keyrelease( event ):
global direction
global x_vel
global y_vel
x_vel -= dir_vel[event.keysym][1]
y_vel -= dir_vel[event.keysym][2]
if abs( x_vel * y_vel ) < 0.1:
direction = None # ref. remark on more complex FSA