Search code examples
pythonbuttonlambdatkintermultiple-arguments

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


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.


Solution

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