python: Taking an Tkinter entry value

DJ80 picture DJ80 · Jan 19, 2013 · Viewed 9k times · Source

I've been trying to get an entry value (the S1 in the code) to set itself as a value (STR in the _attributes dictionary), and I just can't get it to work. I want to make this an eventual toploop, but am going a step at a time on this, as I'm new to programming in general. Am I going about this the right way, or should I just have a button that, when pressed, does a lookup on the entry value at that time and goes with it, instead? I've gone through several tutorials and lessons I've found online for Tkinter, but still seem to be miles away from being able to make anything work the way I expect it to.

#! usr/bin/python27
from Tkinter import *

class Character:

    def __init__(self, **kvargs):
        self._attributes = kvargs

    def set_attributes(self, key, value):
        self._attributes[key] = value
        return

    def get_attributes(self, key):
        return self._attributes.get(key, None)


def attrInput(stat, x, y):
   """Creates a label for entry box"""
   L = Label(B,
             width = 5,
             relief = RIDGE,
             anchor = E,
             text = stat).grid(row = x,
                             column = y)

B = ""
def main():

    Person = Character()

    B = Tk()

    S1 = Entry(B, width = 3)
    S1.grid(row = 0, column = 1)
    S1.bind("<Key>", Person.set_attributes('STR', S1.get()) )
    attrInput("Str: ", 0, 0)

    Button(B, text='Quit', command=B.destroy).grid(row=3, column=0, sticky=W, pady=4)

    B.mainloop()

    print Person.__dict__

if __name__ == '__main__': main()

new code (seems to be working, I'm getting what I want out of it, at least). I'll have to modify it slightly to make it a toploop, but here's the foundation

class Character:

    def __init__(self, **kvargs):
        self._attribute = kvargs

    def set_attribute(self, key, value):
        self._attribute[key] = value
        return

    def get_attribute(self, key):
        return self._attribute.get(key, None)


class attrAsk:

    def __init__(self, master, Char, attrName, Row, Column):
        self.Char = Char
        self.attrName = attrName
        attrInput(attrName+":", Row, Column)
        self.e = Entry(master, width = 3)
        self.e.grid(row = Row, column = Column+1)
        self.e.bind("<KeyRelease>", self.set_attr)

    def set_attr(self, event):
        self.Char.set_attribute(self.attrName, self.e.get())


def attrInput(stat, x, y):
   """Creates a label for entry box"""
   L = Label(box,
             width = 5,
             relief = RIDGE,
             anchor = E,
             text = stat).grid(row = x,
                               column = y)

Person= Character()


box = Tk()

STRENT = attrAsk(box, Person, "STR", 0, 0)
DEXENT = attrAsk(box, Person, "DEX", 1, 0)
CONENT = attrAsk(box, Person, "CON", 2, 0)
INTENT = attrAsk(box, Person, "INT", 3, 0)
WISENT = attrAsk(box, Person, "WIS", 4, 0)
CHAENT = attrAsk(box, Person, "CHA", 5, 0)

Button(box,
       text='Continue',
       command=box.destroy).grid(columnspan = 2,
                                                       row=8,
                                                       column=0,
                                                       sticky=W,
                                                       pady=4)

box.mainloop()

print Person.__dict__

Answer

David Robinson picture David Robinson · Jan 19, 2013

Change the line:

S1.bind("<Key>", Person.set_attributes('STR', S1.get()) )

to something like:

def key_pressed(event):
    Person.set_attributes('STR', S1.get())
S1.bind("<KeyRelease>", key_pressed)

There are two reasons the original code doesn't work:

  1. bind takes a function as its second argument- that function is then called when the event occurs. The expression Person.set_attributes('STR', S1.get()) as you use it, however, just happens immediately. You need to put that expression into a function so that it happens only when the key is pressed.
  2. <Key> means the event occurs when the key is first pressed, but you would rather it happen when the key is released (and therefore the new character has been added). You thus want to use <KeyRelease>.

One other note: it would be a good idea to organize all your functionality, especially the callback methods, into a class. For example:

class Window(object):
    def __init__(self):
        self.person = Character()

        self.B = Tk()

        self.S1 = Entry(B, width = 3)
        self.S1.grid(row = 0, column = 1)

        self.S1.bind("<KeyRelease>", self.key_pressed)
        attrInput("Str: ", 0, 0)

        self.button = Button(B, text='Quit', command=self.B.destroy).grid(row=3, column=0, sticky=W, pady=4)

        self.B.mainloop()

        print self.person.__dict__

    def key_pressed(self, event):
        self.person.set_attributes('STR', self.S1.get())


def main():
    w = Window()


if __name__ == '__main__': main()

The benefit of this organization might not be immediately apparent, but it becomes very useful once you have a large number of callback methods and are keeping track of a large number of widgets.

In response to your comment, you can create both the Entry and the Label objects in a for loop, each on its own row. The key_pressed method can then learn the field and the input text from the event object that gets passed to it, as seen here (try it):

class Window(object):
    def __init__(self):
        self.person = Character()

        self.B = Tk()

        self.fields = ["STR", "DEX", "CON", "INT", "WIS", "CHA"]

        self.inputs = {}
        for i, f in enumerate(self.fields):
            self.inputs[f] = Entry(B, width = 3)
            self.inputs[f].grid(row=i, column=1)
            self.inputs[f].bind("<KeyRelease>", self.key_pressed)
            attrInput(f + ":", i, 0)

        self.button = Button(B, text='Quit', command=self.B.destroy).grid(row=7, column=0, sticky=W, pady=4)

        self.B.mainloop()

        print self.person.__dict__

    def key_pressed(self, event):
        field = self.fields[int(event.widget.grid_info()["row"])]
        self.person.set_attributes(field, event.widget.get())