import customtkinter as ctk class CTkTableFrame(ctk.CTkFrame): def __init__(self, master: ctk.CTk | ctk.CTkToplevel, columns, data: list, callback, width=400, height=200, *args, **kwargs): super().__init__(master, *args, **kwargs) self.columns = columns self.data = data self._callback = callback self.configure(width=width, height=height) self.loading_frame = ctk.CTkFrame(self) self.loading_frame.pack(fill="both", expand=True) self.clean() def clean(self): _old_children = self.winfo_children() for i, widget in enumerate(_old_children): widget.destroy() self.loading_frame = ctk.CTkFrame(self) self.loading_frame.pack(fill="both", expand=True) def create_table(self): """Создает таблицу с заголовками и данными, используя параметры из словаря.""" loading = ctk.CTkLabel(self.loading_frame, text="Загрузка...", font=("Arial", 12)) loading.pack(fill="both", expand=True) scroll_frame = ctk.CTkScrollableFrame(self) _need_to_pack = [] # Применяем шаблон значений по умолчанию default_column = {"width": 0, "align": "left", "name": "N/A"} columns = [] # [{**default_column, **col} if type(col) == dict else {**default_column, "name": col} for col in self.columns] for col in self.columns: if type(col) == dict: if "width" not in col: col["width"] = (len(col["name"]) * 7) + 14 columns.append({**default_column, **col}) else: columns.append({**default_column, "width": (len(col) * 7) + 14, "name": col}) # Функция преобразования align в формат anchor def parse_align(align): return {"left": "w", "right": "e", "center": "center"}.get(align, "w") # Определяем ширины: если текущая ширина меньше вычисленной, то обновляем for i, col in enumerate(columns): max_data_width = max((len(str(row[i])) * 7 for row in self.data if i < len(row)), default=0) + 10 if col["width"] < max_data_width: col["width"] = max_data_width col["align"] = parse_align(col["align"]) # Заголовки header_frame = ctk.CTkFrame(scroll_frame, fg_color="gray30") _need_to_pack.append((header_frame, {"fill": "x", "padx": 2, "pady": 1})) for col in columns: header_label = ctk.CTkLabel( header_frame, text=col["name"], width=col["width"], anchor=col["align"], padx=5 ) _need_to_pack.append((header_label, {"side": "left", "padx": 2, "pady": 3})) loading.configure(text="Подготовка данных...") # Данные for row_index, row_data in enumerate(self.data): row_frame = ctk.CTkFrame(scroll_frame, fg_color="gray20", corner_radius=3, height=20) _need_to_pack.append((row_frame, {"fill": "x", "padx": 2, "pady": 1})) if self._callback: row_frame.bind("", lambda event, idx=row_index, r=row_data: self._callback({"row_index": idx, "row_data": r})) for i, col in enumerate(columns): cell_text = str(row_data[i]) if i < len(row_data) else "" cell_label = ctk.CTkLabel(row_frame, text=cell_text, width=col["width"], anchor=col["align"], padx=5) _need_to_pack.append((cell_label, {"side": "left", "padx": 2, "pady": 2})) if self._callback: cell_label.bind("", lambda event, idx=row_index, r=row_data: self._callback({"row_index": idx, "row_data": r})) loading.configure(text="Отрисовка данных...") for i, (widget, pack_params) in enumerate(_need_to_pack): widget.pack(**pack_params) scroll_frame.pack(fill="both", expand=True, padx=5, pady=5) self.loading_frame.destroy() def edit(self, row_index, new_data): """Редактирует строку по индексу.""" if 0 <= row_index < len(self.data): self.data[row_index] = new_data self.redraw() def add(self, new_data): """Добавляет новую строку.""" self.data.append(new_data) self.redraw() def redraw(self): """Перерисовывает таблицу.""" self.clean() self.create_table()