CustomTkinter icon indicating copy to clipboard operation
CustomTkinter copied to clipboard

how to get index of row on click from GUI

Open karthik-bammidi opened this issue 1 year ago • 6 comments

How to get the row index from GUI based on selected row. If I click on the dropdown of step 2 then I need to get value as 2.

image

karthik-bammidi avatar Dec 05 '23 06:12 karthik-bammidi

Bind event with all Drop-downs or bind an event with frame containing the Drop-downs. .bind("<Button-1>", fun_callback) If you bind it with all the drop-downs, you can directly print the value. If you bind with frame containing drop-downs, you will get event widget (event.widget) like ctk!.ctkframe1!.ctkdropdown1, access the ctkdropdown1 and print value.

dipeshSam avatar Dec 05 '23 07:12 dipeshSam

I was just developing a clickable grid using labels in a scrollable frame. The tricks to parse the widget to get the widget number. Then as you know the order you created them you can determine which widget was clicked. Here is a version that I stripped down for a demo.

I'm new to CustomTkinter and would appreciate any feed back.

import sys
import re
import customtkinter as ctk

def main():
    """ Demonstrate a CustomTinker clickable grid."""
    def handel_label_click(event):
        """ Extracts the number of the label from the event widget. This is then
            used to calculate the x and y indexes into the grid ignoring the row
            label."""
        # Extract tabel number from widget
        label_num_search = re.search(r'ctklabel(\d+)', str(event.widget))
        if not label_num_search:
            print(f"Failed to extract label number from [{event.widget}]")
            return
        label_num = int(label_num_search.group(1))
        
        # y index is simply label number divided by the x length plus 1 for the
        # row label rounded down.
        y = int(label_num / (max_x + 1))
        
        # Use modules x length plus 1 of the label number to set x index.
        # 0 is a special case as this is the last column
        if (label_num % (max_x + 1)) == 0:
            x = max_x - 1
        else:
            x = (label_num % (max_x + 1)) - 2
            
        print(f"Clicked widget {str(event.widget)}"
              f", label number {label_num}"
              f", x index {x}"
              f", y index {y}")
              
        # Change colour of clicked cell
        lbl_grid[y][x+1].configure(fg_color='#FFFF00', text_color='#FF0000')


    # Create a window
    wnd_root = ctk.CTk()
    wnd_root.title("Clickable Grid Demo")

    # Create header frame, pad to align with scrollable frame
    frm_h = ctk.CTkFrame(wnd_root)
    frm_h.pack(padx=5, fill='x', expand=False)

    # Create scrollable frame
    frm_s = ctk.CTkScrollableFrame(wnd_root)
    frm_s.pack(fill='both', expand=True)

    # Set grid size
    max_x = 10
    max_y = 50

    # Create list to hold header labels
    lbl_header = [None] * (max_x+1)    
    # Display header row
    lbl_header[0] = ctk.CTkLabel(frm_h, text='', height=20, width=70)
    lbl_header[0].grid(row=0, column=0)
    for x in range(max_x):
        lbl_header[x+1] = ctk.CTkLabel(frm_h,
                                       text='Column ' + str(x),
                                       height=20,
                                       width=70,
                                       fg_color='#00B0F0')
        lbl_header[x+1].grid(row=-0, column=x+1)

    # Create 2D list to hold labels
    lbl_grid = []
    for s in range(max_y):
        lbl_grid.append([None] * (max_x+1))

    # Display rows of grid
    for y in range(max_y):
        # Display non-clickable row label
        lbl_grid[y][0] = ctk.CTkLabel(frm_s,
                                      text='Row ' + str(y),
                                      height=20,
                                      width=70,
                                      anchor='w',
                                      fg_color='#00B0F0')
        lbl_grid[y][0].grid(row=y, column=0)

        # Display grid and bind left click to cells
        for x in range(max_x):
            lbl_grid[y][x+1] = ctk.CTkLabel(frm_s,
                                            text=str(x) + ',' + str(y),
                                            height=20,
                                            width=70,
                                            fg_color='#FFFFFF')
            lbl_grid[y][x+1].grid(row=y, column=x+1)
            lbl_grid[y][x+1].bind('<Button-1>', handel_label_click)

    # Start CustomTinker event monitor 
    wnd_root.mainloop()


