From c033baf00a11f0f710ebd4c9de75b66438518c51 Mon Sep 17 00:00:00 2001 From: Tune Me In <107177248+TuneMeIn@users.noreply.github.com> Date: Mon, 26 May 2025 20:31:38 +1200 Subject: [PATCH 1/5] Update ctk_scrollable_dropdown.py --- .../ctk_scrollable_dropdown.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/CTkScrollableDropdown/ctk_scrollable_dropdown.py b/CTkScrollableDropdown/ctk_scrollable_dropdown.py index 8867c1d..8a61ff4 100644 --- a/CTkScrollableDropdown/ctk_scrollable_dropdown.py +++ b/CTkScrollableDropdown/ctk_scrollable_dropdown.py @@ -11,7 +11,7 @@ class CTkScrollableDropdown(customtkinter.CTkToplevel): def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, width: int = None, - fg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None, + fg_color=None, bg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None, scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=2, values=[], command=None, image_values=[], alpha: float = 0.97, frame_corner_radius=20, double_click=False, resize=True, frame_border_color=None, text_color=None, autocomplete=False, @@ -29,9 +29,12 @@ def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, self.disable = True self.update() + # Fix for background colour clipping through corners of frame + self.bg_color = self._apply_appearance_mode(self._fg_color) if bg_color is None else bg_color + if sys.platform.startswith("win"): self.after(100, lambda: self.overrideredirect(True)) - self.transparent_color = self._apply_appearance_mode(self._fg_color) + self.transparent_color = self.bg_color self.attributes("-transparentcolor", self.transparent_color) elif sys.platform.startswith("darwin"): self.overrideredirect(True) @@ -40,7 +43,7 @@ def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, self.focus_something = True else: self.overrideredirect(True) - self.transparent_color = '#000001' + self.transparent_color = frame_border_color self.corner = 0 self.padding = 18 self.withdraw() @@ -168,7 +171,6 @@ def fade_out(self): break self.attributes("-alpha", i/100) self.update() - time.sleep(1/100) def fade_in(self): for i in range(0,100,10): @@ -176,7 +178,6 @@ def fade_in(self): break self.attributes("-alpha", i/100) self.update() - time.sleep(1/100) def _init_buttons(self, **button_kwargs): self.i = 0 @@ -228,8 +229,16 @@ def _iconify(self): self.event_generate("<>") self.focus() self.hide = False + + # Reset dropdown to show all options regardless of entry text + for key in self.widgets.keys(): + self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0)) + self.no_match.pack_forget() + self.button_num = len(self.values) + self.place_dropdown() - self._deiconify() + self._deiconify() + if self.focus_something: self.dummy_entry.pack() self.dummy_entry.focus_set() @@ -265,9 +274,10 @@ def live_update(self, string=None): else: self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0)) i+=1 - + self.no_match.pack_forget() if i==1: - self.no_match.pack(fill="x", pady=2, padx=(self.padding, 0)) + self.withdraw() + self.hide = True else: self.no_match.pack_forget() self.button_num = i From 5f371aaa4281848d64215d8fe0ce8cee32011e04 Mon Sep 17 00:00:00 2001 From: Tune Me In <107177248+TuneMeIn@users.noreply.github.com> Date: Mon, 26 May 2025 20:57:59 +1200 Subject: [PATCH 2/5] Update ctk_scrollable_dropdown.py --- CTkScrollableDropdown/ctk_scrollable_dropdown.py | 1 - 1 file changed, 1 deletion(-) diff --git a/CTkScrollableDropdown/ctk_scrollable_dropdown.py b/CTkScrollableDropdown/ctk_scrollable_dropdown.py index 8a61ff4..5b623e3 100644 --- a/CTkScrollableDropdown/ctk_scrollable_dropdown.py +++ b/CTkScrollableDropdown/ctk_scrollable_dropdown.py @@ -5,7 +5,6 @@ import customtkinter import sys -import time import difflib class CTkScrollableDropdown(customtkinter.CTkToplevel): From aa682f150e2579c19eaca34b1ff64d8c4f599a0d Mon Sep 17 00:00:00 2001 From: Tune Me In <107177248+TuneMeIn@users.noreply.github.com> Date: Mon, 26 May 2025 23:22:31 +1200 Subject: [PATCH 3/5] Update ctk_scrollable_dropdown.py --- .../ctk_scrollable_dropdown.py | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/CTkScrollableDropdown/ctk_scrollable_dropdown.py b/CTkScrollableDropdown/ctk_scrollable_dropdown.py index 5b623e3..e5f57a6 100644 --- a/CTkScrollableDropdown/ctk_scrollable_dropdown.py +++ b/CTkScrollableDropdown/ctk_scrollable_dropdown.py @@ -9,7 +9,7 @@ class CTkScrollableDropdown(customtkinter.CTkToplevel): - def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, width: int = None, + def __init__(self, attach, x=None, y=None, button_color=None, max_height: int = 92, width: int = None, fg_color=None, bg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None, scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=2, values=[], command=None, image_values=[], alpha: float = 0.97, frame_corner_radius=20, double_click=False, @@ -28,7 +28,7 @@ def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, self.disable = True self.update() - # Fix for background colour clipping through corners of frame + # Fix for background colour clipping through corners of frame. self.bg_color = self._apply_appearance_mode(self._fg_color) if bg_color is None else bg_color if sys.platform.startswith("win"): @@ -75,10 +75,9 @@ def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, border_color=self.frame_border_color) self.frame._scrollbar.grid_configure(padx=3) self.frame.pack(expand=True, fill="both") - self.dummy_entry = customtkinter.CTkEntry(self.frame, fg_color="transparent", border_width=0, height=1, width=1) - self.no_match = customtkinter.CTkLabel(self.frame, text="No Match") - self.height = height - self.height_new = height + self.max_height = max_height + self.max_height_new = max_height + self.height_new = 0 # Actual dynamic height will be set in place_dropdown. self.width = width self.command = command self.fade = False @@ -148,8 +147,11 @@ def _withdraw(self): def _update(self, a, b, c): self.live_update(self.attach._entry.get()) - - def bind_autocomplete(self, ): + # Close dropdown if no text remains in the combobox entry section. + if self.attach._entry.get() == "": + self.withdraw() + + def bind_autocomplete(self): def appear(x): self.appear = True @@ -205,13 +207,17 @@ def place_dropdown(self): self.y_pos = self.attach.winfo_rooty() + self.attach.winfo_reqheight() + 5 if self.y is None else self.y + self.attach.winfo_rooty() self.width_new = self.attach.winfo_width() if self.width is None else self.width + visible_buttons = self.button_num + pady = 2 # Padding between buttons. if self.resize: - if self.button_num<=5: - self.height_new = self.button_height * self.button_num + 55 - else: - self.height_new = self.button_height * self.button_num + 35 - if self.height_new>self.height: - self.height_new = self.height + # Calculate the total height of buttons including vertical padding. + self.height_new = (self.button_height + 2 * pady) * visible_buttons + 20 + extra_padding = 5 # Extra padding to prevent clipping. + self.height_new += extra_padding + + # Cap height to a maximum value so that the dropdown doesn't go offscreen. + if self.height_new > self.max_height: + self.height_new = self.max_height self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) @@ -229,19 +235,14 @@ def _iconify(self): self.focus() self.hide = False - # Reset dropdown to show all options regardless of entry text + # Reset dropdown to show all options regardless of entry text. for key in self.widgets.keys(): self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0)) - self.no_match.pack_forget() self.button_num = len(self.values) self.place_dropdown() self._deiconify() - if self.focus_something: - self.dummy_entry.pack() - self.dummy_entry.focus_set() - self.after(100, self.dummy_entry.pack_forget) else: self.withdraw() self.hide = True @@ -257,13 +258,13 @@ def _attach_key_press(self, k): self.hide = True def live_update(self, string=None): + match_found = False if not self.appear: return if self.disable: return if self.fade: return if string: string = string.lower() - self._deiconify() - i=1 + visible_buttons = 0 for key in self.widgets.keys(): s = self.widgets[key].cget("text").lower() text_similarity = difflib.SequenceMatcher(None, s[0:len(string)], string).ratio() @@ -272,18 +273,18 @@ def live_update(self, string=None): self.widgets[key].pack_forget() else: self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0)) - i+=1 - self.no_match.pack_forget() - if i==1: + match_found = True + visible_buttons += 1 + if match_found: + self._deiconify() + self.button_num = visible_buttons + self.place_dropdown() + self.hide = False + else: self.withdraw() self.hide = True - else: - self.no_match.pack_forget() - self.button_num = i - self.place_dropdown() else: - self.no_match.pack_forget() self.button_num = len(self.values) for key in self.widgets.keys(): self.widgets[key].destroy() @@ -321,8 +322,8 @@ def hide(self): def configure(self, **kwargs): if "height" in kwargs: - self.height = kwargs.pop("height") - self.height_new = self.height + self.max_height = kwargs.pop("height") + self.max_height_new = self.max_height if "alpha" in kwargs: self.alpha = kwargs.pop("alpha") From 4b73f5ba8d1fc8849c2553957ea0f7874f940ffe Mon Sep 17 00:00:00 2001 From: Tune Me In <107177248+TuneMeIn@users.noreply.github.com> Date: Tue, 27 May 2025 17:22:01 +1200 Subject: [PATCH 4/5] Quick bug patch --- CTkScrollableDropdown/ctk_scrollable_dropdown.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CTkScrollableDropdown/ctk_scrollable_dropdown.py b/CTkScrollableDropdown/ctk_scrollable_dropdown.py index e5f57a6..ef91775 100644 --- a/CTkScrollableDropdown/ctk_scrollable_dropdown.py +++ b/CTkScrollableDropdown/ctk_scrollable_dropdown.py @@ -46,6 +46,7 @@ def __init__(self, attach, x=None, y=None, button_color=None, max_height: int = self.corner = 0 self.padding = 18 self.withdraw() + self.hide = True # self.hide = True self.attach.bind('', lambda e: self._withdraw() if not self.disable else None, add="+") @@ -149,7 +150,8 @@ def _update(self, a, b, c): self.live_update(self.attach._entry.get()) # Close dropdown if no text remains in the combobox entry section. if self.attach._entry.get() == "": - self.withdraw() + self.withdraw() + self.hide = True # Fix for an issue where the dropdown would close, but two clicks were required to open it again. def bind_autocomplete(self): def appear(x): @@ -223,7 +225,7 @@ def place_dropdown(self): self.x_pos, self.y_pos)) self.fade_in() self.attributes('-alpha', self.alpha) - self.attach.focus() + #self.attach.focus() def _iconify(self): if self.attach.cget("state")=="disabled": return @@ -232,10 +234,10 @@ def _iconify(self): self.hide = False if self.hide: self.event_generate("<>") - self.focus() + #self.focus() self.hide = False - # Reset dropdown to show all options regardless of entry text. + # Reset dropdown to show all options regardless of entry text when the dropdown button is manually pressed. for key in self.widgets.keys(): self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0)) self.button_num = len(self.values) From 48fc1b751e276c9b9b85f5a138f31f3bc8370a2e Mon Sep 17 00:00:00 2001 From: Tune Me In <107177248+TuneMeIn@users.noreply.github.com> Date: Tue, 27 May 2025 19:29:38 +1200 Subject: [PATCH 5/5] Another bug fix Fixed issue where autocomplete window would show up, and clicking somewhere other than the dropdown in order to remove the dropdown would require two clicks instead of one. --- CTkScrollableDropdown/ctk_scrollable_dropdown.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/CTkScrollableDropdown/ctk_scrollable_dropdown.py b/CTkScrollableDropdown/ctk_scrollable_dropdown.py index ef91775..bf1bb10 100644 --- a/CTkScrollableDropdown/ctk_scrollable_dropdown.py +++ b/CTkScrollableDropdown/ctk_scrollable_dropdown.py @@ -46,7 +46,6 @@ def __init__(self, attach, x=None, y=None, button_color=None, max_height: int = self.corner = 0 self.padding = 18 self.withdraw() - self.hide = True # self.hide = True self.attach.bind('', lambda e: self._withdraw() if not self.disable else None, add="+") @@ -281,7 +280,6 @@ def live_update(self, string=None): self._deiconify() self.button_num = visible_buttons self.place_dropdown() - self.hide = False else: self.withdraw() self.hide = True