Python/ Tkinter - Button to run function with multiple arguments not working

4.1k views Asked by At

I am trying to write a program that will display 4 rows of 2 columns with column 0 being labels and column 1 being entries. Then, pass those 4 integer entries through as arguments into a function when a button is left clicked. Here is my code so far:

from tkinter import *

root = Tk()


class ClassName():

     def __init__(self, master):

       self.run_button = Button(self.master, text="Button Text", bg="green", fg="black", 
                               command="HERE IS WHERE I NEED HELP")
       self.run_button.grid(row=4, columnspan=2)

       self.label1 = Label(master, text="Entry 1").grid(row=0, sticky=E)
       self.label2 = Label(master, text="Entry 2").grid(row=1, sticky=E)
       self.label3 = Label(master, text="Entry 3").grid(row=2, sticky=E)
       self.label4 = Label(master, text="Entry 4").grid(row=3, sticky=E)

       self.entry1 = Entry(master).grid(row=0, column=1, sticky=W)
       self.entry2 = Entry(master).grid(row=1, column=1, sticky=W)
       self.entry3 = Entry(master).grid(row=2, column=1, sticky=W)
       self.entry4 = Entry(master).grid(row=3, column=1, sticky=W)

I want to then take the 4 entries and pass them through a different function called the_function. All the_function does is print out something based on the values of the 4 entries. So my remaining code looks like this:

def the_function(self, a, b, c, d):
#    Ensure a, b, c, and d are integers, 
#    do some math on the numbers and print something out based on the
#    values of a, b, c and d.

irrelevant_variable = ClassName(root)
root.mainloop()

The function works properly without the GUI but I cannot figure out how to create a button that takes self.entry1 and passes it through as a in the_function.

Other posts have lead me to think I should use the lambda command, but I'm not sure how this would work within this function.

2

There are 2 answers

1
TigerhawkT3 On BEST ANSWER

You're on the right track - lambda allows you to define an anonymous, in-line function:

...command=lambda: the_function(entry1.get(), entry2.get(), entry3.get(), entry4.get())

It basically acts as a wrapper. You could also do this:

def mywrapper(self):
    the_function(entry1.get(), entry2.get(), entry3.get(), entry4.get())

And then bind that to the button:

...command=self.mywrapper)

Alternatively, simply have the_function get the necessary variables itself:

def the_function(self):
    a = entry1.get()
    b = entry2.get()
    c = entry3.get()
    d = entry4.get()
    #    Ensure a, b, c, and d are integers

And bind that function without a lambda:

...command=self.the_function)

However, this won't help in your current code - chaining your geometry management methods onto your widget creation when you're actually interested in referencing the widget will cause a problem. The widget constructors (Button(), Entry(), etc.) will return that widget for future reference. However, the geometry management methods (grid(), pack(), etc.) simply act on their widget and return None. This means that you're assigning None to self.label1, self.entry1, and so on. Separate the widget creation from the geometry management:

self.label1 = Label(master, text="Entry 1")
self.label1.grid(row=0, sticky=E)

You will now have actual widgets to work with.

0
Bryan Oakley On

The normal solution is to have your button call a function that is specific for that button. The job of the function is to gather data, and then act on the data:

def __init__(...):
    ...
    self.run_button = Button(..., command=self.do_run)
    ...

def do_run(self):
    e1 = self.entry1.get()
    e2 = self.entry2.get()
    e3 = self.entry3.get()
    e4 = self.entry4.get()

    self.the_function(e1, e2, e3, e4)

You can use lambda or functools.partial, but that brings disadvantages with no clear advantages. Your code will be easier to write, read and maintain by using real functions rather than lambdas whenever possible.

You can also call the get() methods inside of the_function. The choice is up to you. If the_function can be used in multiple contexts, it can be useful to have it be decoupled from the UI. If the only purpose ever is to always process the values from the entry widgets, you can skip the intermediate function and simply have the_function get the values and then use them.