CustomTkinter icon indicating copy to clipboard operation
CustomTkinter copied to clipboard

bind_class not working as expected with <Button*> Events

Open robinhoheisel opened this issue 2 years ago • 3 comments

While working at a project I discovered that using bind_class with the <Button> related events seem to not work as expected when compared to regular tkinter.

I have prepared two examples:

import customtkinter as ctk
from tkinter import Event


class Example(ctk.CTk):
    def __init__(self):
        super().__init__()

        self.frame = ctk.CTkFrame(self)
        self.frame.pack(expand=True, fill='x')
        self.rows = []
        self.default_row_fg = ctk.ThemeManager.theme['CTkFrame']['fg_color']

        self.generate_rows()

        self.bind_class('row_hover', '<Enter>', self.hover_start)
        self.bind_class('row_hover', '<Leave>', self.hover_end)
        self.bind_class('row_click', '<Button-1>', self.on_click)

    def generate_rows(self, row_count=10) -> None:
        for _i in range(row_count):
            self.add_row()

    def add_row(self, hover_tag='row_hover', click_tag='row_click') -> None:
        frame = ctk.CTkFrame(self.frame, border_width=2)
        frame.bindtags((click_tag, hover_tag,) + frame.bindtags())
        frame.pack(side='top', expand=True, fill='x', padx=5, pady=5)

        self.rows.append(frame)

        label = ctk.CTkLabel(frame, text=f'Test label')
        label.bindtags((click_tag,) + label.bindtags())
        label.pack(expand=True, fill='x', padx=10, pady=10)

    def hover_start(self, event: Event) -> None:
        event.widget.configure(fg_color='green')

    def hover_end(self, event: Event) -> None:
        event.widget.configure(fg_color=self.default_row_fg)

    def on_click(self, event: Event) -> None:
        print('clicked', event.widget.winfo_name())


root = Example()
root.mainloop()

In the above example, the events bound to <Enter> and <Leave> work as expected while the <Button-1> event never fires.

I rebuilt the same example using tkinter inplace of customtkinter, In which the <Button-1> Event functions as expected:

import tkinter as tk

class Example(tk.Tk):
    def __init__(self):
        super().__init__()

        self.frame = tk.Frame(self)
        self.frame.pack(expand=True, fill='x')
        self.rows = []
        self.default_row_fg = self.frame.cget('background')

        self.generate_rows()

        self.bind_class('row_hover', '<Enter>', self.hover_start)
        self.bind_class('row_hover', '<Leave>', self.hover_end)
        self.bind_class('row_click', '<Button-1>', self.on_click)

    def generate_rows(self, row_count=10) -> None:
        for _i in range(row_count):
            self.add_row()

    def add_row(self, hover_tag='row_hover', click_tag='row_click') -> None:
        frame = tk.Frame(self.frame, borderwidth=2, border=2, relief="raised")
        frame.bindtags((click_tag, hover_tag,) + frame.bindtags())
        frame.pack(side='top', expand=True, fill='x', padx=5, pady=5)

        self.rows.append(frame)

        label = tk.Label(frame, text=f'Test label')
        label.bindtags((click_tag,) + label.bindtags())
        label.pack(expand=True, fill='x', padx=10, pady=10)

    def hover_start(self, event: tk.Event) -> None:
        event.widget.configure(background='green')

    def hover_end(self, event: tk.Event) -> None:
        event.widget.configure(background=self.default_row_fg)

    def on_click(self, event: tk.Event) -> None:
        print('clicked', event.widget.winfo_name())

root = Example()
root.mainloop()

Is this the intended behaviour? I am working with python 3.11.4 on Windows 10.

I found the following workaround to get the behaviour I needed, but this feels rather hacky:

class Example(ctk.CTk):
    def __init__(self):
        super().__init__()

        self.frame = ctk.CTkFrame(self)
        self.frame.pack(expand=True, fill='x')
        self.rows = []
        self.default_row_fg = ctk.ThemeManager.theme['CTkFrame']['fg_color']

        self.generate_rows()

        self.bind_class('row_hover', '<Enter>', self.hover_start)
        self.bind_class('row_hover', '<Leave>', self.hover_end)
        # Workaround: bind to <space> instead of <Button-1>
        self.bind_class('row_click', '<space>', self.on_click)

    def generate_rows(self, row_count=10) -> None:
        for _i in range(row_count):
            self.add_row()

    def add_row(self, hover_tag='row_hover', click_tag='row_click') -> None:
        frame = ctk.CTkFrame(self.frame, border_width=2)
        frame.bindtags((click_tag, hover_tag,) + frame.bindtags())
        frame.pack(side='top', expand=True, fill='x', padx=5, pady=5)
        # Workaround: Generate <space> event on <Button-1> event
        frame.bind('<Button-1>', lambda e: frame.event_generate('<space>'))

        self.rows.append(frame)

        label = ctk.CTkLabel(frame, text=f'Test label')
        label.bindtags((click_tag,) + label.bindtags())
        label.pack(expand=True, fill='x', padx=10, pady=10)
        label.bind('<Button-1>', lambda e: frame.event_generate('<space>'))

    def hover_start(self, event: Event) -> None:
        # Workaround: Set Keyboard focus on widget
        event.widget.focus_set()
        event.widget.configure(fg_color='green')

    def hover_end(self, event: Event) -> None:
        event.widget.configure(fg_color=self.default_row_fg)

    def on_click(self, event: Event) -> None:
        print('clicked', event.widget.winfo_name())


root = Example()
root.mainloop()

robinhoheisel avatar Jan 24 '24 09:01 robinhoheisel

Hey,

This is suppsoed to be a minimal example to illustrate what I found. The actual project involves a listview with children composed from a larger number of sub widgets. Which has nothing to do with the issue at hand.

On my machine the click event for the customtkinter variant never triggers, while it does on the regular tkinter variant. That seems inconsistent, which is why I opened this issue.

robinhoheisel avatar Jan 25 '24 11:01 robinhoheisel

i found dat =>

accctualy using in my app, and the job is rly rly goood <3

Ctkmenubar.py, in CustomDropdownMenu.py Customtkinter Menu Bar Author: Akash Bora Custom Dropdown Menu for CTkMenuBar Original Author: LucianoSaldivia | https://github.com/LucianoSaldivia Modified by: Akascape

        if widget.master.winfo_name().startswith("!ctktitlemenu"):
            widget.master.master.bind("<Button-1>", self._checkIfMouseLeft, add="+")
            master = widget.master if master is None else master
            widget.master.menu.append(self)
            
        elif widget.master.winfo_name().startswith("!ctkmenubar"):
            widget.winfo_toplevel().bind("<Double-Button-1>", self._checkIfMouseLeft, add="+")
            master = widget.master.master if master is None else master
            widget.master.menu.append(self)
        else:
            widget.winfo_toplevel().bind("<Double-Button-1>", self._checkIfMouseLeft, add="+")
            master = widget.master if master is None else master

image

MaxNiark avatar Jan 25 '24 11:01 MaxNiark

ahah ok, there is a thing about trigger event

this both action don't make the same result, like your button

image

MaxNiark avatar Jan 25 '24 15:01 MaxNiark