39 Commits

Author SHA1 Message Date
4b600b9179 Bump to 5.0.5 2023-01-22 21:37:49 +01:00
7901edba30 remove unnecessary check_scollbar call in textbox #1020 2023-01-21 22:37:40 +01:00
39447072ac change text fg color 2023-01-21 22:24:54 +01:00
7cb8f64dec fix switch and radiobutton background color #867, check if user set titlebar icon on Windows 2023-01-21 14:22:18 +01:00
359226e468 Bump to 5.0.4 2023-01-21 13:43:54 +01:00
dc751e46d3 add example images 2023-01-21 13:43:18 +01:00
79d5da439b fix readonly background for combobox #983 2023-01-10 15:03:45 +01:00
fac2fa5e68 Merge remote-tracking branch 'origin/master' 2023-01-07 18:37:22 +01:00
2de1b94575 fixed dropdown_fg_color attribute in configure of CTkOptionMenu 2023-01-07 18:37:00 +01:00
a79502dc03 replaced sys.stderr with warnings.warn #932 2023-01-07 01:21:28 +01:00
8a537076ce added icon on Windows for CTkToplevel, fixed #960 2023-01-07 01:16:15 +01:00
1396a7e484 fixed #925 2022-12-25 21:03:33 +01:00
5bbd72b5dc fixed #941 2022-12-25 20:54:40 +01:00
84bfc776b0 Merge remote-tracking branch 'origin/master' 2022-12-10 13:40:29 +01:00
7f5ac69259 Bump to 5.0.3 2022-12-10 13:40:06 +01:00
90157252d0 added icons folder to MANIFEST.in 2022-12-10 13:39:33 +01:00
392586eaa1 removed macOS icon change 2022-12-10 13:38:27 +01:00
f3710de173 changed windows icon 2022-12-10 13:29:35 +01:00
9f8b54563d add icons 2022-12-10 13:17:55 +01:00
28228316eb fix image configure for button #807 2022-12-08 19:33:39 +01:00
61adb1da07 fix readme 2022-12-08 10:35:11 +01:00
042fac7242 fix image type hints #795 2022-12-08 10:34:41 +01:00
a49dde63b3 fix switch #801 2022-12-07 22:15:31 +01:00
f11d727879 fix combobox #800 2022-12-07 22:10:35 +01:00
77595da9f2 Bump to 5.0.2 2022-12-06 23:19:54 +01:00
d43229ef6e fixed long description in setup.cfg 2022-12-06 23:19:43 +01:00
f068cee972 test 2022-12-06 19:29:04 +01:00
62063d6f64 test 2022-12-06 19:28:34 +01:00
3d86b5a14f fix widget bind if clause error 2022-12-06 19:25:00 +01:00
5a17b1243e fixed background for entry readonly state #643 2022-12-06 19:16:05 +01:00
7572f095c2 cget now returns copy of lists 2022-12-06 18:47:39 +01:00
868b2a2f42 allow multiple style strings in font tuple #759 2022-12-06 18:29:09 +01:00
6a3fa7fa29 fixed support for python3.7 #737 2022-12-06 18:13:59 +01:00
a564bc35ef fixed progressbar start stop speed increase #775, fixed transparent textbox #779, fixed binding for all widgets #250 #374 #380 #477 #480 2022-12-06 18:09:20 +01:00
dd223a15b5 fixed progressbar start stop speed increase #775 2022-12-06 11:27:35 +01:00
2c7b2c5030 fixed radiobutton disabled command call bug #677, fixed key error for theme in scrollbar #711, removed bind_all and unbind_all from baseclass, added CTkCanvas and CTkBaseClass for top level import 2022-12-06 11:09:34 +01:00
f4af512290 Bump to 5.0.1 2022-12-01 10:06:54 +01:00
482a6e60b7 fix PIL Image import error 2022-12-01 09:33:13 +01:00
83dedea59c fixed changelog 2022-12-01 00:31:48 +01:00
42 changed files with 463 additions and 232 deletions

View File

@ -5,12 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
ToDo: ToDo:
- change font attribute in wiki
- add new button attributes to wiki
- create grayscale theme file
- cursor configuring - cursor configuring
- overwrite winfo methods - overwrite winfo methods
- set icon (self.call("wm", "iconphoto", self._w, tkinter.PhotoImage(file="test_images/CustomTkinter_logo_single.png")))
- add option to change label position for checkbox, switch, radiobutton #628
## [5.0.0] - 2022-11-13 ## [5.0.0] - 2022-11-13

View File

