fixed focus bug of CTk and CTkToplevel on macOS

This commit is contained in:
Tom Schimansky 2022-10-22 01:20:08 +02:00
parent 2288c255ac
commit 9d7eca7bb1
7 changed files with 64 additions and 38 deletions

View File

@ -102,7 +102,7 @@ class CTkComboBox(CTkBaseClass):
highlightthickness=0, highlightthickness=0,
font=self._apply_font_scaling(self._font)) font=self._apply_font_scaling(self._font))
self._configure_grid_system() self._create_grid()
# insert default value # insert default value
if len(self._values) > 0: if len(self._values) > 0:
@ -123,21 +123,21 @@ class CTkComboBox(CTkBaseClass):
if self._variable is not None: if self._variable is not None:
self._entry.configure(textvariable=self._variable) self._entry.configure(textvariable=self._variable)
def _configure_grid_system(self): def _create_grid(self):
self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew") self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
left_section_width = self._current_width - self._current_height left_section_width = self._current_width - self._current_height
self._entry.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="ew", self._entry.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="ew",
padx=(max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(3)), padx=(max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(3)),
max(self._apply_widget_scaling(self._current_width - left_section_width + 3), self._apply_widget_scaling(3))), max(self._apply_widget_scaling(self._current_width - left_section_width + 3), self._apply_widget_scaling(3))),
pady=self._border_width) pady=self._apply_widget_scaling(self._border_width))
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
# change entry font size and grid padding # change entry font size and grid padding
self._entry.configure(font=self._apply_font_scaling(self._font)) self._entry.configure(font=self._apply_font_scaling(self._font))
self._configure_grid_system() self._create_grid()
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height)) height=self._apply_widget_scaling(self._desired_height))
@ -157,7 +157,7 @@ class CTkComboBox(CTkBaseClass):
# Workaround to force grid to be resized when text changes size. # Workaround to force grid to be resized when text changes size.
# Otherwise grid will lag and only resizes if other mouse action occurs. # Otherwise grid will lag and only resizes if other mouse action occurs.
self._canvas.grid_forget() self._canvas.grid_forget()
self._canvas.grid(row=0, column=0, columnspan=3, sticky="nswe") self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
def destroy(self): def destroy(self):
if isinstance(self._font, CTkFont): if isinstance(self._font, CTkFont):
@ -219,7 +219,7 @@ class CTkComboBox(CTkBaseClass):
if "border_width" in kwargs: if "border_width" in kwargs:
self._border_width = kwargs.pop("border_width") self._border_width = kwargs.pop("border_width")
self._configure_grid_system() self._create_grid()
require_redraw = True require_redraw = True
if "state" in kwargs: if "state" in kwargs:

View File