if __name__ == '__main__':
    sys.exit(main())

DanielJOHara avatar Dec 05 '23 13:12 DanielJOHara

Hi @DanielJOHara, very nice widget idea. I only found a little bug concerning last column in general, but last row and last column element in particular. I slightly modified your code to fix it:

import sys
import re
import customtkinter as ctk

def main():
    def handel_label_click(event, label: ctk.CTkLabel) -> None:
        """ Demonstrate a CustomTinker clickable grid."""
        # Extract tabel number from widget
        label_num_search = re.search(r'ctklabel(\d+)', str(event.widget))
        if not label_num_search:
            print(f"Failed to extract label number from [{event.widget}]")
            return
        label_num = int(label_num_search.group(1))
        
        # y index is simply label number divided by the x length plus 1 for the
        # row label rounded down.
        y = int(label_num / (max_x + 1))
        
        # Use modules x length plus 1 of the label number to set x index.
        # 0 is a special case as this is the last column
        if (label_num % (max_x + 1)) == 0:
            x = max_x - 1
        else:
            x = (label_num % (max_x + 1)) - 2
            
        print(f"Clicked widget {str(event.widget)}"
              f", label number {label_num}"
              f", x index {x}"
              f", y index {y}")
        
        # LITTLE CHANGE 2
        label.configure(fg_color='#FFFF00', text_color='#FF0000')


    # Create a window
    wnd_root = ctk.CTk()
    wnd_root.title("Clickable Grid Demo")

    # Create header frame, pad to align with scrollable frame
    frm_h = ctk.CTkFrame(wnd_root)
    frm_h.pack(padx=5, fill='x', expand=False)

    # Create scrollable frame
    frm_s = ctk.CTkScrollableFrame(wnd_root)
    frm_s.pack(fill='both', expand=True)

    # Set grid size
    max_x = 10
    max_y = 50

    # Create list to hold header labels
    lbl_header = [None] * (max_x+1)    
    # Display header row
    lbl_header[0] = ctk.CTkLabel(frm_h, text='', height=20, width=70)
    lbl_header[0].grid(row=0, column=0)
    for x in range(max_x):
        lbl_header[x+1] = ctk.CTkLabel(frm_h,
                                       text='Column ' + str(x),
                                       height=20,
                                       width=70,
                                       fg_color='#00B0F0')
        lbl_header[x+1].grid(row=-0, column=x+1)

    # Create 2D list to hold labels
    lbl_grid = []
    for s in range(max_y):
        lbl_grid.append([None] * (max_x+1))

    # Display rows of grid
    for y in range(max_y):
        # Display non-clickable row label
        lbl_grid[y][0] = ctk.CTkLabel(frm_s,
                                      text='Row ' + str(y),
                                      height=20,
                                      width=70,
                                      anchor='w',
                                      fg_color='#00B0F0')
        lbl_grid[y][0].grid(row=y, column=0)

        # Display grid and bind left click to cells
        for x in range(max_x):
            lbl_grid[y][x+1] = ctk.CTkLabel(frm_s,
                                            text=str(x) + ',' + str(y),
                                            height=20,
                                            width=70,
                                            fg_color='#FFFFFF')
            lbl_grid[y][x+1].grid(row=y, column=x+1)
            # LITTLE CHANGE 1
            lbl_grid[y][x+1].bind('<Button-1>', lambda event, lbl = lbl_grid[y][x+1]: handel_label_click(event, lbl))

    # Start CustomTinker event monitor 
    wnd_root.mainloop()


if __name__ == '__main__':
    sys.exit(main())

Thanks for sharing your code!

LorenzoMattia avatar Dec 08 '23 15:12 LorenzoMattia

Hi @LorenzoMattia thanks for the feed back I have been looking at this more to develop a version that can scroll the grid in both directions. This is what I came up with. It uses a tkinker canvas as I cant see a CustomTkinker alternative.

I've edited this to an updated version with mouse wheel scrolling.

import sys
import re
import customtkinter as ctk
import tkinter as tk

