added _set_dimensions method to every widget, now called from CTkBaseClass, added separate width and height attributes for small canvas in CTkCheckbox, CTkRadioButton, CTkSwitch

This commit is contained in:
Tom Schimansky 2022-10-14 18:51:44 +02:00
parent eabfa67335
commit 5977dcbaeb
15 changed files with 177 additions and 108 deletions

View File

@ -97,7 +97,6 @@ class CTkButton(CTkBaseClass):
self._canvas.bind("<Leave>", self._on_leave)
self._canvas.bind("<Button-1>", self._clicked)
self._canvas.bind("<Button-1>", self._clicked)
super().bind('<Configure>', self._update_dimensions_event)
# configure cursor and initial draw
self._set_cursor()

View File

@ -17,8 +17,10 @@ class CTkCheckBox(CTkBaseClass):
def __init__(self,
master: any = None,
width: int = 24,
width: int = 100,
height: int = 24,
checkbox_width: int = 24,
checkbox_height: int = 24,
corner_radius: Union[int, str] = "default_theme",
border_width: Union[int, str] = "default_theme",
@ -44,6 +46,10 @@ class CTkCheckBox(CTkBaseClass):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# dimensions
self._checkbox_width = checkbox_width
self._checkbox_height = checkbox_height
# color
self._fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
self._hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
@ -84,13 +90,13 @@ class CTkCheckBox(CTkBaseClass):
highlightthickness=0,
width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
self._bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
self._bg_canvas.grid(row=0, column=0, columnspan=3, sticky="nswe")
self._canvas = CTkCanvas(master=self,
highlightthickness=0,
width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
self._canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, rowspan=1)
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._draw_engine = DrawEngine(self._canvas)
self._canvas.bind("<Enter>", self._on_enter)
@ -99,11 +105,13 @@ class CTkCheckBox(CTkBaseClass):
self._text_label = tkinter.Label(master=self,
bd=0,
padx=0,
pady=0,
text=self._text,
justify=tkinter.LEFT,
font=self._apply_font_scaling(self._font),
textvariable=self._textvariable)
self._text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
self._text_label.grid(row=0, column=2, sticky="w")
self._text_label["anchor"] = "w"
self._text_label.bind("<Enter>", self._on_enter)
@ -125,10 +133,18 @@ class CTkCheckBox(CTkBaseClass):
self._text_label.configure(font=self._apply_font_scaling(self._font))
self._canvas.delete("checkmark")
self._bg_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._bg_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._checkbox_width),
height=self._apply_widget_scaling(self._checkbox_height))
self._draw()
def _set_dimensions(self, width: int = None, height: int = None):
super()._set_dimensions(width, height)
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
def destroy(self):
if self._variable is not None:
self._variable.trace_remove("write", self._variable_callback_name)
@ -136,18 +152,19 @@ class CTkCheckBox(CTkBaseClass):
super().destroy()
def _draw(self, no_color_updates=False):
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height),
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._checkbox_width),
self._apply_widget_scaling(self._checkbox_height),
self._apply_widget_scaling(self._corner_radius),
self._apply_widget_scaling(self._border_width))
if self._check_state is True:
self._draw_engine.draw_checkmark(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height),
self._apply_widget_scaling(self._current_height * 0.58))
self._draw_engine.draw_checkmark(self._apply_widget_scaling(self._checkbox_width),
self._apply_widget_scaling(self._checkbox_height),
self._apply_widget_scaling(self._checkbox_height * 0.58))
else:
self._canvas.delete("checkmark")
if no_color_updates is False or requires_recoloring:
self._bg_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))
@ -179,6 +196,16 @@ class CTkCheckBox(CTkBaseClass):
self._text_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
def configure(self, require_redraw=False, **kwargs):
if "checkbox_width" in kwargs:
self._checkbox_width = kwargs.pop("checkbox_width")
self._canvas.configure(width=self._apply_widget_scaling(self._checkbox_width))
require_redraw = True
if "checkbox_height" in kwargs:
self._checkbox_height = kwargs.pop("checkbox_height")
self._canvas.configure(height=self._apply_widget_scaling(self._checkbox_height))
require_redraw = True
if "text" in kwargs:
self._text = kwargs.pop("text")
self._text_label.configure(text=self._text)
@ -234,6 +261,10 @@ class CTkCheckBox(CTkBaseClass):
return self._corner_radius
elif attribute_name == "border_width":
return self._border_width
elif attribute_name == "checkbox_width":
return self._checkbox_width
elif attribute_name == "checkbox_height":
return self._checkbox_height
elif attribute_name == "fg_color":
return self._fg_color