@ -1,4 +1,5 @@
include customtkinter/assets/* include customtkinter/assets/*
include customtkinter/assets/fonts/* include customtkinter/assets/fonts/*
include customtkinter/assets/fonts/Roboto/* include customtkinter/assets/fonts/Roboto/*
include customtkinter/assets/themes/* include customtkinter/assets/icons/*
include customtkinter/assets/themes/*

View File

@ -11,7 +11,7 @@
![PyPI - Downloads](https://img.shields.io/pypi/dm/customtkinter?color=green&label=downloads) ![PyPI - Downloads](https://img.shields.io/pypi/dm/customtkinter?color=green&label=downloads)
![Downloads](https://static.pepy.tech/personalized-badge/customtkinter?period=total&units=international_system&left_color=grey&right_color=green&left_text=downloads) ![Downloads](https://static.pepy.tech/personalized-badge/customtkinter?period=total&units=international_system&left_color=grey&right_color=green&left_text=downloads)
![PyPI - License](https://img.shields.io/pypi/l/customtkinter) ![PyPI - License](https://img.shields.io/pypi/l/customtkinter)
![Total lines](https://img.shields.io/tokei/lines/github.com/tomschimansky/customtkinter?color=green&label=total%20lines) ![](https://tokei.rs/b1/github/tomschimansky/customtkinter)
</div> </div>

View File

@ -1,4 +1,4 @@
__version__ = "5.0.0" __version__ = "5.0.5"
import os import os
import sys import sys
@ -13,6 +13,10 @@ from .windows.widgets.scaling import ScalingTracker
from .windows.widgets.theme import ThemeManager from .windows.widgets.theme import ThemeManager
from .windows.widgets.core_rendering import DrawEngine from .windows.widgets.core_rendering import DrawEngine
# import base widgets
from .windows.widgets.core_rendering import CTkCanvas
from .windows.widgets.core_widget_classes import CTkBaseClass
# import widgets # import widgets
from .windows.widgets import CTkButton from .windows.widgets import CTkButton
from .windows.widgets import CTkCheckBox from .windows.widgets import CTkCheckBox
@ -74,4 +78,4 @@ def set_window_scaling(scaling_value: float):
def deactivate_automatic_dpi_awareness(): def deactivate_automatic_dpi_awareness():
""" deactivate DPI awareness of current process (windll.shcore.SetProcessDpiAwareness(0)) """ """ deactivate DPI awareness of current process (windll.shcore.SetProcessDpiAwareness(0)) """
ScalingTracker.deactivate_automatic_dpi_awareness = False ScalingTracker.deactivate_automatic_dpi_awareness = True

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -121,7 +121,7 @@
"CTkTextbox": { "CTkTextbox": {
"corner_radius": 6, "corner_radius": 6,
"border_width": 0, "border_width": 0,
"fg_color": ["#F9F9FA", "gray23"], "fg_color": ["#F9F9FA", "#1D1E1E"],
"border_color": ["#979DA2", "#565B5E"], "border_color": ["#979DA2", "#565B5E"],
"text_color":["gray10", "#DCE4EE"], "text_color":["gray10", "#DCE4EE"],
"scrollbar_button_color": ["gray55", "gray41"], "scrollbar_button_color": ["gray55", "gray41"],

View File

@ -53,23 +53,28 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
# set bg of tkinter.Tk # set bg of tkinter.Tk
super().configure(bg=self._apply_appearance_mode(self._fg_color)) super().configure(bg=self._apply_appearance_mode(self._fg_color))
# set title and initial geometry # set title
self.title("CTk") self.title("CTk")
# self.geometry(f"{self._current_width}x{self._current_height}")
# indicator variables
self._iconbitmap_method_called = False # indicates if wm_iconbitmap method got called
self._state_before_windows_set_titlebar_color = None self._state_before_windows_set_titlebar_color = None
self._window_exists = False # indicates if the window is already shown through update() or mainloop() after init self._window_exists = False # indicates if the window is already shown through update() or mainloop() after init
self._withdraw_called_before_window_exists = False # indicates if withdraw() was called before window is first shown through update() or mainloop() self._withdraw_called_before_window_exists = False # indicates if withdraw() 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() self._iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop()
self._block_update_dimensions_event = False
# set CustomTkinter titlebar icon (Windows only)
if sys.platform.startswith("win"):
self.after(200, self._windows_set_titlebar_icon)
# set titlebar color (Windows only)
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
self._windows_set_titlebar_color(self._get_appearance_mode()) self._windows_set_titlebar_color(self._get_appearance_mode())
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)
self._block_update_dimensions_event = False
def destroy(self): def destroy(self):
self._disable_macos_dark_title_bar() self._disable_macos_dark_title_bar()
@ -211,6 +216,19 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
else: else:
return super().cget(attribute_name) return super().cget(attribute_name)
def wm_iconbitmap(self, bitmap=None, default=None):
self._iconbitmap_method_called = True
super().wm_iconbitmap(bitmap, default)
def _windows_set_titlebar_icon(self):
try:
# if not the user already called iconbitmap method, set icon
if not self._iconbitmap_method_called:
customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico"))
except Exception:
pass
@classmethod @classmethod
def _enable_macos_dark_title_bar(cls): def _enable_macos_dark_title_bar(cls):
if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS

View File

@ -38,6 +38,14 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
CTkScalingBaseClass.__init__(self, scaling_type="window") CTkScalingBaseClass.__init__(self, scaling_type="window")
check_kwargs_empty(kwargs, raise_error=True) check_kwargs_empty(kwargs, raise_error=True)
try:
# Set Windows titlebar icon
if sys.platform.startswith("win"):
customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.after(200, lambda: self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico")))
except Exception:
pass
self._current_width = 200 # initial window size, always without scaling self._current_width = 200 # initial window size, always without scaling
self._current_height = 200 self._current_height = 200
self._min_width: int = 0 self._min_width: int = 0
@ -54,19 +62,25 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
# set title of tkinter.Toplevel # set title of tkinter.Toplevel
super().title("CTkToplevel") super().title("CTkToplevel")
# indicator variables
self._iconbitmap_method_called = True
self._state_before_windows_set_titlebar_color = None self._state_before_windows_set_titlebar_color = None
self._windows_set_titlebar_color_called = False # indicates if windows_set_titlebar_color was called, stays True until revert_withdraw_after_windows_set_titlebar_color is called self._windows_set_titlebar_color_called = False # indicates if windows_set_titlebar_color was called, stays True until revert_withdraw_after_windows_set_titlebar_color is called
self._withdraw_called_after_windows_set_titlebar_color = False # indicates if withdraw() was called after windows_set_titlebar_color self._withdraw_called_after_windows_set_titlebar_color = False # indicates if withdraw() 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 self._iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color
self._block_update_dimensions_event = False
# set CustomTkinter titlebar icon (Windows only)
if sys.platform.startswith("win"):
self.after(200, self._windows_set_titlebar_icon)
# set titlebar color (Windows only)
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
self._windows_set_titlebar_color(self._get_appearance_mode()) self._windows_set_titlebar_color(self._get_appearance_mode())
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)
self._block_update_dimensions_event = False
def destroy(self): def destroy(self):
self._disable_macos_dark_title_bar() self._disable_macos_dark_title_bar()
@ -182,6 +196,19 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
else: else:
return super().cget(attribute_name) return super().cget(attribute_name)
def wm_iconbitmap(self, bitmap=None, default=None):
self._iconbitmap_method_called = True
super().wm_iconbitmap(bitmap, default)
def _windows_set_titlebar_icon(self):
try:
# if not the user already called iconbitmap method, set icon
if not self._iconbitmap_method_called:
customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico"))
except Exception:
pass
@classmethod @classmethod
def _enable_macos_dark_title_bar(cls): def _enable_macos_dark_title_bar(cls):
if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS

View File

@ -1,4 +1,5 @@
import sys import sys
import warnings
import tkinter import tkinter
import tkinter.ttk as ttk import tkinter.ttk as ttk
from typing import Union, Callable, Tuple from typing import Union, Callable, Tuple
@ -158,15 +159,15 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
return font return font
elif type(font) == tuple and len(font) == 1: elif type(font) == tuple and len(font) == 1:
sys.stderr.write(f"{type(self).__name__} Warning: font {font} given without size, will be extended with default text size of current theme\n") warnings.warn(f"{type(self).__name__} Warning: font {font} given without size, will be extended with default text size of current theme\n")
return font[0], ThemeManager.theme["text"]["size"] return font[0], ThemeManager.theme["text"]["size"]
elif type(font) == tuple and 2 <= len(font) <= 3: elif type(font) == tuple and 2 <= len(font) <= 6:
return font return font
else: else:
raise ValueError(f"Wrong font type {type(font)}\n" + raise ValueError(f"Wrong font type {type(font)}\n" +
f"For consistency, Customtkinter requires the font argument to be a tuple of len 2 or 3 or an instance of CTkFont.\n" + f"For consistency, Customtkinter requires the font argument to be a tuple of len 2 to 6 or an instance of CTkFont.\n" +
f"\nUsage example:\n" + f"\nUsage example:\n" +
f"font=customtkinter.CTkFont(family='<name>', size=<size in px>)\n" + f"font=customtkinter.CTkFont(family='<name>', size=<size in px>)\n" +
f"font=('<name>', <size in px>)\n") f"font=('<name>', <size in px>)\n")
@ -178,8 +179,8 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
elif isinstance(image, CTkImage): elif isinstance(image, CTkImage):
return image return image
else: else:
sys.stderr.write(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. " + warnings.warn(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. " +
f"Image can not be scaled on HighDPI displays, use CTkImage instead.\n") f"Image can not be scaled on HighDPI displays, use CTkImage instead.\n")
return image return image
def _update_dimensions_event(self, event): def _update_dimensions_event(self, event):
@ -240,6 +241,18 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
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))
def bind(self, sequence=None, command=None, add=None):
raise NotImplementedError
def unbind(self, sequence=None, funcid=None):
raise NotImplementedError
def unbind_all(self, sequence):
raise AttributeError("'unbind_all' is not allowed, because it would delete necessary internal callbacks for all widgets")
def bind_all(self, sequence=None, func=None, add=None):
raise AttributeError("'bind_all' is not allowed, could result in undefined behavior")
def place(self, **kwargs): def place(self, **kwargs):
""" """
Place a widget in the parent widget. Use as options: Place a widget in the parent widget. Use as options:

View File

@ -40,7 +40,7 @@ class CTkButton(CTkBaseClass):
text: str = "CTkButton", text: str = "CTkButton",
font: Optional[Union[tuple, CTkFont]] = None, font: Optional[Union[tuple, CTkFont]] = None,
textvariable: Union[tkinter.Variable, None] = None, textvariable: Union[tkinter.Variable, None] = None,
image: Union[tkinter.PhotoImage, CTkImage, None] = None, image: Union[CTkImage, None] = None,
state: str = "normal", state: str = "normal",
hover: bool = True, hover: bool = True,
command: Union[Callable[[], None], None] = None, command: Union[Callable[[], None], None] = None,
@ -100,16 +100,38 @@ class CTkButton(CTkBaseClass):
self._draw_engine = DrawEngine(self._canvas) self._draw_engine = DrawEngine(self._canvas)
self._draw_engine.set_round_to_even_numbers(self._round_width_to_even_numbers, self._round_height_to_even_numbers) # rendering options self._draw_engine.set_round_to_even_numbers(self._round_width_to_even_numbers, self._round_height_to_even_numbers) # rendering options
# canvas event bindings
self._canvas.bind("<Enter>", self._on_enter)
self._canvas.bind("<Leave>", self._on_leave)
self._canvas.bind("<Button-1>", self._clicked)
self._canvas.bind("<Button-1>", self._clicked)
# configure cursor and initial draw # configure cursor and initial draw
self._create_bindings()
self._set_cursor() self._set_cursor()
self._draw() self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter)
if self._text_label is not None:
self._text_label.bind("<Enter>", self._on_enter)
if self._image_label is not None:
self._image_label.bind("<Enter>", self._on_enter)
if sequence is None or sequence == "<Leave>":
self._canvas.bind("<Leave>", self._on_leave)
if self._text_label is not None:
self._text_label.bind("<Leave>", self._on_leave)
if self._image_label is not None:
self._image_label.bind("<Leave>", self._on_leave)
if sequence is None or sequence == "<Button-1>":
self._canvas.bind("<Button-1>", self._clicked)
if self._text_label is not None:
self._text_label.bind("<Button-1>", self._clicked)
if self._image_label is not None:
self._image_label.bind("<Button-1>", self._clicked)
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
@ -391,7 +413,7 @@ class CTkButton(CTkBaseClass):
self._image = self._check_image_type(kwargs.pop("image")) self._image = self._check_image_type(kwargs.pop("image"))
if isinstance(self._image, CTkImage): if isinstance(self._image, CTkImage):
self._image.add_configure_callback(self._update_image) self._image.add_configure_callback(self._update_image)
require_redraw = True self._update_image()
if "state" in kwargs: if "state" in kwargs:
self._state = kwargs.pop("state") self._state = kwargs.pop("state")
@ -532,17 +554,30 @@ class CTkButton(CTkBaseClass):
if self._command is not None: if self._command is not None:
return self._command() return self._command()
def bind(self, sequence: str = None, command: Callable = None, add: str = None) -> str: def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" called on the tkinter.Label and tkinter.Canvas """ """ called on the tkinter.Canvas """
canvas_bind_return = self._canvas.bind(sequence, command, add) if not (add == "+" or add is True):
label_bind_return = self._text_label.bind(sequence, command, add) raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
return canvas_bind_return + " + " + label_bind_return self._canvas.bind(sequence, command, add=True)
def unbind(self, sequence: str, funcid: str = None): if self._text_label is not None:
self._text_label.bind(sequence, command, add=True)
if self._image_label is not None:
self._image_label.bind(sequence, command, add=True)
def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Label and tkinter.Canvas """ """ called on the tkinter.Label and tkinter.Canvas """
canvas_bind_return, label_bind_return = funcid.split(" + ") if funcid is not None:
self._canvas.unbind(sequence, canvas_bind_return) raise ValueError("'funcid' argument can only be None, because there is a bug in" +
self._text_label.unbind(sequence, label_bind_return) " tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None)
if self._text_label is not None:
self._text_label.unbind(sequence, None)
if self._image_label is not None:
self._image_label.unbind(sequence, None)
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self): def focus(self):
return self._text_label.focus() return self._text_label.focus()

View File

@ -103,10 +103,6 @@ class CTkCheckBox(CTkBaseClass):
self._canvas.grid(row=0, column=0, sticky="e") self._canvas.grid(row=0, column=0, sticky="e")
self._draw_engine = DrawEngine(self._canvas) self._draw_engine = DrawEngine(self._canvas)
self._canvas.bind("<Enter>", self._on_enter)
self._canvas.bind("<Leave>", self._on_leave)
self._canvas.bind("<Button-1>", self.toggle)
self._text_label = tkinter.Label(master=self, self._text_label = tkinter.Label(master=self,
bd=0, bd=0,
padx=0, padx=0,
@ -118,17 +114,26 @@ class CTkCheckBox(CTkBaseClass):
self._text_label.grid(row=0, column=2, sticky="w") self._text_label.grid(row=0, column=2, sticky="w")
self._text_label["anchor"] = "w" self._text_label["anchor"] = "w"
self._text_label.bind("<Enter>", self._on_enter)
self._text_label.bind("<Leave>", self._on_leave)
self._text_label.bind("<Button-1>", self.toggle)
# register variable callback and set state according to variable # register variable callback and set state according to variable
if self._variable is not None and self._variable != "": if self._variable is not None and self._variable != "":
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback) self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
self._check_state = True if self._variable.get() == self._onvalue else False self._check_state = True if self._variable.get() == self._onvalue else False
self._draw() # initial draw self._create_bindings()
self._set_cursor() self._set_cursor()
self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter)
self._text_label.bind("<Enter>", self._on_enter)
if sequence is None or sequence == "<Leave>":
self._canvas.bind("<Leave>", self._on_leave)
self._text_label.bind("<Leave>", self._on_leave)
if sequence is None or sequence == "<Button-1>":
self._canvas.bind("<Button-1>", self.toggle)
self._text_label.bind("<Button-1>", self.toggle)
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
@ -430,13 +435,21 @@ class CTkCheckBox(CTkBaseClass):
def get(self) -> Union[int, str]: def get(self) -> Union[int, str]:
return self._onvalue if self._check_state is True else self._offvalue return self._onvalue if self._check_state is True else self._offvalue
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" called on the tkinter.Canvas """ """ called on the tkinter.Canvas """
return self._canvas.bind(sequence, command, add) if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True)
self._text_label.bind(sequence, command, add=True)
def unbind(self, sequence, funcid=None): def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Canvas """ """ called on the tkinter.Label and tkinter.Canvas """
return self._canvas.unbind(sequence, funcid) if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None)
self._text_label.unbind(sequence, None)
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self): def focus(self):
return self._text_label.focus() return self._text_label.focus()

View File

@ -1,5 +1,6 @@
import tkinter import tkinter
import sys import sys
import copy
from typing import Union, Tuple, Callable, List, Optional from typing import Union, Tuple, Callable, List, Optional
from .core_widget_classes import DropdownMenu from .core_widget_classes import DropdownMenu
@ -102,26 +103,29 @@ class CTkComboBox(CTkBaseClass):
font=self._apply_font_scaling(self._font)) font=self._apply_font_scaling(self._font))
self._create_grid() self._create_grid()
self._create_bindings()
# insert default value
if len(self._values) > 0:
self._entry.insert(0, self._values[0])
else:
self._entry.insert(0, "CTkComboBox")
self._draw() # initial draw self._draw() # initial draw
# event bindings
self._canvas.tag_bind("right_parts", "<Enter>", self._on_enter)
self._canvas.tag_bind("dropdown_arrow", "<Enter>", self._on_enter)
self._canvas.tag_bind("right_parts", "<Leave>", self._on_leave)
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)
if self._variable is not None: if self._variable is not None:
self._entry.configure(textvariable=self._variable) self._entry.configure(textvariable=self._variable)
# insert default value
if self._variable is None:
if len(self._values) > 0:
self._entry.insert(0, self._values[0])
else:
self._entry.insert(0, "CTkComboBox")
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None:
self._canvas.tag_bind("right_parts", "<Enter>", self._on_enter)
self._canvas.tag_bind("dropdown_arrow", "<Enter>", self._on_enter)
self._canvas.tag_bind("right_parts", "<Leave>", self._on_leave)
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)
def _create_grid(self): def _create_grid(self):
self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew") self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
@ -197,6 +201,7 @@ class CTkComboBox(CTkBaseClass):
self._entry.configure(bg=self._apply_appearance_mode(self._fg_color), self._entry.configure(bg=self._apply_appearance_mode(self._fg_color),
fg=self._apply_appearance_mode(self._text_color), fg=self._apply_appearance_mode(self._text_color),
readonlybackground=self._apply_appearance_mode(self._fg_color),
disabledbackground=self._apply_appearance_mode(self._fg_color), disabledbackground=self._apply_appearance_mode(self._fg_color),
disabledforeground=self._apply_appearance_mode(self._text_color_disabled), disabledforeground=self._apply_appearance_mode(self._text_color_disabled),
highlightcolor=self._apply_appearance_mode(self._fg_color), highlightcolor=self._apply_appearance_mode(self._fg_color),
@ -322,7 +327,7 @@ class CTkComboBox(CTkBaseClass):
elif attribute_name == "dropdown_font": elif attribute_name == "dropdown_font":
return self._dropdown_menu.cget("font") return self._dropdown_menu.cget("font")
elif attribute_name == "values": elif attribute_name == "values":
return self._values return copy.copy(self._values)
elif attribute_name == "state": elif attribute_name == "state":
return self._state return self._state
elif attribute_name == "hover": elif attribute_name == "hover":
@ -391,17 +396,23 @@ class CTkComboBox(CTkBaseClass):
def get(self) -> str: def get(self) -> str:
return self._entry.get() return self._entry.get()
def _clicked(self, event=0): def _clicked(self, event=None):
if self._state is not tkinter.DISABLED and len(self._values) > 0: if self._state is not tkinter.DISABLED and len(self._values) > 0:
self._open_dropdown_menu() self._open_dropdown_menu()
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence=None, command=None, add=True):
""" called on the tkinter.Entry """ """ called on the tkinter.Entry """
return self._entry.bind(sequence, command, add) if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._entry.bind(sequence, command, add=True)
def unbind(self, sequence, funcid=None): def unbind(self, sequence=None, funcid=None):
""" called on the tkinter.Entry """ """ called on the tkinter.Entry """
return self._entry.unbind(sequence, funcid) if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._entry.unbind(sequence, None) # unbind all callbacks for sequence
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self): def focus(self):
return self._entry.focus() return self._entry.focus()

View File

@ -90,16 +90,20 @@ class CTkEntry(CTkBaseClass):
textvariable=self._textvariable, textvariable=self._textvariable,
**pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes)) **pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes))
self._create_grid()
check_kwargs_empty(kwargs, raise_error=True) check_kwargs_empty(kwargs, raise_error=True)
self._entry.bind('<FocusOut>', self._entry_focus_out) self._create_grid()
self._entry.bind('<FocusIn>', self._entry_focus_in)
self._activate_placeholder() self._activate_placeholder()
self._create_bindings()
self._draw() self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<FocusIn>":
self._entry.bind("<FocusIn>", self._entry_focus_in)
if sequence is None or sequence == "<FocusOut>":
self._entry.bind("<FocusOut>", self._entry_focus_out)
def _create_grid(self): def _create_grid(self):
self._canvas.grid(column=0, row=0, sticky="nswe") self._canvas.grid(column=0, row=0, sticky="nswe")
@ -163,6 +167,7 @@ class CTkEntry(CTkBaseClass):
outline=self._apply_appearance_mode(self._bg_color)) outline=self._apply_appearance_mode(self._bg_color))
self._entry.configure(bg=self._apply_appearance_mode(self._bg_color), self._entry.configure(bg=self._apply_appearance_mode(self._bg_color),
disabledbackground=self._apply_appearance_mode(self._bg_color), disabledbackground=self._apply_appearance_mode(self._bg_color),
readonlybackground=self._apply_appearance_mode(self._bg_color),
highlightcolor=self._apply_appearance_mode(self._bg_color)) highlightcolor=self._apply_appearance_mode(self._bg_color))
else: else:
self._canvas.itemconfig("inner_parts", self._canvas.itemconfig("inner_parts",
@ -170,6 +175,7 @@ class CTkEntry(CTkBaseClass):
outline=self._apply_appearance_mode(self._fg_color)) outline=self._apply_appearance_mode(self._fg_color))
self._entry.configure(bg=self._apply_appearance_mode(self._fg_color), self._entry.configure(bg=self._apply_appearance_mode(self._fg_color),
disabledbackground=self._apply_appearance_mode(self._fg_color), disabledbackground=self._apply_appearance_mode(self._fg_color),
readonlybackground=self._apply_appearance_mode(self._fg_color),
highlightcolor=self._apply_appearance_mode(self._fg_color)) highlightcolor=self._apply_appearance_mode(self._fg_color))
self._canvas.itemconfig("border_parts", self._canvas.itemconfig("border_parts",
@ -275,13 +281,19 @@ class CTkEntry(CTkBaseClass):
else: else:
return super().cget(attribute_name) # cget of CTkBaseClass return super().cget(attribute_name) # cget of CTkBaseClass
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence=None, command=None, add=True):
""" called on the tkinter.Entry """ """ called on the tkinter.Entry """
return self._entry.bind(sequence, command, add) if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._entry.bind(sequence, command, add=True)
def unbind(self, sequence, funcid=None): def unbind(self, sequence=None, funcid=None):
""" called on the tkinter.Entry """ """ called on the tkinter.Entry """
return self._entry.unbind(sequence, funcid) if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._entry.unbind(sequence, None) # unbind all callbacks for sequence
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def _activate_placeholder(self): def _activate_placeholder(self):
if self._entry.get() == "" and self._placeholder_text is not None and (self._textvariable is None or self._textvariable == ""): if self._entry.get() == "" and self._placeholder_text is not None and (self._textvariable is None or self._textvariable == ""):
@ -295,7 +307,7 @@ class CTkEntry(CTkBaseClass):
self._entry.insert(0, self._placeholder_text) self._entry.insert(0, self._placeholder_text)
def _deactivate_placeholder(self): def _deactivate_placeholder(self):
if self._placeholder_text_active: if self._placeholder_text_active and self._entry.cget("state") != "readonly":
self._placeholder_text_active = False self._placeholder_text_active = False
self._entry.config(fg=self._apply_appearance_mode(self._text_color), self._entry.config(fg=self._apply_appearance_mode(self._text_color),

View File

@ -182,10 +182,15 @@ class CTkFrame(CTkBaseClass):
else: else:
return super().cget(attribute_name) return super().cget(attribute_name)
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence=None, command=None, add=True):
""" called on the tkinter.Canvas """ """ called on the tkinter.Canvas """
return self._canvas.bind(sequence, command, add) if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True)
def unbind(self, sequence, funcid=None): def unbind(self, sequence=None, funcid=None):
""" called on the tkinter.Canvas """ """ called on the tkinter.Canvas """
return self._canvas.unbind(sequence, funcid) if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None)

View File

@ -32,7 +32,7 @@ class CTkLabel(CTkBaseClass):
text: str = "CTkLabel", text: str = "CTkLabel",
font: Optional[Union[tuple, CTkFont]] = None, font: Optional[Union[tuple, CTkFont]] = None,
image: Union[tkinter.PhotoImage, CTkImage, None] = None, image: Union[CTkImage, None] = None,
compound: str = "center", compound: str = "center",
anchor: str = "center", # label anchor: center, n, e, s, w anchor: str = "center", # label anchor: center, n, e, s, w
wraplength: int = 0, wraplength: int = 0,
@ -247,17 +247,20 @@ class CTkLabel(CTkBaseClass):
else: else:
return super().cget(attribute_name) # cget of CTkBaseClass return super().cget(attribute_name) # cget of CTkBaseClass
def bind(self, sequence: str = None, command: Callable = None, add: str = None) -> str: def bind(self, sequence: str = None, command: Callable = None, add: str = True):
""" called on the tkinter.Label and tkinter.Canvas """ """ called on the tkinter.Label and tkinter.Canvas """
canvas_bind_return = self._canvas.bind(sequence, command, add) if not (add == "+" or add is True):
label_bind_return = self._label.bind(sequence, command, add) raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
return canvas_bind_return + " + " + label_bind_return self._canvas.bind(sequence, command, add=True)
self._label.bind(sequence, command, add=True)
def unbind(self, sequence: str, funcid: str = None): def unbind(self, sequence: str = None, funcid: Optional[str] = None):
""" called on the tkinter.Label and tkinter.Canvas """ """ called on the tkinter.Label and tkinter.Canvas """
canvas_bind_return, label_bind_return = funcid.split(" + ") if funcid is not None:
self._canvas.unbind(sequence, canvas_bind_return) raise ValueError("'funcid' argument can only be None, because there is a bug in" +
self._label.unbind(sequence, label_bind_return) " tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None)
self._label.unbind(sequence, None)
def focus(self): def focus(self):
return self._label.focus() return self._label.focus()

View File

@ -1,4 +1,5 @@
import tkinter import tkinter
import copy
import sys import sys
from typing import Union, Tuple, Callable, Optional from typing import Union, Tuple, Callable, Optional
@ -107,10 +108,6 @@ class CTkOptionMenu(CTkBaseClass):
pady=0, pady=0,
borderwidth=1, borderwidth=1,
text=self._current_value) text=self._current_value)
self._create_grid()
if not self._dynamic_resizing:
self.grid_propagate(0)
if self._cursor_manipulation_enabled: if self._cursor_manipulation_enabled:
if sys.platform == "darwin": if sys.platform == "darwin":
@ -118,17 +115,11 @@ class CTkOptionMenu(CTkBaseClass):
elif sys.platform.startswith("win"): elif sys.platform.startswith("win"):
self.configure(cursor="hand2") self.configure(cursor="hand2")
# event bindings self._create_grid()
self._canvas.bind("<Enter>", self._on_enter) if not self._dynamic_resizing:
self._canvas.bind("<Leave>", self._on_leave) self.grid_propagate(0)
self._canvas.bind("<Button-1>", self._clicked)
self._canvas.bind("<Button-1>", self._clicked)
self._text_label.bind("<Enter>", self._on_enter)
self._text_label.bind("<Leave>", self._on_leave)
self._text_label.bind("<Button-1>", self._clicked)
self._text_label.bind("<Button-1>", self._clicked)
self._create_bindings()
self._draw() # initial draw self._draw() # initial draw
if self._variable is not None: if self._variable is not None:
@ -136,6 +127,18 @@ class CTkOptionMenu(CTkBaseClass):
self._current_value = self._variable.get() self._current_value = self._variable.get()
self._text_label.configure(text=self._current_value) self._text_label.configure(text=self._current_value)
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter)
self._text_label.bind("<Enter>", self._on_enter)
if sequence is None or sequence == "<Leave>":
self._canvas.bind("<Leave>", self._on_leave)
self._text_label.bind("<Leave>", self._on_leave)
if sequence is None or sequence == "<Button-1>":
self._canvas.bind("<Button-1>", self._clicked)
self._text_label.bind("<Button-1>", self._clicked)
def _create_grid(self): def _create_grid(self):
self._canvas.grid(row=0, column=0, sticky="nsew") self._canvas.grid(row=0, column=0, sticky="nsew")
@ -240,8 +243,8 @@ class CTkOptionMenu(CTkBaseClass):
self._text_color = self._check_color_type(kwargs.pop("text_color")) self._text_color = self._check_color_type(kwargs.pop("text_color"))
require_redraw = True require_redraw = True
if "dropdown_color" in kwargs: if "dropdown_fg_color" in kwargs:
self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_color")) self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_fg_color"))
if "dropdown_hover_color" in kwargs: if "dropdown_hover_color" in kwargs:
self._dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color")) self._dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
@ -326,7 +329,7 @@ class CTkOptionMenu(CTkBaseClass):
elif attribute_name == "dropdown_font": elif attribute_name == "dropdown_font":
return self._dropdown_menu.cget("font") return self._dropdown_menu.cget("font")
elif attribute_name == "values": elif attribute_name == "values":
return self._values return copy.copy(self._values)
elif attribute_name == "variable": elif attribute_name == "variable":
return self._variable return self._variable
elif attribute_name == "state": elif attribute_name == "state":
@ -393,17 +396,21 @@ class CTkOptionMenu(CTkBaseClass):
if self._state is not tkinter.DISABLED and len(self._values) > 0: if self._state is not tkinter.DISABLED and len(self._values) > 0:
self._open_dropdown_menu() self._open_dropdown_menu()
def bind(self, sequence: str = None, command: Callable = None, add: str = None) -> str: def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" called on the tkinter.Label and tkinter.Canvas """ """ called on the tkinter.Canvas """
canvas_bind_return = self._canvas.bind(sequence, command, add) if not (add == "+" or add is True):
label_bind_return = self._text_label.bind(sequence, command, add) raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
return canvas_bind_return + " + " + label_bind_return self._canvas.bind(sequence, command, add=True)
self._text_label.bind(sequence, command, add=True)
def unbind(self, sequence: str, funcid: str = None): def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Label and tkinter.Canvas """ """ called on the tkinter.Label and tkinter.Canvas """
canvas_bind_return, label_bind_return = funcid.split(" + ") if funcid is not None:
self._canvas.unbind(sequence, canvas_bind_return) raise ValueError("'funcid' argument can only be None, because there is a bug in" +
self._text_label.unbind(sequence, label_bind_return) " tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None)
self._text_label.unbind(sequence, None)
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self): def focus(self):
return self._text_label.focus() return self._text_label.focus()

View File

@ -1,6 +1,10 @@
import tkinter import tkinter
import math import math
from typing import Union, Tuple, Optional from typing import Union, Tuple, Optional, Callable
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
from .core_rendering import CTkCanvas from .core_rendering import CTkCanvas
from .theme import ThemeManager from .theme import ThemeManager
@ -29,7 +33,7 @@ class CTkProgressBar(CTkBaseClass):
variable: Union[tkinter.Variable, None] = None, variable: Union[tkinter.Variable, None] = None,
orientation: str = "horizontal", orientation: str = "horizontal",
mode: str = "determinate", mode: Literal["determinate", "indeterminate"] = "determinate",
determinate_speed: float = 1, determinate_speed: float = 1,
indeterminate_speed: float = 1, indeterminate_speed: float = 1,
**kwargs): **kwargs):
@ -58,6 +62,7 @@ class CTkProgressBar(CTkBaseClass):
self._variable = variable self._variable = variable
self._variable_callback_blocked = False self._variable_callback_blocked = False
self._variable_callback_name = None self._variable_callback_name = None
self._loop_after_id = None
# shape # shape
self._corner_radius = ThemeManager.theme["CTkProgressBar"]["corner_radius"] if corner_radius is None else corner_radius self._corner_radius = ThemeManager.theme["CTkProgressBar"]["corner_radius"] if corner_radius is None else corner_radius
@ -249,13 +254,15 @@ class CTkProgressBar(CTkBaseClass):
return self._determinate_value return self._determinate_value
def start(self): def start(self):
""" start indeterminate mode """ """ start automatic mode """
if not self._loop_running: if not self._loop_running:
self._loop_running = True self._loop_running = True
self._internal_loop() self._internal_loop()
def stop(self): def stop(self):
""" stop indeterminate mode """ """ stop automatic mode """
if self._loop_after_id is not None:
self.after_cancel(self._loop_after_id)
self._loop_running = False self._loop_running = False
def _internal_loop(self): def _internal_loop(self):
@ -265,13 +272,14 @@ class CTkProgressBar(CTkBaseClass):
if self._determinate_value > 1: if self._determinate_value > 1:
self._determinate_value -= 1 self._determinate_value -= 1
self._draw() self._draw()
self.after(20, self._internal_loop) self._loop_after_id = self.after(20, self._internal_loop)
else: else:
self._indeterminate_value += self._indeterminate_speed self._indeterminate_value += self._indeterminate_speed
self._draw() self._draw()
self.after(20, self._internal_loop) self._loop_after_id = self.after(20, self._internal_loop)
def step(self): def step(self):
""" increase progress """
if self._mode == "determinate": if self._mode == "determinate":
self._determinate_value += self._determinate_speed / 50 self._determinate_value += self._determinate_speed / 50
if self._determinate_value > 1: if self._determinate_value > 1:
@ -281,13 +289,18 @@ class CTkProgressBar(CTkBaseClass):
self._indeterminate_value += self._indeterminate_speed self._indeterminate_value += self._indeterminate_speed
self._draw() self._draw()
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" called on the tkinter.Canvas """ """ called on the tkinter.Canvas """
return self._canvas.bind(sequence, command, add) if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True)
def unbind(self, sequence, funcid=None): def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Canvas """ """ called on the tkinter.Label and tkinter.Canvas """
return self._canvas.unbind(sequence, funcid) if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None)
def focus(self): def focus(self):
return self._canvas.focus() return self._canvas.focus()

View File

@ -85,6 +85,7 @@ class CTkRadioButton(CTkBaseClass):
self.grid_columnconfigure(0, weight=0) self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6)) self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
self.grid_columnconfigure(2, weight=1) self.grid_columnconfigure(2, weight=1)
self.grid_rowconfigure(0, weight=1)
self._bg_canvas = CTkCanvas(master=self, self._bg_canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
@ -99,10 +100,6 @@ class CTkRadioButton(CTkBaseClass):
self._canvas.grid(row=0, column=0) self._canvas.grid(row=0, column=0)
self._draw_engine = DrawEngine(self._canvas) self._draw_engine = DrawEngine(self._canvas)
self._canvas.bind("<Enter>", self._on_enter)
self._canvas.bind("<Leave>", self._on_leave)
self._canvas.bind("<Button-1>", self.invoke)
self._text_label = tkinter.Label(master=self, self._text_label = tkinter.Label(master=self,
bd=0, bd=0,
padx=0, padx=0,
@ -114,16 +111,25 @@ class CTkRadioButton(CTkBaseClass):
self._text_label.grid(row=0, column=2, sticky="w") self._text_label.grid(row=0, column=2, sticky="w")
self._text_label["anchor"] = "w" self._text_label["anchor"] = "w"
self._text_label.bind("<Enter>", self._on_enter)
self._text_label.bind("<Leave>", self._on_leave)
self._text_label.bind("<Button-1>", self.invoke)
if self._variable is not None: if self._variable is not None:
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback) self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
self._check_state = True if self._variable.get() == self._value else False self._check_state = True if self._variable.get() == self._value else False
self._draw() # initial draw self._create_bindings()
self._set_cursor() self._set_cursor()
self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter)
self._text_label.bind("<Enter>", self._on_enter)
if sequence is None or sequence == "<Leave>":
self._canvas.bind("<Leave>", self._on_leave)
self._text_label.bind("<Leave>", self._on_leave)
if sequence is None or sequence == "<Button-1>":
self._canvas.bind("<Button-1>", self.invoke)
self._text_label.bind("<Button-1>", self.invoke)
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
@ -377,8 +383,8 @@ class CTkRadioButton(CTkBaseClass):
self._check_state = True self._check_state = True
self.select() self.select()
if self._command is not None: if self._command is not None:
self._command() self._command()
def select(self, from_variable_callback=False): def select(self, from_variable_callback=False):
self._check_state = True self._check_state = True
@ -398,13 +404,21 @@ class CTkRadioButton(CTkBaseClass):
self._variable.set("") self._variable.set("")
self._variable_callback_blocked = False self._variable_callback_blocked = False
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" called on the tkinter.Canvas """ """ called on the tkinter.Canvas """
return self._canvas.bind(sequence, command, add) if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True)
self._text_label.bind(sequence, command, add=True)
def unbind(self, sequence, funcid=None): def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Canvas """ """ called on the tkinter.Label and tkinter.Canvas """
return self._canvas.unbind(sequence, funcid) if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None)
self._text_label.unbind(sequence, None)
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self): def focus(self):
return self._text_label.focus() return self._text_label.focus()

View File

@ -49,8 +49,8 @@ class CTkScrollbar(CTkBaseClass):
# color # color
self._fg_color = ThemeManager.theme["CTkScrollbar"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True) self._fg_color = ThemeManager.theme["CTkScrollbar"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
self._button_color = ThemeManager.theme["CTkScrollbar"]["scrollbar_color"] if button_color is None else self._check_color_type(button_color) self._button_color = ThemeManager.theme["CTkScrollbar"]["button_color"] if button_color is None else self._check_color_type(button_color)
self._button_hover_color = ThemeManager.theme["CTkScrollbar"]["scrollbar_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color) self._button_hover_color = ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
# shape # shape
self._corner_radius = ThemeManager.theme["CTkScrollbar"]["corner_radius"] if corner_radius is None else corner_radius self._corner_radius = ThemeManager.theme["CTkScrollbar"]["corner_radius"] if corner_radius is None else corner_radius
@ -71,14 +71,22 @@ class CTkScrollbar(CTkBaseClass):
self._canvas.place(x=0, y=0, relwidth=1, relheight=1) self._canvas.place(x=0, y=0, relwidth=1, relheight=1)
self._draw_engine = DrawEngine(self._canvas) self._draw_engine = DrawEngine(self._canvas)
self._canvas.bind("<Enter>", self._on_enter) self._create_bindings()
self._canvas.bind("<Leave>", self._on_leave)
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)
self._draw() self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None:
self._canvas.tag_bind("border_parts", "<Button-1>", self._clicked)
if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter)
if sequence is None or sequence == "<Leave>":
self._canvas.bind("<Leave>", self._on_leave)
if sequence is None or sequence == "<B1-Motion>":
self._canvas.bind("<B1-Motion>", self._clicked)
if sequence is None or sequence == "<MouseWheel>":
self._canvas.bind("<MouseWheel>", self._mouse_scroll_event)
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
@ -249,13 +257,19 @@ class CTkScrollbar(CTkBaseClass):
def get(self): def get(self):
return self._start_value, self._end_value return self._start_value, self._end_value
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence=None, command=None, add=True):
""" called on the tkinter.Canvas """ """ called on the tkinter.Canvas """
return self._canvas.bind(sequence, command, add) if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True)
def unbind(self, sequence, funcid=None): def unbind(self, sequence=None, funcid=None):
""" called on the tkinter.Canvas """ """ called on the tkinter.Canvas, restores internal callbacks """
return self._canvas.unbind(sequence, funcid) if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None) # unbind all callbacks for sequence
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self): def focus(self):
return self._canvas.focus() return self._canvas.focus()

View File

@ -1,5 +1,10 @@
import tkinter import tkinter
from typing import Union, Tuple, List, Dict, Callable, Optional, Literal import copy
from typing import Union, Tuple, List, Dict, Callable, Optional
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
from .theme import ThemeManager from .theme import ThemeManager
from .font import CTkFont from .font import CTkFont
@ -317,7 +322,7 @@ class CTkSegmentedButton(CTkFrame):
elif attribute_name == "font": elif attribute_name == "font":
return self._font return self._font
elif attribute_name == "values": elif attribute_name == "values":
return self._value_list return copy.copy(self._value_list)
elif attribute_name == "variable": elif attribute_name == "variable":
return self._variable return self._variable
elif attribute_name == "dynamic_resizing": elif attribute_name == "dynamic_resizing":
@ -408,3 +413,9 @@ class CTkSegmentedButton(CTkFrame):
else: else:
raise ValueError(f"CTkSegmentedButton does not contain value '{value}'") raise ValueError(f"CTkSegmentedButton does not contain value '{value}'")
def bind(self, sequence=None, command=None, add=None):
raise NotImplementedError
def unbind(self, sequence=None, funcid=None):
raise NotImplementedError

View File

@ -96,11 +96,7 @@ class CTkSlider(CTkBaseClass):
self._canvas.grid(column=0, row=0, rowspan=1, columnspan=1, sticky="nswe") self._canvas.grid(column=0, row=0, rowspan=1, columnspan=1, sticky="nswe")
self._draw_engine = DrawEngine(self._canvas) self._draw_engine = DrawEngine(self._canvas)
self._canvas.bind("<Enter>", self._on_enter) self._create_bindings()
self._canvas.bind("<Leave>", self._on_leave)
self._canvas.bind("<Button-1>", self._clicked)
self._canvas.bind("<B1-Motion>", self._clicked)
self._set_cursor() self._set_cursor()
self._draw() # initial draw self._draw() # initial draw
@ -110,6 +106,17 @@ class CTkSlider(CTkBaseClass):
self.set(self._variable.get(), from_variable_callback=True) self.set(self._variable.get(), from_variable_callback=True)
self._variable_callback_blocked = False self._variable_callback_blocked = False
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter)
if sequence is None or sequence == "<Leave>":
self._canvas.bind("<Leave>", self._on_leave)
if sequence is None or sequence == "<Button-1>":
self._canvas.bind("<Button-1>", self._clicked)
if sequence is None or sequence == "<B1-Motion>":
self._canvas.bind("<B1-Motion>", self._clicked)
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
@ -366,13 +373,19 @@ class CTkSlider(CTkBaseClass):
if not self._variable_callback_blocked: if not self._variable_callback_blocked:
self.set(self._variable.get(), from_variable_callback=True) self.set(self._variable.get(), from_variable_callback=True)
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" called on the tkinter.Canvas """ """ called on the tkinter.Canvas """
return self._canvas.bind(sequence, command, add) if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True)
def unbind(self, sequence, funcid=None): def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Canvas """ """ called on the tkinter.Label and tkinter.Canvas """
return self._canvas.unbind(sequence, funcid) if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None)
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self): def focus(self):
return self._canvas.focus() return self._canvas.focus()

View File

@ -92,6 +92,7 @@ class CTkSwitch(CTkBaseClass):
self.grid_columnconfigure(0, weight=0) self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6)) self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
self.grid_columnconfigure(2, weight=1) self.grid_columnconfigure(2, weight=1)
self.grid_rowconfigure(0, weight=1)
self._bg_canvas = CTkCanvas(master=self, self._bg_canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
@ -106,10 +107,6 @@ class CTkSwitch(CTkBaseClass):
self._canvas.grid(row=0, column=0, sticky="") self._canvas.grid(row=0, column=0, sticky="")
self._draw_engine = DrawEngine(self._canvas) self._draw_engine = DrawEngine(self._canvas)
self._canvas.bind("<Enter>", self._on_enter)
self._canvas.bind("<Leave>", self._on_leave)
self._canvas.bind("<Button-1>", self.toggle)
self._text_label = tkinter.Label(master=self, self._text_label = tkinter.Label(master=self,
bd=0, bd=0,
padx=0, padx=0,
@ -121,16 +118,25 @@ class CTkSwitch(CTkBaseClass):
self._text_label.grid(row=0, column=2, sticky="w") self._text_label.grid(row=0, column=2, sticky="w")
self._text_label["anchor"] = "w" self._text_label["anchor"] = "w"
self._text_label.bind("<Enter>", self._on_enter)
self._text_label.bind("<Leave>", self._on_leave)
self._text_label.bind("<Button-1>", self.toggle)
if self._variable is not None and self._variable != "": if self._variable is not None and self._variable != "":
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback) self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
self.c_heck_state = True if self._variable.get() == self._onvalue else False self._check_state = True if self._variable.get() == self._onvalue else False
self._draw() # initial draw self._create_bindings()
self._set_cursor() self._set_cursor()
self._draw() # initial draw
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter)
self._text_label.bind("<Enter>", self._on_enter)
if sequence is None or sequence == "<Leave>":
self._canvas.bind("<Leave>", self._on_leave)
self._text_label.bind("<Leave>", self._on_leave)
if sequence is None or sequence == "<Button-1>":
self._canvas.bind("<Button-1>", self.toggle)
self._text_label.bind("<Button-1>", self.toggle)
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
@ -216,23 +222,29 @@ class CTkSwitch(CTkBaseClass):
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color)) self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
if self._border_color == "transparent": if self._border_color == "transparent":
self._canvas.itemconfig("border_parts", fill=self._apply_appearance_mode(self._bg_color), self._canvas.itemconfig("border_parts",
fill=self._apply_appearance_mode(self._bg_color),
outline=self._apply_appearance_mode(self._bg_color)) outline=self._apply_appearance_mode(self._bg_color))
else: else:
self._canvas.itemconfig("border_parts", fill=self._apply_appearance_mode(self._border_color), self._canvas.itemconfig("border_parts",
fill=self._apply_appearance_mode(self._border_color),
outline=self._apply_appearance_mode(self._border_color)) outline=self._apply_appearance_mode(self._border_color))
self._canvas.itemconfig("inner_parts", fill=self._apply_appearance_mode(self._fg_color), self._canvas.itemconfig("inner_parts",
fill=self._apply_appearance_mode(self._fg_color),
outline=self._apply_appearance_mode(self._fg_color)) outline=self._apply_appearance_mode(self._fg_color))
if self._progress_color == "transparent": if self._progress_color == "transparent":
self._canvas.itemconfig("progress_parts", fill=self._apply_appearance_mode(self._fg_color), self._canvas.itemconfig("progress_parts",
fill=self._apply_appearance_mode(self._fg_color),
outline=self._apply_appearance_mode(self._fg_color)) outline=self._apply_appearance_mode(self._fg_color))
else: else:
self._canvas.itemconfig("progress_parts", fill=self._apply_appearance_mode(self._progress_color), self._canvas.itemconfig("progress_parts",
fill=self._apply_appearance_mode(self._progress_color),
outline=self._apply_appearance_mode(self._progress_color)) outline=self._apply_appearance_mode(self._progress_color))
self._canvas.itemconfig("slider_parts", fill=self._apply_appearance_mode(self._button_color), self._canvas.itemconfig("slider_parts",
fill=self._apply_appearance_mode(self._button_color),
outline=self._apply_appearance_mode(self._button_color)) outline=self._apply_appearance_mode(self._button_color))
if self._state == tkinter.DISABLED: if self._state == tkinter.DISABLED:
@ -437,13 +449,21 @@ class CTkSwitch(CTkBaseClass):
elif self._variable.get() == self._offvalue: elif self._variable.get() == self._offvalue:
self.deselect(from_variable_callback=True) self.deselect(from_variable_callback=True)
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" called on the tkinter.Canvas """ """ called on the tkinter.Canvas """
return self._canvas.bind(sequence, command, add) if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._canvas.bind(sequence, command, add=True)
self._text_label.bind(sequence, command, add=True)
def unbind(self, sequence, funcid=None): def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Canvas """ """ called on the tkinter.Label and tkinter.Canvas """
return self._canvas.unbind(sequence, funcid) if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None)
self._text_label.unbind(sequence, None)
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self): def focus(self):
return self._text_label.focus() return self._text_label.focus()

View File

@ -1,5 +1,5 @@
import tkinter import tkinter
from typing import Union, Tuple, Optional from typing import Union, Tuple, Optional, Callable
from .core_rendering import CTkCanvas from .core_rendering import CTkCanvas
from .ctk_scrollbar import CTkScrollbar from .ctk_scrollbar import CTkScrollbar
@ -86,7 +86,6 @@ class CTkTextbox(CTkBaseClass):
highlightthickness=0, highlightthickness=0,
relief="flat", relief="flat",
insertbackground=self._apply_appearance_mode(self._text_color), insertbackground=self._apply_appearance_mode(self._text_color),
bg=self._apply_appearance_mode(self._fg_color),
**pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes)) **pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes))
check_kwargs_empty(kwargs, raise_error=True) check_kwargs_empty(kwargs, raise_error=True)
@ -120,7 +119,7 @@ class CTkTextbox(CTkBaseClass):
self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True) 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) self.after(50, self._check_if_scrollbars_needed, None, True)
self._draw() self._draw()
def _create_grid_for_text_and_scrollbars(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False): def _create_grid_for_text_and_scrollbars(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False):
@ -152,7 +151,7 @@ class CTkTextbox(CTkBaseClass):
else: else:
self._y_scrollbar.grid_forget() self._y_scrollbar.grid_forget()
def _check_if_scrollbars_needed(self, event=None, continue_loop: bool = True): def _check_if_scrollbars_needed(self, event=None, continue_loop: bool = False):
""" Method hides or places the scrollbars if they are needed on key release event of tkinter.text widget """ """ Method hides or places the scrollbars if they are needed on key release event of tkinter.text widget """
if self._scrollbars_activated: if self._scrollbars_activated:
@ -227,10 +226,10 @@ class CTkTextbox(CTkBaseClass):
self._textbox.configure(fg=self._apply_appearance_mode(self._text_color), self._textbox.configure(fg=self._apply_appearance_mode(self._text_color),
bg=self._apply_appearance_mode(self._bg_color), bg=self._apply_appearance_mode(self._bg_color),
insertbackground=self._apply_appearance_mode(self._text_color)) insertbackground=self._apply_appearance_mode(self._text_color))
self._x_scrollbar.configure(fg_color=self._bg_color, scrollbar_color=self._scrollbar_button_color, self._x_scrollbar.configure(fg_color=self._bg_color, button_color=self._scrollbar_button_color,
scrollbar_hover_color=self._scrollbar_button_hover_color) button_hover_color=self._scrollbar_button_hover_color)
self._y_scrollbar.configure(fg_color=self._bg_color, scrollbar_color=self._scrollbar_button_color, self._y_scrollbar.configure(fg_color=self._bg_color, button_color=self._scrollbar_button_color,
scrollbar_hover_color=self._scrollbar_button_hover_color) button_hover_color=self._scrollbar_button_hover_color)
else: else:
self._canvas.itemconfig("inner_parts", self._canvas.itemconfig("inner_parts",
fill=self._apply_appearance_mode(self._fg_color), fill=self._apply_appearance_mode(self._fg_color),
@ -327,18 +326,18 @@ class CTkTextbox(CTkBaseClass):
else: else:
return super().cget(attribute_name) return super().cget(attribute_name)
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" called on the tkinter.Text """ """ called on the tkinter.Canvas """
if not (add == "+" or add is True):
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
self._textbox.bind(sequence, command, add=True)
# if sequence is <KeyRelease>, allow only to add the binding to keep the _textbox_modified_event() being called def unbind(self, sequence: str = None, funcid: str = None):
if sequence == "<KeyRelease>": """ called on the tkinter.Label and tkinter.Canvas """
return self._textbox.bind(sequence, command, add="+") if funcid is not None:
else: raise ValueError("'funcid' argument can only be None, because there is a bug in" +
return self._textbox.bind(sequence, command, add) " tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._textbox.unbind(sequence, None)
def unbind(self, sequence, funcid=None):
""" called on the tkinter.Text """
return self._textbox.unbind(sequence, funcid)
def focus(self): def focus(self):
return self._textbox.focus() return self._textbox.focus()
@ -350,7 +349,6 @@ class CTkTextbox(CTkBaseClass):
return self._textbox.focus_force() return self._textbox.focus_force()
def insert(self, index, text, tags=None): def insert(self, index, text, tags=None):
self._check_if_scrollbars_needed()
return self._textbox.insert(index, text, tags) return self._textbox.insert(index, text, tags)
def get(self, index1, index2=None): def get(self, index1, index2=None):

View File

@ -1,6 +1,10 @@
from tkinter.font import Font from tkinter.font import Font
import copy import copy
from typing import List, Callable, Tuple, Optional, Literal from typing import List, Callable, Tuple, Optional
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
from ..theme import ThemeManager from ..theme import ThemeManager
@ -51,7 +55,6 @@ class CTkFont(Font):
self._size_configure_callback_list.remove(callback) self._size_configure_callback_list.remove(callback)
def create_scaled_tuple(self, font_scaling: float) -> Tuple[str, int, str]: def create_scaled_tuple(self, font_scaling: float) -> Tuple[str, int, str]:
""" return scaled tuple representation of font in the form (family: str, size: int, style: str)""" """ return scaled tuple representation of font in the form (family: str, size: int, style: str)"""
return self._family, round(-abs(self._size) * font_scaling), self._tuple_style_string return self._family, round(-abs(self._size) * font_scaling), self._tuple_style_string

View File

@ -19,8 +19,8 @@ class CTkImage:
_checked_PIL_import = False _checked_PIL_import = False
def __init__(self, def __init__(self,
light_image: Image.Image = None, light_image: "Image.Image" = None,
dark_image: Image.Image = None, dark_image: "Image.Image" = None,
size: Tuple[int, int] = (20, 20)): size: Tuple[int, int] = (20, 20)):
if not self._checked_PIL_import: if not self._checked_PIL_import:
@ -92,21 +92,21 @@ class CTkImage:
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[1] * 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:
return self._scaled_light_photo_images[scaled_size] return self._scaled_light_photo_images[scaled_size]
else: else:
self._scaled_light_photo_images[scaled_size] = ImageTk.PhotoImage(self._light_image.resize(scaled_size)) self._scaled_light_photo_images[scaled_size] = ImageTk.PhotoImage(self._light_image.resize(scaled_size))
return self._scaled_light_photo_images[scaled_size] return self._scaled_light_photo_images[scaled_size]
def _get_scaled_dark_photo_image(self, scaled_size: Tuple[int, int]) -> ImageTk.PhotoImage: def _get_scaled_dark_photo_image(self, scaled_size: Tuple[int, int]) -> "ImageTk.PhotoImage":
if scaled_size in self._scaled_dark_photo_images: if scaled_size in self._scaled_dark_photo_images:
return self._scaled_dark_photo_images[scaled_size] return self._scaled_dark_photo_images[scaled_size]
else: else:
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: str) -> 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)
if appearance_mode == "light" and self._light_image is not None: if appearance_mode == "light" and self._light_image is not None:

View File

@ -82,8 +82,8 @@ class CTkScalingBaseClass:
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 3 <= len(font) <= 6:
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")

View File

@ -1,4 +1,3 @@
import tkinter
import customtkinter import customtkinter
customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light" customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light"
@ -8,7 +7,6 @@ app = customtkinter.CTk()
app.geometry("400x780") app.geometry("400x780")
app.title("CustomTkinter simple_example.py") app.title("CustomTkinter simple_example.py")
def button_callback(): def button_callback():
print("Button click", combobox_1.get()) print("Button click", combobox_1.get())
@ -20,7 +18,7 @@ def slider_callback(value):
frame_1 = customtkinter.CTkFrame(master=app) frame_1 = customtkinter.CTkFrame(master=app)
frame_1.pack(pady=20, padx=60, fill="both", expand=True) frame_1.pack(pady=20, padx=60, fill="both", expand=True)
label_1 = customtkinter.CTkLabel(master=frame_1, justify=tkinter.LEFT) label_1 = customtkinter.CTkLabel(master=frame_1, justify=customtkinter.LEFT)
label_1.pack(pady=10, padx=10) label_1.pack(pady=10, padx=10)
progressbar_1 = customtkinter.CTkProgressBar(master=frame_1) progressbar_1 = customtkinter.CTkProgressBar(master=frame_1)
@ -42,12 +40,12 @@ optionmenu_1.set("CTkOptionMenu")
combobox_1 = customtkinter.CTkComboBox(frame_1, values=["Option 1", "Option 2", "Option 42 long long long..."]) combobox_1 = customtkinter.CTkComboBox(frame_1, values=["Option 1", "Option 2", "Option 42 long long long..."])
combobox_1.pack(pady=10, padx=10) combobox_1.pack(pady=10, padx=10)
optionmenu_1.set("CTkComboBox") combobox_1.set("CTkComboBox")
checkbox_1 = customtkinter.CTkCheckBox(master=frame_1) checkbox_1 = customtkinter.CTkCheckBox(master=frame_1)
checkbox_1.pack(pady=10, padx=10) checkbox_1.pack(pady=10, padx=10)
radiobutton_var = tkinter.IntVar(value=1) radiobutton_var = customtkinter.IntVar(value=1)
radiobutton_1 = customtkinter.CTkRadioButton(master=frame_1, variable=radiobutton_var, value=1) radiobutton_1 = customtkinter.CTkRadioButton(master=frame_1, variable=radiobutton_var, value=1)
radiobutton_1.pack(pady=10, padx=10) radiobutton_1.pack(pady=10, padx=10)

View File

@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
github_url = "https://github.com/TomSchimansky/CustomTkinter" github_url = "https://github.com/TomSchimansky/CustomTkinter"
[tool.tbump.version] [tool.tbump.version]
current = "5.0.0" current = "5.0.5"
# Example of a semver regexp. # Example of a semver regexp.
# Make sure this matches current_version before # Make sure this matches current_version before

View File

@ -1,8 +1,8 @@
[metadata] [metadata]
name = customtkinter name = customtkinter
version = 5.0.0 version = 5.0.5
description = Create modern looking GUIs with Python description = Create modern looking GUIs with Python
long_description = file: Readme.md long_description = A modern and customizable python UI-library based on Tkinter: https://github.com/TomSchimansky/CustomTkinter
long_description_content_type = text/markdown long_description_content_type = text/markdown
url = https://github.com/TomSchimansky/CustomTkinter url = https://github.com/TomSchimansky/CustomTkinter
author = Tom Schimansky author = Tom Schimansky
@ -17,7 +17,6 @@ classifiers =
python_requires = >=3.7 python_requires = >=3.7
packages = packages =
customtkinter customtkinter
customtkinter.utility
customtkinter.windows customtkinter.windows
customtkinter.windows.widgets customtkinter.windows.widgets
customtkinter.windows.widgets.appearance_mode customtkinter.windows.widgets.appearance_mode
@ -27,6 +26,7 @@ packages =
customtkinter.windows.widgets.image customtkinter.windows.widgets.image
customtkinter.windows.widgets.scaling customtkinter.windows.widgets.scaling
customtkinter.windows.widgets.theme customtkinter.windows.widgets.theme
customtkinter.windows.widgets.utility
install_requires = install_requires =
darkdetect darkdetect
typing_extensions; python_version<="3.7" typing_extensions; python_version<="3.7"

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB