views:

340

answers:

2

I have a problem in Python.

I'm using Tkinter and have four bind events, that listen to key presses on my form. My problem is, that these don't run asynchronously. So, for example I can press one button, and the events are recognized. But when I press and hold two keys at the same time, just one event gets fired.

Is there an alternative way to do this?

    self.f.bind("w", self.player1Up)
    self.f.bind("s", self.player1Down)
    self.f.bind("o", self.player2Up)
    self.f.bind("l", self.player2Down)
A: 

You could bind to "<Key>" and then check event.char and perform the action you want based on the value of that? Granted, I have no idea if this works when multiple keys are pressed at the same time, it may still run in to the exact same problem. I haven't used Tk in ages.

"<Key> The user pressed any key. The key is provided in the char member of the event object passed to the callback (this is an empty string for special keys)."

Wayne
http://groups.google.com/group/comp.lang.python/browse_thread/thread/88788c3b0b0d51d1/c4b7efe2d71bda93 .. this looks to have the solution you may be looking for, but overall I'd say switching to PyGame might be your better option here.
Wayne
+3  A: 

Unfortunately, you are somewhat at the mercy of the underlying auto-repeat mechanism of your system. For example, on the mac I'm using at the moment if I press and hold "w" I'll get a stream of press and release events. While pressed, if I press "o" I get a stream of presses and releases for "o" but no more events for "w".

You will need to set up a mini state machine, and bind to both key press and key release events. This will let you track which keys are pressed and which are not. Then, each time you draw a frame you can query the machine to see which keys are pressed and act accordingly.

Here's a quick hack I threw together. I've only tested it on my mac, and only with python 2.5. I've made no real attempt at being "pythonic" or efficient. The code merely serves to illustrate the technique. With this code you can simultaneously press either "w" or "s" and "o" or "l" to move two paddles up and down.

'''Example that demonstrates keeping track of multiple key events'''
from Tkinter import *

class Playfield:
    def __init__(self):
        # this dict keeps track of keys that have been pressed but not
        # released
        self.pressed = {}

        self._create_ui()

    def start(self):
        self._animate()
        self.root.mainloop()

    def _create_ui(self):
        self.root = Tk()
        self.p1label = Label(text="press w, s to move player 1 up, down", 
                             anchor="w")
        self.p2label = Label(text="press o, l to move player 2 up, down", 
                             anchor="w")
        self.canvas = Canvas(width=440, height=440)
        self.canvas.config(scrollregion=(-20, -20, 420, 420))

        self.p1label.pack(side="top", fill="x")
        self.p2label.pack(side="top", fill="x")
        self.canvas.pack(side="top", fill="both", expand="true")

        self.p1 = Paddle(self.canvas, tag="p1", color="red", x=0, y=0)
        self.p2 = Paddle(self.canvas, tag="p2", color="blue", x=400, y=0)

        self._set_bindings()

    def _animate(self):
        if self.pressed["w"]: self.p1.move_up()
        if self.pressed["s"]: self.p1.move_down()
        if self.pressed["o"]: self.p2.move_up()
        if self.pressed["l"]: self.p2.move_down()
        self.p1.redraw()
        self.p2.redraw()
        self.root.after(10, self._animate)

    def _set_bindings(self):
        for char in ["w","s","o", "l"]:
            self.root.bind("<KeyPress-%s>" % char, self._pressed)
            self.root.bind("<KeyRelease-%s>" % char, self._released)
            self.pressed[char] = False

    def _pressed(self, event):
        self.pressed[event.char] = True

    def _released(self, event):
        self.pressed[event.char] = False

class Paddle():
    def __init__(self, canvas, tag, color="red", x=0, y=0):
        self.canvas = canvas
        self.tag = tag
        self.x = x
        self.y = y
        self.color = color
        self.redraw()

    def move_up(self):
        self.y = max(self.y -2, 0)

    def move_down(self):
        self.y = min(self.y + 2, 400)

    def redraw(self):
        x0 = self.x - 10
        x1 = self.x + 10
        y0 = self.y - 20
        y1 = self.y + 20
        self.canvas.delete(self.tag)
        self.canvas.create_rectangle(x0,y0,x1,y1,tags=self.tag, fill=self.color)

if __name__ == "__main__":
    p = Playfield()
    p.start()
Bryan Oakley