Join 3,553 readers in helping fund MetaFilter (Hide)


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


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:
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


... 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


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 Are there "alphabet songs...   |  Given 4 months of free time, h... Newer »
This thread is closed to new comments.