1

Python 3.1, tkinter/ttk

I wrote something very simple to try and understand how tkinter's variables linked to widgets can be stored in a different class to the widget. Code below.

Questions:

1) why doesn't pressing the button change the label?

2) do I need quite so many selfs? Can the variables within each method manage without self. on the start?

Hopefully the answer will be a useful learning exercise for other tkinter newbies...

from tkinter import *
from tkinter.ttk import *

root = Tk()

class Store:

    def __init__(self):
        self.v = IntVar()
        self.v.set(0)

    def set(self, v):
        self.v.set(v)

class Main:

    def __init__(self):

        self.counter = 0

        self.label = Label(root, textvariable = a.v)
        self.label.pack()

        self.button = Button(root, command = self.counter, text = '+1')
        self.button.pack()

    def counter(self):

        self.counter = self.counter + 1
        a.set(self.counter)

a = Store()
b = Main()

root.mainloop()
Tom
  • 371
  • 1
  • 6
  • 16

2 Answers2

3

Your problem is that you have both a method and a variable named counter. When you click the button, your function isn't being called so the variable isn't being set. At the time you create the button, tkinter thinks that self.counter is a variable rather than a command.

The solution to this particular problem is to rename either the function or the variable. It then should work as you expect.

To answer the question about "too many selfs": you need to use self so that python knows that the object you are referring to should be available everywhere within a specific instance of the object. So, yes, you need all those self's, if you want to refer to variables outside of the function they are defined in.

As for self in the definition of a method, those too are necessary. When you do object.method(), python will automatically send a reference to the object as the first argument to a method, so that the method knows specifically which method is being acted upon.

If you want to know more about the use of "self", there's a specific question related to self here: What is the purpose of self?

Community
  • 1
  • 1
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • I don't think this answer is helpful in a way of learning *practical* and *clean* code... – Peter Varo Sep 12 '13 at 18:15
  • @PeterVaro: that looks like a retaliatory downvote to me, though I don't really care about reputation. You are correct that it isn't particularly helpful in learning practical and clean code, but that's not what was asked. The user specifically asked why pressing the button in the given code didn't work, and I answered that. I think it's disingenuous to say this isn't a helpful answer, and might give the OP the impression my answer is somehow wrong even though it correctly spells out the actual problem with the code. – Bryan Oakley Sep 12 '13 at 19:18
  • Just as mine does @Bryan Oakley! Once you remove the -1 from my answer, I will do the same from you. I also don't care about reps, but since my answer is working (both versions) and are correct, clean and practical ones, I don't know why you gave me -1 in the first place instead of a warning in a comment, or something like that.. – Peter Varo Sep 12 '13 at 19:23
  • @PeterVaro: I don't bargain with votes. I vote for things that are helpful, I down-vote things that aren't, and I don't change my votes so that other people will remove negative votes on me. I hope you'll do the same, that's what makes the voting system useful. Giving someone a negative vote because they gave you a negative vote helps no-one. – Bryan Oakley Sep 12 '13 at 19:41
  • @PeterVaro: re "Just as mine does". No, your answer does not. Part of the question was "why doesn't pressing the button change the label?", and you never came close to addressing that. Nor did you address the question about `self`. You _did_ provide a good alternative solution, but that's not what was asked. If you did that _as well as_ answering the actual question that was asked, you would definitely get my up-vote in spite of you voting me down out of spite. – Bryan Oakley Sep 12 '13 at 19:49
  • You are absolutely right @BryanOakley, I did this, because I thought it was you who warned me AND dv me, and even though I updated my answer, you still did not remove that dv.. Really sorry for this behaviour. I want to remove my dv from your post, but I can't (SO doesn't let me) until you make a minimal edit/change on your post. Please do it, so I could fix my mistake! Thank you! – Peter Varo Sep 12 '13 at 20:41
2

If I were you, I will do this:

import tkinter

root = tkinter.Tk()
var  = tkinter.IntVar()

label  = tkinter.Label(root, textvariable=var)
button = tkinter.Button(root, command=lambda: var.set(var.get() + 1), text='+1')

label.pack()
button.pack()

root.mainloop()

UPDATE:

But, if you insist doing this with two classes (where the first class is only a somewhat pointless interface), then I would do this:

import tkinter

class Store:
    def __init__(self):
        self.variable = tkinter.IntVar()

    def add(self, value):
        var = self.variable
        var.set(var.get() + value)
        return var.get()

class Main(tkinter.Tk):
    def __init__(self, *args, **kwargs):
        tkinter.Tk.__init__(self, *args, **kwargs)
        var = Store()
        self.label  = tkinter.Label(self, textvariable=var.variable)
        self.button = tkinter.Button(self, command=lambda: var.add(1), text='+1')
        self.label.pack()
        self.button.pack()


root = Main()
root.mainloop()

As you may notice, I used in both times the get() and set() methods of the IntVar (in your version I do this thru the Store interface we made), therefore you don't need to use a new variable (like self.counter) because the variable we instantiated is storing the data.

The other thing I used is a lambda expression instead of a full-blown function definition, since we only want to get the current value, add 1 to it, and store it as the new value.

And in my version, the Main class is a subclass of the original tkinter.Tk class -- that's why I call it's __init__ method inside the Main's __init__ method.

Peter Varo
  • 11,726
  • 7
  • 55
  • 77
  • Your answer doesn't seem to answer the question that was asked. The question is specifically asking about multiple classes. Plus, you put some code up without any sort of explanation. If someone is just learning Tkinter, it will be hard for them to understand what you did differently to solve the problem. – Bryan Oakley Sep 12 '13 at 17:36
  • I fixed my answer, although @BryanOakley I don't think that I have to explain my short version, since the only *magic* it has is the `lambda` expression, which is not a tkinter, but a Python *issue*. Anyway, give me back my reps:) – Peter Varo Sep 12 '13 at 18:11
  • 1
    the explanation is necessary, even for short answers, when answering questions from people who are inexperienced. How else are they to know that the solution to the problem is (or isn't) the use of lambda, or the removal of classes, or the fact that you do or don't use `self`, or something else entirely? They don't know any of this stuff, so when you just give them a few lines of code they may very well conclude that classes shouldn't ever be used with tkinter, even though that's not the point you were trying to make. – Bryan Oakley Sep 12 '13 at 18:21
  • Actually I did explain my answer, added the *two-classes* version, and you still did not remove the -1.. Explain that to me @Bryan Oakley, please! – Peter Varo Sep 12 '13 at 18:24
  • I didn't down-vote, but I'm not going to up-vote to offset, because you didn't answer the question that was asked. You said you didn't think the explanation was necessary, I merely wanted to explain why I think it was. – Bryan Oakley Sep 12 '13 at 19:20
  • OK, in that case, sorry for bothering you with that -1. Please edit your answer (just a *poke*) so I could remove my -1 from it! – Peter Varo Sep 12 '13 at 19:24