architecture fixes

This commit is contained in:
Tom Schimansky 2022-11-01 00:37:30 +01:00
parent 302313916a
commit 7374e7a3bc
27 changed files with 274 additions and 263 deletions

View File

@ -70,6 +70,9 @@ from .windows.ctk_input_dialog import CTkInputDialog
# font classes # font classes
from .windows.widgets.font.ctk_font import CTkFont from .windows.widgets.font.ctk_font import CTkFont
# image classes
from .windows.widgets.image.ctk_image import CTkImage
def set_appearance_mode(mode_string: str): def set_appearance_mode(mode_string: str):
""" possible values: light, dark, system """ """ possible values: light, dark, system """

View File

@ -63,10 +63,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
self._iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop() self._iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop()
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if self._appearance_mode == 1: self._windows_set_titlebar_color(self._get_appearance_mode())
self._windows_set_titlebar_color("dark")
else:
self._windows_set_titlebar_color("light")
self.bind('<Configure>', self._update_dimensions_event) self.bind('<Configure>', self._update_dimensions_event)
self.bind('<FocusIn>', self._focus_in_event) self.bind('<FocusIn>', self._focus_in_event)
@ -97,12 +94,12 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
# detected_width = event.width # detected_width = event.width
# detected_height = event.height # detected_height = event.height
if self._current_width != round(detected_width / self._window_scaling) or self._current_height != round(detected_height / self._window_scaling): if self._current_width != self._reverse_window_scaling(detected_width) or self._current_height != self._reverse_window_scaling(detected_height):
self._current_width = round(detected_width / self._window_scaling) # adjust current size according to new size given by event self._current_width = self._reverse_window_scaling(detected_width) # adjust current size according to new size given by event
self._current_height = round(detected_height / self._window_scaling) # _current_width and _current_height are independent of the scale self._current_height = self._reverse_window_scaling(detected_height) # _current_width and _current_height are independent of the scale
def _set_scaling(self, new_widget_scaling, new_window_scaling): def _set_scaling(self, new_widget_scaling, new_window_scaling):
self._window_scaling = new_window_scaling super()._set_scaling(new_widget_scaling, new_window_scaling)
# block update_dimensions_event to prevent current_width and current_height to get updated # block update_dimensions_event to prevent current_width and current_height to get updated
self._block_update_dimensions_event = True self._block_update_dimensions_event = True
@ -164,12 +161,9 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
self._last_resizable_args = ([], {"width": width, "height": height}) self._last_resizable_args = ([], {"width": width, "height": height})
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if self._appearance_mode == 1: self._windows_set_titlebar_color(self._get_appearance_mode())
self._windows_set_titlebar_color("dark")
else:
self._windows_set_titlebar_color("light")
def minsize(self, width=None, height=None): def minsize(self, width: int = None, height: int = None):
self._min_width = width self._min_width = width
self._min_height = height self._min_height = height
if self._current_width < width: if self._current_width < width:
@ -178,7 +172,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
self._current_height = height self._current_height = height
super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height)) super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height))
def maxsize(self, width=None, height=None): def maxsize(self, width: int = None, height: int = None):
self._max_width = width self._max_width = width
self._max_height = height self._max_height = height
if self._current_width > width: if self._current_width > width:
@ -297,16 +291,10 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
else: else:
pass # wait for update or mainloop to be called pass # wait for update or mainloop to be called
def _set_appearance_mode(self, mode_string): def _set_appearance_mode(self, mode_string: str):
if mode_string.lower() == "dark": super()._set_appearance_mode(mode_string)
self._appearance_mode = 1
elif mode_string.lower() == "light":
self._appearance_mode = 0
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if self._appearance_mode == 1: self._windows_set_titlebar_color(mode_string)
self._windows_set_titlebar_color("dark")
else:
self._windows_set_titlebar_color("light")
super().configure(bg=self._apply_appearance_mode(self._fg_color)) super().configure(bg=self._apply_appearance_mode(self._fg_color))

View File

@ -60,10 +60,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color self._iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if self._appearance_mode == 1: self._windows_set_titlebar_color(self._get_appearance_mode())
self._windows_set_titlebar_color("dark")
else:
self._windows_set_titlebar_color("light")
self.bind('<Configure>', self._update_dimensions_event) self.bind('<Configure>', self._update_dimensions_event)
self.bind('<FocusIn>', self._focus_in_event) self.bind('<FocusIn>', self._focus_in_event)
@ -85,12 +82,12 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
detected_width = self.winfo_width() # detect current window size detected_width = self.winfo_width() # detect current window size
detected_height = self.winfo_height() detected_height = self.winfo_height()
if self._current_width != round(detected_width / self._window_scaling) or self._current_height != round(detected_height / self._window_scaling): if self._current_width != self._reverse_window_scaling(detected_width) or self._current_height != self._reverse_window_scaling(detected_height):
self._current_width = round(detected_width / self._window_scaling) # adjust current size according to new size given by event self._current_width = self._reverse_window_scaling(detected_width) # adjust current size according to new size given by event
self._current_height = round(detected_height / self._window_scaling) # _current_width and _current_height are independent of the scale self._current_height = self._reverse_window_scaling(detected_height) # _current_width and _current_height are independent of the scale
def _set_scaling(self, new_widget_scaling, new_window_scaling): def _set_scaling(self, new_widget_scaling, new_window_scaling):
self._window_scaling = new_window_scaling super()._set_scaling(new_widget_scaling, new_window_scaling)
# force new dimensions on window by using min, max, and geometry # force new dimensions on window by using min, max, and geometry
super().minsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height)) super().minsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))
@ -134,10 +131,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._last_resizable_args = ([], {"width": width, "height": height}) self._last_resizable_args = ([], {"width": width, "height": height})
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if self._appearance_mode == 1: self.after(10, lambda: self._windows_set_titlebar_color(self._get_appearance_mode()))
self.after(10, lambda: self._windows_set_titlebar_color("dark"))
else:
self.after(10, lambda: self._windows_set_titlebar_color("light"))
def minsize(self, width=None, height=None): def minsize(self, width=None, height=None):
self._min_width = width self._min_width = width
@ -259,15 +253,9 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._iconify_called_after_windows_set_titlebar_color = False self._iconify_called_after_windows_set_titlebar_color = False
def _set_appearance_mode(self, mode_string): def _set_appearance_mode(self, mode_string):
if mode_string.lower() == "dark": super()._set_appearance_mode(mode_string)
self._appearance_mode = 1
elif mode_string.lower() == "light":
self._appearance_mode = 0
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if self._appearance_mode == 1: self._windows_set_titlebar_color(mode_string)
self._windows_set_titlebar_color("dark")
else:
self._windows_set_titlebar_color("light")
super().configure(bg=self._apply_appearance_mode(self._fg_color)) super().configure(bg=self._apply_appearance_mode(self._fg_color))

