Skip to content Skip to sidebar Skip to footer

Tkinter Button In Class Can't Call Function

Total noob, seriously and angrily struggling with Python... What I'm trying to do SHOULD be simple: Make a button. Connect that button go a function. Click button --> run funct

Solution 1:

If you want your functions to be designed outside of the class, but want to call it from a button defined in the class, the solution is to create a method in your class which takes the input values and then passes them to the external function.

This is called decoupling. Your function is decoupled from the implementation of the UI, and it means that you are free to completely change the implementation of the UI without changing the function, and vice versa. It also means that you can reuse the same function in many different programs without modification.

The overall structure of your code should look something like this:

# this is the external function, which could be in the same
# file or imported from some other module
def convert(kg):
    pounds = kg * 2.2046
    return pounds

class Example(...):
    def initUI(self):
        ...
        self.entry_kg = Entry(...)
        btn_convert=Button(..., command=self.do_convert)
        ...

    def do_convert(self):
        kg = float(self.entry_kg.get())
        result = convert(kg)
        print("%d kg = %d lb" % (kg, result))

Solution 2:

Here's a modified version of your code that works. The changes have been indicated with ALL CAPS line comments. I obviously misunderstood your question (which does say you could figure out how to make the convert() function part of the class. However, you mentioned you wanted the opposite of that, so I'm modified the code here accordingly.

Essentially the problem boils down to the convert() function needing to access a tkinter.Entry widget that's created somewhere else—inside your Example class in this case.

One way of doing that would be to save the widget in a global variable and access it through the variable name assigned to it. Many folks do that because it's easiest to thing to do, but as you should know, global variables are considered a bad practice and are generally something to be avoided.

There's a common way to avoid needing one with tkinter, which is sometimes called "The extra arguments trick". To use it all you need to do is create a short anonymous function with an argument that has a default value defined—which in this case will be the Entry widget you want passed to the now "wrapped" convert() function. This can be done using what's called a lambda expression. The revised code below and the comments in it show and describe how to do this:

from tkinter import *
from tkinter.ttk import Frame, Button, Style


def convert(entry_widget):  # ADDED WIDGET ARGUMENT
    """ Some function outside class. """
    print("clicked")
    kg = entry_widget.get()  # REFERENCE ENTRY WIDGET PASSED AS ARGUMENT
    print(kg)


class Example(Frame):
    def __init__(self):
        super().__init__()

        self.initUI()    # initiate the GUI

    def initUI(self):
        self.master.title("Weight Converter")
        self.pack(fill=BOTH, expand=True)

        frame_kg = Frame(self)   # frame for Kilograms
        frame_kg.pack(fill=X)

        lbl_kg = Label(frame_kg, text="Kilograms", width=16)
        lbl_kg.pack(side=LEFT, padx=5, pady=5)

        entry_kg = Entry(frame_kg)
        entry_kg.pack(fill=X, padx=(5, 30), expand=True)

        frame_btn = Frame(self)    # frame for buttons
        frame_btn.pack(fill=BOTH, expand=True, padx=20, pady=5)

        btn_convert=Button(frame_btn, text="Convert",
                        # DEFINE ANONYMOUS FUNCTION WITH DEFAULT ARGUMENT SO IT'S
                        # AUTOMATICALLY PASSED TO THE TARGET FUNCTION.
                        command=lambda entry_obj=entry_kg: convert(entry_obj))
        btn_convert.pack(side=LEFT, padx=5, pady=5)


def main():
    root = Tk()
    root.geometry("300x200+300+200")
    app = Example()
    root.mainloop()

if __name__ == '__main__':
    main()

Solution 3:

Your entry_kg is not known anywhere outside the scope of initUI method. That is why. You could convert it from a method variable to instance attribute to be within reach for class methods by replacing:

entry_kg = Entry(frame_kg)
entry_kg.pack(fill=X, padx=(5, 30), expand=True)

with:

self.entry_kg = Entry(frame_kg)
self.entry_kg.pack(fill=X, padx=(5, 30), expand=True)

Only then:

You can mention it in a class method like:

...
kg = self.entry_kg.get()

That way you if you make your convert a method under Example again:

def initUI(self):
...
def convert(self):           # is defined under the same scope as initUI
    print("clicked")
    kg = self.entry_kg.get() # notice that it now refers to self.e...
    print(kg)

also don't forget to replace command option as well:

btn_convert=Button(..., command=self.convert)

Or only then:

When outside the class scope, by using dot notation on the object that the class creates:

def main():

    root = Tk()
    root.geometry("300x200+300+200")
    app = Example()
    kg = app.entry_kg.get() # This would return an empty string, but it would still be valid
    root.mainloop()

to be used with global methods, such as the current state of convert you need to make app (the object it is an attribute of) global, or pass it explicitly to the method.


Post a Comment for "Tkinter Button In Class Can't Call Function"