mashedpotatoes
mashedpotatoes

Reputation: 435

How to pass two or more arguments to a Button command?

I'd like to pass 2 variable arguments in a function to be assigned to a Button command. These variables change within a for loop that also creates the buttons.

Mostly inspired by some of the top answers here and here, here are my fail attempts at trying to solve this problem based on what I read:

I tried using partial:

self.dct[(i, j)] = Button(command=partial(self.on_click, i, j))

Another try:

self.dct[(i, j)] = Button(command=partial(partial(self.on_click, i), j))

And another:

self.dct[(i, j)] = Button(command=partial(self.on_click, [i, j]))

.. Guess what?

tup = [i, j]
self.dct[(i, j)] = Button(command=partial(self.on_click, tup))

And then, lambda:

self.dct[(i, j)] = Button(command=lambda i=i, j=j: self.on_click(i, j))

Here's my code:

import tkinter as tk

from functools import partial


class Board(tk.Frame):
    board = None
    images = None
    tile = None

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.tile = {}
        for i in range(10):
            for j in range(10):
                self.tile[(i, j)]['btn_obj'] = tk.Button(self.board, command=partial(partial(self.on_click, i), j))

    def on_click(self, i, j):
        print("X: {}, Y:{}".format(j, i))

partial always causes an error like this:

TypeError: on_click() takes 2 positional arguments but 3 were given

It's always mismatched number of arguments.

Meanwhile, lambda gets the wrong value of the variables, causing something of an error in tkinter's part.

Upvotes: 1

Views: 1491

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 385970

The lambda in your question should work:

tk.Button(self.board, command=lambda i=i, j=j: self.on_click(i,j))

This binds the value i and j as the default values for the lambda parameters.

Personally I prefer lambda over partial, mainly because I don't have to import the functools module. If you wish to use partial, it should look like your first example:

tk.Button(self, command=partial(self.on_click, i, j))

In both cases, on_click will be passed the correct value for i and j based on their values when the button is created.

Here is an example based on your code, but with some unnecessary code removed for clarity:

import tkinter as tk
from functools import partial


class Board(tk.Frame):
    def __init__(self, parent, method):
        tk.Frame.__init__(self, parent, bd=2, relief="sunken")
        for i in range(10):
            for j in range(10):
                if method == "lambda":
                    button = tk.Button(self, command=lambda i=i, j=j: self.on_click(i,j))
                else:
                    button = tk.Button(self, command=partial(self.on_click, i, j))
                button.grid(row=i, column=j)

    def on_click(self, i, j):
        print("X: {}, Y:{}".format(j, i))

root = tk.Tk()
board1 = Board(root, method="lambda")
board2 = Board(root, method="partial")
board1.pack(side="top", fill="both", expand=True)
board2.pack(side="top", fill="both", expand=True)
root.mainloop()

Upvotes: 2

Related Questions