1

I was curious if it is possible to create an clickable element that contains multiple elements?

ttk.Button appears to take text or an image.

I would like to have a clickable element that will have 8 text items and 2 images inside it. Clicking anywhere in that element will trigger the same backend method.

Any code examples would be helpful as still wading through TKinter -- only my second project to use it.

eat-sleep-code
  • 4,753
  • 13
  • 52
  • 98

2 Answers2

2

Use bindtags and give the same tag for all widgets that you want to be clickable, then use bind_class to bind all the widgets.

Here's an example

import tkinter as tk

def clicked(event):
    print("Clicked !")


class ClickableElement(tk.Frame):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.bindtags('Click')  # or pass a list if you want to have multiple tag names

        for x in range(8):
            lbl = tk.Label(self, text=f"Text {x}")
            lbl.bindtags('Click')
            lbl.pack()
        

root = tk.Tk()
root.geometry("200x200")


frame = ClickableElement(root) # or tk.Frame(root, class_='Click')
frame.pack(fill='both', expand=True)


frame2 = tk.Frame(root, bg='red')
frame2.pack(expand=True, fill='both')


root.bind_class('Click', "<Button-1>", clicked)
root.mainloop()

The above example will make both text and Frame clickable. You can expand on this to include images. Alternatively, You can use bind on each widget inside the frame.

Art
  • 2,836
  • 4
  • 17
  • 34
  • Thanks! I think these responses are getting things pointed in the right direction, but I am running into some problems getting items to actually display. Would you mind taking a look above. The code runs but the items aren't getting put on the canvas. I am guessing the structure of my code is causing some issue with items updating. – eat-sleep-code Jun 05 '21 at 16:10
  • 1
    @eat-sleep-code your infinite `while` loop and `time.sleep` must be blocking tkinters mainloop. I suggest you ask that as another question. Changing this question will make this post confusing to other readers. – Art Jun 05 '21 at 16:16
  • figured if I posted another question would be even more confusing as my original question is still potentially open. Removing the `while True` and `time.sleep` just causes the program to exit after showing the light gray screen. app.update() should be sufficient for updating the screen. – eat-sleep-code Jun 05 '21 at 16:33
  • @eat-sleep-code Your code is not reproducible. create a Minimal, reproducible, example and paste it in pastebin or create a new post describing your problem exactly, you are more likely to receive help if you do so. People running into problems when using `time.sleep` or infinite `while` loops in their programs are quite common specifically when starting out with a GUI framework. Have you thought about using `widget.after` , which might be what you are looking for. – Art Jun 05 '21 at 16:43
  • @eat-sleep-code would you mind marking my answer as accepted since you seemed to be using my solution? – Art Jun 08 '21 at 05:03
  • oops thought I had. Did it now. – eat-sleep-code Jun 08 '21 at 05:09
0

What you can do is get the children of the tk.Frame and bind them to a function.

from tkinter import *
from tkinter import messagebox
class ClickFrame:
    def __init__(self,root):
        self.root = root
        self.root.geometry("700x500")
        Label(self.root,text="Clickable Frame!",font=("arial",15)).pack(fill=X,side=TOP)
        self.Frame1 = Frame(self.root,bg="light grey")
        self.Frame1.pack(fill=BOTH,expand=1,padx=100,pady=100)
        for  i in range(8):
            Label(self.Frame1,text=f"This is Label {i}").pack(pady=5,anchor="w",padx=5)
        self.Frame1.bind("<Button-1>",self.detect_click)
        for wid in self.Frame1.winfo_children():
            wid.bind("<Button-1>",self.detect_click)
    def detect_click(self,event,*args):
        
        messagebox.showerror("Clicked",f"Clicked  the widget {event.widget}")
        print(event.widget,type(event.widget))
        
        
root=Tk()
ob=ClickFrame(root)
root.mainloop()

You also can use bind_all() to bind . However, this will bind everything in the window.

from tkinter import *
from tkinter import messagebox
class ClickFrame:
    def __init__(self,root):
        self.root = root
        self.root.geometry("700x500")
        Label(self.root,text="Clickable Frame!",font=("arial",15)).pack(fill=X,side=TOP)
        self.Frame1 = Frame(self.root,bg="light grey")
        self.Frame1.pack(fill=BOTH,expand=1,padx=100,pady=100)
        for  i in range(8):
            Label(self.Frame1,text=f"This is Labe {i}").pack(pady=5,anchor="w",padx=5)
        self.Frame1.bind_all("<Button-1>",self.detect_click)
    def detect_click(self,event,*args):
        messagebox.showerror("Clicked",f"Clicked  the widget {event.widget}")
        
root=Tk()
ob=ClickFrame(root)
root.mainloop()