Python-Tkinter data passing
April 18, 2011 1:13 PM   Subscribe

I'm writing a program in Python using Tkinter for GUI stuff. How can I pass data back from my buttons to the logic of my program?

Here is a minimal example:
from Tkinter import *;

class MyButton(Button):
	def callback(self):
		pass;
	
	def __init__(self,parent,text=None):
		Button.__init__(self,parent,command=self.callback,text=text);

window = Tk();

for string in ["a","b"]:
	button = MyButton(window,text=string);
	button.pack();

counter = 0;
# ???

mainloop();
In this example I would like to update the counter to keep track of how many button presses there have been (either a or b, or any button if I extend the example). I'm interested in both the "right" way and the way that would commonly be used. Google has failed me entirely.
posted by anaelith to Computers & Internet (9 answers total) 2 users marked this as a favorite
 
I am going to take a crack at this even though I'm not a python developer.

Pass a callback into the MyButton constructor in your loop, use that callback as the command param in the button ctor. In the callback increment the counter.
posted by Ad hominem at 1:36 PM on April 18, 2011


Response by poster: Wouldn't the counter then be local to the callback function--and not persistent? I know I could use global variables, but there has to be a better way right..something like a return statement?
posted by anaelith at 1:54 PM on April 18, 2011


Actually, that code is already set up to run the callback() method whenever the button is clicked. So you can just implement that method to do what you want. Here's how to make it increment your counter variable:
	def callback(self):
		global counter
		counter += 1
Three things to note:
  • Tkinter is widely considered obsolete; it might be better to go with a modern library like PyQt.
  • It's not good style to use semicolons in Python. (They're optional.)
  • Global variables are bad. It's almost always better to use an instance variable on a class. It's common to wrap all of your UI code in a big UI class; that would be a good place to put the "counter" var or any other state you want to modify in your UI callbacks.

posted by abcde at 1:57 PM on April 18, 2011 [2 favorites]


globals = bad


You need to somehow construct a reference object for your counter. You can't do this directly for an int, but you could wrap the int in some kind of object... i.e., a list, or better yet, as a member of some class instance. Then pass the reference object to the button.
from Tkinter import *;

class MyButton(Button):
	def callback(self):
		self.counter_ref[0] += 1
	
	def __init__(self,parent,text=None, counter_ref):
		Button.__init__(self,parent,command=self.callback,text=text);
		self.counter_ref = counter_ref
               


window = Tk();

counter = [0];

for string in ["a","b"]:
	button = MyButton(window,text=string,counter);
	button.pack();


mainloop();

posted by qxntpqbbbqxl at 2:02 PM on April 18, 2011


Best answer: ... but for thoroughness, here's how I would do it
window = Tk();

class Counter:
	def __init__(self):
		self.value = 0
	def increment(self):
		self.value += 1

counter = Counter()

for string in ["a","b"]:
	button = Button(window,text=string,command=counter.increment);
	button.pack();


mainloop();

posted by qxntpqbbbqxl at 2:05 PM on April 18, 2011 [1 favorite]


But then the counter is per instance, didn't he want a global counter for all button clicks ?
posted by Ad hominem at 2:12 PM on April 18, 2011


ok I agree with qxntpqbbbqxl's most recent version
posted by Ad hominem at 2:15 PM on April 18, 2011


Response by poster: Unfortunately Tkinter isn't my choice. Neither is Python, really.

I use semicolons in Python the same way I use indentation in non-space-sensitive languages, it is just easier for me to be consistent. (I usually use gratuitous return statements, too. Python doesn't complain because you don't need them; C doesn't complain because it's happy returning garbage...)

Wrapping my data up will work. I still don't like it, the pass-by-reference stuff that Python does seems like a disaster waiting to happen, but if that's the best way...
posted by anaelith at 2:30 PM on April 18, 2011


Ah. I see now that you wanted to know specifically how to pass the information of the click event into your code. I thought you were just asking how to run code whenever the button is clicked. I was thrown off because you use a global counter var in your example, and it's pretty obvious how to do it with globals.

In practice, you wouldn't usually do anything fancy like manipulating a counter variable reference or making a separate counter class. You just use a UI object and put a callback on there.
import Tkinter as tk

class UI(object):
    def __init__(self):
        self.clickcount = 0

        window = tk.Tk()
        for buttontext in ["a", "b"]:
            button = tk.Button(window, text=buttontext, command=self.onbuttonclick)
            button.pack()

    def onbuttonclick(self):
        self.clickcount += 1

    def go(self):
        tk.mainloop()

def main():
    ui = UI()
    ui.go()

main()
If you still want to use your MyButton class, you could set up MyButton.__init__() function to take a "UI callback" argument, then call that from MyButton.callback().
posted by abcde at 4:22 PM on April 18, 2011


« Older Alphabet songs around the world?   |   Getting off the advanced plateau Newer »
This thread is closed to new comments.