/
O.S.Prog
/
DiScan
Обзор
Документация
Войти
/
O.S.Prog
/
DiScan
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
gui/table_view.py
218 строк
8 KB
O.S.Prog
Initial commit: DiScan v1.1.0
18 июл 2026, 15:51
18 июл 2026, 15:51
238a6ec
Код
Авторство
О чём код?
# gui/table_view.py """ Таблица для отображения результатов """ import tkinter as tk from tkinter import ttk from typing import List, Optional, Tuple import locale from core.models import PatientInfo class PatientTableView(ttk.Frame): """Таблица с результатами поиска пациентов""" def __init__(self, parent): super().__init__(parent) # Настройка локали для правильной сортировки русского алфавита try: locale.setlocale(locale.LC_COLLATE, 'ru_RU.UTF-8') except: try: locale.setlocale(locale.LC_COLLATE, 'Russian_Russia.1251') except: pass # Создаем TreeView с колонками self.tree = ttk.Treeview( self, columns=('name', 'id', 'date', 'images', 'files'), show='tree headings', selectmode='browse' ) # Настройка колонок self.tree.heading('#0', text='', anchor=tk.W) self.tree.column('#0', width=0, stretch=False) self.tree.heading('name', text='ФИО пациента', anchor=tk.W, command=lambda: self._sort_by_column('name', False)) self.tree.column('name', width=350, minwidth=200) self.tree.heading('id', text='ID пациента', anchor=tk.W, command=lambda: self._sort_by_column('id', False)) self.tree.column('id', width=220, minwidth=150) self.tree.heading('date', text='Дата', anchor=tk.CENTER, command=lambda: self._sort_by_column('date', False)) self.tree.column('date', width=120, minwidth=100) self.tree.heading('images', text='Снимков', anchor=tk.CENTER, command=lambda: self._sort_by_column('images', False)) self.tree.column('images', width=80, minwidth=70) self.tree.heading('files', text='Файлов', anchor=tk.CENTER, command=lambda: self._sort_by_column('files', False)) self.tree.column('files', width=80, minwidth=70) # Скроллбар scrollbar = ttk.Scrollbar(self, 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._items = {} self._selected = None self._data = [] self._sort_column = None self._sort_reverse = False self.context_menu = None def set_data(self, patients: List[PatientInfo]): """Устанавливает данные в таблицу""" self._data = patients.copy() self._display_data(patients) def _display_data(self, patients: List[PatientInfo]): """Отображает данные в таблице""" self.clear() for patient in patients: tags = () if patient.error: tags = ('error',) name = patient.patient_name if len(name) > 40: name = name[:37] + "..." item = self.tree.insert( '', 'end', values=( name, patient.patient_id, patient.study_date, patient.image_count, patient.total_files ), tags=tags ) self._items[item] = patient self.tree.tag_configure('error', background='#ffe6e6') if self._items: first_item = list(self._items.keys())[0] self.tree.selection_set(first_item) self._selected = self._items[first_item] def clear(self): """Очищает таблицу""" for item in self.tree.get_children(): self.tree.delete(item) self._items.clear() self._selected = None def get_selected(self) -> Optional[PatientInfo]: """Возвращает выделенного пациента""" selection = self.tree.selection() if selection: item = selection[0] return self._items.get(item) if self._items: first_item = list(self._items.keys())[0] return self._items.get(first_item) return None def bind_selection(self, callback): """Привязывает callback на выбор""" self.tree.bind('<<TreeviewSelect>>', callback) def bind_double_click(self, callback): """Привязывает callback на двойной клик""" self.tree.bind('<Double-Button-1>', callback) # ===== КОНТЕКСТНОЕ МЕНЮ ===== def set_context_menu(self, menu): """Устанавливает контекстное меню для таблицы""" self.context_menu = menu self.tree.bind("<Button-3>", self._show_context_menu) self.tree.bind("<Control-Button-1>", self._show_context_menu) def _show_context_menu(self, event): """Показывает контекстное меню""" if self.context_menu: item = self.tree.identify_row(event.y) if item: self.tree.selection_set(item) self.context_menu.tk_popup(event.x_root, event.y_root) else: self.context_menu.tk_popup(event.x_root, event.y_root) # ===== СОРТИРОВКА ===== def _sort_by_column(self, column: str, reverse: bool): """Сортирует данные по указанной колонке""" if self._sort_column == column: reverse = not self._sort_reverse else: reverse = False self._sort_column = column self._sort_reverse = reverse sorted_data = sorted( self._data, key=lambda p: self._get_sort_key(p, column), reverse=reverse ) self._display_data(sorted_data) self._update_sort_indicator(column, reverse) def _get_sort_key(self, patient: PatientInfo, column: str): """Возвращает ключ для сортировки""" if column == 'name': return self._locale_sort_key(patient.patient_name) elif column == 'id': return patient.patient_id elif column == 'date': return patient.study_date elif column == 'images': return patient.image_count elif column == 'files': return patient.total_files return "" def _locale_sort_key(self, text: str) -> str: """Возвращает ключ для сортировки с учётом русского алфавита""" text = text.upper() try: return locale.strxfrm(text) except: return text def _update_sort_indicator(self, column: str, reverse: bool): """Обновляет индикатор сортировки в заголовке""" for col in ('name', 'id', 'date', 'images', 'files'): current_text = self.tree.heading(col, 'text') for arrow in [' ▲', ' ▼']: if current_text.endswith(arrow): current_text = current_text[:-2] self.tree.heading(col, text=current_text) arrow = ' ▼' if reverse else ' ▲' current_text = self.tree.heading(column, 'text') self.tree.heading(column, text=current_text + arrow) def pack(self, *args, **kwargs): """Переопределяем pack для удобства""" super().pack(*args, **kwargs)