View File

@ -1,27 +1,46 @@
from typing import Union, Tuple, List from typing import Union, Tuple, List
from abc import ABC, abstractmethod
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker
class CTkAppearanceModeBaseClass(ABC): class CTkAppearanceModeBaseClass:
"""
Super-class that manages the appearance mode. Methods:
- destroy() must be called when sub-class is destroyed
- _set_appearance_mode() abstractmethod, gets called when appearance mode changes, must be overridden
- _apply_appearance_mode()
"""
def __init__(self): def __init__(self):
AppearanceModeTracker.add(self._set_appearance_mode, self) AppearanceModeTracker.add(self._set_appearance_mode, self)
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.__appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
def destroy(self): def destroy(self):
AppearanceModeTracker.remove(self._set_appearance_mode) AppearanceModeTracker.remove(self._set_appearance_mode)
def _apply_appearance_mode(self, color: Union[str, Tuple[str, str], List[str]]) -> str: def _set_appearance_mode(self, mode_string: str):
""" color can be either a single hex color string or a color name or it can be a """ can be overridden but super method must be called at the beginning """
tuple color with (light_color, dark_color). The functions returns if mode_string.lower() == "dark":
always a single color string """ self.__appearance_mode = 1
elif mode_string.lower() == "light":
self.__appearance_mode = 0
if type(color) == tuple or type(color) == list: def _get_appearance_mode(self) -> str:
return color[self._appearance_mode] """ get appearance mode as a string, 'light' or 'dark' """
if self.__appearance_mode == 0:
return "light"
else:
return "dark"
def _apply_appearance_mode(self, color: Union[str, Tuple[str, str], List[str]]) -> str:
"""
color can be either a single hex color string or a color name or it can be a
tuple color with (light_color, dark_color). The functions returns
always a single color string
"""
if isinstance(color, (tuple, list)):
return color[self.__appearance_mode]
else: else:
return color return color
@abstractmethod
def _set_appearance_mode(self, mode_string: str):
return

View File

@ -83,12 +83,12 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass):
else: else:
super().configure(tearoff=False, super().configure(tearoff=False,
relief="flat", relief="flat",
activebackground=ThemeManager._apply_appearance_mode(self._hover_color, self._appearance_mode), activebackground=self._apply_appearance_mode(self._hover_color),
borderwidth=0, borderwidth=0,
activeborderwidth=0, activeborderwidth=0,
bg=ThemeManager._apply_appearance_mode(self._fg_color, self._appearance_mode), bg=self._apply_appearance_mode(self._fg_color),
fg=ThemeManager._apply_appearance_mode(self._text_color, self._appearance_mode), fg=self._apply_appearance_mode(self._text_color),
activeforeground=ThemeManager._apply_appearance_mode(self._text_color, self._appearance_mode), activeforeground=self._apply_appearance_mode(self._text_color),
font=self._apply_font_scaling(self._font)) font=self._apply_font_scaling(self._font))
def _add_menu_commands(self): def _add_menu_commands(self):

View File

@ -98,7 +98,9 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
CTkScalingBaseClass.destroy(self) CTkScalingBaseClass.destroy(self)
def _draw(self, no_color_updates: bool = False): def _draw(self, no_color_updates: bool = False):
return """ can be overridden but super method must be called """
if no_color_updates is False:
super().configure(bg=self._apply_appearance_mode(self._bg_color))
def config(self, *args, **kwargs): def config(self, *args, **kwargs):
raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.") raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.")
@ -164,9 +166,9 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
def _update_dimensions_event(self, event): def _update_dimensions_event(self, event):
# only redraw if dimensions changed (for performance), independent of scaling # only redraw if dimensions changed (for performance), independent of scaling
if round(self._current_width) != round(event.width / self._widget_scaling) or round(self._current_height) != round(event.height / self._widget_scaling): if round(self._current_width) != round(self._reverse_widget_scaling(event.width)) or round(self._current_height) != round(self._reverse_widget_scaling(event.height)):
self._current_width = (event.width / self._widget_scaling) # adjust current size according to new size given by event self._current_width = self._reverse_widget_scaling(event.width) # adjust current size according to new size given by event
self._current_height = (event.height / self._widget_scaling) # _current_width and _current_height are independent of the scale self._current_height = self._reverse_widget_scaling(event.height) # _current_width and _current_height are independent of the scale
self._draw(no_color_updates=True) # faster drawing without color changes self._draw(no_color_updates=True) # faster drawing without color changes
@ -198,16 +200,11 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
return "#FFFFFF", "#000000" return "#FFFFFF", "#000000"
def _set_appearance_mode(self, mode_string): def _set_appearance_mode(self, mode_string):
if mode_string.lower() == "dark": super()._set_appearance_mode(mode_string)
self._appearance_mode = 1
elif mode_string.lower() == "light":
self._appearance_mode = 0
super().configure(bg=self._apply_appearance_mode(self._bg_color))
self._draw() self._draw()
def _set_scaling(self, new_widget_scaling, new_window_scaling): def _set_scaling(self, new_widget_scaling, new_window_scaling):
self._widget_scaling = new_widget_scaling super()._set_scaling(new_widget_scaling, new_window_scaling)
super().configure(width=self._apply_widget_scaling(self._desired_width), super().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))

View File