def main():
    """Demonstrate a CustomTkinker scrollable and clickable grid."""
    def scroll_x(*args):
        """Scroll horizontally for scrollbar movements."""
        cnv_ne.xview(*args)
        cnv_se.xview(*args)

    def scroll_y(*args):
        """Scroll vertically for scrollbar movements."""
        cnv_se.yview(*args)
        cnv_sw.yview(*args)

    def handle_mousewheel(event):
        """Scroll vertically for mouse wheel."""
        cnv_se.yview_scroll(int(-1*(event/120)), "units")
        cnv_sw.yview_scroll(int(-1*(event/120)), "units")
        
    def handle_mousewheel_shift(event):
        """Scroll horizontally for mouse wheel.."""
        cnv_ne.xview_scroll(int(-1*(event/120)), "units")
        cnv_se.xview_scroll(int(-1*(event/120)), "units")

    def handel_label_click(event, label: ctk.CTkLabel) -> None:
        """Calculate x and y coordinates from event widget text and capture
           label widget."""
        # Extract label number from widget
        label_num_search = re.search(r'ctklabel(\d+)?', str(event.widget))
        if not label_num_search:
            print(f"Failed to extract label number from"
                  f" event widget [{event.widget}]")
            return
        
        # First label has no number
        if not label_num_search.group(1):
            label_num = 1
        else:
            label_num = int(label_num_search.group(1))

        # y index is label number minus one divided by number of columns rounded
        # down
        y = int((label_num - 1) / max_x)

        # Use modules number of columns of the label number to set x index.
        # 0 is a special case as this is the last column
        if label_num % max_x == 0:
            x = max_x - 1
        else:
            x = label_num % max_x - 1
        
        print(f"Cliked widget {event.widget}"
              f" Label Number {label_num}"
              f" x index {x}"
              f" y index {y}")

        # Set clicked label yellow with red text
        label.configure(fg_color='#FFFF00', text_color='#FF0000')

    max_x = 25
    max_y = 50

    ht_lbl = 60
    wd_lbl = 120

    ht_se = 700
    wd_se = 1400
    font = ('Roboto', 13)

    # Create a window
    wnd_root = ctk.CTk()
    wnd_root.title("Scrollable and Clickable Grid Demo")

    # Define frames to split window into 
    #  nw - static top left
    #  ne - header scrollable left and right 
    #  sw - row labels scrollable up and down
    #  se - grid scrollable in both directions
    frm_nw = ctk.CTkFrame(wnd_root)
    frm_ne = ctk.CTkFrame(wnd_root)
    frm_sw = ctk.CTkFrame(wnd_root)
    frm_se = ctk.CTkFrame(wnd_root)

    frm_nw.grid(row=0, column=0, sticky='nsew')
    frm_ne.grid(row=0, column=1, sticky='nsew')
    frm_sw.grid(row=1, column=0, sticky='nsew')
    frm_se.grid(row=1, column=1, sticky='nsew')

    # Define scroll bars
    hsb = ctk.CTkScrollbar(wnd_root, orientation='horizontal', command=scroll_x)
    vsb = ctk.CTkScrollbar(wnd_root, orientation='vertical', command=scroll_y)

    hsb.grid(row=2, column=0, columnspan=2, sticky='ew')
    vsb.grid(row=0, column=2, rowspan=2, sticky='ns')

    # Make grid resizeable
    wnd_root.grid_columnconfigure(1, weight=1)
    wnd_root.grid_rowconfigure(1, weight=1)

    # Canvas with internal frame for three scrollable frames
    cnv_ne = tk.Canvas(frm_ne, width=wd_se, height=0)
    cnv_sw = tk.Canvas(frm_sw, width=0, height=ht_se)
    cnv_se = tk.Canvas(frm_se, width=wd_se, height=ht_se)

    frm_cnv_ne = ctk.CTkFrame(cnv_ne)
    frm_cnv_sw = ctk.CTkFrame(cnv_sw)
    frm_cnv_se = ctk.CTkFrame(cnv_se)

    cnv_ne.config(yscrollcommand=vsb.set, highlightthickness=0)
    cnv_sw.config(xscrollcommand=hsb.set, highlightthickness=0)
    cnv_se.config(xscrollcommand=hsb.set, yscrollcommand=vsb.set, highlightthickness=0)

    cnv_ne.pack(fill='both', side='left', expand=True)
    cnv_sw.pack(fill='both', side='left', expand=True)
    cnv_se.pack(fill='both', side='left', expand=True)

    cnv_ne.create_window(0, 0, window=frm_cnv_ne, anchor='nw')
    cnv_sw.create_window(0, 0, window=frm_cnv_sw, anchor='nw')
    cnv_se.create_window(0, 0, window=frm_cnv_se, anchor='nw')

    # Write row label header, this will set the height of the header row
    # and width of the row label column
    lbl_row_hdr = ctk.CTkLabel(frm_nw,
                               text='Row #',
                               height=ht_lbl,
                               width=wd_lbl,
                               font=font,
                               anchor='w',
                               fg_color='#00B0F0')
    lbl_row_hdr.grid(row=0, column=0, padx=1, pady=1)

    # Create list of header labels
    lbl_header = [None] * max_x   
    for x in range(max_x):
        lbl_header[x] = ctk.CTkLabel(frm_cnv_ne,
                                     text='Header ' + str(x),
                                     height=ht_lbl,
                                     width=wd_lbl,
                                     font=font,
                                     fg_color='#00B0F0')
        lbl_header[x].grid(row=0, column=x, padx=1, pady=1)

    # Create list of row labels
    lbl_row = [None] * max_y   
    for y in range(max_y):
        lbl_row[y] = ctk.CTkLabel(frm_cnv_sw,
                                  text='Row ' + str(y),
                                  height=ht_lbl,
                                  width=wd_lbl,
                                  font=font,
                                  anchor='w',
                                  fg_color='#00B0F0')
        lbl_row[y].grid(row=y, column=0, padx=1, pady=1)

    # Create 2D list to hold grid labels
    lbl_grid = []
    for y in range(max_y):
        lbl_grid.append([None] * max_x)
        
    for y in range(max_y):
        for x in range(max_x):
            
            lbl_grid[y][x] = ctk.CTkLabel(frm_cnv_se,
                                          text=str(x) + ',' + str(y),
                                          height=ht_lbl,
                                          width=wd_lbl,
                                          font=font,
                                          fg_color='#FFFFFF')
            lbl_grid[y][x].grid(row=y, column=x, padx=1, pady=1)
            lbl_grid[y][x].bind('<Button-1>',
                lambda event, lbl = lbl_grid[y][x]: handel_label_click(event, lbl))

    # Size canvases to size of frame with widgets
    cnv_ne.update_idletasks()
    cnv_sw.update_idletasks()
    cnv_se.update_idletasks()

    cnv_ne.config(scrollregion=frm_cnv_ne.bbox())
    cnv_sw.config(scrollregion=frm_cnv_sw.bbox())
    cnv_se.config(scrollregion=frm_cnv_se.bbox())

    # Bind mouse wheel for scrolling
    cnv_se.bind_all("<MouseWheel>", lambda e: handle_mousewheel(e.delta))
    cnv_se.bind_all("<Shift-MouseWheel>", lambda e: handle_mousewheel_shift(e.delta))
    cnv_se.bind_all("<Button-4>", lambda e: handle_mousewheel(120))
    cnv_se.bind_all("<Button-5>", lambda e: handle_mousewheel(-120))
    cnv_se.bind_all("<Shift-Button-4>", lambda e: handle_mousewheel_shift(120))
    cnv_se.bind_all("<Shift-Button-5>", lambda e: handle_mousewheel_shift(-120))

    # Start CustomTinker event monitor 
    wnd_root.mainloop()


if __name__ == '__main__':
    sys.exit(main())

DanielJOHara avatar Dec 13 '23 22:12 DanielJOHara

Hi @DanielJOHara, nice! CustomTkinter does not have a built-in frame that can scroll in both x and y direction. However, I found this really cool widget implemented by Akascape that does exactly what you are looking for.

LorenzoMattia avatar Dec 16 '23 13:12 LorenzoMattia

Hi

He reached out to me and pointed it out to me. It is very well written. But I need to have extra frames to hold the header and row labels that scroll with the grid only in one direction each. I did use the binding he had to the mouse wheel which would have taken me a while to work out. I have a cleaner version now that I will update in my post in case anyone picks it up.

Thanks Daniel

DanielJOHara avatar Dec 16 '23 19:12 DanielJOHara