removed bg and background argument support from CTk and CTkToplevel

This commit is contained in:
Tom Schimansky 2022-10-16 20:13:19 +02:00
parent 53b0d04e4b
commit 1ae794272b
16 changed files with 184 additions and 138 deletions

View File

@ -73,6 +73,9 @@ from .windows.ctk_tk import CTk
from .windows.ctk_toplevel import CTkToplevel
from .windows.ctk_input_dialog import CTkInputDialog
# util classes
from .utility.ctk_font import CTkFont
def set_appearance_mode(mode_string: str):
""" possible values: light, dark, system """

View File

@ -3,4 +3,4 @@ class Settings:
cursor_manipulation_enabled = True
deactivate_macos_window_header_manipulation = False
deactivate_windows_window_header_manipulation = False
use_dropdown_fallback = True
# use_dropdown_fallback = True

View File

View File

@ -0,0 +1,94 @@
from tkinter.font import Font
import copy
import sys
from ..scaling_tracker import ScalingTracker
from ..theme_manager import ThemeManager
class CTkFont(Font):
"""
Font object with size in pixel independent of scaling.
"""
def __init__(self,
family: str = "default_theme",
size: int = "default_theme",
weight: str = "normal",
slant: str = "roman",
underline: bool = False,
overstrike: bool = False):
# unscaled font size in px
self._size = ThemeManager.theme["text"]["size"] if size == "default_theme" else size
if self._size < 0:
sys.stderr.write(f"Warning: size {self._size} of CTkFont don't has to be negative, it's measured in pixel by default\n")
super().__init__(family=ThemeManager.theme["text"]["font"] if family == "default_theme" else family,
size=self._size,
weight=weight,
slant=slant,
underline=underline,
overstrike=overstrike)
def _set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
self._widget_scaling = new_widget_scaling
super().configure(size=round(self._apply_widget_scaling(self._size)))
def _apply_widget_scaling(self, value: int) -> int:
if isinstance(value, int):
return round(value * self._widget_scaling)
else:
raise ValueError(f"CTkFont can not scale size of type {type(value)}, only int allowed")
def _reverse_widget_scaling(self, value: int) -> int:
if isinstance(value, int):
return round(value / self._widget_scaling)
else:
raise ValueError(f"CTkFont can not scale size of type {type(value)}, only int allowed")
def config(self, *args, **kwargs):
raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.")
def configure(self, **kwargs):
if "size" in kwargs:
self._size = kwargs.pop("size")
super().configure(size=self._apply_widget_scaling(self._size))
super().configure(**kwargs)
def cget(self, attribute_name) -> any:
if attribute_name == "size":
return self._size
else:
super().cget(attribute_name)
def copy(self) -> "CTkFont":
return copy.deepcopy(self)
def measure(self, text, displayof=None) -> int:
""" measure width of text in px independent of scaling """
return self._reverse_widget_scaling(super().measure(text, displayof=displayof))
def metrics(self, *options: any, **kw: any) -> dict:
""" metrics of font, all values independent of scaling """
metrics_dict = super().metrics(*options, **kw)
if "ascent" in metrics_dict:
metrics_dict["ascent"] = self._reverse_widget_scaling(metrics_dict["ascent"])
if "descent" in metrics_dict:
metrics_dict["descent"] = self._reverse_widget_scaling(metrics_dict["descent"])
if "linespace" in metrics_dict:
metrics_dict["linespace"] = self._reverse_widget_scaling(metrics_dict["linespace"])
return metrics_dict
def actual(self, option: any = None, displayof: any = None) -> dict:
""" get back a dictionary of the font's actual attributes, which may differ from the ones you requested, size independent of scaling """
actual_dict = super().actual(option, displayof)
if "size" in actual_dict:
actual_dict["size"] = self._reverse_widget_scaling(actual_dict["size"])
return actual_dict

View File

@ -0,0 +1,23 @@
def pop_from_dict_by_set(dictionary: dict, valid_keys: set):
""" remove and create new dict with key value pairs of dictionary, where key is in valid_keys """
new_dictionary = {}
for key in list(dictionary.keys()):
if key in valid_keys:
new_dictionary[key] = dictionary.pop(key)
return new_dictionary
def check_kwargs_empty(kwargs_dict, raise_error=False) -> bool:
""" returns True if kwargs are empty, False otherwise, raises error if not empty """
if len(kwargs_dict) > 0:
if raise_error:
raise ValueError(f"{list(kwargs_dict.keys())} are not supported arguments. Look at the documentation for supported arguments.")
else:
return True
else:
return False