@ -48,7 +48,7 @@ class CTkButton(CTkBaseClass):
anchor: str = "center", anchor: str = "center",
**kwargs): **kwargs):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to 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) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
@ -74,6 +74,8 @@ class CTkButton(CTkBaseClass):
self._image_label: Union[tkinter.Label, None] = None self._image_label: Union[tkinter.Label, None] = None
self._text = text self._text = text
self._text_label: Union[tkinter.Label, None] = None self._text_label: Union[tkinter.Label, None] = None
if isinstance(self._image, CTkImage):
self._image.add_configure_callback(self._update_image)
# font # font
self._font = CTkFont() if font == "default_theme" else self._check_font_type(font) self._font = CTkFont() if font == "default_theme" else self._check_font_type(font)
@ -135,12 +137,19 @@ class CTkButton(CTkBaseClass):
self._canvas.grid_forget() self._canvas.grid_forget()
self._canvas.grid(row=0, column=0, rowspan=5, columnspan=5, sticky="nsew") self._canvas.grid(row=0, column=0, rowspan=5, columnspan=5, sticky="nsew")
def _update_image(self):
if self._image_label is not None:
self._image_label.configure(image=self._image.create_scaled_photo_image(self._get_widget_scaling(),
self._get_appearance_mode()))
def destroy(self): def destroy(self):
if isinstance(self._font, CTkFont): if isinstance(self._font, CTkFont):
self._font.remove_size_configure_callback(self._update_font) self._font.remove_size_configure_callback(self._update_font)
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
if self._background_corner_colors is not None: if self._background_corner_colors is not None:
self._draw_engine.draw_background_corners(self._apply_widget_scaling(self._current_width), self._draw_engine.draw_background_corners(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height)) self._apply_widget_scaling(self._current_height))
@ -233,7 +242,7 @@ class CTkButton(CTkBaseClass):
else: else:
self._image_label.configure(bg=self._apply_appearance_mode(self._fg_color)) self._image_label.configure(bg=self._apply_appearance_mode(self._fg_color))
self._image_label.configure(image=self._image) # set image self._update_image() # set image
else: else:
# delete text_label if no text given # delete text_label if no text given
@ -354,7 +363,6 @@ class CTkButton(CTkBaseClass):
require_redraw = True # text_label will be created in .draw() require_redraw = True # text_label will be created in .draw()
else: else:
self._text_label.configure(text=self._text) self._text_label.configure(text=self._text)
self._create_grid()
if "font" in kwargs: if "font" in kwargs:
if isinstance(self._font, CTkFont): if isinstance(self._font, CTkFont):
@ -371,8 +379,11 @@ class CTkButton(CTkBaseClass):
self._text_label.configure(textvariable=self._textvariable) self._text_label.configure(textvariable=self._textvariable)
if "image" in kwargs: if "image" in kwargs:
if isinstance(self._image, CTkImage):
self._image.remove_configure_callback(self._update_image)
self._image = kwargs.pop("image") self._image = kwargs.pop("image")
self._create_grid() if isinstance(self._image, CTkImage):
self._image.add_configure_callback(self._update_image)
require_redraw = True require_redraw = True
if "state" in kwargs: if "state" in kwargs:

View File

@ -43,7 +43,7 @@ class CTkCheckBox(CTkBaseClass):
variable: tkinter.Variable = None, variable: tkinter.Variable = None,
**kwargs): **kwargs):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to 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) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# dimensions # dimensions
@ -169,6 +169,8 @@ class CTkCheckBox(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
requires_recoloring_1 = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._checkbox_width), requires_recoloring_1 = 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._checkbox_height),
self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._corner_radius),

View File

@ -44,7 +44,7 @@ class CTkComboBox(CTkBaseClass):
justify: str = "left", justify: str = "left",
**kwargs): **kwargs):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to 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) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# color variables # color variables
@ -167,6 +167,8 @@ class CTkComboBox(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
left_section_width = self._current_width - self._current_height left_section_width = self._current_width - self._current_height
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border_vertical_split(self._apply_widget_scaling(self._current_width), requires_recoloring = self.draw_engine.draw_rounded_rect_with_border_vertical_split(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height), self._apply_widget_scaling(self._current_height),

View File

@ -148,6 +148,8 @@ class CTkEntry(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color)) self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width), requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),

View File

@ -29,7 +29,7 @@ class CTkFrame(CTkBaseClass):
overwrite_preferred_drawing_method: str = None, overwrite_preferred_drawing_method: str = None,
**kwargs): **kwargs):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to 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) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
@ -91,6 +91,8 @@ class CTkFrame(CTkBaseClass):
self._draw() self._draw()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
if not self._canvas.winfo_exists(): if not self._canvas.winfo_exists():
return return

View File

@ -35,7 +35,7 @@ class CTkLabel(CTkBaseClass):
anchor: str = "center", # label anchor: center, n, e, s, w anchor: str = "center", # label anchor: center, n, e, s, w
**kwargs): **kwargs):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height) super().__init__(master=master, bg_color=bg_color, width=width, height=height)
# color # color
@ -117,6 +117,8 @@ class CTkLabel(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width), requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height), self._apply_widget_scaling(self._current_height),
self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._corner_radius),

View File

@ -43,7 +43,7 @@ class CTkOptionMenu(CTkBaseClass):
anchor: str = "w", anchor: str = "w",
**kwargs): **kwargs):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to 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) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# color variables # color variables
@ -180,6 +180,8 @@ class CTkOptionMenu(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
left_section_width = self._current_width - self._current_height left_section_width = self._current_width - self._current_height
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border_vertical_split(self._apply_widget_scaling(self._current_width), requires_recoloring = self._draw_engine.draw_rounded_rect_with_border_vertical_split(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height), self._apply_widget_scaling(self._current_height),

View File

@ -46,7 +46,7 @@ class CTkProgressBar(CTkBaseClass):
else: else:
height = 8 height = 8
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to 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) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
@ -110,6 +110,8 @@ class CTkProgressBar(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
if self._orientation.lower() == "horizontal": if self._orientation.lower() == "horizontal":
orientation = "w" orientation = "w"
elif self._orientation.lower() == "vertical": elif self._orientation.lower() == "vertical":

View File

@ -42,7 +42,7 @@ class CTkRadioButton(CTkBaseClass):
command: Callable = None, command: Callable = None,
**kwargs): **kwargs):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to 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) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# dimensions # dimensions
@ -163,6 +163,8 @@ class CTkRadioButton(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._radiobutton_width), 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._radiobutton_height),
self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._corner_radius),

View File

@ -44,7 +44,7 @@ class CTkScrollbar(CTkBaseClass):
else: else:
height = 200 height = 200
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to 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) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
@ -118,6 +118,8 @@ class CTkScrollbar(CTkBaseClass):
return self._start_value, self._end_value return self._start_value, self._end_value
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
corrected_start_value, corrected_end_value = self._get_scrollbar_values_for_minimum_pixel_size() corrected_start_value, corrected_end_value = self._get_scrollbar_values_for_minimum_pixel_size()
requires_recoloring = self._draw_engine.draw_rounded_scrollbar(self._apply_widget_scaling(self._current_width), requires_recoloring = self._draw_engine.draw_rounded_scrollbar(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height), self._apply_widget_scaling(self._current_height),
@ -219,9 +221,9 @@ class CTkScrollbar(CTkBaseClass):
def _clicked(self, event): def _clicked(self, event):
if self._orientation == "vertical": if self._orientation == "vertical":
value = ((event.y - self._border_spacing) / (self._current_height - 2 * self._border_spacing)) / self._widget_scaling value = self._reverse_widget_scaling(((event.y - self._border_spacing) / (self._current_height - 2 * self._border_spacing)))
else: else:
value = ((event.x - self._border_spacing) / (self._current_width - 2 * self._border_spacing)) / self._widget_scaling value = self._reverse_widget_scaling(((event.x - self._border_spacing) / (self._current_width - 2 * self._border_spacing)))
current_scrollbar_length = self._end_value - self._start_value current_scrollbar_length = self._end_value - self._start_value
value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2))) value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2)))

