关于 python:如何在 tkinter 中编写两个同时按键事件,以使用按键事件字典对角移动画布项目?

How can I program two simultaneous key press events in tkinter to move a canvas item diagonally using a dictionary of keypress events?

下面是在画布上移动方块的代码。
它捕获箭头键按下事件并向上、向下、向左和向右移动方块。一次按两个箭头(例如向上和向左)不会诊断性地移动方块。相反,它仅沿所需的两个方向之一移动。

如何更改此代码以实现正方形的平滑对角线移动。感谢您的宝贵时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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)


我如何编程...事件

程序部分:
Tkinter 可以自己生成 UI 事件,而无需在 UI 的"前面"实际发生外部刺激。因此,"我如何对事件进行编程"部分是用这种方法完成的:

1
2
3
4
5
6
7
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 在其他帖子中很好解释的那样,UI 检测应牢记,持有 ArrowUp 和/或 ArrowLeft 可能会在现实中导致自动生成的 UI-* 事件序列,而不是一个\\的控制(过去使用 BIOS 键盘打字速率设置,一旦键盘检测到按键被按下,负责自动重复击键......还没有结束......)

如何阅读输入刺激

Tkinter 有一组强大的 MVC-Controller-Part 方法来处理(自然的 UI 检测和人工"注入"由 .event_generate() )事件。这对于任务的其他方面很重要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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 配备了以下方法:

1
2
3
4
5
6
7
#                      |<<_aNamedEVENT_>>|<<______________________________aHANDLER>>|
#                      |or               |                                          |
#                      |<<_VirtualEVENT>>|                                          |
#                      |                 |                                          |
.bind(                 "<KeyPress-Left>", self.__doWidgetBoundTaskSpecificHANDLER  )
.bind_class("Button", "<KeyPress-Left>", self.__doClass_BoundTaskSpecificHANDLER  )
.bind_all(             "<KeyPress-Left>", self.__doApplicBoundTaskSpecificHANDLER  )

如何编程与字典一起移动

这是一个绿色领域的问题,如果限制在 MVC-Model-Part 中使用字典,那么就可以了。在上述问题之后,您的有限状态自动机 (FSA) 的方向(不仅基于 { <KeyPress> | <KeyRelease> } 一对盲状态转换\\'触发器,而且基于键序列,具有 TimeDOMAIN接近操作和具有扩展,单键和双键按压状态语法{零,LeftUpRightDownLeft && UpLeft && DnRight && UpRight Dn } 和处理 ) 变得有点复杂,但是对于第一个原型,您可以只更改字典分配规则并从以下内容开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

我只是在玩同时按键,结果发现它们之间的间隔最多为 10 毫秒,因此如果您检查两个按钮是否在 20 毫秒内被按下,您可以认为它们是同时按下的。不过,我确信有一个更优雅的解决方案。