diff --git a/ComConfigCopy.py b/ComConfigCopy.py index 25ce503..f0cf505 100644 --- a/ComConfigCopy.py +++ b/ComConfigCopy.py @@ -38,6 +38,7 @@ from about_window import AboutWindow from TFTPServer import TFTPServer import socket from update_checker import UpdateChecker +from send2trash import send2trash # Версия программы VERSION = "1.0.2" @@ -1248,201 +1249,201 @@ class SerialAppGUI(tk.Tk): consecutive_errors = 0 MAX_CONSECUTIVE_ERRORS = 3 # Максимальное количество последовательных ошибок - def check_connection(): - """Проверка состояния соединения""" - # Если выполнение остановлено пользователем, просто возвращаем False без сообщений - if self.execution_stop: - return False - - if not self.connection or not self.connection.is_open: - # Если остановка произошла во время проверки, не показываем сообщение - if self.execution_stop: - return False - - self.append_file_exec_text("[ERROR] Соединение потеряно!\n") - # Автоматически ставим на паузу - self.execution_paused = True - self.after(0, lambda: self.pause_button.config(text="▶ Продолжить")) - - # Показываем сообщение только если это не ручная остановка - if not self.execution_stop: - self.after(0, lambda: messagebox.showerror( - "Ошибка соединения", - "Соединение с устройством потеряно!\nВыполнение команд приостановлено.\n\n" - "Пожалуйста:\n" - "1. Проверьте подключение\n" - "2. Нажмите 'Продолжить' после восстановления соединения\n" - " или 'Остановить' для прекращения выполнения" - )) - return False - return True - def handle_no_response(cmd_or_block, is_block=False): """Обработка отсутствия ответа от устройства""" nonlocal consecutive_errors consecutive_errors += 1 if consecutive_errors >= MAX_CONSECUTIVE_ERRORS: - self.append_file_exec_text( - f"[ERROR] Обнаружено {consecutive_errors} последовательных ошибок!\n" - "Возможно, устройство не отвечает или проблемы с соединением.\n" - ) - # Автоматически ставим на паузу - self.execution_paused = True - self.after(0, lambda: self.pause_button.config(text="▶ Продолжить")) - # Показываем сообщение пользователю - self.after(0, lambda: messagebox.showerror( - "Устройство не отвечает", - f"Обнаружено {consecutive_errors} последовательных ошибок!\n\n" - "Возможные причины:\n" - "1. Устройство не отвечает на команды\n" - "2. Проблемы с соединением\n" - "3. Неверный формат команд\n\n" - "Выполнение приостановлено.\n" - "Проверьте подключение и состояние устройства,\n" - "затем нажмите 'Продолжить' или 'Остановить'." - )) + if not self.execution_stop: # Проверяем, не была ли выполнена остановка + self.append_file_exec_text( + f"[ERROR] Обнаружено {consecutive_errors} последовательных ошибок!\n" + "Возможно, устройство не отвечает или проблемы с соединением.\n" + ) + # Автоматически ставим на паузу + self.execution_paused = True + self.after(0, lambda: self.pause_button.config(text="▶ Продолжить")) + # Показываем сообщение пользователю + self.after(0, lambda: messagebox.showerror( + "Устройство не отвечает", + f"Обнаружено {consecutive_errors} последовательных ошибок!\n\n" + "Возможные причины:\n" + "1. Устройство не отвечает на команды\n" + "2. Проблемы с соединением\n" + "3. Неверный формат команд\n\n" + "Выполнение приостановлено.\n" + "Проверьте подключение и состояние устройства,\n" + "затем нажмите 'Продолжить' или 'Остановить'." + )) return False return True + + def wait_before_next_command(): + """Ожидание перед следующей командой с учетом паузы""" + while self.execution_paused and not self.execution_stop: + time.sleep(0.1) + if self.execution_stop: + return False + time.sleep(1) # Базовая задержка между командами + return True - if copy_mode == "line": - # Построчный режим - while self.current_command_index < len(self.commands): - if self.execution_stop: - break - - if self.execution_paused: - time.sleep(0.1) - continue - - # Проверяем соединение перед каждой командой - if not check_connection(): - continue - - cmd = self.commands[self.current_command_index] - try: - success, response = send_command_and_process_response( - self.connection, - cmd, - self.settings.get("timeout", 10), - max_attempts=3, - log_callback=self.append_file_exec_text, - login=self.settings.get("login"), - password=self.settings.get("password"), - is_gui=True - ) - - if not success: - self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n") - # Проверяем соединение после неудачной попытки - if not check_connection(): - continue - # Обрабатываем отсутствие ответа - if not handle_no_response(cmd): - continue - else: - # Сбрасываем счетчик ошибок при успешном выполнении - consecutive_errors = 0 + try: + if copy_mode == "line": + # Построчный режим + while self.current_command_index < len(self.commands): + if self.execution_stop: + break - self.current_command_index += 1 - self.after(0, self.update_progress) - time.sleep(1) # Задержка между командами - - except Exception as e: - self.append_file_exec_text(f"[ERROR] Ошибка при выполнении команды: {str(e)}\n") - if not check_connection(): + if not wait_before_next_command(): + break + + # Проверяем соединение перед каждой командой + if not self.check_connection(): + if self.execution_stop: # Если это остановка, прерываем выполнение + break continue - break - else: - # Блочный режим - blocks = generate_command_blocks(self.commands, block_size) - total_blocks = len(blocks) - current_block = 0 - - while current_block < total_blocks: - if self.execution_stop: - break - - if self.execution_paused: - time.sleep(0.1) - continue - - # Проверяем соединение перед каждым блоком - if not check_connection(): - continue - - block = blocks[current_block] - try: - # Выводим блок команд без [CMD] префикса - self.append_file_exec_text(f"Выполнение блока команд:\n{block}\n") - success, response = send_command_and_process_response( - self.connection, - block, - self.settings.get("timeout", 10), - max_attempts=3, - log_callback=None, # Отключаем вывод для первой попытки - login=self.settings.get("login"), - password=self.settings.get("password"), - is_gui=True - ) - - if not success or (response and '^' in response): - self.append_file_exec_text("[WARNING] Ошибка при выполнении блока команд. Отправляю команды по отдельности...\n") - # Проверяем соединение перед отправкой отдельных команд - if not check_connection(): - continue + + cmd = self.commands[self.current_command_index] + try: + success, response = send_command_and_process_response( + self.connection, + cmd, + self.settings.get("timeout", 10), + max_attempts=3, + log_callback=self.append_file_exec_text if not self.execution_stop else None, + login=self.settings.get("login"), + password=self.settings.get("password"), + is_gui=True + ) + + if self.execution_stop: + break - # Обрабатываем отсутствие ответа для блока - if not success and not handle_no_response(block, True): - continue - - # Отправляем команды блока по отдельности - for cmd in block.splitlines(): - if self.execution_stop: - break - if cmd.strip(): - if not check_connection(): + if not success: + if not self.execution_stop: + self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n") + # Проверяем соединение после неудачной попытки + if not self.check_connection(): + if self.execution_stop: break - success, resp = send_command_and_process_response( - self.connection, - cmd, - self.settings.get("timeout", 10), - max_attempts=3, - log_callback=self.append_file_exec_text, - login=self.settings.get("login"), - password=self.settings.get("password"), - is_gui=True - ) - if not success: - self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n") - if not check_connection(): - break - # Обрабатываем отсутствие ответа для отдельной команды - if not handle_no_response(cmd): - break - else: - # Сбрасываем счетчик ошибок при успешном выполнении - consecutive_errors = 0 - else: - # Если блок выполнился успешно, выводим ответ и сбрасываем счетчик ошибок - consecutive_errors = 0 - if response: - self.append_file_exec_text(f"Ответ устройства:\n{response}\n") + continue + # Обрабатываем отсутствие ответа + if not handle_no_response(cmd): + continue + else: + # Сбрасываем счетчик ошибок при успешном выполнении + consecutive_errors = 0 + + self.current_command_index += 1 + self.after(0, self.update_progress) + + except Exception as e: + if not self.execution_stop: + self.append_file_exec_text(f"[ERROR] Ошибка при выполнении команды: {str(e)}\n") + break + else: + # Блочный режим + blocks = generate_command_blocks(self.commands, block_size) + total_blocks = len(blocks) + current_block = 0 + + while current_block < total_blocks: + if self.execution_stop: + break + + if not wait_before_next_command(): + break - # Обновляем прогресс на основе количества выполненных блоков - current_block += 1 - self.current_command_index = (current_block * 100) // total_blocks - self.after(0, self.update_progress) - time.sleep(1) - - except Exception as e: - self.append_file_exec_text(f"[ERROR] Ошибка при выполнении блока команд: {str(e)}\n") - if not check_connection(): + # Проверяем соединение перед каждым блоком + if not self.check_connection(): + if self.execution_stop: + break continue - break - # Завершение выполнения - self.after(0, self.execution_completed) + block = blocks[current_block] + try: + if not self.execution_stop: + self.append_file_exec_text(f"Выполнение блока команд:\n{block}\n") + success, response = send_command_and_process_response( + self.connection, + block, + self.settings.get("timeout", 10), + max_attempts=3, + log_callback=None, + login=self.settings.get("login"), + password=self.settings.get("password"), + is_gui=True + ) + + if self.execution_stop: + break + + if not success or (response and '^' in response): + if not self.execution_stop: + self.append_file_exec_text("[WARNING] Ошибка при выполнении блока команд. Отправляю команды по отдельности...\n") + + # Проверяем соединение перед отправкой отдельных команд + if not self.check_connection(): + if self.execution_stop: + break + continue + + # Обрабатываем отсутствие ответа для блока + if not success and not handle_no_response(block, True): + continue + + # Отправляем команды блока по отдельности + for cmd in block.splitlines(): + if self.execution_stop: + break + if cmd.strip(): + if not self.check_connection(): + break + success, resp = send_command_and_process_response( + self.connection, + cmd, + self.settings.get("timeout", 10), + max_attempts=3, + log_callback=self.append_file_exec_text if not self.execution_stop else None, + login=self.settings.get("login"), + password=self.settings.get("password"), + is_gui=True + ) + + if self.execution_stop: + break + + if not success: + if not self.execution_stop: + self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n") + if not self.check_connection(): + break + if not handle_no_response(cmd): + break + else: + consecutive_errors = 0 + + if not wait_before_next_command(): + break + + # Если блок выполнился успешно + if success and not (response and '^' in response): + consecutive_errors = 0 + if response and not self.execution_stop: + self.append_file_exec_text(f"Ответ устройства:\n{response}\n") + + # Обновляем прогресс + current_block += 1 + self.current_command_index = (current_block * 100) // total_blocks + self.after(0, self.update_progress) + + except Exception as e: + if not self.execution_stop: + self.append_file_exec_text(f"[ERROR] Ошибка при выполнении блока команд: {str(e)}\n") + break + finally: + # Завершение выполнения + self.after(0, self.execution_completed) def execution_completed(self): """Обработка завершения выполнения в главном потоке""" @@ -1489,27 +1490,38 @@ class SerialAppGUI(tk.Tk): self.append_file_exec_text("[INFO] Выполнение возобновлено.\n") def stop_execution(self): + """Остановка выполнения команд""" + # Устанавливаем флаг остановки self.execution_stop = True self.execution_paused = False self.timer_running = False - # Отключаемся от COM-порта + # Очищаем очередь команд + if hasattr(self, 'commands'): + delattr(self, 'commands') + + # Сбрасываем индекс текущей команды и прогресс + self.current_command_index = 0 + self.progress_bar['value'] = 0 + self.update_progress() + + # Отключаемся от COM-порта без вывода сообщений об ошибках if self.connection: try: self.stop_port_monitoring() self.connection.close() + except: + pass + finally: self.connection = None - self.append_file_exec_text("[INFO] Соединение закрыто.\n") - except Exception as e: - self.append_file_exec_text(f"[ERROR] Ошибка при закрытии соединения: {str(e)}\n") - self.current_command_index = 0 # Сбрасываем индекс текущей команды - self.progress_bar['value'] = 0 # Сбрасываем прогресс-бар - self.update_progress() # Обновляем отображение прогресса + # Выводим только одно сообщение об остановке self.append_file_exec_text("[INFO] Выполнение остановлено.\n") - self.reset_execution_buttons() # Сбрасываем состояние кнопок - self.update_status_bar() # Обновляем статус бар + # Сбрасываем состояние кнопок + self.reset_execution_buttons() + self.update_status_bar() + def reset_execution_buttons(self): self.start_button.config(state="normal") self.pause_button.config(state="disabled", text="⏸ Пауза") @@ -1959,36 +1971,31 @@ class SerialAppGUI(tk.Tk): self.baudrate_label.config(text=f"Скорость: {baudrate}") self.copy_mode_label.config(text=f"Режим: {copy_mode}") - def on_file_exec_drop(self, event): - """Обработка drop файла во вкладке выполнения команд""" - try: - files = event.data.split() - if files: - file = files[0] # Берем только первый файл - if file.lower().endswith('.txt'): - filename = os.path.basename(file) - new_path = os.path.join("Configs", filename) - - if os.path.exists(new_path): - if not messagebox.askyesno( - "Подтверждение", - f"Файл {filename} уже существует. Перезаписать?" - ): - return - - # Копируем файл в папку Configs - with open(file, 'r', encoding='utf-8') as source: - content = source.read() - with open(new_path, 'w', encoding='utf-8') as dest: - dest.write(content) - - # Устанавливаем путь к файлу - self.file_exec_var.set(new_path) - messagebox.showinfo("Успех", "Файл конфигурации успешно добавлен") - else: - messagebox.showerror("Ошибка", "Поддерживаются только текстовые файлы (.txt)") - except Exception as e: - messagebox.showerror("Ошибка", f"Не удалось добавить файл: {str(e)}") + def check_connection(self): + """Проверка состояния соединения""" + # Если выполнение остановлено пользователем, просто возвращаем False без сообщений + if self.execution_stop: + return False + + if not self.connection or not self.connection.is_open: + # Если это не ручная остановка, показываем сообщение + if not self.execution_stop: + self.append_file_exec_text("[ERROR] Соединение потеряно!\n") + # Автоматически ставим на паузу + self.execution_paused = True + self.after(0, lambda: self.pause_button.config(text="▶ Продолжить")) + + # Показываем сообщение только если это не ручная остановка + self.after(0, lambda: messagebox.showerror( + "Ошибка соединения", + "Соединение с устройством потеряно!\nВыполнение команд приостановлено.\n\n" + "Пожалуйста:\n" + "1. Проверьте подключение\n" + "2. Нажмите 'Продолжить' после восстановления соединения\n" + " или 'Остановить' для прекращения выполнения" + )) + return False + return True # Класс для окна выбора конфигурации class ConfigSelectorWindow(tk.Toplevel): @@ -2041,12 +2048,20 @@ class ConfigSelectorWindow(tk.Toplevel): self.tree.column("#0", width=400, stretch=True) self.tree.heading("#0", text="Конфигурация") - # Создаем контекстное меню - self.context_menu = tk.Menu(self, tearoff=0) - self.context_menu.add_command(label="Редактировать", command=self.edit_selected) - self.context_menu.add_command(label="Переименовать", command=self.rename_selected) - self.context_menu.add_separator() - self.context_menu.add_command(label="Удалить", command=self.delete_selected) + # Создаем контекстное меню для файлов + self.file_menu = tk.Menu(self, tearoff=0) + self.file_menu.add_command(label="✍️ Редактировать", command=self.edit_selected) + self.file_menu.add_command(label="📝 Переименовать", command=self.rename_selected) + self.file_menu.add_command(label="📦 Переместить", command=self.move_selected) + self.file_menu.add_separator() + self.file_menu.add_command(label="🗑️ Удалить", command=self.delete_selected) + + # Создаем контекстное меню для папок + self.folder_menu = tk.Menu(self, tearoff=0) + self.folder_menu.add_command(label="📝 Переименовать", command=self.rename_selected) + self.folder_menu.add_command(label="📦 Переместить", command=self.move_selected) + self.folder_menu.add_separator() + self.folder_menu.add_command(label="🗑️ Удалить", command=self.delete_selected) # Привязываем события self.tree.bind("", self.on_double_click) @@ -2095,8 +2110,13 @@ class ConfigSelectorWindow(tk.Toplevel): item = self.tree.identify('item', event.x, event.y) if item: self.tree.selection_set(item) - if self.tree.item(item)['text'].startswith("📄"): # Только для файлов - self.context_menu.post(event.x_root, event.y_root) + item_text = self.tree.item(item)['text'] + + # Определяем, это файл или папка + if item_text.startswith("📁"): # Папка + self.folder_menu.post(event.x_root, event.y_root) + elif item_text.startswith("📄"): # Файл + self.file_menu.post(event.x_root, event.y_root) def get_selected_item(self): """Получение выбранного элемента""" @@ -2113,18 +2133,95 @@ class ConfigSelectorWindow(tk.Toplevel): self.edit_config(path) def rename_selected(self): - """Переименование выбранного файла""" + """Переименование выбранного элемента""" item_id = self.get_selected_item() if item_id: path = self.get_full_path(item_id) - self.rename_config(item_id, path) + item_text = self.tree.item(item_id)['text'] + + # Определяем, это файл или папка + is_folder = item_text.startswith("📁") + current_name = item_text[2:].strip() # Убираем эмодзи + + # Запрашиваем новое имя + new_name = simpledialog.askstring( + "Переименование", + "Введите новое имя:" if is_folder else "Введите новое имя файла:", + initialvalue=current_name + ) + + if new_name: + try: + if not is_folder and not new_name.endswith('.txt'): + new_name += '.txt' + + new_path = os.path.join(os.path.dirname(path), new_name) + + if os.path.exists(new_path): + if not messagebox.askyesno( + "Подтверждение", + f"{'Папка' if is_folder else 'Файл'} с таким именем уже существует. Перезаписать?" + ): + return + + os.rename(path, new_path) + self.load_configs() # Перезагружаем список + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось переименовать: {str(e)}") + + def move_selected(self): + """Перемещение выбранного элемента""" + item_id = self.get_selected_item() + if item_id: + source_path = self.get_full_path(item_id) + item_text = self.tree.item(item_id)['text'] + is_folder = item_text.startswith("📁") + + if os.path.exists(source_path): + # Создаем окно выбора папки + folder_selector = FolderSelectorDialog(self, "Configs") + if folder_selector.result: + target_folder = folder_selector.result + try: + # Получаем имя элемента из исходного пути + name = os.path.basename(source_path) + # Формируем путь назначения + target_path = os.path.join(target_folder, name) + + # Проверяем, существует ли элемент в целевой папке + if os.path.exists(target_path): + if not messagebox.askyesno( + "Подтверждение", + f"{'Папка' if is_folder else 'Файл'} {name} уже существует в целевой папке. Перезаписать?" + ): + return + + # Перемещаем элемент + os.rename(source_path, target_path) + self.load_configs() # Обновляем список + messagebox.showinfo("Успех", f"{'Папка' if is_folder else 'Файл'} успешно перемещен(а)") + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось переместить: {str(e)}") def delete_selected(self): - """Удаление выбранного файла""" + """Удаление выбранного элемента""" item_id = self.get_selected_item() if item_id: path = self.get_full_path(item_id) - self.delete_config(path) + item_text = self.tree.item(item_id)['text'] + is_folder = item_text.startswith("📁") + + if os.path.exists(path): + # Запрашиваем подтверждение + if messagebox.askyesno( + "Подтверждение", + f"Вы действительно хотите переместить {'папку' if is_folder else 'файл'} {os.path.basename(path)} в корзину?" + ): + try: + send2trash(path) + self.load_configs() # Перезагружаем список + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось переместить в корзину: {str(e)}") def load_configs(self): """Загрузка конфигураций из папки Configs""" @@ -2237,35 +2334,6 @@ class ConfigSelectorWindow(tk.Toplevel): self.master.load_config_file() self.destroy() - def rename_config(self, item_id, old_path): - """Переименование конфигурации""" - if not os.path.isfile(old_path): - return - - # Получаем текущее имя файла без эмодзи - current_name = self.tree.item(item_id)['text'] - if current_name.startswith("📄 "): - current_name = current_name[2:].strip() - - # Запрашиваем новое имя - new_name = simpledialog.askstring( - "Переименование", - "Введите новое имя файла:", - initialvalue=current_name - ) - - if new_name: - if not new_name.endswith('.txt'): - new_name += '.txt' - - new_path = os.path.join(os.path.dirname(old_path), new_name) - - try: - os.rename(old_path, new_path) - self.load_configs() # Перезагружаем список - except Exception as e: - messagebox.showerror("Ошибка", f"Не удалось переименовать файл: {str(e)}") - def delete_config(self, path): """Удаление конфигурации""" if not os.path.isfile(path): @@ -2274,13 +2342,13 @@ class ConfigSelectorWindow(tk.Toplevel): # Запрашиваем подтверждение if messagebox.askyesno( "Подтверждение", - f"Вы действительно хотите удалить файл {os.path.basename(path)}?" + f"Вы действительно хотите переместить файл {os.path.basename(path)} в корзину?" ): try: - os.remove(path) + send2trash(path) self.load_configs() # Перезагружаем список except Exception as e: - messagebox.showerror("Ошибка", f"Не удалось удалить файл: {str(e)}") + messagebox.showerror("Ошибка", f"Не удалось переместить файл в корзину: {str(e)}") def on_double_click(self, event): """Обработка двойного клика""" @@ -2308,6 +2376,99 @@ class ConfigSelectorWindow(tk.Toplevel): y = (self.winfo_screenheight() // 2) - (height // 2) self.geometry(f"{width}x{height}+{x}+{y}") +class FolderSelectorDialog(tk.Toplevel): + def __init__(self, parent, root_folder): + super().__init__(parent) + self.title("Выберите папку") + self.geometry("500x400") # Увеличиваем начальный размер окна + self.result = None + + # Создаем основной фрейм с отступами + main_frame = ttk.Frame(self, padding="10") + main_frame.pack(fill=BOTH, expand=True) + + # Добавляем метку с инструкцией + ttk.Label(main_frame, text="Выберите папку назначения:").pack(anchor=W, pady=(0, 5)) + + # Создаем фрейм для дерева и скроллбара + tree_frame = ttk.Frame(main_frame) + tree_frame.pack(fill=BOTH, expand=True) + + # Создаем и настраиваем дерево папок + self.tree = ttk.Treeview(tree_frame, selectmode="browse") + self.tree.pack(side=LEFT, fill=BOTH, expand=True) + + # Добавляем скроллбар + scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) + scrollbar.pack(side=RIGHT, fill=Y) + self.tree.configure(yscrollcommand=scrollbar.set) + + # Загружаем структуру папок + self.load_folders(root_folder) + + # Кнопки + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill=X, pady=(10, 0)) + + # Кнопки справа + ttk.Button(button_frame, text="❌ Отмена", command=self.on_cancel).pack(side=RIGHT, padx=(5, 0)) + ttk.Button(button_frame, text="✔️ Выбрать папку", command=self.on_select).pack(side=RIGHT) + + # Центрируем окно + self.center_window() + + # Делаем окно модальным + self.transient(parent) + self.grab_set() + + # Устанавливаем минимальный размер окна + self.minsize(400, 300) # Увеличиваем минимальный размер окна + + parent.wait_window(self) + + def center_window(self): + """Центрирование окна на экране""" + self.update_idletasks() + width = self.winfo_width() + height = self.winfo_height() + x = (self.winfo_screenwidth() // 2) - (width // 2) + y = (self.winfo_screenheight() // 2) - (height // 2) + self.geometry(f"{width}x{height}+{x}+{y}") + + def load_folders(self, root_folder): + """Загрузка структуры папок""" + def add_folder(path, parent=""): + try: + items = os.listdir(path) + for item in sorted(items): + full_path = os.path.join(path, item) + if os.path.isdir(full_path): + folder_id = self.tree.insert( + parent, + "end", + text="📁 " + item, + values=(full_path,), + open=False + ) + add_folder(full_path, folder_id) + except Exception: + pass + + # Добавляем корневую папку + root_id = self.tree.insert("", "end", text="📁 Configs", values=(root_folder,), open=True) + add_folder(root_folder, root_id) + + def on_select(self): + """Обработка выбора папки""" + selection = self.tree.selection() + if selection: + self.result = self.tree.item(selection[0])['values'][0] + self.destroy() + + def on_cancel(self): + """Отмена выбора""" + self.destroy() + # ========================== # Основной запуск приложения # ========================== diff --git a/requirements.txt b/requirements.txt index 55d38bb..99af89c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ tftpy>=0.8.0 pyserial>=3.5 requests>=2.31.0 -packaging>=23.2 \ No newline at end of file +packaging>=23.2 +send2trash>=1.8.0 \ No newline at end of file