View File

@ -119,7 +119,6 @@ class CTkComboBox(CTkBaseClass):
self._canvas.tag_bind("dropdown_arrow", "<Leave>", self._on_leave)
self._canvas.tag_bind("right_parts", "<Button-1>", self._clicked)
self._canvas.tag_bind("dropdown_arrow", "<Button-1>", self._clicked)
super().bind('<Configure>', self._update_dimensions_event)
if self._variable is not None:
self._entry.configure(textvariable=self._variable)

View File

@ -97,7 +97,6 @@ class CTkEntry(CTkBaseClass):
self._check_kwargs_empty(kwargs, raise_error=True)
super().bind('<Configure>', self._update_dimensions_event)
self._entry.bind('<FocusOut>', self._entry_focus_out)
self._entry.bind('<FocusIn>', self._entry_focus_in)

View File

@ -62,8 +62,6 @@ class CTkFrame(CTkBaseClass):
self._draw_engine = DrawEngine(self._canvas)
self._overwrite_preferred_drawing_method = overwrite_preferred_drawing_method
super().bind('<Configure>', self._update_dimensions_event)
self._draw()
def winfo_children(self) -> List[any]:

View File

@ -76,7 +76,6 @@ class CTkLabel(CTkBaseClass):
self._check_kwargs_empty(kwargs, raise_error=True)
super().bind('<Configure>', self._update_dimensions_event)
self._draw()
def _set_scaling(self, *args, **kwargs):

View File

@ -129,8 +129,6 @@ class CTkOptionMenu(CTkBaseClass):
self._text_label.bind("<Button-1>", self._clicked)
self._text_label.bind("<Button-1>", self._clicked)
super().bind('<Configure>', self._update_dimensions_event)
self._draw() # initial draw
if self._variable is not None:

View File

@ -81,9 +81,6 @@ class CTkProgressBar(CTkBaseClass):
self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nswe")
self._draw_engine = DrawEngine(self._canvas)
# Each time an item is resized due to pack position mode, the binding Configure is called on the widget
super().bind('<Configure>', self._update_dimensions_event)
self._draw() # initial draw
if self._variable is not None:

View File

@ -17,8 +17,10 @@ class CTkRadioButton(CTkBaseClass):
def __init__(self,
master: any = None,
width: int = 22,
width: int = 100,
height: int = 22,
radiobutton_width: int = 22,
radiobutton_height: int = 22,
corner_radius: Union[int, str] = "default_theme",
border_width_unchecked: Union[int, str] = "default_theme",
border_width_checked: Union[int, str] = "default_theme",
@ -43,6 +45,10 @@ class CTkRadioButton(CTkBaseClass):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# dimensions
self._radiobutton_width = radiobutton_width
self._radiobutton_height = radiobutton_height
# color
self._fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
self._hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
@ -81,13 +87,13 @@ class CTkRadioButton(CTkBaseClass):
highlightthickness=0,
width=self._apply_widget_scaling(self._current_width),
height=self._apply_widget_scaling(self._current_height))
self._bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
self._bg_canvas.grid(row=0, column=0, columnspan=3, sticky="nswe")
self._canvas = CTkCanvas(master=self,
highlightthickness=0,
width=self._apply_widget_scaling(self._current_width),
height=self._apply_widget_scaling(self._current_height))
self._canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1)
width=self._apply_widget_scaling(self._radiobutton_width),
height=self._apply_widget_scaling(self._radiobutton_height))
self._canvas.grid(row=0, column=0)
self._draw_engine = DrawEngine(self._canvas)
self._canvas.bind("<Enter>", self._on_enter)
@ -96,11 +102,13 @@ class CTkRadioButton(CTkBaseClass):
self._text_label = tkinter.Label(master=self,
bd=0,
padx=0,
pady=0,
text=self._text,
justify=tkinter.LEFT,
font=self._apply_font_scaling(self._font),
textvariable=self._textvariable)
self._text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
self._text_label.grid(row=0, column=2, sticky="w")
self._text_label["anchor"] = "w"
self._text_label.bind("<Enter>", self._on_enter)
@ -120,10 +128,18 @@ class CTkRadioButton(CTkBaseClass):
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
self._text_label.configure(font=self._apply_font_scaling(self._font))
self._bg_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._bg_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._radiobutton_width),
height=self._apply_widget_scaling(self._radiobutton_height))
self._draw()
def _set_dimensions(self, width: int = None, height: int = None):
super()._set_dimensions(width, height)
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
def destroy(self):
if self._variable is not None:
self._variable.trace_remove("write", self._variable_callback_name)
@ -131,11 +147,12 @@ class CTkRadioButton(CTkBaseClass):
super().destroy()
def _draw(self, no_color_updates=False):
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height),
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._radiobutton_width),
self._apply_widget_scaling(self._radiobutton_height),
self._apply_widget_scaling(self._corner_radius),
self._apply_widget_scaling(self._border_width))
if no_color_updates is False or requires_recoloring:
self._bg_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))
@ -160,6 +177,16 @@ class CTkRadioButton(CTkBaseClass):
self._text_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
def configure(self, require_redraw=False, **kwargs):
if "radiobutton_width" in kwargs:
self._radiobutton_width = kwargs.pop("radiobutton_width")
self._canvas.configure(width=self._apply_widget_scaling(self._radiobutton_width))
require_redraw = True
if "radiobutton_height" in kwargs:
self._radiobutton_height = kwargs.pop("radiobutton_height")
self._canvas.configure(height=self._apply_widget_scaling(self._radiobutton_height))
require_redraw = True
if "text" in kwargs:
self._text = kwargs.pop("text")
self._text_label.configure(text=self._text)
@ -220,6 +247,10 @@ class CTkRadioButton(CTkBaseClass):
return self._border_width_unchecked
elif attribute_name == "border_width_checked":
return self._border_width_checked
elif attribute_name == "radiobutton_width":
return self._radiobutton_width
elif attribute_name == "radiobutton_height":
return self._radiobutton_height
elif attribute_name == "fg_color":
return self._fg_color

