3

I have the following code,

If I press 'Left Arrow Key', it only prints move player to left But I need a functionality in which pressing the given arrow key moves the player in the given direction.

Is there a way in which I can detect key press event in my move_dir function
PS: fairly new to 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()
Vabs
  • 485
  • 1
  • 5
  • 17
  • I provided a general answer here: http://stackoverflow.com/questions/21532523/tkinter-loop-and-serial-write/21533765#21533765 – User Feb 03 '14 at 17:47

2 Answers2

2

If you're wanting to animate something, you should use after to set up an animation loop. An animation loop looks like this:

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

You can put whatever code you want to draw a frame. In your case it sounds like you want to move something left or right. The <delay> parameter defines your frame rate. For example, to get 30FPS your delay would be about 33 (1000ms / 30). The only important thing to be aware of is that <draw one from of your animation> needs to run pretty quickly (10's of milliseconds or less) in order to not block the GUI.

For your particular problem, you can set a variable to define the direction when the user presses a key, then unset the variable when they release the key. Your event handler and animate function might look something like this:

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

See how that works? When you press a key it defines the direction. Then, every 33 milliseconds you check for the direction, and move your player if the direction is defined. When the user releases the button, the direction becomes undefined and the movement stops.

Putting it all together, and using a class to avoid using global variables, it looks something like the following. This creates a ball on a canvas which you can move left, right, up and down:

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()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thanks Bryan. I think the question is going in a different direction. I have to control a stepper motor from my arrow keys instead of moving a `player`. So `left` or `right` key stroke move my motor in that direction. I don't care about the UI. Learning about guiLoop was nice. – Vabs Jan 29 '14 at 22:25
  • @Vabs: the same technique would work for stepper motors. You can do whatever you want in the "animiate" function -- you don't have to draw anything at all. I just wanted an example that was easy to see what was happening -- stackoverflow doesn't have stepper motors :-). If all you're doing is moving it one step for each keypress (instead of moving it while the key is being held down) you can avoid all of this and just make a binding directly on each key. – Bryan Oakley Jan 29 '14 at 23:14
  • The `self.after(50, self.animate)` is also what I use in the guiLoop. – User Jan 30 '14 at 16:23
1

EDIT 4

You have a while loop that you want to combine with the Tkinter mainloop. In this case you want to move when the key is pressed and stop moving when a key is released. The code below allows you to do this:

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()

To see how this is implemented you can read the source code or read the second answer by Bryan Oakley.

EDIT 3

There is no way to detect a keypress in the move_dir function directly. You can use root.update() in your move_dir function to make it possible for kr, kp to be executed when root.update is called. root.update() alse repaints the windows so that changes can be seen by the user.

root.mainloop() can be seen as while True: root.update()

User
  • 14,131
  • 2
  • 40
  • 59
  • Thanks for the reply, but this is not what I am looking for. Let me explain again, if I press right key, I want to move towards right as long as no other key is pressed. In your example, it moves one position and stops, but I need to keep increment that position, until Space or Left or Escape is pressed – Vabs Jan 29 '14 at 20:35
  • I edited the post. I guessed that :) And edited again.. changed the call of move_dir. I created guiLoop to make it possible to use while loops and Tkinter. Let me know if you do not want to use it. – User Jan 29 '14 at 20:42
  • Let me know if you can use it with guiLoop. I would be very happy. There are possibilities to do it without a while loop with continuous function calls. – User Jan 29 '14 at 20:56
  • Thanks for the prompt responses. I am using guiLoop, but I would love to know about other possibilities also. – Vabs Jan 29 '14 at 21:17
  • 1
    My spider senses tingle whenever I see an infinite loop in a GUI program. Even though you have a yield in there, I think there are better ways to accomplish the same thing. – Bryan Oakley Jan 29 '14 at 21:22
  • Please show the better ways. I was feeling that it is somehow sad that GUIs do not allow for these loops but since we have yield I can now do what I always wanted :) not dictated by the GUI. I remember it was really hard for me as a beginner (1/2 to 1 year of Python) to get the GUI thing right. – User Jan 29 '14 at 21:37
  • @User: I've provided an answer that leverages the event loop without requiring threads or an internal infinite loop. See http://stackoverflow.com/a/21443616/7432 – Bryan Oakley Jan 29 '14 at 22:56