From d9db3b64af1d1accb751093ac0031bda8dbd369a Mon Sep 17 00:00:00 2001 From: TomSchimansky Date: Fri, 19 Aug 2022 00:13:00 +0200 Subject: [PATCH] fixed withdraw and iconify functionality for CTk and CTkToplevel #277 #305 #302 --- customtkinter/appearance_mode_tracker.py | 2 +- customtkinter/windows/ctk_tk.py | 30 ++++++++++++-- customtkinter/windows/ctk_toplevel.py | 41 ++++++++++++++++++- .../test_ctk_toplevel.py | 9 ++++ .../test_iconify_destroy.py | 8 ++-- 5 files changed, 80 insertions(+), 10 deletions(-) diff --git a/customtkinter/appearance_mode_tracker.py b/customtkinter/appearance_mode_tracker.py index 5301e31..210b8bf 100644 --- a/customtkinter/appearance_mode_tracker.py +++ b/customtkinter/appearance_mode_tracker.py @@ -45,7 +45,7 @@ class AppearanceModeTracker: cls.app_list.append(app) if not cls.update_loop_running: - app.after(500, cls.update) + app.after(cls.update_loop_interval, cls.update) cls.update_loop_running = True @classmethod diff --git a/customtkinter/windows/ctk_tk.py b/customtkinter/windows/ctk_tk.py index 89f85f7..5c05a20 100644 --- a/customtkinter/windows/ctk_tk.py +++ b/customtkinter/windows/ctk_tk.py @@ -52,7 +52,8 @@ class CTk(tkinter.Tk): super().title("CTk") self.geometry(f"{self.current_width}x{self.current_height}") - self.window_exists = False # indicates if the window is already shown through update() or mainloop() + self.state_before_windows_set_titlebar_color = None + self.window_exists = False # indicates if the window is already shown through update() or mainloop() after init self.withdraw_called_before_window_exists = False # indicates if withdraw() was called before window is first shown through update() or mainloop() self.iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop() @@ -121,7 +122,9 @@ class CTk(tkinter.Tk): self.window_exists = True if not self.withdraw_called_before_window_exists and not self.iconify_called_before_window_exists: + # print("window dont exists -> deiconify in update") self.deiconify() + super().update() def mainloop(self, *args, **kwargs): @@ -129,7 +132,9 @@ class CTk(tkinter.Tk): self.window_exists = True if not self.withdraw_called_before_window_exists and not self.iconify_called_before_window_exists: + # print("window dont exists -> deiconify in mainloop") self.deiconify() + super().mainloop(*args, **kwargs) def resizable(self, *args, **kwargs): @@ -279,8 +284,15 @@ class CTk(tkinter.Tk): if sys.platform.startswith("win") and not Settings.deactivate_windows_window_header_manipulation: - super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible - if not self.window_exists: + if self.window_exists: + self.state_before_windows_set_titlebar_color = self.state() + # print("window_exists -> state_before_windows_set_titlebar_color: ", self.state_before_windows_set_titlebar_color) + + if self.state_before_windows_set_titlebar_color != "iconic" or self.state_before_windows_set_titlebar_color != "withdrawn": + super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible + else: + # print("window dont exists -> withdraw and update") + super().withdraw() super().update() if color_mode.lower() == "dark": @@ -309,7 +321,17 @@ class CTk(tkinter.Tk): print(err) if self.window_exists: - self.deiconify() + # print("window_exists -> return to original state: ", self.state_before_windows_set_titlebar_color) + if self.state_before_windows_set_titlebar_color == "normal": + self.deiconify() + elif self.state_before_windows_set_titlebar_color == "iconic": + self.iconify() + elif self.state_before_windows_set_titlebar_color == "zoomed": + self.state("zoomed") + else: + self.state(self.state_before_windows_set_titlebar_color) # other states + else: + pass # wait for update or mainloop to be called def set_appearance_mode(self, mode_string): if mode_string.lower() == "dark": diff --git a/customtkinter/windows/ctk_toplevel.py b/customtkinter/windows/ctk_toplevel.py index 3fa4f15..4291ad2 100644 --- a/customtkinter/windows/ctk_toplevel.py +++ b/customtkinter/windows/ctk_toplevel.py @@ -48,6 +48,11 @@ class CTkToplevel(tkinter.Toplevel): super().configure(bg=ThemeManager.single_color(self.fg_color, self.appearance_mode)) super().title("CTkToplevel") + self.state_before_windows_set_titlebar_color = None + self.windows_set_titlebar_color_called = False # indicates if windows_set_titlebar_color was called, stays True until revert_withdraw_after_windows_set_titlebar_color is called + self.withdraw_called_after_windows_set_titlebar_color = False # indicates if withdraw() was called after windows_set_titlebar_color + self.iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color + if sys.platform.startswith("win"): if self.appearance_mode == 1: self.windows_set_titlebar_color("dark") @@ -143,6 +148,16 @@ class CTkToplevel(tkinter.Toplevel): self.disable_macos_dark_title_bar() super().destroy() + def withdraw(self): + if self.windows_set_titlebar_color_called: + self.withdraw_called_after_windows_set_titlebar_color = True + super().withdraw() + + def iconify(self): + if self.windows_set_titlebar_color_called: + self.iconify_called_after_windows_set_titlebar_color = True + super().iconify() + def resizable(self, *args, **kwargs): super().resizable(*args, **kwargs) self.last_resizable_args = (args, kwargs) @@ -234,6 +249,7 @@ class CTkToplevel(tkinter.Toplevel): if sys.platform.startswith("win") and not Settings.deactivate_windows_window_header_manipulation: + self.state_before_windows_set_titlebar_color = self.state() super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible super().update() @@ -261,7 +277,30 @@ class CTkToplevel(tkinter.Toplevel): except Exception as err: print(err) - self.deiconify() + self.windows_set_titlebar_color_called = True + self.after(5, self.revert_withdraw_after_windows_set_titlebar_color) + + def revert_withdraw_after_windows_set_titlebar_color(self): + """ if in a short time (5ms) after """ + if self.windows_set_titlebar_color_called: + + if self.withdraw_called_after_windows_set_titlebar_color: + pass # leave it withdrawed + elif self.iconify_called_after_windows_set_titlebar_color: + super().iconify() + else: + if self.state_before_windows_set_titlebar_color == "normal": + self.deiconify() + elif self.state_before_windows_set_titlebar_color == "iconic": + self.iconify() + elif self.state_before_windows_set_titlebar_color == "zoomed": + self.state("zoomed") + else: + self.state(self.state_before_windows_set_titlebar_color) # other states + + self.windows_set_titlebar_color_called = False + self.withdraw_called_after_windows_set_titlebar_color = False + self.iconify_called_after_windows_set_titlebar_color = False def set_appearance_mode(self, mode_string): if mode_string.lower() == "dark": diff --git a/test/manual_integration_tests/test_ctk_toplevel.py b/test/manual_integration_tests/test_ctk_toplevel.py index 6e2ed7b..88e8f1c 100644 --- a/test/manual_integration_tests/test_ctk_toplevel.py +++ b/test/manual_integration_tests/test_ctk_toplevel.py @@ -1,5 +1,7 @@ import customtkinter +customtkinter.set_appearance_mode("dark") + class ToplevelWindow(customtkinter.CTkToplevel): def __init__(self, *args, closing_event=None, **kwargs): @@ -11,6 +13,9 @@ class ToplevelWindow(customtkinter.CTkToplevel): self.label = customtkinter.CTkLabel(self, text="ToplevelWindow") self.label.pack(padx=20, pady=20) + self.button_1 = customtkinter.CTkButton(self, text="set dark", command=lambda: customtkinter.set_appearance_mode("dark")) + self.button_1.pack(side="top", padx=40, pady=40) + def closing(self): self.destroy() if self.closing_event is not None: @@ -24,6 +29,10 @@ class App(customtkinter.CTk): self.button_1 = customtkinter.CTkButton(self, text="Open CTkToplevel", command=self.open_toplevel) self.button_1.pack(side="top", padx=40, pady=40) + self.button_2 = customtkinter.CTkButton(self, text="iconify toplevel", command=lambda: self.toplevel_window.iconify()) + self.button_2.pack(side="top", padx=40, pady=40) + self.button_3 = customtkinter.CTkButton(self, text="set light", command=lambda: customtkinter.set_appearance_mode("light")) + self.button_3.pack(side="top", padx=40, pady=40) self.toplevel_window = None diff --git a/test/manual_integration_tests/test_iconify_destroy.py b/test/manual_integration_tests/test_iconify_destroy.py index 3492547..9a59107 100644 --- a/test/manual_integration_tests/test_iconify_destroy.py +++ b/test/manual_integration_tests/test_iconify_destroy.py @@ -3,20 +3,20 @@ import customtkinter app = customtkinter.CTk() app.geometry("400x240") -app.withdraw() +app.iconify() app.after(1000, app.deiconify) def button_function(): top = customtkinter.CTkToplevel(app) - top.withdraw() + top.iconify() - app.after(1000, top.deiconify) # show toplevel + app.after(1500, top.deiconify) # show toplevel app.after(2000, top.iconify) # hide toplevel app.after(2500, top.deiconify) # show toplevel app.after(3500, app.iconify) # hide app app.after(4000, app.deiconify) # show app - app.after(5000, app.destroy) # destroy everything + app.after(4500, top.lift) # show app button = customtkinter.CTkButton(app, command=button_function)