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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.
1
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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,935 May-25-2021, 09:15 PM
Last Post: KDog
  Passing arguments into function, tkinter nanok66 3 7,008 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