如何在tkinter中编程实现两个同时的按键事件,通过按键事件字典对画布项目进行对角移动?

0 投票
2 回答
3302 浏览
提问于 2025-04-29 12:15

下面是一个用来在画布上移动方块的代码。它可以捕捉到你按下的箭头键事件,然后让方块向上、向下、向左和向右移动。如果你同时按下两个箭头键(比如上和左),方块不会对角移动,而是只会朝其中一个方向移动。

我该如何修改这个代码,让方块能够平滑地对角移动呢?谢谢你的帮助。

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 个回答

0

我在玩同时按键的事情,发现它们之间的时间差最多只有10毫秒。所以如果你检查两个按钮是否在20毫秒内被按下,就可以认为它们是同时按下的。不过,我相信还有更好的解决办法。

2

如何编程处理...事件

编程部分: 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

撰写回答