Загрузка данных
# Dozor/modules/utils/window_behavior.py
import tkinter as tk
class LevelOneBehavior:
"""
Поведение Главного окна (Уровень 1).
Применяется к объекту класса tk.Tk
"""
def setup_level_one(self):
# 1. Окно неизменно в размерах
self.resizable(False, False)
# 5. Раскрываться не должно (уже выполнено resizable False)
# 2. Открытие строго по центру экрана
self.update_idletasks() # Обновляем данные о размере окна перед расчетом
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
size = tuple(int(_) for _ in self.geometry().split('+')[0].split('x'))
x = (screen_width - size[0]) // 2
y = (screen_height - size[1]) // 2
self.geometry(f"{size[0]}x{size[1]}+{x}+{y}")
# 3. Возможность передвижения пользователем
# Эмуляция захвата за любое место окна (или привяжите к конкретному виджету шапки)
self.bind("<ButtonPress-1>", self.save_offset)
self.bind("<B1-Motion>", self.drag_window)
# 4. Сворачивание (-) и закрытие (X) работают из коробки.
# Кнопка "Свернуть" работает всегда, скрыть её нельзя без сторонних библиотек.
# Но можно заблокировать действие разворачивания двойным кликом по шапке
self.bind("<Double-Button-1>", lambda e: None)
# Хранилище для приклеенных окон 2 уровня
self._child_windows = []
self._drag_start_x = 0
self._drag_start_y = 0
def save_offset(self, event):
self._drag_start_x = event.x_root - self.winfo_x()
self._drag_start_y = event.y_root - self.winfo_y()
def drag_window(self, event):
x = event.x_root - self._drag_start_x
y = event.y_root - self._drag_start_y
self.geometry(f"+{x}+{y}")
# 7. Тащим за собой окна 2 уровня
for child_data in self._child_windows:
win, offset_x, offset_y = child_data
win.geometry(f"+{x + offset_x}+{y + offset_y}")
def register_child_window(self, window, offset_x=0, offset_y=0):
"""Регистрирует дочернее окно 2 уровня."""
self._child_windows.append((window, offset_x, offset_y))
class LevelTwoBehavior:
"""
Поведение окон второго уровня.
Применяется к объекту класса tk.Toplevel
"""
def setup_level_two(self, parent_main_window):
self.parent_main = parent_main_window
# 1. Может увеличиваться, но не меньше интерфейса
width, height = int(self.winfo_reqwidth()), int(self.winfo_reqheight())
self.minsize(width, height)
self.resizable(True, True) # Разрешаем менять размер вправо и вниз
# 2. Центрирование относительно главного окна
p_x = self.parent_main.winfo_x()
p_y = self.parent_main.winfo_y()
p_w = self.parent_main.winfo_width()
p_h = self.parent_main.winfo_height()
x = p_x + (p_w - width) // 2
y = p_y + (p_h - height) // 2
self.geometry(f"+{x}+{y}")
# Сохраняем смещение для синхронного движения (требование №3)
self._offset_x = x - p_x
self._offset_y = y - p_y
# Регистрация у родителя
self.parent_main.register_child_window(self, self._offset_x, self._offset_y)
# 4. Блокировка сворачивания через "-" (кнопка останется, но ничего не сделает)
# Полностью убрать стандартные кнопки Min/Max в чистом Tkinter сложно,
# это требует вызова WinAPI через ctypes. Оставляем заглушку на событие.
self.protocol("WM_MINIMIZE_WINDOW", self.do_nothing)
# 5. Закрытие и разворачивание
# Протокол закрытия (крестик)
self.protocol("WM_DELETE_WINDOW", self.destroy)
# Свой функционал для разворачивания (квадратик), если нужна анимация,
# иначе оставляем стандартное поведение системы.
# Перетаскивание (приклеено к родителю)
self.bind("<ButtonPress-1>", self.save_offset)
self.bind("<B1-Motion>", self.drag_with_parent)
def do_nothing(self, event=None):
pass # Заглушка для блокировки сворачивания
def save_offset(self, event):
self._win_click_x = event.x
self._win_click_y = event.y
def drag_with_parent(self, event):
dx = event.x_root - self._win_click_x
dy = event.y_root - self._win_click_y
new_parent_x = dx - self._offset_x
new_parent_y = dy - self._offset_y
# Двигаем родителя
self.parent_main.geometry(f"+{new_parent_x}+{new_parent_y}")
# Двигаем себя (геометрия обновится также через регистрацию в родителе при его движении)
self.geometry(f"+{dx}+{dy}")
class LevelThreeBehavior:
"""
Поведение окон третьего уровня.
Применяется к объекту класса tk.Toplevel
"""
def setup_level_three(self):
# 1. Минимальный размер
width, height = int(self.winfo_reqwidth()), int(self.winfo_reqheight())
self.minsize(width, height)
self.resizable(True, True)
# 2. Левый верхний угол независимо от других
self.geometry("+0+0")
# 4. Блокировка сворачивания
self.protocol("WM_MINIMIZE_WINDOW", self.do_nothing)
# 5. Закрытие
self.protocol("WM_DELETE_WINDOW", self.destroy)
# 3. Свободное передвижение
self.bind("<ButtonPress-1>", self.save_offset)
self.bind("<B1-Motion>", self.drag_free)
def do_nothing(self, event=None):
pass
def save_offset(self, event):
self._win_click_x = event.x
self._win_click_y = event.y
def drag_free(self, event):
dx = event.x_root - self._win_click_x
dy = event.y_root - self._win_click_y
self.geometry(f"+{dx}+{dy}")