CustomTkinter icon indicating copy to clipboard operation
CustomTkinter copied to clipboard

Is it possible to make an overlay over an entire frame to catch events?

Open svenakela opened this issue 3 months ago • 3 comments

I have a scrollable frame that is populated with a list of frames. These frames include labels and such, imagine a mp3 playlist because that is what it is.

When a user clicks anywhere on the mp3 frame that file should be activated. If I bind an event to the frame (or the canvas) itself the click will only work if it happens on the background i.e. parts of the frame that is free from other objects.

If the click event happens on the title, artist or the timing labels, nothing happens.

Is there a way to make a frame or canvas overlaying the entire frame and catch the event?

Something like this pseudo code:

class Playable(CTkFrame):
    def __init__(self, master:any, controller:UiController, pm:PlayableMeta):
        super().__init__(master)
        self.pm = pm
        self.controller = controller
        self.grid_rowconfigure((0, 1), weight=1)
        self.columnconfigure(index=0, weight=1)
        self.columnconfigure(index=1, weight=5)
        self.columnconfigure(index=2, weight=1)

        self.lbl_title = CTkLabel(self,
                                 text=pm.title,
                                 font=("Arial", 28), anchor="nw")
        self.lbl_artist = CTkLabel(self,
                                 text=pm.artist,
                                 font=("Arial", 16), anchor="nw")
        self.lbl_length = CTkLabel(self,
                                 text="00:00.000",
                                 font=("Arial", 16), bg_color="#228B22", anchor="e", padx=5, pady=5)

        # ... and so on, until...

        # Will not work on the labels above
        self._canvas.bind("<Button-1>", lambda event: self.play())

        # So I thought of something like
        self.overlay = CTkFrame(self, bg_color="transparent", fg_color="transparent")
        self.overlay.place(x=0, y=0, ...whatever needed to fill up everything)
        self.overlay.bind("<Button-1>", lambda event: self.play())


svenakela avatar Nov 06 '25 14:11 svenakela

@svenakela There is no need to use any type of overlay. Use bind_all() method, it will allow you to read events from all sub widgets of a widget or frame.

Regards.

dipeshSam avatar Nov 08 '25 07:11 dipeshSam

@svenakela There is no need to use any type of overlay. Use bind_all() method, it will allow you to read events from all sub widgets of a widget or frame.

Regards.

Hmmm... bind_all causes the event to happen everywhere in the UI, not only the specific frame. It also seems it is not recommended to use bind_all in customtkinter. It would be fine for binding keyboard shortcuts though.

svenakela avatar Nov 08 '25 12:11 svenakela

@svenakela There are two approaches to achieve this.

  1. Separate class for event handing (Recommended)
from customtkinter import CTk, CTkFrame, CTkLabel

class MyLabel(CTkLabel):
    def __init__(self, master: CTk, *args, **kwargs):
        super().__init__(master, *args, **kwargs)

        self.bind("<Button-1>", self._on_click)
        self.bind("<FocusIn>", self._on_focus)
        self.bind("<FocusOut>", self._on_focus_out)

    def _on_click(self, e=None):
        print(f"Label with text '{self._text}' was clicked!")

    def _on_focus(self, e=None):
        print("Focused!")

    def _on_focus_out(self, e=None):
        print("Focused out!")



class Playable(CTkFrame):
    def __init__(self, master):
        super().__init__(master)
    
        self.grid_rowconfigure((0, 1), weight=1)
        self.columnconfigure(index=0, weight=1)
        self.columnconfigure(index=1, weight=5)
        self.columnconfigure(index=2, weight=1)

        self.lbl_title = MyLabel(self,
                                 text="Sample title",
                                 font=("Arial", 28), anchor="nw")

        self.lbl_artist = MyLabel(self,
                                 text="RAKA",
                                 font=("Arial", 16), anchor="nw")

        self.lbl_length = MyLabel(self,
                                 text="00:00.000",
                                 font=("Arial", 16), bg_color="#228B22", anchor="e", padx=5, pady=5)

        # Above labels are already able to listen and handle events 
        self.lbl_title.grid(row=0, column=0)
        self.lbl_artist.grid(row=1, column=0)
        self.lbl_length.grid(row=2, column=0)

        
        
if __name__ == "__main__":
    app = CTk()

    frm = Playable(app)
    frm.place(relx=0.5, anchor="center", rely=0.5)


    app.mainloop()
  1. Use bind_all() with Tcl commands (For study perpose)
from customtkinter import CTk, CTkFrame, CTkLabel
from tkinter import Event, Frame


class Playable(CTkFrame):
    def __init__(self, master):
        super().__init__(master)
    
        self.grid_rowconfigure((0, 1), weight=1)
        self.columnconfigure(index=0, weight=1)
        self.columnconfigure(index=1, weight=5)
        self.columnconfigure(index=2, weight=1)

        self.lbl_title = CTkLabel(self,
                                 text="Sample title",
                                 font=("Arial", 28), anchor="nw")
        self.lbl_title.grid(row=0, column=0)

        self.lbl_artist = CTkLabel(self,
                                 text="RAKA",
                                 font=("Arial", 16), anchor="nw")
        self.lbl_artist.grid(row=1, column=0)

        self.lbl_length = CTkLabel(self,
                                 text="00:00.000",
                                 font=("Arial", 16), bg_color="#228B22", anchor="e", padx=5, pady=5)
        self.lbl_length.grid(row=2, column=0)

        # ... and so on, until...

        # Will work on the labels above
        Frame.bind_all(self, "<Button-1>", lambda event: print("Clicked!"), add="+")     # It must be `add="+"`


if __name__ == "__main__":
    app = CTk()

    frm = Playable(app)
    frm.place(relx=0.5, anchor="center", rely=0.5)


    app.mainloop()


dipeshSam avatar Nov 09 '25 10:11 dipeshSam