Python Forum
[Tkinter] Passing information with a function
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Passing information with a function
#1
I am unsure if this is the right forum, but since it concerns a wigdet it seemed appropriate.

In line 26 the line "strength_entry.bind('<Return>', update_score(ab=2))" uses braces behind the calling the function "update_score". If I remove the braces and change the argumetn "ab" to "0" in line 19 I will be able to change the label with the use of the enter button in the GUI. But since I want to be able to expand the number of wigdets with more entry and have them all use the same function I cant let it be written in line 19. I want to be able to expand the library in line 18 later. (The "40" is just a test to see if I could be able to change it.)

To clearify, the function will run one time when I specify which element in the library I want to use, and not run everytime I press enter. Is there a way to write this?

I am learning Python by creating this program, but sometimes i get stuck at stuff like this.

from tkinter import *
import math

parent_w = Tk()
ability_w = Frame(parent_w)
ability_w.grid(row=0, column=0)


class AbilityScore:
    def __init__(self, score):
        self.score = score

    def my_score(self):
        return math.floor(int(self.score - 10) / 2)


def update_score(*args, ab):
    abilities_list = [strength_entry.get(), 40]
    strength_label.config(text=math.floor((int(abilities_list[ab]) - 10) / 2))


strength_entry = Entry(ability_w, widt=3, font=("Arial", 20), justify=CENTER)
strength_entry.insert(0, 0)
strength_label = Label(ability_w, font=("Arial", 15), text=AbilityScore(int(strength_entry.get())).my_score())
strength_text = Label(ability_w, text="Strength", font=("Arial", 20))
strength_entry.bind('<Return>', update_score(ab=0))

strength_entry.grid(row=0, column=1)
strength_text.grid(row=0, rowspan=2, column=0)
strength_label.grid(row=1, column=1)

parent_w.geometry("400x250")
parent_w.mainloop()
Reply
#2
Your code is a bit chaotic for me to understand what you are trying to do.
The following example shows that event has a widget attribute that is the widget that called the event handler,
it also shows the use of partial to pass extra information.
import tkinter as tk
from functools import partial


class TkApp(tk.Tk):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.entry1 = tk.Entry(self)
        self.entry1.pack(padx=5, pady=5)
        self.entry1.bind('<Return>', self.on_entries)
        self.entry2 = tk.Entry(self)
        self.entry2.pack(padx=5, pady=5)
        self.entry2.bind('<Return>', partial(
            self.on_entries, identity='entry2'))

    def on_entries(self, event, identity=None):
        if event.widget == self.entry1:
            print('This was called from entry1\n'
                  f'Entry value was {event.widget.get()}')
        if identity == 'entry2':
            print('This was called from entry2\n'
                  f'Entry value was {event.widget.get()}\n'
                  f'identity value was {identity}')


if __name__ == '__main__':
    app = TkApp()
    app.mainloop()

Note: see Namespace flooding with * imports

Using classes with GUI's makes the code more organised, grouping related parts together that are self-contained.
Reply
#3
This may be a good place to use a partial function (functools library) or a lambda expression. Either give you control of what gets passed to the function. I will use a lambda expression to bind each entry to a different label.
import tkinter as tk

def update_label(event, label):
    label['text'] = event.widget.get()

root = tk.Tk()
frame = tk.Frame(root)
frame.pack()

label = tk.Label(frame, text='Label 1')
label.pack()
entry = tk.Entry(frame)
entry.pack()
entry.bind('<Return>', lambda event, arg=label: update_label(event, arg))

label = tk.Label(frame, text='Label 2')
label.pack()
entry = tk.Entry(frame)
entry.pack()
entry.bind('<Return>', lambda event, arg=label: update_label(event, arg))

root.mainloop()
This is the lambda expression.
lambda event, arg=label: update_label(event, arg)
event is the event argument passed when bind calls the function when <Return> is pressed. arg is an additional argument passed to the function. I assign the value to label, so the label widget is passed as the second argument when update_label is called.

Below is the same example using partial.
import tkinter as tk
from functools import partial

def update_label(event, label):
    label['text'] = event.widget.get()

root = tk.Tk()
frame = tk.Frame(root)
frame.pack()

label = tk.Label(frame, text='Label 1')
label.pack()
entry = tk.Entry(frame)
entry.pack()
entry.bind('<Return>', partial(update_label, label=label))

label = tk.Label(frame, text='Label 2')
label.pack()
entry = tk.Entry(frame)
entry.pack()
entry.bind('<Return>', partial(update_label, label=label))

root.mainloop()
Reply
#4
What you really want is an Entry widget that calls a command when Return is pressed. The code below makes an Entry widget for numbers that calls a command when Return is pressed or the widget loses focus. Losing focus is a common way to force a widget to "accept" input.
import tkinter as tk
from functools import partial

class NumberEntry(tk.Entry):
    """I am like an Entry widget, but for numbers"""
    def __init__(self, *args, command=None, justify=tk.RIGHT, **kvargs):
        self.var = tk.StringVar()
        super().__init__(*args, textvariable=self.var, justify=justify, **kvargs)
        self.bind('<FocusOut>', self._validate)
        self.bind('<Return>', self._validate)
        self.var.trace_add('write', lambda a, b, c: self._changed())
        self.text_changed = False
        self.command = command
        self.set(0)

    def text(self):
        """Return text in entry"""
        return self.var.get()

    def get(self):
        """Return my numeric value"""
        return self.value

    def set(self, value):
        """Set my numeric value"""
        self.value = value
        self.var.set(str(value))
        self.changed = False

    def _changed(self):
        """Called when something changes text"""
        self.text_changed = True

    def _validate(self, event):
        """Called wnen lose focus or return key is pressed"""
        if self._changed:
            try:
                self.set(float(self.var.get()))
            except ValueError:
                self.set(self.value)
            if self.command is not None:
                self.command(self.value)


def update_label(value, label):
    label['text'] = str(value)

root = tk.Tk()
frame = tk.Frame(root)
frame.pack()

label = tk.Label(frame, text='Label 1')
label.pack()
NumberEntry(frame, command=partial(update_label, label=label)).pack()

label = tk.Label(frame, text='Label 2')
label.pack()
NumberEntry(frame, command=partial(update_label, label=label)).pack()

root.mainloop()
This is more code, but you pay the price of a new class only once and reap the reward each time it is used.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Tkinter] Passing variable to function. KDog 2 2,109 May-25-2021, 09:15 PM
Last Post: KDog
  Passing arguments into function, tkinter nanok66 3 4,876 Apr-18-2020, 11:53 PM
Last Post: nanok66

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020