View File

@ -76,7 +76,6 @@ class CTkScrollbar(CTkBaseClass):
self._canvas.tag_bind("border_parts", "<Button-1>", self._clicked)
self._canvas.bind("<B1-Motion>", self._clicked)
self._canvas.bind("<MouseWheel>", self._mouse_scroll_event)
super().bind('<Configure>', self._update_dimensions_event)
self._draw()

View File

@ -100,9 +100,6 @@ class CTkSlider(CTkBaseClass):
self._canvas.bind("<Button-1>", self._clicked)
self._canvas.bind("<B1-Motion>", self._clicked)
# Each time an item is resized due to pack position mode, the binding Configure is called on the widget
super().bind('<Configure>', self._update_dimensions_event)
self._set_cursor()
self._draw() # initial draw

View File

@ -17,8 +17,10 @@ class CTkSwitch(CTkBaseClass):
def __init__(self,
master: any = None,
width: int = 36,
height: int = 18,
width: int = 100,
height: int = 24,
switch_width: int = 36,
switch_height: int = 18,
corner_radius: Union[int, str] = "default_theme",
border_width: Union[int, str] = "default_theme",
button_length: Union[int, str] = "default_theme",
@ -45,6 +47,10 @@ class CTkSwitch(CTkBaseClass):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# dimensions
self._switch_width = switch_width
self._switch_height = switch_height
# color
self._border_color = border_color
self._fg_color = ThemeManager.theme["color"]["switch"] if fg_color == "default_theme" else fg_color
@ -86,13 +92,13 @@ class CTkSwitch(CTkBaseClass):
highlightthickness=0,
width=self._apply_widget_scaling(self._current_width),
height=self._apply_widget_scaling(self._current_height))
self._bg_canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=3, rowspan=1, sticky="nswe")
self._bg_canvas.grid(row=0, column=0, columnspan=3, sticky="nswe")
self._canvas = CTkCanvas(master=self,
highlightthickness=0,
width=self._apply_widget_scaling(self._current_width),
height=self._apply_widget_scaling(self._current_height))
self._canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, sticky="nswe")
width=self._apply_widget_scaling(self._switch_width),
height=self._apply_widget_scaling(self._switch_height))
self._canvas.grid(row=0, column=0, sticky="nswe")
self._draw_engine = DrawEngine(self._canvas)
self._canvas.bind("<Enter>", self._on_enter)
@ -101,11 +107,13 @@ class CTkSwitch(CTkBaseClass):
self._text_label = tkinter.Label(master=self,
bd=0,
padx=0,
pady=0,
text=self._text,
justify=tkinter.LEFT,
font=self._apply_font_scaling(self._font),
textvariable=self._textvariable)
self._text_label.grid(row=0, column=2, padx=0, pady=0, sticky="w")
self._text_label.grid(row=0, column=2, sticky="w")
self._text_label["anchor"] = "w"
self._text_label.bind("<Enter>", self._on_enter)
@ -125,10 +133,18 @@ class CTkSwitch(CTkBaseClass):
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
self._text_label.configure(font=self._apply_font_scaling(self._font))
self._bg_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._bg_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._switch_width),
height=self._apply_widget_scaling(self._switch_height))
self._draw()
def _set_dimensions(self, width: int = None, height: int = None):
super()._set_dimensions(width, height)
self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
def destroy(self):
# remove variable_callback from variable callbacks if variable exists
if self._variable is not None:
@ -161,16 +177,16 @@ class CTkSwitch(CTkBaseClass):
def _draw(self, no_color_updates=False):
if self._check_state is True:
requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height),
requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._switch_width),
self._apply_widget_scaling(self._switch_height),
self._apply_widget_scaling(self._corner_radius),
self._apply_widget_scaling(self._border_width),
self._apply_widget_scaling(self._button_length),
self._apply_widget_scaling(self._corner_radius),
1, "w")
else:
requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height),
requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._switch_width),
self._apply_widget_scaling(self._switch_height),
self._apply_widget_scaling(self._corner_radius),
self._apply_widget_scaling(self._border_width),
self._apply_widget_scaling(self._button_length),
@ -209,6 +225,16 @@ class CTkSwitch(CTkBaseClass):
self._text_label.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
def configure(self, require_redraw=False, **kwargs):
if "switch_width" in kwargs:
self._switch_width = kwargs.pop("switch_width")
self._canvas.configure(width=self._apply_widget_scaling(self._switch_width))
require_redraw = True
if "switch_height" in kwargs:
self._switch_height = kwargs.pop("switch_height")
self._canvas.configure(height=self._apply_widget_scaling(self._switch_height))
require_redraw = True
if "text" in kwargs:
self._text = kwargs.pop("text")
self._text_label.configure(text=self._text)
@ -277,6 +303,10 @@ class CTkSwitch(CTkBaseClass):
return self._border_width
elif attribute_name == "button_length":
return self._button_length
elif attribute_name == "switch_width":
return self._switch_width
elif attribute_name == "switch_height":
return self._switch_height
elif attribute_name == "fg_color":
return self._fg_color

