CustomTkinter
CustomTkinter copied to clipboard
how to get index of row on click from GUI
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.
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.
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())
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!
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())
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.
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