View File

@ -52,7 +52,7 @@ class CTkSlider(CTkBaseClass):
else: else:
height = 16 height = 16
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to 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) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# color # color
@ -145,6 +145,8 @@ class CTkSlider(CTkBaseClass):
self.configure(cursor="arrow") self.configure(cursor="arrow")
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
if self._orientation.lower() == "horizontal": if self._orientation.lower() == "horizontal":
orientation = "w" orientation = "w"
elif self._orientation.lower() == "vertical": elif self._orientation.lower() == "vertical":

View File

@ -45,7 +45,7 @@ class CTkSwitch(CTkBaseClass):
state: str = tkinter.NORMAL, state: str = tkinter.NORMAL,
**kwargs): **kwargs):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to 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) super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# dimensions # dimensions
@ -193,6 +193,7 @@ class CTkSwitch(CTkBaseClass):
self._text_label.configure(cursor="hand2") self._text_label.configure(cursor="hand2")
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
if self._check_state is True: if self._check_state is True:
requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._switch_width), requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._switch_width),

View File

@ -188,6 +188,8 @@ class CTkTabview(CTkBaseClass):
return new_tab return new_tab
def _draw(self, no_color_updates: bool = False): def _draw(self, no_color_updates: bool = False):
super()._draw(no_color_updates)
if not self._canvas.winfo_exists(): if not self._canvas.winfo_exists():
return return

View File

@ -51,7 +51,7 @@ class CTkTextbox(CTkBaseClass):
activate_scrollbars: bool = True, activate_scrollbars: bool = True,
**kwargs): **kwargs):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height) super().__init__(master=master, bg_color=bg_color, width=width, height=height)
# color # color
@ -210,6 +210,7 @@ class CTkTextbox(CTkBaseClass):
super().destroy() super().destroy()
def _draw(self, no_color_updates=False): def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
if not self._canvas.winfo_exists(): if not self._canvas.winfo_exists():
return return

View File

@ -18,11 +18,16 @@ class CTkImage:
_checked_PIL_import = False _checked_PIL_import = False
def __init__(self, light_image: Image.Image = None, dark_image: Image.Image = None, size: Tuple[int, int] = None): def __init__(self,
light_image: Image.Image = None,
dark_image: Image.Image = None,
size: Tuple[int, int] = (20, 20)):
if not self._checked_PIL_import: if not self._checked_PIL_import:
self._check_pil_import() self._check_pil_import()
self._light_image = light_image self._light_image = light_image
print(self._light_image)
self._dark_image = dark_image self._dark_image = dark_image
self._check_images() self._check_images()
self._size = size self._size = size
@ -33,8 +38,10 @@ class CTkImage:
@classmethod @classmethod
def _check_pil_import(cls): def _check_pil_import(cls):
if "Image" not in dir() or "ImageTk" not in dir(): try:
raise ImportError("CTkImage: Couldn't import PIL.Image or PIL.ImageTk. PIL must be installed.") _, _ = Image, ImageTk
except NameError:
raise ImportError("PIL.Image and PIL.ImageTk couldn't be imported")
def add_configure_callback(self, callback: Callable): def add_configure_callback(self, callback: Callable):
""" add function, that gets called when image got configured """ """ add function, that gets called when image got configured """
@ -84,7 +91,7 @@ class CTkImage:
raise ValueError(f"CTkImage: light_image size {self._light_image.size} must be the same as dark_image size {self._dark_image.size}.") raise ValueError(f"CTkImage: light_image size {self._light_image.size} must be the same as dark_image size {self._dark_image.size}.")
def _get_scaled_size(self, widget_scaling: float) -> Tuple[int, int]: def _get_scaled_size(self, widget_scaling: float) -> Tuple[int, int]:
return round(self._size[0] * widget_scaling), round(self._size[0] * widget_scaling) return round(self._size[0] * widget_scaling), round(self._size[1] * widget_scaling)
def _get_scaled_light_photo_image(self, scaled_size: Tuple[int, int]) -> ImageTk.PhotoImage: def _get_scaled_light_photo_image(self, scaled_size: Tuple[int, int]) -> ImageTk.PhotoImage:
if scaled_size in self._scaled_light_photo_images: if scaled_size in self._scaled_light_photo_images:
@ -100,17 +107,18 @@ class CTkImage:
self._scaled_dark_photo_images[scaled_size] = ImageTk.PhotoImage(self._dark_image.resize(scaled_size)) self._scaled_dark_photo_images[scaled_size] = ImageTk.PhotoImage(self._dark_image.resize(scaled_size))
return self._scaled_dark_photo_images[scaled_size] return self._scaled_dark_photo_images[scaled_size]
def create_scaled_photo_image(self, widget_scaling: float, appearance_mode: int) -> ImageTk.PhotoImage: def create_scaled_photo_image(self, widget_scaling: float, appearance_mode: str) -> ImageTk.PhotoImage:
scaled_size = self._get_scaled_size(widget_scaling) scaled_size = self._get_scaled_size(widget_scaling)
print(scaled_size)
if appearance_mode == 0 and self._light_image is not None: if appearance_mode == "light" and self._light_image is not None:
return self._get_scaled_light_photo_image(scaled_size) return self._get_scaled_light_photo_image(scaled_size)
elif appearance_mode == 0 and self._light_image is None: elif appearance_mode == "light" and self._light_image is None:
return self._get_scaled_dark_photo_image(scaled_size) return self._get_scaled_dark_photo_image(scaled_size)
elif appearance_mode == 1 and self._dark_image is not None: elif appearance_mode == "dark" and self._dark_image is not None:
return self._get_scaled_dark_photo_image(scaled_size) return self._get_scaled_dark_photo_image(scaled_size)
elif appearance_mode == 1 and self._dark_image is None: elif appearance_mode == "dark" and self._dark_image is None:
return self._get_scaled_light_photo_image(scaled_size) return self._get_scaled_light_photo_image(scaled_size)

View File

