Is it possible to make an overlay over an entire frame to catch events?
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
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.
@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 There are two approaches to achieve this.
- 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()
- 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()