View File

@ -7,6 +7,7 @@ from ..theme_manager import ThemeManager
from ..settings import Settings
from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass
from ..utility.ctk_font import CTkFont
class CTkButton(CTkBaseClass):
@ -34,7 +35,7 @@ class CTkButton(CTkBaseClass):
round_height_to_even_numbers: bool = True,
text: str = "CTkButton",
font: any = "default_theme",
font: Union[tuple, CTkFont] = "default_theme",
textvariable: tkinter.Variable = None,
image: tkinter.PhotoImage = None,
state: str = "normal",
@ -67,7 +68,7 @@ class CTkButton(CTkBaseClass):
self._image_label: Union[tkinter.Label, None] = None
self._text = text
self._text_label: Union[tkinter.Label, None] = None
self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font
self._font = CTkFont() if font == "default_theme" else self._check_font_type_and_values(font)
# callback and hover functionality
self._command = command

View File

@ -9,8 +9,6 @@ from ..settings import Settings
from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass
from .widget_helper_functions import filter_dict_by_set
class CTkComboBox(CTkBaseClass):
"""

View File

@ -6,7 +6,7 @@ from ..theme_manager import ThemeManager
from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass
from .widget_helper_functions import pop_from_dict_by_set
from customtkinter.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
class CTkEntry(CTkBaseClass):
@ -95,7 +95,7 @@ class CTkEntry(CTkBaseClass):
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)))
self._check_kwargs_empty(kwargs, raise_error=True)
check_kwargs_empty(kwargs, raise_error=True)
self._entry.bind('<FocusOut>', self._entry_focus_out)
self._entry.bind('<FocusIn>', self._entry_focus_in)

View File

@ -6,7 +6,7 @@ from ..theme_manager import ThemeManager
from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass
from .widget_helper_functions import pop_from_dict_by_set
from customtkinter.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
class CTkLabel(CTkBaseClass):
@ -74,7 +74,7 @@ class CTkLabel(CTkBaseClass):
self._text_label.grid(row=0, column=0, sticky=text_label_grid_sticky,
padx=self._apply_widget_scaling(min(self._corner_radius, round(self._current_height/2))))
self._check_kwargs_empty(kwargs, raise_error=True)
check_kwargs_empty(kwargs, raise_error=True)
self._draw()

View File

@ -7,7 +7,7 @@ from ..theme_manager import ThemeManager
from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass
from .widget_helper_functions import pop_from_dict_by_set
from customtkinter.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
class CTkTextbox(CTkBaseClass):
@ -87,7 +87,7 @@ class CTkTextbox(CTkBaseClass):
bg=ThemeManager.single_color(self._fg_color, self._appearance_mode),
**pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes))
self._check_kwargs_empty(kwargs, raise_error=True)
check_kwargs_empty(kwargs, raise_error=True)
# scrollbars
self._scrollbars_activated = activate_scrollbars

View File

@ -1,7 +1,6 @@
import tkinter
import tkinter.ttk as ttk
import copy
import re
from typing import Union, Callable, Tuple
try:
@ -15,7 +14,9 @@ from ..appearance_mode_tracker import AppearanceModeTracker
from ..scaling_tracker import ScalingTracker
from ..theme_manager import ThemeManager
from .widget_helper_functions import pop_from_dict_by_set
from ..utility.ctk_font import CTkFont
from ..utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
class CTkBaseClass(tkinter.Frame):
@ -36,7 +37,7 @@ class CTkBaseClass(tkinter.Frame):
super().__init__(master=master, width=width, height=height, **pop_from_dict_by_set(kwargs, self._valid_tk_frame_attributes))
# check if kwargs is empty, if not raise error for unsupported arguments
self._check_kwargs_empty(kwargs, raise_error=True)
check_kwargs_empty(kwargs, raise_error=True)
# dimensions
self._current_width = width # _current_width and _current_height in pixel, represent current size of the widget
@ -138,16 +139,10 @@ class CTkBaseClass(tkinter.Frame):
raise ValueError(f"'{attribute_name}' is not a supported argument. Look at the documentation for supported arguments.")
@staticmethod
def _check_kwargs_empty(kwargs_dict, raise_error=False) -> bool:
""" returns True if kwargs are empty, False otherwise, raises error if not empty """
if len(kwargs_dict) > 0:
if raise_error:
raise ValueError(f"{list(kwargs_dict.keys())} are not supported arguments. Look at the documentation for supported arguments.")
else:
return True
else:
return False
def _check_font_type_and_values(font: any):
if not isinstance(font, CTkFont):
raise ValueError(f"\nFor consistency, Customtkinter requires the font argument {font} to be an instance of CTkFont.\n" +
f"\nUsage example: font=customtkinter.CTkFont(family='name', size='size in px')\n(other arguments in the documentation)\n")
def _update_dimensions_event(self, event):
# only redraw if dimensions changed (for performance), independent of scaling
@ -224,26 +219,14 @@ class CTkBaseClass(tkinter.Frame):
else:
return value
def _apply_font_scaling(self, font):
if type(font) == tuple or type(font) == list:
def _apply_font_scaling(self, font: Union[tuple, CTkFont]) -> Union[tuple, CTkFont]:
if type(font) == tuple:
font_list = list(font)
for i in range(len(font_list)):
if (type(font_list[i]) == int or type(font_list[i]) == float) and font_list[i] < 0:
font_list[i] = int(font_list[i] * self._widget_scaling)
if len(font_list) >= 2 and type(font_list[1]) == int:
font_list[1] = int(font_list[1] * self._widget_scaling)
return tuple(font_list)
elif type(font) == str:
for negative_number in re.findall(r" -\d* ", font):
font = font.replace(negative_number, f" {int(int(negative_number) * self._widget_scaling)} ")
return font
elif isinstance(font, tkinter.font.Font):
new_font_object = copy.copy(font)
if font.cget("size") < 0:
new_font_object.config(size=int(font.cget("size") * self._widget_scaling))
return new_font_object
else:
elif isinstance(font, CTkFont):
return font
def _apply_argument_scaling(self, kwargs: dict) -> dict:

View File

@ -1,22 +0,0 @@
def filter_dict_by_set(dictionary: dict, valid_keys: set):
""" create new dict with key value pairs of dictionary, where key is in valid_keys """
new_dictionary = {}
for key, value in dictionary.items():
if key in valid_keys:
new_dictionary[key] = value
return new_dictionary
def pop_from_dict_by_set(dictionary: dict, valid_keys: set):
""" remove and create new dict with key value pairs of dictionary, where key is in valid_keys """
new_dictionary = {}
for key in list(dictionary.keys()):
if key in valid_keys:
new_dictionary[key] = dictionary.pop(key)
return new_dictionary

View File

@ -5,7 +5,6 @@ import os
import platform
import ctypes
import re
import time
from typing import Union, Tuple
from ..appearance_mode_tracker import AppearanceModeTracker
@ -13,6 +12,8 @@ from ..theme_manager import ThemeManager
from ..scaling_tracker import ScalingTracker
from ..settings import Settings
from ..utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
class CTk(tkinter.Tk):
"""
@ -20,14 +21,21 @@ class CTk(tkinter.Tk):
For detailed information check out the documentation.
"""
def __init__(self, *args,
_valid_tk_constructor_arguments = {"screenName", "baseName", "className", "useTk", "sync", "use"}
_valid_tk_configure_arguments = {'bd', 'borderwidth', 'class', 'menu', 'relief', 'screen',
'use', 'container', 'cursor', 'height',
'highlightthickness', 'padx', 'pady', 'takefocus', 'visual', 'width'}
def __init__(self,
fg_color: Union[str, Tuple[str, str]] = "default_theme",
**kwargs):
ScalingTracker.activate_high_dpi_awareness() # make process DPI aware
self._enable_macos_dark_title_bar()
super().__init__(*args, **kwargs)
super().__init__(**pop_from_dict_by_set(kwargs, self._valid_tk_constructor_arguments))
check_kwargs_empty(kwargs, raise_error=True)
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self._set_appearance_mode, self)
@ -47,11 +55,6 @@ class CTk(tkinter.Tk):
self._fg_color = ThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
if "bg" in kwargs:
self._fg_color = kwargs.pop("bg")
elif "background" in kwargs:
self._fg_color = kwargs.pop("background")
super().configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
super().title("CTk")
self.geometry(f"{self._current_width}x{self._current_height}")
@ -234,40 +237,19 @@ class CTk(tkinter.Tk):
else:
return value
def configure(self, *args, **kwargs):
bg_changed = False
if "bg" in kwargs:
self._fg_color = kwargs["bg"]
bg_changed = True
kwargs["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
elif "background" in kwargs:
self._fg_color = kwargs["background"]
bg_changed = True
kwargs["background"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
elif "fg_color" in kwargs:
def configure(self, **kwargs):
if "fg_color" in kwargs:
self._fg_color = kwargs.pop("fg_color")
kwargs["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
bg_changed = True
elif len(args) > 0 and type(args[0]) == dict:
if "bg" in args[0]:
self._fg_color = args[0]["bg"]
bg_changed = True
args[0]["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
elif "background" in args[0]:
self._fg_color = args[0]["background"]
bg_changed = True
args[0]["background"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
if bg_changed:
from ..widgets.widget_base_class import CTkBaseClass
super().configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
for child in self.winfo_children():
if isinstance(child, CTkBaseClass) and hasattr(child, "_fg_color"):
try:
child.configure(bg_color=self._fg_color)
except Exception:
pass
super().configure(*args, **kwargs)
super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_configure_arguments))
check_kwargs_empty(kwargs)
def cget(self, attribute_name: str) -> any:
if attribute_name == "fg_color":

View File

@ -12,6 +12,8 @@ from ..theme_manager import ThemeManager
from ..settings import Settings
from ..scaling_tracker import ScalingTracker
from ..utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
class CTkToplevel(tkinter.Toplevel):
"""
@ -19,12 +21,21 @@ class CTkToplevel(tkinter.Toplevel):
For detailed information check out the documentation.
"""
_valid_tk_toplevel_arguments = {"bd", "borderwidth", "class", "container", "cursor", "height",
"highlightbackground", "highlightthickness", "menu", "relief",
"screen", "takefocus", "use", "visual", "width"}
def __init__(self, *args,
fg_color: Union[str, Tuple[str, str]] = "default_theme",
**kwargs):
self._enable_macos_dark_title_bar()
super().__init__(*args, **kwargs)
super().__init__(*args, **pop_from_dict_by_set(kwargs, self._valid_tk_toplevel_arguments))
check_kwargs_empty(kwargs, raise_error=True)
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self._set_appearance_mode, self)
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
# add set_scaling method to callback list of ScalingTracker for automatic scaling changes
@ -41,13 +52,6 @@ class CTkToplevel(tkinter.Toplevel):
self._fg_color = ThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
if "bg" in kwargs:
self._fg_color = kwargs.pop("bg")
elif "background" in kwargs:
self.fg_color = kwargs.pop("background")
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self._set_appearance_mode, self)
super().configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
super().title("CTkToplevel")
@ -195,40 +199,19 @@ class CTkToplevel(tkinter.Toplevel):
self._current_height = height
super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
def configure(self, *args, **kwargs):
bg_changed = False
if "bg" in kwargs:
self._fg_color = kwargs["bg"]
bg_changed = True
kwargs["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
elif "background" in kwargs:
self._fg_color = kwargs["background"]
bg_changed = True
kwargs["background"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
elif "fg_color" in kwargs:
def configure(self, **kwargs):
if "fg_color" in kwargs:
self._fg_color = kwargs.pop("fg_color")
kwargs["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
bg_changed = True
elif len(args) > 0 and type(args[0]) == dict:
if "bg" in args[0]:
self._fg_color = args[0]["bg"]
bg_changed = True
args[0]["bg"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
elif "background" in args[0]:
self._fg_color = args[0]["background"]
bg_changed = True
args[0]["background"] = ThemeManager.single_color(self._fg_color, self._appearance_mode)
if bg_changed:
from ..widgets.widget_base_class import CTkBaseClass
super().configure(bg=ThemeManager.single_color(self._fg_color, self._appearance_mode))
for child in self.winfo_children():
if isinstance(child, CTkBaseClass) and hasattr(child, "_fg_color"):
try:
child.configure(bg_color=self._fg_color)
except Exception:
pass
super().configure(*args, **kwargs)
super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_toplevel_arguments))
check_kwargs_empty(kwargs)
def cget(self, attribute_name: str) -> any:
if attribute_name == "fg_color":

View File

@ -5,7 +5,6 @@ app = tkinter.Tk()
app.geometry("400x350")
app.title("simple_example_standard_tkinter.py")
def button_function():
print("button pressed")

View File

@ -138,6 +138,8 @@ class App(customtkinter.CTk):
self.seg_button_1.configure(values=["CTkSegmentedButton", "Value 2", "Value 3"])
self.seg_button_1.set("Value 2")
self.attributes("-fullscreen", True)
def open_input_dialog(self):
dialog = customtkinter.CTkInputDialog(master=self, text="Type in a number:", title="CTkInputDialog")
print("CTkInputDialog:", dialog.get_input())