@ -1,5 +1,5 @@
import tkinter
from typing import Union, Tuple from typing import Union, Tuple
from abc import ABC, abstractmethod
import copy import copy
import re import re
try: try:
@ -9,69 +9,109 @@ except ImportError:
from .scaling_tracker import ScalingTracker from .scaling_tracker import ScalingTracker
from ..font.ctk_font import CTkFont from ..font.ctk_font import CTkFont
from ..image.ctk_image import CTkImage
class CTkScalingBaseClass(ABC): class CTkScalingBaseClass():
"""
Super-class that manages the scaling values and callbacks.
Works for widgets and windows, type must be set in init method with
scaling_type attribute. Methods:
- _set_scaling() abstractmethod, gets called when scaling changes, must be overridden
- destroy() must be called when sub-class is destroyed
- _apply_widget_scaling()
- _reverse_widget_scaling()
- _apply_window_scaling()
- _reverse_window_scaling()
- _apply_font_scaling()
- _apply_argument_scaling()
- _apply_geometry_scaling()
- _reverse_geometry_scaling()
- _parse_geometry_string()
"""
def __init__(self, scaling_type: Literal["widget", "window"] = "widget"): def __init__(self, scaling_type: Literal["widget", "window"] = "widget"):
self._scaling_type = scaling_type self.__scaling_type = scaling_type
if self._scaling_type == "widget": if self.__scaling_type == "widget":
ScalingTracker.add_widget(self._set_scaling, self) # add callback for automatic scaling changes ScalingTracker.add_widget(self._set_scaling, self) # add callback for automatic scaling changes
self._widget_scaling = ScalingTracker.get_widget_scaling(self) self.__widget_scaling = ScalingTracker.get_widget_scaling(self)
elif self._scaling_type == "window": elif self.__scaling_type == "window":
ScalingTracker.activate_high_dpi_awareness() # make process DPI aware ScalingTracker.activate_high_dpi_awareness() # make process DPI aware
ScalingTracker.add_window(self._set_scaling, self) # add callback for automatic scaling changes ScalingTracker.add_window(self._set_scaling, self) # add callback for automatic scaling changes
self._window_scaling = ScalingTracker.get_window_scaling(self) self.__window_scaling = ScalingTracker.get_window_scaling(self)
def destroy(self): def destroy(self):
if self._scaling_type == "widget": if self.__scaling_type == "widget":
ScalingTracker.remove_widget(self._set_scaling, self) ScalingTracker.remove_widget(self._set_scaling, self)
elif self._scaling_type == "window": elif self.__scaling_type == "window":
ScalingTracker.remove_window(self._set_scaling, self) ScalingTracker.remove_window(self._set_scaling, self)
def _apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]: def _set_scaling(self, new_widget_scaling, new_window_scaling):
assert self._scaling_type == "widget" """ can be overridden, but super method must be called at the beginning """
self.__widget_scaling = new_widget_scaling
self.__window_scaling = new_window_scaling
if isinstance(value, (int, float)): def _get_widget_scaling(self) -> float:
return value * self._widget_scaling return self.__widget_scaling
else:
return value def _get_window_scaling(self) -> float:
return self.__window_scaling
def _apply_widget_scaling(self, value: Union[int, float]) -> Union[float]:
assert self.__scaling_type == "widget"
return value * self.__widget_scaling
def _reverse_widget_scaling(self, value: Union[int, float]) -> Union[float]:
assert self.__scaling_type == "widget"
return value / self.__widget_scaling
def _apply_window_scaling(self, value: Union[int, float]) -> int:
assert self.__scaling_type == "window"
return int(value * self.__window_scaling)
def _reverse_window_scaling(self, scaled_value: Union[int, float]) -> int:
assert self.__scaling_type == "window"
return int(scaled_value / self.__window_scaling)
def _apply_font_scaling(self, font: Union[Tuple, CTkFont]) -> tuple: def _apply_font_scaling(self, font: Union[Tuple, CTkFont]) -> tuple:
""" Takes CTkFont object and returns tuple font with scaled size, has to be called again for every change of font object """ """ Takes CTkFont object and returns tuple font with scaled size, has to be called again for every change of font object """
assert self._scaling_type == "widget" assert self.__scaling_type == "widget"
if type(font) == tuple: if type(font) == tuple:
if len(font) == 1: if len(font) == 1:
return font return font
elif len(font) == 2: elif len(font) == 2:
return font[0], -abs(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], -abs(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")
elif isinstance(font, CTkFont): elif isinstance(font, CTkFont):
return font.create_scaled_tuple(self._widget_scaling) return font.create_scaled_tuple(self.__widget_scaling)
else: else:
raise ValueError(f"Can not scale font '{font}' of type {type(font)}. font needs to be tuple or instance of CTkFont") raise ValueError(f"Can not scale font '{font}' of type {type(font)}. font needs to be tuple or instance of CTkFont")
def _apply_argument_scaling(self, kwargs: dict) -> dict: def _apply_argument_scaling(self, kwargs: dict) -> dict:
assert self._scaling_type == "widget" assert self.__scaling_type == "widget"
scaled_kwargs = copy.copy(kwargs) scaled_kwargs = copy.copy(kwargs)
# scale padding values
if "pady" in scaled_kwargs: if "pady" in scaled_kwargs:
if isinstance(scaled_kwargs["pady"], (int, float, str)): if isinstance(scaled_kwargs["pady"], (int, float)):
scaled_kwargs["pady"] = self._apply_widget_scaling(scaled_kwargs["pady"]) scaled_kwargs["pady"] = self._apply_widget_scaling(scaled_kwargs["pady"])
elif isinstance(scaled_kwargs["pady"], tuple): elif isinstance(scaled_kwargs["pady"], tuple):
scaled_kwargs["pady"] = tuple([self._apply_widget_scaling(v) for v in scaled_kwargs["pady"]]) scaled_kwargs["pady"] = tuple([self._apply_widget_scaling(v) for v in scaled_kwargs["pady"]])
if "padx" in kwargs: if "padx" in kwargs:
if isinstance(scaled_kwargs["padx"], (int, float, str)): if isinstance(scaled_kwargs["padx"], (int, float)):
scaled_kwargs["padx"] = self._apply_widget_scaling(scaled_kwargs["padx"]) scaled_kwargs["padx"] = self._apply_widget_scaling(scaled_kwargs["padx"])
elif isinstance(scaled_kwargs["padx"], tuple): elif isinstance(scaled_kwargs["padx"], tuple):
scaled_kwargs["padx"] = tuple([self._apply_widget_scaling(v) for v in scaled_kwargs["padx"]]) scaled_kwargs["padx"] = tuple([self._apply_widget_scaling(v) for v in scaled_kwargs["padx"]])
# scaled x, y values for place geometry manager
if "x" in scaled_kwargs: if "x" in scaled_kwargs:
scaled_kwargs["x"] = self._apply_widget_scaling(scaled_kwargs["x"]) scaled_kwargs["x"] = self._apply_widget_scaling(scaled_kwargs["x"])
if "y" in scaled_kwargs: if "y" in scaled_kwargs:
@ -93,41 +133,29 @@ class CTkScalingBaseClass(ABC):
return width, height, x, y return width, height, x, y
def _apply_geometry_scaling(self, geometry_string: str) -> str: def _apply_geometry_scaling(self, geometry_string: str) -> str:
assert self._scaling_type == "window" assert self.__scaling_type == "window"
width, height, x, y = self._parse_geometry_string(geometry_string) width, height, x, y = self._parse_geometry_string(geometry_string)
if x is None and y is None: # no <x> and <y> in geometry_string if x is None and y is None: # no <x> and <y> in geometry_string
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}" return f"{round(width * self.__window_scaling)}x{round(height * self.__window_scaling)}"
elif width is None and height is None: # no <width> and <height> in geometry_string elif width is None and height is None: # no <width> and <height> in geometry_string
return f"+{x}+{y}" return f"+{x}+{y}"
else: else:
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}+{x}+{y}" return f"{round(width * self.__window_scaling)}x{round(height * self.__window_scaling)}+{x}+{y}"
def _reverse_geometry_scaling(self, scaled_geometry_string: str) -> str: def _reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
assert self._scaling_type == "window" assert self.__scaling_type == "window"
width, height, x, y = self._parse_geometry_string(scaled_geometry_string) width, height, x, y = self._parse_geometry_string(scaled_geometry_string)
if x is None and y is None: # no <x> and <y> in geometry_string if x is None and y is None: # no <x> and <y> in geometry_string
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}" return f"{round(width / self.__window_scaling)}x{round(height / self.__window_scaling)}"
elif width is None and height is None: # no <width> and <height> in geometry_string elif width is None and height is None: # no <width> and <height> in geometry_string
return f"+{x}+{y}" return f"+{x}+{y}"
else: else:
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}+{x}+{y}" return f"{round(width / self.__window_scaling)}x{round(height / self.__window_scaling)}+{x}+{y}"
def _apply_window_scaling(self, value):
assert self._scaling_type == "window"
if isinstance(value, (int, float)):
return int(value * self._window_scaling)
else:
return value
@abstractmethod
def _set_scaling(self, new_widget_scaling, new_window_scaling):
return

