Python Forum
[Tkinter] Binding Entry box to <Button-3> created in for loop
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Binding Entry box to <Button-3> created in for loop
#1
I am building a scoresheet that will score 2 teams of 5 players each as they compete to answer 20 questions. That means that I need 200 Entry boxes which I can create with a for loop. I am trying to bind them to the <Button-3> event but so far nothing I have tried works and googling for help has lead me nowhere. I want to create a Toplevel window that raises via the <Button-3> event so I can select scores that meet the rules of scoring. This will simplify the complicate scoring rules for the user.

from tkinter import *

#============================ Define Functions Here ============================
def call_top():
    pass
#============================ Define a Window Here =============================
root = Tk()
root.title ('Quiz Score Sheet')

# Window Size
win_width = 1000
win_height = 500

#Center the Window on the Screen
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
x_coord = (screen_width / 2) - (win_width / 2)
y_coord = (screen_height / 2) - (win_height / 2)
root.geometry("%dx%d+%d+%d" % (win_width, win_height, x_coord, y_coord))

#============================== Add Widgets Here ===============================
# Add 3 Frames, 1 for each Team and 1 for Column Headers
c_frm = Frame(root, width=900, height=30, bd=3, bg='#d5dbd6')
c_frm.pack()
r_frm = Frame(root, width=900, height=250, bd=3, bg='#ef5f5f')
r_frm.pack()
g_frm = Frame(root, width=900, height=250, bd=3, bg='#5aeda8')
g_frm.pack()

# I need 20 column headers, 1 for each question and a Header for column 0
Label(c_frm, text='Contestants', bd=1, relief='solid', font=('bold', 10), width=20).grid(row=0, column=0)
for r in range(21, 1, -1):
    Label(c_frm, text=r-1, bd=1, relief='solid', font=('bold', 10), width=4).grid(row=0, column=r, padx=(2,0))

# I need 10 row headers for Player Names on the Left side of the window
PLAYERS = [
    ('Player 1', 1),
    ('Player 2', 2),
    ('Player 3', 3),
    ('Player 4', 4),
    ('Player 5', 5),
    ]

for player, r in PLAYERS:
    Label(r_frm, text=player, bd=1, relief='solid', font=('bold', 10), width=20).grid(row=r, column=0, columnspan=2)
    Label(g_frm, text=player, bd=1, relief='solid', font=('bold', 10), width=20).grid(row=r, column=0, columnspan=2)

# Now add the Entry boxes for Scoring Answers to the 20 Questions and Bind <Button-3>
for r in range(1, 6):   # I need 5 rows numbered 1-5
    for c in range(2, 22):  # I need 20 columns in each row numbered 1-20
        Entry(r_frm, bd=1, relief='solid', justify='center', width=4).grid(row=r, column=c)
        Entry.bind('<Button-3>', call_top)
        Entry(g_frm, bd=1, relief='solid', justify='center', width=4).grid(row=r, column=c)
        Entry.bind('<Button-3>', call_top)    
#============================== Show it All Here ===============================

root.mainloop()
I get the following Error at the Entry.bind lines;
Error:
Traceback (most recent call last): File "/media/brad/PYTHON 3/MyStuff_py/for_loop_widgets.py", line 52, in <module> Entry.bind('<Button-3>', call_top) File "/usr/local/lib/python3.7/tkinter/__init__.py", line 1251, in bind return self._bind(('bind', self._w), sequence, func, add) AttributeError: 'str' object has no attribute '_bind'
Quite frankly I just don't get it. If I create each Entry box individually with 3 lines per Entry widget (Create, Grid and Bind) it works. If I # the Entry.bind('<Button-3>', call_top) lines it builds the score sheet but the Entry boxes are not bound to an event. Any direction you can point me to would be a big help. Thanks
Reply
#2
This part of Your code:
        Entry(r_frm, bd=1, relief='solid', justify='center', width=4).grid(row=r, column=c)
        Entry.bind('<Button-3>', call_top)
the Entry widget does not have a name, therefore the bind statement Entry.bind('<Button-3>', call_top)
is trying to bind Tkinter's 'Entry' object, not your widget.

With entries in a loop like this,
create a list or dictionary prior to entering loop, and append each entry to the list, add the binding to each element as appended.
If you use a dictionary, you can append a key to each entry which will aid you if you need to change the configuration for an individual entry some time in the future.

Example (this for buttons, but concept is the same):
self.buttons = {}

for name in ['A', 'B', 'C']:
            self.buttons[name] = {}
            self.buttons[name]['name'] = name
            self.buttons[name]['button'] =  tk.Button(self.f2, fg="green", text=f"Solve for {name}")
            self.buttons[name]['button'].bind('<Button-1>', lambda event, bname=self.buttons[name]['name']: self.solve_equation(event, bname))
            self.buttons[name]['button'].pack(side=tk.LEFT)
