2

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.

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
Mark C
  • 41
  • 1
  • 5
  • 2
    You [should not assign a widget on the same line that you `grid` it](http://stackoverflow.com/questions/1101750/tkinter-attributeerror-nonetype-object-has-no-attribute-get). Are you using a tutorial? I'm trying to determine if this common mistake has a single source, or if every new user falls victim to it independently. – Kevin Aug 17 '15 at 18:18
  • Mark, please respond to Kevin's question in a comment here. Please post a link to the tutorial you're using so we can try to prevent others from having the same problem in the future. – Russia Must Remove Putin Aug 17 '15 at 18:41
  • @Kevin - I don't usually miss widget assignments with geometry method chaining, but I did today. :) I'll mention it in my answer. – TigerhawkT3 Aug 17 '15 at 18:42
  • The tutorial I had referenced that suggested formatting the widgets on the same line I introduce them was posted on YouTube by thenewboston entitled "Python GUI with Tkinter - 4 - Grid Layout". Perhaps I would be better suited in going through a full tutorial and then approaching the project. Any credible tutorial suggestions? – Mark C Aug 17 '15 at 20:52
  • Technically, you can do this chaining and it will display the widgets, but it becomes unsuitable the moment you want to refer back to one of those widgets, such as getting the content of an `Entry` widget. I like [Effbot](http://effbot.org/tkinterbook/), but that's more of a reference with occasional examples than a full tutorial. – TigerhawkT3 Aug 17 '15 at 21:12

2 Answers2

3

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.

TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
  • I decided to have `the_function` get the values and then call it explicitly via the `command` parameter as well as reformat the widget creation/ manipulation and it works. Thank you. – Mark C Aug 17 '15 at 20:50
0

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.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685