View File

@ -13,13 +13,13 @@ class App(customtkinter.CTk):
self.title("CustomTkinter complex_example.py") self.title("CustomTkinter complex_example.py")
self.geometry(f"{1100}x{580}") self.geometry(f"{1100}x{580}")
self.minsize(800, 400) #self.minsize(800, 400)
#self.maxsize(1200, 700) #self.maxsize(1200, 700)
self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed
# configure grid layout (4x4) # configure grid layout (4x4)
self.grid_columnconfigure(1, weight=1) self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure((2, 3, 4), weight=0, minsize=200) self.grid_columnconfigure((2, 3), weight=0)
self.grid_rowconfigure((0, 1, 2), weight=1) self.grid_rowconfigure((0, 1, 2), weight=1)
# create sidebar frame with widgets # create sidebar frame with widgets
@ -47,63 +47,18 @@ class App(customtkinter.CTk):
# create main entry and button # create main entry and button
self.entry = customtkinter.CTkEntry(self, placeholder_text="CTkEntry") self.entry = customtkinter.CTkEntry(self, placeholder_text="CTkEntry")
self.entry.grid(row=3, column=1, columnspan=2, padx=(20, 10), pady=(10, 20), sticky="nsew") self.entry.grid(row=3, column=1, columnspan=2, padx=(20, 0), pady=(20, 20), sticky="nsew")
self.main_button_1 = customtkinter.CTkButton(master=self, fg_color=None, border_width=2) self.main_button_1 = customtkinter.CTkButton(master=self, fg_color=None, border_width=2)
self.main_button_1.grid(row=3, column=3, padx=(10, 20), pady=(10, 20), sticky="nsew") self.main_button_1.grid(row=3, column=3, padx=(20, 20), pady=(20, 20), sticky="nsew")
# create textbox # create textbox
self.textbox = customtkinter.CTkTextbox(self) self.textbox = customtkinter.CTkTextbox(self, width=250)
self.textbox.grid(row=0, column=1, padx=(20, 10), pady=(20, 10), sticky="nsew") self.textbox.grid(row=0, column=1, padx=(20, 0), pady=(20, 0), sticky="nsew")
# create radiobutton frame
self.radiobutton_frame = customtkinter.CTkFrame(self)
self.radiobutton_frame.grid(row=0, column=3, padx=(10, 10), pady=(20, 10), sticky="nsew")
self.radio_var = tkinter.IntVar(value=0)
self.label_radio_group = customtkinter.CTkLabel(master=self.radiobutton_frame, text="CTkRadioButton Group:")
self.label_radio_group.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="")
self.radio_button_1 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=0)
self.radio_button_1.grid(row=1, column=2, pady=10, padx=20, sticky="n")
self.radio_button_2 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=1)
self.radio_button_2.grid(row=2, column=2, pady=10, padx=20, sticky="n")
self.radio_button_3 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=2)
self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n")
# create checkbox and switch frame
self.checkbox_slider_frame = customtkinter.CTkFrame(self)
self.checkbox_slider_frame.grid(row=0, column=4, padx=(10, 20), pady=(20, 10), sticky="nsew")
self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_1.grid(row=1, column=0, pady=(20, 10), padx=20, sticky="n")
self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_2.grid(row=2, column=0, pady=10, padx=20, sticky="n")
self.switch_1 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame, command=lambda: print("switch 1 toggle"))
self.switch_1.grid(row=3, column=0, pady=10, padx=20, sticky="n")
self.switch_2 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame)
self.switch_2.grid(row=4, column=0, pady=(10, 20), padx=20, sticky="n")
# create slider and progressbar frame
self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color=None)
self.slider_progressbar_frame.grid(row=1, column=1, columnspan=2, padx=(20, 10), pady=(10, 10), sticky="nsew")
self.slider_progressbar_frame.grid_columnconfigure(0, weight=1)
self.slider_progressbar_frame.grid_rowconfigure(4, weight=1)
self.seg_button_1 = customtkinter.CTkSegmentedButton(self.slider_progressbar_frame)
self.seg_button_1.grid(row=0, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.progressbar_1 = customtkinter.CTkProgressBar(self.slider_progressbar_frame)
self.progressbar_1.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.progressbar_2 = customtkinter.CTkProgressBar(self.slider_progressbar_frame)
self.progressbar_2.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.slider_1 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=1, number_of_steps=4)
self.slider_1.grid(row=3, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.slider_2 = customtkinter.CTkSlider(self.slider_progressbar_frame, orientation="vertical")
self.slider_2.grid(row=0, column=1, rowspan=5, padx=(10, 10), pady=(10, 10), sticky="ns")
self.progressbar_3 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orientation="vertical")
self.progressbar_3.grid(row=0, column=2, rowspan=5, padx=(10, 20), pady=(10, 10), sticky="ns")
# create tabview # create tabview
self.tabview = customtkinter.CTkTabview(self) self.tabview = customtkinter.CTkTabview(self, width=250)
self.tabview.grid(row=1, column=3, columnspan=2, padx=(10, 20), pady=(10, 10), sticky="nsew") self.tabview.grid(row=0, column=2, padx=(20, 0), pady=(20, 0), sticky="nsew")
self.tabview.add("CTkTabview") self.tabview.add("CTkTabview")
self.tabview.add("Tab 2") self.tabview.add("Tab 2")
self.tabview.add("Tab 3") self.tabview.add("Tab 3")
@ -121,6 +76,51 @@ class App(customtkinter.CTk):
command=self.open_input_dialog) command=self.open_input_dialog)
self.string_input_button.grid(row=2, column=0, padx=20, pady=(10, 10)) self.string_input_button.grid(row=2, column=0, padx=20, pady=(10, 10))
# create radiobutton frame
self.radiobutton_frame = customtkinter.CTkFrame(self)
self.radiobutton_frame.grid(row=0, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew")
self.radio_var = tkinter.IntVar(value=0)
self.label_radio_group = customtkinter.CTkLabel(master=self.radiobutton_frame, text="CTkRadioButton Group:")
self.label_radio_group.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="")
self.radio_button_1 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=0)
self.radio_button_1.grid(row=1, column=2, pady=10, padx=20, sticky="n")
self.radio_button_2 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=1)
self.radio_button_2.grid(row=2, column=2, pady=10, padx=20, sticky="n")
self.radio_button_3 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=2)
self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n")
# create checkbox and switch frame
self.checkbox_slider_frame = customtkinter.CTkFrame(self)
self.checkbox_slider_frame.grid(row=1, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew")
self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_1.grid(row=1, column=0, pady=(20, 10), padx=20, sticky="n")
self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_2.grid(row=2, column=0, pady=10, padx=20, sticky="n")
self.switch_1 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame, command=lambda: print("switch 1 toggle"))
self.switch_1.grid(row=3, column=0, pady=10, padx=20, sticky="n")
self.switch_2 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame)
self.switch_2.grid(row=4, column=0, pady=(10, 20), padx=20, sticky="n")
# create slider and progressbar frame
self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color=None)
self.slider_progressbar_frame.grid(row=1, column=1, columnspan=2, padx=(20, 0), pady=(20, 0), sticky="nsew")
self.slider_progressbar_frame.grid_columnconfigure(0, weight=1)
self.slider_progressbar_frame.grid_rowconfigure(4, weight=1)
self.seg_button_1 = customtkinter.CTkSegmentedButton(self.slider_progressbar_frame)
self.seg_button_1.grid(row=0, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.progressbar_1 = customtkinter.CTkProgressBar(self.slider_progressbar_frame)
self.progressbar_1.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.progressbar_2 = customtkinter.CTkProgressBar(self.slider_progressbar_frame)
self.progressbar_2.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.slider_1 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=1, number_of_steps=4)
self.slider_1.grid(row=3, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
self.slider_2 = customtkinter.CTkSlider(self.slider_progressbar_frame, orientation="vertical")
self.slider_2.grid(row=0, column=1, rowspan=5, padx=(10, 10), pady=(10, 10), sticky="ns")
self.progressbar_3 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orientation="vertical")
self.progressbar_3.grid(row=0, column=2, rowspan=5, padx=(10, 20), pady=(10, 10), sticky="ns")
# set default values # set default values
self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton") self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton")
self.checkbox_2.configure(state="disabled") self.checkbox_2.configure(state="disabled")