Reply
#3
I'm sorry but your suggestion example is just way over my head. I don't get it. Here is my attempt at giving the Entry box a name but it fails at the bind too. The 5th & 8th lines on each iteration populates the er_name & eg_name variables just fine as err0c2 and egr0c2 on the first time through and then increment the numerals each time loop occurs but when I use those names in lines 6 & 9 those variables get reassigned to None.

The bind lines (7 & 10) generate the following error message because you simply can't bind to nothing. Huh

Traceback (most recent call last):
File "/media/brad/PYTHON 3/MyStuff_py/for_loop_widgets_2.py", line 56, in <module>
er_name.bind('<Button-3>', call_top)
AttributeError: 'NoneType' object has no attribute 'bind'

er_name = Tk.StringVar
eg_name = Tk.StringVar
for r in range(0, 5):   # I need 5 rows numbered 0-4
    for c in range(2, 22):  # I need 20 columns in each row in columns 2-21
        er_name = 'err' + str(r) + 'c' + str(c)
        er_name = Entry(r_frm, bd=1, relief='solid', justify='center', width=4).grid(row=r, column=c)
        er_name.bind('<Button-3>', call_top)
        eg_name = 'egr' + str(r) + 'c' + str(c)
        eg_name = Entry(g_frm, bd=1, relief='solid', justify='center', width=4).grid(row=r, column=c)
        eg_name.bind('<Button-3>', call_top)
Reply
#4
Thank you, Thank you, Thank you, it works now!! You got me started again!

I now have a new problem that is puzzling the pie out of me though. To make this work I created a List (like you suggested) and used the List by it's index to name the Entry widgets. The output is perfect, exactly what I wanted to see on the screen. So just above the mainloop() I set focus to one of these Entry widgets and the shell started barking at me again. Using the print() I print the contents of the List to the shell as;

1) after it is created
2) after it is appended to the List
3) after the List is created
4) by it's index in the loop just before it is used in the loop
5) just after it is used in the loop and finally
6) after the loop is completed.

This is what I see in the shell just before entering the loop - rr0c2.
This is what I see after the loop is completed for the same index id - .!frame2.!entry.
Below you can see that continues.

gr0c2
.!frame3.!entry
rr0c3
.!frame2.!entry2
gr0c3
.!frame3.!entry2

Below is the Traceback error when I try to set focus by widget_name method;

Error:
Traceback (most recent call last): File "/media/brad/PYTHON 3/MyStuff_py/for_loop_widgets_1.py", line 95, in <module> rr0c2.focus() NameError: name 'rr0c2' is not defined
Below is the error message IDLE gives me when it hits this line.

.!frame2.!entry.focus() generates a SyntaxError - invalid syntax

I can click in any of the entry boxes and enter data. The bind works as I can right-click on any of the boxes and a Toplevel window appears with Radiobuttons on it. What I can't do is programmatically read or write values to any of the Entry widgets. I can see them but how to I reference them in code?

I have taken 2 more steps but I am still not there. What am I missing??? Below is my code.

from tkinter import *

#============================ Define Functions Here ============================
def call_top(event):
        top = Toplevel()
        top.title("Individual Score")
        top.geometry('250x250')
        r = IntVar()
        r.set(20)
        Radiobutton(top, text='Correct Answer', variable=r, value=20).pack(padx=10)
        Radiobutton(top, text='Wrong Answer', variable=r, value=-20).pack(padx=10)
        Radiobutton(top, text='3rd Correct Bonus', variable=r, value=30).pack(padx=10)
        Radiobutton(top, text='4th Correct Bonus', variable=r, value=35).pack(padx=10)
        Radiobutton(top, text='5th Correct Bonus', variable=r, value=40).pack(padx=10)
        button = Button(top, text="Dismiss", width=15, command=top.destroy).pack()
        return r

#============================ Define a Window Here =============================
root = Tk()
root.title ('NWYM Bible Quizzing')

# Window Size
win_width = 1000
win_height = 500

#Center the Window on the Screen
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
x_coord = (screen_width / 2) - (win_width / 2)
y_coord = (screen_height / 2) - (win_height / 2)
root.geometry("%dx%d+%d+%d" % (win_width, win_height, x_coord, y_coord))

#============================== Add Widgets Here ===============================
# Add 3 Frames, 1 for each Team and 1 for Column Headers
c_frm = Frame(root, width=900, height=30, bd=3, bg='#d5dbd6')
c_frm.pack()
r_frm = Frame(root, width=900, height=250, bd=3, bg='#ef5f5f')
r_frm.pack()
g_frm = Frame(root, width=900, height=250, bd=3, bg='#5aeda8')
g_frm.pack()

# I need 20 column headers, 1 for each question and a Header for column 0
Label(c_frm, text='Contestants', bd=1, relief='solid', font=('bold', 10), width=20).grid(row=0, column=0)
for r in range(21, 1, -1):
    Label(c_frm, text=r-1, bd=1, relief='solid', font=('bold', 10), width=4).grid(row=0, column=r, padx=(2,0))

