Yoda
Yoda

Reputation: 690

Tkinter bind event passed to function instead of variable

I am trying to make a simple color picker for an application. I am generating an array of labels with different background colors. When the label is clicked, I want to put the hex color into an entry field in the parent widget.

Everything loads correctly, but it seems I am passing the bind event instance to my set_color method, and not actually the hex color. What am I doing wrong?

I could use buttons with commands, but those take longer to load.

# Python 2.7
import Tkinter as tk
from tkFont import Font
import math

class ColorPicker(tk.Toplevel):
    def __init__(self, parent):
        tk.Toplevel.__init__(self, parent)
        self.parent = parent
        self.title("ColorPicker")

        self.frame = tk.Frame(self)
        self.frame.pack()

        self.buttonfont = Font(family="Arial", size=5)

        ROW, COL = 0, 0
        COLORS = xrange(1, int("FFFFFF", base=16), 50000)
        for color in COLORS:
            hexcolor = "#" + str(hex(color))[2:]
            hexcolor += "0"*(7 - len(hexcolor))

            l = tk.Label(self.frame, bg=hexcolor, text=hexcolor, font=self.buttonfont)
            l.bind("<Button-1>", lambda x=hexcolor: self.set_color(x))
            l.grid(row=ROW, column=COL)

            ROW += 1
            if ROW > math.sqrt(len(COLORS)):
                ROW = 0
                COL += 1

    def set_color(self, color):
        self.parent.entry_background_color.delete(0, tk.END)
        self.parent.entry_background_color.insert(0, color)
        self.destroy()

And here is a small example that runs and reproduces the behavior.

import Tkinter as tk

def p(s, *args):
    print(s)

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

for i in range(3):
    label = tk.Label(app, text="Press Me")
    label.pack()
    label.bind("<Button-1>", lambda i=i: p("Hello World {} times".format(i)))

app.mainloop()

Upvotes: 1

Views: 115

Answers (1)

figbeam
figbeam

Reputation: 7176

Bind is generating an event which you must consume within the lambda:

l.bind("<Button-1>", lambda event, x=hexcolor: self.set_color(x))
#                             ^         ^
#         consume event-------|         | and then assign x

Otherwise lambda will assign the event to hexcolor.

Update

As for the problems with labels not being responsive, I have not been able to reproduce that. I did however give it some thought and came up with a way of assigning ROW and COL which feels more pythonic:

import Tkinter as tk
import math

class ColorPicker(tk.Toplevel):
    def __init__(self, parent):
        tk.Toplevel.__init__(self, parent)
        self.parent = parent
        self.title("ColorPicker")

        self.frame = tk.Frame(self)
        self.frame.pack()

        ROW, COL = 0, 0
        COLORS = xrange(1, int("FFFFFF", base=16), 50000)
        for count, color in enumerate(COLORS):
            hexcolor = "#" + str(hex(color))[2:]
            hexcolor += "0"*(7 - len(hexcolor))

            ROW = count // int(math.sqrt(len(COLORS)))
            COL = count % int(math.sqrt(len(COLORS)))

            l = tk.Label(self.frame, bg=hexcolor, text=hexcolor)
            l.bind("<Button-1>", lambda event, x=hexcolor: self.set_color(x))
            l.grid(row=ROW, column=COL)

    def set_color(self, color):
        print color

root = tk.Tk()
app = ColorPicker(root)

Beware that I'm running Python 3.6.5 and there may be some differences.

Upvotes: 1

Related Questions