View File

@ -1,77 +1,22 @@
import PIL.ImageTk
import customtkinter import customtkinter
import tkinter
from PIL import Image, ImageTk from PIL import Image, ImageTk
import os import os
PATH = os.path.dirname(os.path.realpath(__file__)) PATH = os.path.dirname(os.path.realpath(__file__))
customtkinter.set_appearance_mode("System") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
app = customtkinter.CTk()
class App(customtkinter.CTk): switch_1 = customtkinter.CTkSwitch(app, text="darkmode", command=lambda: customtkinter.set_appearance_mode("dark" if switch_1.get() == 1 else "light"))
def __init__(self): switch_1.pack(padx=20, pady=20)
super().__init__()
self.geometry("450x260")
self.title("CustomTkinter example_button_images.py")
self.grid_rowconfigure(0, weight=1) image_1 = customtkinter.CTkImage(light_image=Image.open(PATH + "/test_images/add_folder_dark.png"),
self.grid_columnconfigure(0, weight=1, minsize=200) dark_image=Image.open(PATH + "/test_images/add_folder_light.png"),
size=(30, 50))
image_1.configure(dark_image=Image.open(PATH + "/test_images/add_folder_light.png"))
self.frame_1 = customtkinter.CTkFrame(master=self, width=250, height=240, corner_radius=15) button_1 = customtkinter.CTkButton(app, image=image_1)
self.frame_1.grid(row=0, column=0, padx=20, pady=20, sticky="nsew") button_1.pack(padx=20, pady=20)
self.frame_1.grid_columnconfigure(0, weight=1)
self.frame_1.grid_columnconfigure(1, weight=1)
self.settings_image = self.load_image("/test_images/settings.png", 20) app.mainloop()
self.bell_image = self.load_image("/test_images/bell.png", 20)
self.add_folder_image = self.load_image("/test_images/add-folder.png", 20)
self.add_list_image = self.load_image("/test_images/add-folder.png", 20)
self.add_user_image = self.load_image("/test_images/add-user.png", 20)
self.chat_image = self.load_image("/test_images/chat.png", 20)
self.home_image = self.load_image("/test_images/home.png", 20)
self.button_1 = customtkinter.CTkButton(master=self.frame_1, image=self.settings_image, text="Add Folder", height=32,
compound="right", command=self.button_function)
self.button_1.grid(row=1, column=0, columnspan=2, padx=20, pady=(20, 10), sticky="ew")
self.button_2 = customtkinter.CTkButton(master=self.frame_1, image=self.add_list_image, text="Add Item", height=32,
compound="right", fg_color="#D35B58", hover_color="#C77C78",
command=self.button_function)
self.button_2.grid(row=2, column=0, columnspan=2, padx=20, pady=10, sticky="ew")
self.button_3 = customtkinter.CTkButton(master=self.frame_1, image=self.chat_image, text="", width=40, height=40,
corner_radius=10, fg_color="gray40", hover_color="gray25",
command=self.button_function)
self.button_3.grid(row=3, column=0, columnspan=1, padx=20, pady=10, sticky="w")
self.button_4 = customtkinter.CTkButton(master=self.frame_1, image=self.home_image, text="", width=40, height=40,
corner_radius=10, fg_color="gray40", hover_color="gray25",
command=self.button_function)
self.button_4.grid(row=3, column=1, columnspan=1, padx=20, pady=10, sticky="e")
self.button_5 = customtkinter.CTkButton(master=self, image=self.add_user_image, text="Add User", width=130, height=60, border_width=2,
corner_radius=10, compound="bottom", border_color="#D35B58", fg_color=("gray84", "gray25"),
hover_color="#C77C78", command=self.button_function)
self.button_5.grid(row=0, column=1, padx=20, pady=20)
self.scaling_button = customtkinter.CTkSegmentedButton(self, values=[0.8, 0.9, 1.0, 1.1, 1.2, 1.5],
command=lambda v: customtkinter.set_widget_scaling(v))
self.scaling_button.grid(row=1, column=0, pady=(0, 20))
self.mode_switch = customtkinter.CTkSwitch(self, text="darkmode", onvalue="dark", offvalue="light",
command=lambda: customtkinter.set_appearance_mode(self.mode_switch.get()))
self.mode_switch.grid(row=1, column=1, pady=(0, 20))
def load_image(self, path, image_size):
""" load rectangular image with path relative to PATH """
return ImageTk.PhotoImage(Image.open(PATH + path).resize((image_size, image_size)))
def button_function(self):
print("button pressed")
if __name__ == "__main__":
app = App()
app.mainloop()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -7,7 +7,7 @@ app.geometry("600x950")
switch_1 = customtkinter.CTkSwitch(app, text="darkmode", command=lambda: customtkinter.set_appearance_mode("dark" if switch_1.get() == 1 else "light")) switch_1 = customtkinter.CTkSwitch(app, text="darkmode", command=lambda: customtkinter.set_appearance_mode("dark" if switch_1.get() == 1 else "light"))
switch_1.pack(padx=20, pady=20) switch_1.pack(padx=20, pady=20)
seg_1 = customtkinter._CTkSegmentedButton(app, values=[]) seg_1 = customtkinter.CTkSegmentedButton(app, values=[])
seg_1.configure(values=["value 1", "Value 2", "Value 42", "Value 123", "longlonglong"]) seg_1.configure(values=["value 1", "Value 2", "Value 42", "Value 123", "longlonglong"])
seg_1.pack(padx=20, pady=20) seg_1.pack(padx=20, pady=20)
@ -16,7 +16,7 @@ frame_1.pack(padx=20, pady=20, fill="x")
seg_2_var = customtkinter.StringVar(value="value 1") seg_2_var = customtkinter.StringVar(value="value 1")
seg_2 = customtkinter._CTkSegmentedButton(frame_1, values=["value 1", "Value 2", "Value 42"], variable=seg_2_var) seg_2 = customtkinter.CTkSegmentedButton(frame_1, values=["value 1", "Value 2", "Value 42"], variable=seg_2_var)
seg_2.configure(values=[]) seg_2.configure(values=[])
seg_2.configure(values=["value 1", "Value 2", "Value 42"]) seg_2.configure(values=["value 1", "Value 2", "Value 42"])
seg_2.pack(padx=20, pady=10) seg_2.pack(padx=20, pady=10)
@ -32,14 +32,14 @@ frame_1_1.pack(padx=20, pady=10, fill="x")
switch_2 = customtkinter.CTkSwitch(frame_1_1, text="change fg", command=lambda: frame_1_1.configure(fg_color="red" if switch_2.get() == 1 else "green")) switch_2 = customtkinter.CTkSwitch(frame_1_1, text="change fg", command=lambda: frame_1_1.configure(fg_color="red" if switch_2.get() == 1 else "green"))
switch_2.pack(padx=20, pady=20) switch_2.pack(padx=20, pady=20)
seg_3 = customtkinter._CTkSegmentedButton(frame_1_1, values=["value 1", "Value 2", "Value 42"]) seg_3 = customtkinter.CTkSegmentedButton(frame_1_1, values=["value 1", "Value 2", "Value 42"])
seg_3.pack(padx=20, pady=10) seg_3.pack(padx=20, pady=10)
seg_4 = customtkinter._CTkSegmentedButton(app) seg_4 = customtkinter.CTkSegmentedButton(app)
seg_4.pack(padx=20, pady=20) seg_4.pack(padx=20, pady=20)
seg_5_var = customtkinter.StringVar(value="kfasjkfdklaj") seg_5_var = customtkinter.StringVar(value="kfasjkfdklaj")
seg_5 = customtkinter._CTkSegmentedButton(app, corner_radius=1000, border_width=0, unselected_color="green", seg_5 = customtkinter.CTkSegmentedButton(app, corner_radius=1000, border_width=0, unselected_color="green",
variable=seg_5_var) variable=seg_5_var)
seg_5.pack(padx=20, pady=20) seg_5.pack(padx=20, pady=20)
seg_5.configure(values=["1", "2", "3", "4"]) seg_5.configure(values=["1", "2", "3", "4"])
@ -56,7 +56,7 @@ label_seg_5 = customtkinter.CTkLabel(app, textvariable=seg_5_var)
label_seg_5.pack(padx=20, pady=20) label_seg_5.pack(padx=20, pady=20)
seg_6_var = customtkinter.StringVar(value="kfasjkfdklaj") seg_6_var = customtkinter.StringVar(value="kfasjkfdklaj")
seg_6 = customtkinter._CTkSegmentedButton(app, width=300) seg_6 = customtkinter.CTkSegmentedButton(app, width=300)
seg_6.pack(padx=20, pady=20) seg_6.pack(padx=20, pady=20)
entry_6 = customtkinter.CTkEntry(app) entry_6 = customtkinter.CTkEntry(app)
entry_6.pack(padx=20, pady=(0, 20)) entry_6.pack(padx=20, pady=(0, 20))
@ -70,7 +70,7 @@ label_6.pack(padx=20, pady=(0, 20))
seg_6.configure(height=50, variable=seg_6_var) seg_6.configure(height=50, variable=seg_6_var)
seg_6.delete("CTkSegmentedButton") seg_6.delete("CTkSegmentedButton")
seg_7 = customtkinter._CTkSegmentedButton(app, values=["disabled seg button", "2", "3"]) seg_7 = customtkinter.CTkSegmentedButton(app, values=["disabled seg button", "2", "3"])
seg_7.pack(padx=20, pady=20) seg_7.pack(padx=20, pady=20)
seg_7.configure(state="disabled") seg_7.configure(state="disabled")
seg_7.set("2") seg_7.set("2")

View File

@ -2,7 +2,7 @@ import customtkinter
app = customtkinter.CTk() app = customtkinter.CTk()
tabview_1 = customtkinter._CTkTabview(app) tabview_1 = customtkinter.CTkTabview(app)
tabview_1.pack(padx=20, pady=20) tabview_1.pack(padx=20, pady=20)
tab_1 = tabview_1.add("tab 1") tab_1 = tabview_1.add("tab 1")