【问题标题】:Tkinter getting key pressed event from a functionTkinter 从函数中获取按键事件
【发布时间】:2014-01-29 19:29:25
【问题描述】:

我有以下代码,

如果我按“左箭头键”,它只会打印 move player to left 但是我需要一种功能,在该功能中按下给定的箭头键可以将玩家移动到给定的方向。

有没有办法在我的move_dir 函数中检测按键事件
PS:对python相当陌生

import Tkinter as tk

move = 1
pos = -1


def move_dir():
    global move
    global pos
    while move ==1:
        if pos == 0:
            print 'move player to left'

        elif pos == 1:
            print 'move player to right'

        elif pos == -1:
            print 'stop moving!'

def kr(event):
    global move
    global pos
    global root
    if event.keysym == 'Right':
        move = 1
        pos = 0
        move_dir()
        print 'right ended'
    elif event.keysym == 'Left':
        move = 1
        pos = 1
        move_dir()
        print 'left ended'
    elif event.keysym == 'Space':
        move = 0
        move_dir()
    elif event.keysym == 'Escape':
        root.destroy()

root = tk.Tk()
print( "Press arrow key (Escape key to exit):" )
root.bind_all('<KeyRelease>', kr)
root.mainloop()

【问题讨论】:

标签: python tkinter


【解决方案1】:

如果您想要制作动画,您应该使用after 来设置动画循环。动画循环如下所示:

def animate():
    <draw one frame of your animation>
    after(<delay>, animate)

您可以输入任何您想绘制框架的代码。在您的情况下,听起来您想向左或向右移动某些东西。 &lt;delay&gt; 参数定义您的帧速率。例如,要获得 30FPS,您的延迟大约为 33 (1000ms / 30)。唯一需要注意的是&lt;draw one from of your animation&gt; 需要运行得非常快(10 毫秒或更短)才能不阻塞 GUI。

对于您的特定问题,您可以设置一个变量来定义用户按键时的方向,然后在他们释放按键时取消设置该变量。您的事件处理程序和动画函数可能如下所示:

def animate():
    if direction is not None:
        print "move player to the ", direction
    after(33, animate)

def on_keypress(event):
    global direction
    if event.keysym == "Left":
        direction = "left"
    elif event.keysum == "right":
        direction = "right"

def on_keyrelease(event):
    global direction
    direction = None

看看它是如何工作的?当你按下一个键时,它定义了方向。然后,每 33 毫秒检查一次方向,如果方向已定义,则移动播放器。当用户释放按钮时,方向变为未定义并且移动停止。

把它们放在一起,并使用一个类来避免使用全局变量,它看起来像下面这样。这会在画布上创建一个球,您可以向左、向右、向上和向下移动:

import Tkinter as tk

class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        self.direction = None

        self.canvas = tk.Canvas(width=400, height=400)
        self.canvas.pack(fill="both", expand=True)
        self.canvas.create_oval(190, 190, 210, 210, 
                                tags=("ball",),
                                outline="red", fill="red")

        self.canvas.bind("<Any-KeyPress>", self.on_press)
        self.canvas.bind("<Any-KeyRelease>", self.on_release)
        self.canvas.bind("<1>", lambda event: self.canvas.focus_set())

        self.animate()

    def on_press(self, event):
        delta = {
            "Right": (1,0),
            "Left": (-1, 0),
            "Up": (0,-1),
            "Down": (0,1)
        }
        self.direction = delta.get(event.keysym, None)

    def on_release(self, event):
        self.direction = None

    def animate(self):
        if self.direction is not None:
            self.canvas.move("ball", *self.direction)
        self.after(50, self.animate)

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()

【讨论】:

  • 谢谢布莱恩。我认为这个问题正在朝着不同的方向发展。我必须通过箭头键控制步进电机,而不是移动player。所以leftright 击键使我的电机朝那个方向移动。我不关心用户界面。学习 guiLoop 很好。
  • @Vabs:同样的技术也适用于步进电机。你可以在“动画”功能中做任何你想做的事——你根本不需要画任何东西。我只是想要一个很容易看到发生了什么的例子——stackoverflow 没有步进电机:-)。如果您所做的只是为每次按键移动一步(而不是在按住键时移动它),您可以避免所有这些,只需直接在每个键上进行绑定。
  • self.after(50, self.animate)也是我在guiLoop中使用的。
【解决方案2】:

编辑 4

你有一个 while 循环,你想与 Tkinter 主循环结合。 在这种情况下,您希望在按下键时移动并在释放键时停止移动。 下面的代码允许你这样做:

import Tkinter as tk
from guiLoop import guiLoop # https://gist.github.com/niccokunzmann/8673951#file-guiloop-py

direction = 0
pos = 0 # the position should increase and decrease depending on left and right
# I assume pos can be ... -3 -2 -1 0 1 2 3 ...

@guiLoop
def move_dir():
    global pos
    while True: # edit 1: now looping always
        print 'moving', direction 
        pos = pos + direction
        yield 0.5 # move once every 0.5 seconds

def kp(event):
    global direction # edit 2
    if event.keysym == 'Right':
        direction  = 1 # right is positive
    elif event.keysym == 'Left':
        direction = -1
    elif event.keysym == 'Space':
        direction = 0 # 0 is do not move
    elif event.keysym == 'Escape':
        root.destroy()

def kr(event):
    global direction
    direction = 0

root = tk.Tk()
print( "Press arrow key (Escape key to exit):" )
root.bind_all('<KeyPress>', kp)
root.bind_all('<KeyRelease>', kr)
move_dir(root)
root.mainloop()

要了解这是如何实现的,您可以阅读源代码或阅读 Bryan Oakley 的第二个答案。

编辑 3

无法直接检测move_dir 函数中的按键。 您可以在move_dir 函数中使用root.update(),以便在调用root.update 时执行krkproot.update() 还会重新绘制窗口,以便用户可以看到更改。

root.mainloop()可以看成while True: root.update()

【讨论】:

  • 感谢您的回复,但这不是我要找的。让我再解释一下,如果我按右键,只要没有按其他键,我就想向右移动。在您的示例中,它移动一个位置并停止,但我需要保持递增该位置,直到按下 Space 或 Left 或 Escape
  • 我编辑了这篇文章。我猜到了 :) 并再次编辑.. 更改了 move_dir 的调用。我创建了 guiLoop 以使使用 while 循环和 Tkinter 成为可能。如果您不想使用它,请告诉我。
  • 让我知道你是否可以将它与 guiLoop 一起使用。我会很高兴的。有可能在没有连续函数调用的while循环的情况下做到这一点。
  • 感谢您的及时回复。我正在使用 guiLoop,但我也很想知道其他可能性。
  • 每当我在 GUI 程序中看到无限循环时,我的蜘蛛都会感觉到刺痛。即使你有一个收益,我认为有更好的方法来完成同样的事情。
猜你喜欢
  • 1970-01-01
  • 2015-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-19
  • 2012-10-27
相关资源
最近更新 更多