diff --git a/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py b/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py index afd9431d..0c32677d 100644 --- a/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py +++ b/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py @@ -70,6 +70,11 @@ class GeometryCallDict(TypedDict): # add configure callback to tkinter.Frame super().bind('', self._update_dimensions_event) + # focus handling + self._has_focus: bool = False + self._focus_enabled: bool = True + self._enable_focus_handling() + # overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget as well if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame, tkinter.LabelFrame, ttk.Frame, ttk.LabelFrame, ttk.Notebook)) and not isinstance(self.master, (CTkBaseClass, CTkAppearanceModeBaseClass)): master_old_configure = self.master.config @@ -90,6 +95,37 @@ def new_configure(*args, **kwargs): self.master.config = new_configure self.master.configure = new_configure + + def _enable_focus_handling(self): + """Enable keyboard focus handling (Tab navigation).""" + + if not self._focus_enabled: + return + + # allow widget to receive focus via Tab + try: + super().configure(takefocus=True) + except Exception: + pass + + # bind focus events + super().bind("", self._on_focus_in, add="+") + super().bind("", self._on_focus_out, add="+") + + def _on_focus_in(self, event=None): + self._has_focus = True + self._apply_focus_state() + + def _on_focus_out(self, event=None): + self._has_focus = False + self._apply_focus_state() + + def _apply_focus_state(self): + """ + Called when focus state changes. + Subclasses override this to apply visual focus indication. + """ + pass def destroy(self): """ Destroy this and all descendants widgets. """ diff --git a/customtkinter/windows/widgets/ctk_checkbox.py b/customtkinter/windows/widgets/ctk_checkbox.py index 42f04f5e..905dfbd7 100644 --- a/customtkinter/windows/widgets/ctk_checkbox.py +++ b/customtkinter/windows/widgets/ctk_checkbox.py @@ -101,6 +101,8 @@ def __init__(self, width=self._apply_widget_scaling(self._checkbox_width), height=self._apply_widget_scaling(self._checkbox_height)) self._canvas.grid(row=0, column=0, sticky="e") + self._canvas.configure(takefocus=True) + self._draw_engine = DrawEngine(self._canvas) self._text_label = tkinter.Label(master=self, @@ -118,10 +120,25 @@ def __init__(self, if self._variable is not None and self._variable != "": self._variable_callback_name = self._variable.trace_add("write", self._variable_callback) self._check_state = True if self._variable.get() == self._onvalue else False + + self.bind("", lambda e: self._highlight(True)) + self.bind("", lambda e: self._highlight(False)) self._create_bindings() self._set_cursor() self._draw() + + def _highlight(self, is_active): + if is_active: + # Changes colors of text and border to be the theme the user chooses. + themed_border = ThemeManager.theme["CTkButton"]["fg_color"] # mimic the CTkButton theme fg_color + themed_text = ThemeManager.theme["CTkButton"]["fg_color"] # mimic the CTkButton theme fg_color + self.configure(border_color=themed_border, border_width=2, text_color=themed_text) + else: + # Pull original themed values to restore them + themed_border = ThemeManager.theme["CTkCheckBox"]["border_color"] # return normal CTkCheckBox colors + themed_text = ThemeManager.theme["CTkCheckBox"]["text_color"] # return normal CTkCheckBox colors + self.configure(border_color=themed_border, border_width=2, text_color=themed_text) def _create_bindings(self, sequence: Optional[str] = None): """ set necessary bindings for functionality of widget, will overwrite other bindings """ @@ -134,6 +151,9 @@ def _create_bindings(self, sequence: Optional[str] = None): if sequence is None or sequence == "": self._canvas.bind("", self.toggle) self._text_label.bind("", self.toggle) + if sequence is None or sequence == "": + self._canvas.bind("", self.toggle) + self._text_label.bind("", self.toggle) def _set_scaling(self, *args, **kwargs): super()._set_scaling(*args, **kwargs)