@ -5,6 +5,7 @@ from .ctk_canvas import CTkCanvas
from ..theme_manager import ThemeManager from ..theme_manager import ThemeManager
from ..draw_engine import DrawEngine from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass from .widget_base_class import CTkBaseClass
from ..utility.ctk_font import CTkFont
from customtkinter.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty from customtkinter.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
@ -37,7 +38,7 @@ class CTkEntry(CTkBaseClass):
textvariable: tkinter.Variable = None, textvariable: tkinter.Variable = None,
placeholder_text: str = None, placeholder_text: str = None,
font: Union[str, Tuple] = "default_theme", font: Union[tuple, CTkFont] = "default_theme",
state: str = tkinter.NORMAL, state: str = tkinter.NORMAL,
**kwargs): **kwargs):
@ -59,7 +60,6 @@ class CTkEntry(CTkBaseClass):
self._border_width = ThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width self._border_width = ThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width
# text and state # text and state
self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font
self._is_focused: bool = True self._is_focused: bool = True
self._placeholder_text = placeholder_text self._placeholder_text = placeholder_text
self._placeholder_text_active = False self._placeholder_text_active = False
@ -68,6 +68,11 @@ class CTkEntry(CTkBaseClass):
self._state = state self._state = state
self._textvariable_callback_name: str = "" self._textvariable_callback_name: str = ""
# font
self._font = CTkFont() if font == "default_theme" else self._check_font_type(font)
if isinstance(self._font, CTkFont):
self._font.add_size_configure_callback(self._update_font)
if not (self._textvariable is None or self._textvariable == ""): if not (self._textvariable is None or self._textvariable == ""):
self._textvariable_callback_name = self._textvariable.trace_add("write", self._textvariable_callback) self._textvariable_callback_name = self._textvariable.trace_add("write", self._textvariable_callback)
@ -75,7 +80,6 @@ class CTkEntry(CTkBaseClass):
highlightthickness=0, highlightthickness=0,
width=self._apply_widget_scaling(self._current_width), width=self._apply_widget_scaling(self._current_width),
height=self._apply_widget_scaling(self._current_height)) height=self._apply_widget_scaling(self._current_height))
self._canvas.grid(column=0, row=0, sticky="nswe")
self._draw_engine = DrawEngine(self._canvas) self._draw_engine = DrawEngine(self._canvas)
self._entry = tkinter.Entry(master=self, self._entry = tkinter.Entry(master=self,
@ -86,14 +90,8 @@ class CTkEntry(CTkBaseClass):
state=self._state, state=self._state,
textvariable=self._textvariable, textvariable=self._textvariable,
**pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes)) **pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes))
if self._corner_radius >= self._minimum_x_padding:
self._entry.grid(column=0, row=0, sticky="nswe", self._create_grid()
padx=min(self._apply_widget_scaling(self._corner_radius), round(self._apply_widget_scaling(self._current_height/2))),
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1)))
else:
self._entry.grid(column=0, row=0, sticky="nswe",
padx=self._apply_widget_scaling(self._minimum_x_padding),
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1)))
check_kwargs_empty(kwargs, raise_error=True) check_kwargs_empty(kwargs, raise_error=True)
@ -103,6 +101,18 @@ class CTkEntry(CTkBaseClass):
self._activate_placeholder() self._activate_placeholder()
self._draw() self._draw()
def _create_grid(self):
self._canvas.grid(column=0, row=0, sticky="nswe")
if self._corner_radius >= self._minimum_x_padding:
self._entry.grid(column=0, row=0, sticky="nswe",
padx=min(self._apply_widget_scaling(self._corner_radius), round(self._apply_widget_scaling(self._current_height/2))),
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1)))
else:
self._entry.grid(column=0, row=0, sticky="nswe",
padx=self._apply_widget_scaling(self._minimum_x_padding),
pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1)))
def _textvariable_callback(self, var_name, index, mode): def _textvariable_callback(self, var_name, index, mode):
if self._textvariable.get() == "": if self._textvariable.get() == "":
self._activate_placeholder() self._activate_placeholder()
@ -111,10 +121,8 @@ class CTkEntry(CTkBaseClass):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
self._entry.configure(font=self._apply_font_scaling(self._font)) self._entry.configure(font=self._apply_font_scaling(self._font))
self._entry.grid(column=0, row=0, sticky="we",
padx=self._apply_widget_scaling(self._corner_radius) if self._corner_radius >= 6 else self._apply_widget_scaling(6))
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
self._create_grid()
self._draw(no_color_updates=True) self._draw(no_color_updates=True)
def _set_dimensions(self, width=None, height=None): def _set_dimensions(self, width=None, height=None):
@ -124,6 +132,21 @@ class CTkEntry(CTkBaseClass):
height=self._apply_widget_scaling(self._desired_height)) height=self._apply_widget_scaling(self._desired_height))
self._draw() self._draw()
def _update_font(self):
""" pass font to tkinter widgets with applied font scaling and update grid with workaround """
self._entry.configure(font=self._apply_font_scaling(self._font))
# Workaround to force grid to be resized when text changes size.
# Otherwise grid will lag and only resizes if other mouse action occurs.
self._canvas.grid_forget()
self._canvas.grid(column=0, row=0, sticky="nswe")
def destroy(self):
if isinstance(self._font, CTkFont):
self._font.remove_size_configure_callback(self._update_font)
super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode)) self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
@ -180,16 +203,12 @@ class CTkEntry(CTkBaseClass):
if "border_width" in kwargs: if "border_width" in kwargs:
self._border_width = kwargs.pop("border_width") self._border_width = kwargs.pop("border_width")
self._create_grid()
require_redraw = True require_redraw = True
if "corner_radius" in kwargs: if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius") self._corner_radius = kwargs.pop("corner_radius")
if self._corner_radius >= self._minimum_x_padding: self._create_grid()
self._entry.grid(column=0, row=0, sticky="we",
padx=min(self._apply_widget_scaling(self._corner_radius), round(self._apply_widget_scaling(self._current_height/2))))
else:
self._entry.grid(column=0, row=0, sticky="we",
padx=self._apply_widget_scaling(self._minimum_x_padding))
require_redraw = True require_redraw = True
if "placeholder_text" in kwargs: if "placeholder_text" in kwargs:
@ -209,8 +228,13 @@ class CTkEntry(CTkBaseClass):
self._entry.configure(textvariable=self._textvariable) self._entry.configure(textvariable=self._textvariable)
if "font" in kwargs: if "font" in kwargs:
self._font = kwargs.pop("font") if isinstance(self._font, CTkFont):
self._entry.configure(font=self._apply_font_scaling(self._font)) self._font.remove_size_configure_callback(self._update_font)
self._font = self._check_font_type(kwargs.pop("font"))
if isinstance(self._font, CTkFont):
self._font.add_size_configure_callback(self._update_font)
self._update_font()
if "show" in kwargs: if "show" in kwargs:
if self._placeholder_text_active: if self._placeholder_text_active:

