import ctypes from threading import Thread from typing import Optional, Union, Tuple from customtkinter import CTkToplevel, CTkProgressBar, CTkLabel, ThemeManager, CTkFont from loguru import logger as _logger logger = _logger.bind(module="CTkLoadingBox", prefix="misc") class CTkLoadingBox(CTkToplevel): def __init__(self, title: str = "CTkDialog", text: str = "CTkDialog", font: Optional[Union[tuple, CTkFont]] = None, hide_topbar: bool = False, fg_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: Optional[Union[str, Tuple[str, str]]] = None, button_fg_color: Optional[Union[str, Tuple[str, str]]] = None, button_hover_color: Optional[Union[str, Tuple[str, str]]] = None, button_text_color: Optional[Union[str, Tuple[str, str]]] = None, entry_fg_color: Optional[Union[str, Tuple[str, str]]] = None, entry_border_color: Optional[Union[str, Tuple[str, str]]] = None, entry_text_color: Optional[Union[str, Tuple[str, str]]] = None, parent=None): super().__init__(fg_color=fg_color) self._fg_color = ThemeManager.theme["CTkToplevel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color) self._text_color = ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else self._check_color_type(button_hover_color) self._button_fg_color = ThemeManager.theme["CTkButton"]["fg_color"] if button_fg_color is None else self._check_color_type(button_fg_color) self._button_hover_color = ThemeManager.theme["CTkButton"]["hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color) self._button_text_color = ThemeManager.theme["CTkButton"]["text_color"] if button_text_color is None else self._check_color_type(button_text_color) self._entry_fg_color = ThemeManager.theme["CTkEntry"]["fg_color"] if entry_fg_color is None else self._check_color_type(entry_fg_color) self._entry_border_color = ThemeManager.theme["CTkEntry"]["border_color"] if entry_border_color is None else self._check_color_type(entry_border_color) self._entry_text_color = ThemeManager.theme["CTkEntry"]["text_color"] if entry_text_color is None else self._check_color_type(entry_text_color) if hide_topbar: self.overrideredirect(True) # Округление окна hwnd = ctypes.windll.user32.GetParent(self.winfo_id()) self.bind("", self.__start_move) self.bind("", self.__on_move) self._running: bool = False self._progress = 0 self._text = [text, ""] self._font = font # self.geometry("300x100") self.transient(parent) self.title(title) self.lift() # lift window on top self.attributes("-topmost", True) # stay on top self.protocol("WM_DELETE_WINDOW", self._on_closing) self._create_widgets() self.resizable(False, False) # self.grab_set() # make other windows not clickable self.withdraw() self._req_events() def __start_move(self, event): self.x = event.x self.y = event.y def __on_move(self, event): self.geometry(f"+{event.x_root - self.x}+{event.y_root - self.y}") def _req_events(self): event.register("loading.open", self.open) event.register("loading.close", self.close) event.register("loading.set_text", self.set_text) event.register("loading.set_subtext", self.set_subtext) event.register("loading.set_progress", self.set_progress) def open(self, *_, **__): self.grab_set() self.deiconify() def close(self, *_, **__): self.grab_release() self.withdraw() self.set_text("closed") self.set_subtext("closed") self.set_progress(1) def _create_widgets(self): # self.message_label = CTkLabel(self, text=self._text) self.message_label = CTkLabel( self, width=300, wraplength=300, fg_color="transparent", text=self._text[0] ) self.message_label.pack(pady=5) # Прогресс-бар self.progress_bar = CTkProgressBar(self, mode="determinate", width=250) self.progress_bar.pack(pady=10) self.progress_bar.set(0) # Устанавливаем начальное значение прогресса # Текст с процентами self.percent_label = CTkLabel( self, width=300, wraplength=300, fg_color="transparent", text="0%" ) self.percent_label.pack(pady=5) def _on_closing(self): self.grab_release() # self.destroy() def __run(self, f, *args, **kwargs): self._running = True try: f(*args, **kwargs) except Exception as e: logger.exception(e) finally: self._running = False self.grab_release() self.destroy() self.quit() def process(self, func, *args, **kwargs): Thread(target=self.__run, args=(func, self, *args), kwargs=kwargs, daemon=True).start() self.mainloop() def _set_subtext(self, subtext): self._text[1] = subtext self.message_label.configure(text=f"{self._text[0]}: {self._text[1]}") def set_subtext(self, subtext, *_, **__): self.after(0, self._set_subtext, subtext) def _set_text(self, text): self._text[0] = text self.message_label.configure(text=f"{self._text[0]}") def set_text(self, text, *_, **__): self.after(0, self._set_text, text) def _set_progress(self, value): if 0.0 <= value <= 1.0: self._progress = value self.progress_bar.set(value) # Обновляем прогресс-бар self.percent_label.configure(text=f"{int(value * 100)}%") # Обновляем текст процентов def set_progress(self, value, *_, **__): self.after(0, self._set_progress, value) if __name__ == '__main__': c = CTkLoadingBox("Title", "Message") c.set_progress(0.475) c.mainloop()