/
kurzoid
/
gaugematch
Обзор
Документация
Войти
/
kurzoid
/
gaugematch
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/forms/data_forms/base_data_form.py
295 строк
13 KB
kurzoid
окно редактирования сведений о резервуарах
25 окт 2025, 17:24
25 окт 2025, 17:24
ef9196c
Код
Авторство
О чём код?
import tkinter as tk from tkinter import ttk, messagebox from src.forms.base_form import BaseForm class BaseDataForm(BaseForm): def __init__(self, parent, controller, table_name, columns_config, window_title): super().__init__(parent, controller) self.table_name = table_name self.columns_config = columns_config self.window_title = window_title self.editing_id = None self._create_data_widgets() self.load_data() def _create_data_widgets(self): """Создание виджетов для редактирования данных""" # Основной контейнер main_frame = ttk.Frame(self) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # Таблица данных self._create_table(main_frame) # Панель кнопок self._create_buttons(main_frame) def _create_table(self, parent): """Создание таблицы для отображения данных""" # Фрейм для таблицы с прокруткой table_frame = ttk.Frame(parent) table_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) # Создаем Treeview с настройками для отображения границ columns = [col['name'] for col in self.columns_config if not col.get('hidden', False)] self.tree = ttk.Treeview( table_frame, columns=columns, show='headings', height=15, style="Custom.Treeview" # Применяем кастомный стиль ) # Создаем кастомный стиль для Treeview с границами self._create_treeview_style() # Настраиваем заголовки и ширину колонок for col_config in self.columns_config: if not col_config.get('hidden', False): self.tree.heading(col_config['name'], text=col_config['title']) width = col_config.get('width', self._calculate_column_width(col_config['title'])) self.tree.column(col_config['name'], width=width, minwidth=width) # Scrollbar для таблицы scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # Привязываем двойной клик для редактирования self.tree.bind('<Double-1>', self._on_item_double_click) def _create_treeview_style(self): """Создание кастомного стиля для Treeview с границами""" style = ttk.Style() # Создаем стиль для Treeview с границами style.configure("Custom.Treeview", background="white", foreground="black", rowheight=25, fieldbackground="white", borderwidth=1, relief="solid") # Стиль для заголовков style.configure("Custom.Treeview.Heading", background="lightgray", foreground="black", relief="solid", borderwidth=1, font=('', 10, 'bold')) # Стиль для выделенной строки style.map("Custom.Treeview", background=[('selected', '#0078d7')], foreground=[('selected', 'white')]) def _calculate_column_width(self, text): """Вычисление ширины колонки на основе текста заголовка""" base_width = 100 char_width = 8 return base_width + len(text) * char_width def _create_buttons(self, parent): """Создание панели кнопок""" button_frame = ttk.Frame(parent) button_frame.pack(fill=tk.X) # Кнопки слева left_frame = ttk.Frame(button_frame) left_frame.pack(side=tk.LEFT) ttk.Button(left_frame, text="Добавить", command=self._add_record).pack(side=tk.LEFT, padx=5) ttk.Button(left_frame, text="Удалить", command=self._delete_record).pack(side=tk.LEFT, padx=5) ttk.Button(left_frame, text="Сохранить", command=self._save_record).pack(side=tk.LEFT, padx=5) # Кнопка "Закрыть" справа ttk.Button(button_frame, text="Закрыть", command=self._close_form).pack(side=tk.RIGHT, padx=5) def _create_edit_dialog(self): """Создание диалогового окна для редактирования""" dialog = tk.Toplevel(self) dialog.title(self.window_title) dialog.geometry("500x350") dialog.transient(self) # Поля для ввода entries = {} visible_columns = [col for col in self.columns_config if col['name'] != 'id'] for i, col_config in enumerate(visible_columns): ttk.Label(dialog, text=col_config['title'] + ":").grid( row=i, column=0, padx=5, pady=5, sticky="w" ) if col_config.get('type') == 'combobox': entry = ttk.Combobox(dialog, width=40) if 'values' in col_config: entry['values'] = col_config['values'] else: # Создаем Entry с границами entry = tk.Entry( dialog, width=40, borderwidth=1, relief="solid", background="white", font=("", 10) ) entry.grid(row=i, column=1, padx=5, pady=5, sticky="ew") entries[col_config['name']] = entry # Кнопки диалога button_frame = ttk.Frame(dialog) button_frame.grid(row=len(visible_columns), column=0, columnspan=2, pady=10) ttk.Button(button_frame, text="OK", command=lambda: self._on_dialog_ok(dialog, entries)).pack(side=tk.LEFT, padx=5) ttk.Button(button_frame, text="Отмена", command=dialog.destroy).pack(side=tk.LEFT, padx=5) dialog.columnconfigure(1, weight=1) # Отложенный вызов grab_set() после того как окно станет видимым dialog.after(100, lambda: self._safe_grab_set(dialog)) return dialog, entries def _safe_grab_set(self, dialog): """Безопасный вызов grab_set() с обработкой ошибок""" try: dialog.grab_set() except tk.TclError as e: print(f"Предупреждение: не удалось захватить фокус: {e}") # Продолжаем работу без grab_set def _close_form(self): """Закрытие формы""" self.master.destroy() def load_data(self): """Загрузка данных из базы - должен быть переопределен в дочерних классах""" raise NotImplementedError("Метод load_data должен быть реализован в дочернем классе") def _add_record(self): """Добавление новой записи""" self.editing_id = None dialog, entries = self._create_edit_dialog() dialog.title(f"Добавить - {self.window_title}") def _edit_record(self): """Редактирование выбранной записи""" selected = self.tree.selection() if not selected: messagebox.showwarning("Внимание", "Выберите запись для редактирования") return self.editing_id = selected[0] dialog, entries = self._create_edit_dialog() dialog.title(f"Редактировать - {self.window_title}") # Заполняем поля данными выбранной записи item = self.tree.item(selected[0]) values = item['values'] # Сопоставляем значения с колонками visible_columns = [col for col in self.columns_config if not col.get('hidden', False)] for i, col_config in enumerate(visible_columns): if col_config['name'] in entries: entries[col_config['name']].delete(0, tk.END) entries[col_config['name']].insert(0, str(values[i])) def _delete_record(self): """Удаление выбранной записи""" selected = self.tree.selection() if not selected: messagebox.showwarning("Внимание", "Выберите запись для удаления") return if messagebox.askyesno("Подтверждение", "Удалить выбранную запись?"): try: if self._delete_data(selected[0]): self.tree.delete(selected[0]) messagebox.showinfo("Успех", "Запись удалена") else: messagebox.showerror("Ошибка", "Не удалось удалить запись") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось удалить запись: {str(e)}") def _save_record(self): """Сохранение данных""" try: if self._save_all_data(): messagebox.showinfo("Успех", "Данные сохранены") else: messagebox.showerror("Ошибка", "Не удалось сохранить данные") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось сохранить данные: {str(e)}") def _on_dialog_ok(self, dialog, entries): """Обработка нажатия OK в диалоге""" try: data = {} for col_name, entry in entries.items(): if isinstance(entry, ttk.Combobox): data[col_name] = entry.get() else: data[col_name] = entry.get() # Валидация данных if not self._validate_data(data): return # Сохранение в БД if self.editing_id: # Обновление существующей записи if self._update_data(self.editing_id, data): self.load_data() dialog.destroy() messagebox.showinfo("Успех", "Данные обновлены") else: messagebox.showerror("Ошибка", "Не удалось обновить данные") else: # Добавление новой записи new_id = self._insert_data(data) if new_id: self.load_data() dialog.destroy() messagebox.showinfo("Успех", "Данные добавлены") else: messagebox.showerror("Ошибка", "Не удалось добавить данные") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось сохранить данные: {str(e)}") def _on_item_double_click(self, event): """Обработка двойного клика по записи""" self._edit_record() def _validate_data(self, data): """Валидация введенных данных - должен быть переопределен""" return True def _insert_data(self, data): """Добавление данных - должен быть переопределен""" raise NotImplementedError("Метод _insert_data должен быть реализован в дочернем классе") def _update_data(self, record_id, data): """Обновление данных - должен быть переопределен""" raise NotImplementedError("Метод _update_data должен быть реализован в дочернем классе") def _delete_data(self, record_id): """Удаление данных - должен быть переопределен""" raise NotImplementedError("Метод _delete_data должен быть реализован в дочернем классе") def _save_all_data(self): """Сохранение всех данных - должен быть переопределен""" raise NotImplementedError("Метод _save_all_data должен быть реализован в дочернем классе")