2

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__
Cœur
  • 37,241
  • 25
  • 195
  • 267
DJ80
  • 43
  • 1
  • 1
  • 8

1 Answers1

6

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())
David Robinson
  • 77,383
  • 16
  • 167
  • 187
  • @Mr.Robinson I want to do this for a series of inputs. is there some way i could code that to be reusable, or am I just stuck making one function per input? – DJ80 Jan 19 '13 at 05:09
  • 1
    @DJ80 you might not need one function per input, but you need to better define your problem so it can be properly answered. – mmgp Jan 19 '13 at 05:18
  • I want to have a series of 6 entries like S1 that assign values into the Person _attributes dictionary, and several other types of input in other forms that do the same. I was hoping to do it through a series of widgets (some Entry, some Spinbox, some List). Is there a function that I could write that would allow me to take an argument while executing key_pressed so that I could attach the appropriate value to the proper key in the dictionary? – DJ80 Jan 19 '13 at 05:41
  • 1
    @DJ80: Wild guess: is it STR, CON, DEX, INT, WIS, CHA? – David Robinson Jan 19 '13 at 16:16
  • @DavidRobinson, yeah, you figured out my secret ;) I had managed to get something working this morning by making a Class that I may be able to use for these entries and more. I'll edit my original post to add the new class. If you can look at it to make sure i'm not f'ing myself later in the program, I'd appreciate it. – DJ80 Jan 19 '13 at 21:20
  • @DJ80: That solution is pretty good. I would still recommend organizing everything into a class. – David Robinson Jan 19 '13 at 21:34