View File

@ -239,9 +239,9 @@ class CTkBaseClass(tkinter.Frame):
if len(font) == 1: if len(font) == 1:
return font return font
elif len(font) == 2: elif len(font) == 2:
return font[0], round(font[1] * self._widget_scaling) return font[0], -abs(round(font[1] * self._widget_scaling))
elif len(font) == 3: elif len(font) == 3:
return font[0], round(font[1] * self._widget_scaling), font[2] return font[0], -abs(round(font[1] * self._widget_scaling)), font[2]
else: else:
raise ValueError(f"Can not scale font {font}. font needs to be tuple of len 1, 2 or 3") raise ValueError(f"Can not scale font {font}. font needs to be tuple of len 1, 2 or 3")

View File

@ -76,9 +76,9 @@ class CTk(tkinter.Tk):
self._block_update_dimensions_event = False self._block_update_dimensions_event = False
def _focus_in_event(self, event): def _focus_in_event(self, event):
# sometimes window looses focus on macOS if window is selected from Mission Control, so focus has to be forced again # sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again
if sys.platform == "darwin": if sys.platform == "darwin":
self.focus_force() self.lift()
def _update_dimensions_event(self, event=None): def _update_dimensions_event(self, event=None):
if not self._block_update_dimensions_event: if not self._block_update_dimensions_event:

View File

@ -70,9 +70,9 @@ class CTkToplevel(tkinter.Toplevel):
self.bind('<FocusIn>', self._focus_in_event) self.bind('<FocusIn>', self._focus_in_event)
def _focus_in_event(self, event): def _focus_in_event(self, event):
# sometimes window looses focus on macOS if window is selected from Mission Control, so focus has to be forced again # sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again
if sys.platform == "darwin": if sys.platform == "darwin":
self.focus_force() self.lift()
def _update_dimensions_event(self, event=None): def _update_dimensions_event(self, event=None):
detected_width = self.winfo_width() # detect current window size detected_width = self.winfo_width() # detect current window size

View File

@ -42,7 +42,7 @@ b.pack(pady=2)
b1 = customtkinter.CTkButton(frame_1, text="object default modified") b1 = customtkinter.CTkButton(frame_1, text="object default modified")
b1.pack(pady=(10, 2)) b1.pack(pady=(10, 2))
b1.cget("font").configure(size=9) b1.cget("font").configure(size=9)
print(b1.cget("font").cget("size"), b1.cget("font").cget("family")) print("test_font.py:", b1.cget("font").cget("size"), b1.cget("font").cget("family"))
b2 = customtkinter.CTkButton(frame_1, text="object default overridden") b2 = customtkinter.CTkButton(frame_1, text="object default overridden")
b2.pack(pady=10) b2.pack(pady=10)
@ -58,7 +58,9 @@ for i in range(30):
c.grid(row=i, column=2, pady=1) c.grid(row=i, column=2, pady=1)
c = customtkinter.CTkComboBox(frame_2, font=label_font, height=15) c = customtkinter.CTkComboBox(frame_2, font=label_font, height=15)
c.grid(row=i, column=3, pady=1) c.grid(row=i, column=3, pady=1)
frame_2.grid_columnconfigure((0, 1, 2, 3), weight=1) e = customtkinter.CTkEntry(frame_2, font=label_font, height=15, placeholder_text="testtest")
e.grid(row=i, column=4, pady=1)
frame_2.grid_columnconfigure((0, 1, 2, 3, 4), weight=1)
app.after(1500, lambda: label_font.configure(size=10)) app.after(1500, lambda: label_font.configure(size=10))
# app.after(1500, lambda: l.configure(text="dshgfldjskhfjdslafhdjsgkkjdaslö")) # app.after(1500, lambda: l.configure(text="dshgfldjskhfjdslafhdjsgkkjdaslö"))

View File

@ -8,9 +8,9 @@ app.title("CustomTkinter Test")
def change_state(widget): def change_state(widget):
if widget.state == tkinter.NORMAL: if widget.cget("state") == tkinter.NORMAL:
widget.configure(state=tkinter.DISABLED) widget.configure(state=tkinter.DISABLED)
elif widget.state == tkinter.DISABLED: elif widget.cget("state") == tkinter.DISABLED:
widget.configure(state=tkinter.NORMAL) widget.configure(state=tkinter.NORMAL)