View File

@ -92,10 +92,9 @@ class CTkTabview(CTkBaseClass):
self._tab_dict: Dict[str, CTkFrame] = {}
self._name_list: List[str] = [] # list of unique tab names in order of tabs
self._current_name: str = ""
self._command = command
super().bind('<Configure>', self._update_dimensions_event)
self._draw()
def _segmented_button_callback(self, selected_name):
self._current_name = selected_name

View File

@ -104,9 +104,6 @@ class CTkTextbox(CTkBaseClass):
orientation="vertical",
command=self._textbox.yview)
self._textbox.configure(yscrollcommand=self._y_scrollbar.set)
#self._y_scrollbar.grid(row=0, column=1, rowspan=1, columnspan=1, sticky="ns",
# padx=(self._apply_widget_scaling(3), self._apply_widget_scaling(self._border_spacing + self._border_width)),
# pady=(self._apply_widget_scaling(self._corner_radius + self._border_width), 0))
self._x_scrollbar = CTkScrollbar(self,
height=8,
@ -118,15 +115,10 @@ class CTkTextbox(CTkBaseClass):
orientation="horizontal",
command=self._textbox.xview)
self._textbox.configure(xscrollcommand=self._x_scrollbar.set)
#self._x_scrollbar.grid(row=1, column=0, rowspan=1, columnspan=1, sticky="ew",
# pady=(self._apply_widget_scaling(3), self._apply_widget_scaling(self._border_spacing + self._border_width)),
# padx=(self._apply_widget_scaling(self._corner_radius + self._border_width), 0))
self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
self.after(50, self._check_if_scrollbars_needed)
super().bind('<Configure>', self._update_dimensions_event)
self._draw()
def _create_grid_for_text_and_scrollbars(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False):

View File

@ -67,6 +67,7 @@ class CTkBaseClass(tkinter.Frame):
self._bg_color: Union[str, Tuple[str, str]] = self._detect_color_of_master() if bg_color is None else bg_color
super().configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
super().bind('<Configure>', self._update_dimensions_event)
# 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)) and not isinstance(self.master, (CTkBaseClass, CTk, CTkToplevel)):