Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ class GeometryCallDict(TypedDict):
# add configure callback to tkinter.Frame
super().bind('<Configure>', 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
Expand All @@ -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("<FocusIn>", self._on_focus_in, add="+")
super().bind("<FocusOut>", 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. """
Expand Down
20 changes: 20 additions & 0 deletions customtkinter/windows/widgets/ctk_checkbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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("<FocusIn>", lambda e: self._highlight(True))
self.bind("<FocusOut>", 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 """
Expand All @@ -134,6 +151,9 @@ def _create_bindings(self, sequence: Optional[str] = None):
if sequence is None or sequence == "<Button-1>":
self._canvas.bind("<Button-1>", self.toggle)
self._text_label.bind("<Button-1>", self.toggle)
if sequence is None or sequence == "<space>":
self._canvas.bind("<space>", self.toggle)
self._text_label.bind("<space>", self.toggle)

def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs)
Expand Down