# I need 10 row headers for Player Names on the Left side of the window
PLAYERS = [
    ('Player 1', 0),
    ('Player 2', 1),
    ('Player 3', 2),
    ('Player 4', 3),
    ('Player 5', 4),
    ]

for player, r in PLAYERS:
    Label(r_frm, text=player, bd=1, relief='solid', font=('bold', 10), width=20).grid(row=r, column=0, columnspan=2)
    Label(g_frm, text=player, bd=1, relief='solid', font=('bold', 10), width=20).grid(row=r, column=0, columnspan=2)

# Now add the Entry boxes for Scoring Answers to the 20 Questions and Bind <Button-3>
# Create a List of unique names for the widget
widget_name = []  # An empty list
r_name = ''       # An empty string variable for Entry boxes in the Red Frame
g_name = ''       # An empty string variable for Entry boxes in the Green Frame
for r in range(0, 5):   # I need 5 rows numbered 0-4
    for c in range(2, 22):  # I need 20 columns in each row in columns 2-21
        r_name = 'rr' + str(r) + 'c' + str(c)
        g_name = 'gr' + str(r) + 'c' + str(c)
        widget_name.append(r_name)
        #print(r_name)
        widget_name.append(g_name)
        #print(g_name)
        
#print(widget_name)
i = 0
for r in range(0, 5):   # I need 5 rows numbered 0-4
    for c in range(2, 22):  # I need 20 columns in each row in columns 2-21
        #print(widget_name[i])
        widget_name[i] = Entry(r_frm, bd=1, relief='solid', justify='center', width=4)
        widget_name[i].grid(row=r, column=c)
        widget_name[i].bind('<Button-3>', call_top)
        #print(widget_name[i])
        i += 1
        #print(widget_name[i])
        widget_name[i] = Entry(g_frm, bd=1, relief='solid', justify='center', width=4)
        widget_name[i].grid(row=r, column=c)
        widget_name[i].bind('<Button-3>', call_top)
        #print(widget_name[i])
        i += 1
    
#============================== Show it All Here ===============================
#i = 0
#for name in widget_name:
#    print(name)
#    print(widget_name[i])
#    i += 1
#rr0c2.focus()  # Generates a Traceback error NameError: name 'rr0c2 is not defined
#.!frame2.!entry.focus()  # Generates a SyntaxError - invalid syntax
root.mainloop()
Reply
#5
Here is some code that makes 100 entry widgets organized in 5 columns and all of them bound so clicking Button-3 calls a function.
import tkinter as tk

def button3_clicked(team, player, question, var):
    """This is a callback for a Button-3 event"""
    # Print out the team, player and question numbers as well as the answer.
    print(team, player, question, var.get())

def make_player(window, team, player):
    """I make 20 entries for a player"""
    boxes = []
    for i in range(20):
        var = tk.IntVar()
        entry = tk.Entry(window, textvariable=var)
        var.set(i+player*100)  # Set a value to verify button event works
        entry.grid(row=i, column=player)
        entry.bind('<Button-3>', \
                   lambda event, q= i: \
                   button3_clicked(team, player, q, var))
        boxes.append(var)
    return boxes

root = tk.Tk()
team1 = [make_player(root, 0, 0),
         make_player(root, 0, 1),
         make_player(root, 0, 2),
         make_player(root, 0, 3),
         make_player(root, 0, 4)]

tk.mainloop()
If you right click inside a box it calls the button3_clicked function and prints out the value in the Entry box. I used an IntVar, but if you want these to contain strings it is easy enough to use a StringVar. Entry's are a lot easier to work with using a variable than trying to work directly with the widget.
Reply
#6
Let me study this but I think it is my answer.

Thank you
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Key Binding scope angus1964 1 1,170 Jun-30-2022, 08:17 PM
Last Post: deanhystad
  kivy binding issue hammer 8 2,939 Nov-07-2021, 11:34 PM
Last Post: hammer
  [Tkinter] define entry via class on a loop drSlump 9 3,375 Oct-27-2021, 05:01 PM
Last Post: deanhystad
  [Tkinter] How to terminate a loop in python with button in GUI Joni_Engr 6 10,962 Sep-09-2021, 06:33 PM
Last Post: deanhystad
  [Tkinter] binding versus disable DPaul 6 6,599 May-05-2021, 05:17 PM
Last Post: DPaul
  [PyQt] Loop triggered by button help Purple0 1 2,297 May-17-2020, 02:57 AM
Last Post: deanhystad
  TkInter Binding Buttons ifigazsi 5 4,166 Apr-06-2020, 08:30 AM
Last Post: ifigazsi
  Making text clickable with binding DT2000 10 5,031 Apr-02-2020, 10:11 PM
Last Post: DT2000
  Transfer Toplevel window entry to root window entry with TKinter HBH 0 4,420 Jan-23-2020, 09:00 PM
Last Post: HBH
  [Tkinter] Setting Binding to Entry created with a loop? p_hobbs 1 2,041 Nov-25-2019, 10:29 AM
Last Post: Larz60+

Forum Jump:

User Panel Messages

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