0

I'm working with the tkinter module for the first time and I found the textvariable argument when creating labels/buttons and what not. It only ever updates the first time. I've twisted the method of retrieving the command ever which way but nothing seems to be working. I've hit a brick wall.

from tkinter import *

x = 1
y = 1
z = 0

class App:
    def __init__(self, master):
        frame = Frame(master)
        frame.pack()

        textVar = StringVar()
        self.button = Button(
            frame, textvariable=textVar, command=textVar.set(str(fibonacci()))
        )
        self.button.pack()

def fibonacci():
    global x, y, z
    z = x
    x = x + y
    y = z
    return x

root = Tk()
app = App(root)
root.mainloop()
martineau
  • 119,623
  • 25
  • 170
  • 301
Xevion
  • 570
  • 5
  • 17
  • 1
    The keyword argument `command` takes a function, whereas you're passing *an instance* of the function. Define a function that updates `textVar` and assign the function without parentheses to `command`. – r.ook Nov 10 '18 at 07:15

3 Answers3

0

Take a look at this statement fragment:

self.button = Button(..., command=textVar.set(str(fibonacci()))

It is functionally equivalent to this:

result = textVar.set(str(fibonacci()))
self.button = Button(..., command=result)

See the problem? You are calling textVar.set(...) immediately, and using the result of that as the value for the command attribute. textVar.set(...) returns None, so you're effective doing command=None.

Don't try to put code into the command argument. Instead, create a proper function and make that the value of the command attribute. That will make your code easier to understand, easier to maintain, and easier to debug.

Example:

def __init__(self, master):
    ...
    self.button = Button(..., command=self.do_something)
    ...

The second problem that you have is that you're using a local variable to store the result of StringVar(). That means it will get deleted by the garbage collector once the function has finished running.

To fix this, assign the attribute to an instance variable:

def __init(self, master):
    ...
    self.textVar = StringVar()
    self.button = Button(..., command=self.do_something)
    ...

def do_something()
    self.textVar.set(str(fibonacci()))
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • It seems there is a massive difference between `command=self.update()` and `command=self.update`... Is this the difference between a function's reference and attempting to call it? I'm not sure of the proper way to phrase it. – Xevion Nov 10 '18 at 17:01
  • @Xevion: yes, that is the difference. Though, `self.update()` doesn’t _attempt_ to call it, it actually calls it. – Bryan Oakley Nov 10 '18 at 20:24
0

There's several problems with your code. First you need to make textVar a instance attribute so it remains in existence even after the __init__() method returns. In your code it's a local variable that will go away when it returns.

Secondly, the command option for Button should be set to a function to call later when it's clicked, but your code is calling it once while creating the widget itself. You can fix that by using a lambda expression to define an "anonymous" function that calls the desired function whenever it's called.

x = 1
y = 1
z = 0

class App:
    def __init__(self, master):
        frame = Frame(master)
        frame.pack()

        self.textVar = StringVar()  # Make an instance attribute.
        self.textVar.set(x)  # Set to show initial value of x.
        self.button = Button(frame, textvariable=self.textVar,
                             command=lambda: self.textVar.set(fibonacci()))
        self.button.pack()


def fibonacci():
    global x, y, z
    z = x
    x = x + y
    y = z
    return x

root = Tk()
app = App(root)
root.mainloop()

You could do the same thing without using lambda as shown in the code below. Note the use of command=self.button_click_callback which just specifies the function's name but doesn't actually call it at the point like using command=self.button_click_callback() would. In addition, since button_click_callback was defined as a class method, it will automatically get a self argument passed to (unlike the function defined in-line via the lamba expression).

Anyway, as you can see, it takes a few more lines of code to do things this way.

from tkinter import *

x = 1
y = 1
z = 0

class App:
    def __init__(self, master):
        frame = Frame(master)
        frame.pack()

        self.textVar = StringVar()  # Define as a instance attribute.
        self.textVar.set(x)  # Set to show initial value of x.
        self.button = Button(frame, textvariable=self.textVar,
                             command=self.button_click_callback)
        self.button.pack()

    def button_click_callback(self):
        """ Called whenever button is clicked. """
        self.textVar.set(fibonacci())

def fibonacci():
    global x, y, z
    z = x
    x = x + y
    y = z
    return x

root = Tk()
app = App(root)
root.mainloop()
martineau
  • 119,623
  • 25
  • 170
  • 301
  • I don't understand Lambda very much in Python. I did a bunch of practice problems in Java but I'm having trouble figuring how just typing `lambda: ` in front of that completely changes my problem. Also, yes, I do get how the local variable / garbage collection works. I'll keep that in mind. – Xevion Nov 10 '18 at 16:56
-2

I personally gave up with textvariable on windows PC with anaconda environment (use of Spyder): I have too many levels and nesting and things gets anywhere lost.. especially due to "callback" situation with the command and lambda use. looks like others were confused with textvariable too (recommendation is there not to use it.. Tkinter Entry not showing the current value of textvariable ) Like you, I was using textvariable initialized with .set('is it there?') in an init part, and re-used later in an entry, dont show the text 'is it there'. Sorry for not having a clear solution, but it looks like the life can go on without this textvariable.

the best summary link I could find till now is there How to pass arguments to a Button command in Tkinter? . everything is there: lambda explained, callback explained a bit, textvariable not updated in the called function due to callback situation, use global variables for making changes in parameter etc.

floppy_molly
  • 175
  • 1
  • 10