35 Commits

Author SHA1 Message Date
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
41 changed files with 411 additions and 216 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).
ToDo:
- change font attribute in wiki
- add new button attributes to wiki
- create grayscale theme file
- cursor configuring
- 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

View File

@ -1,4 +1,5 @@
include customtkinter/assets/*
include customtkinter/assets/fonts/*
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)
![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)
![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>

View File

@ -1,4 +1,4 @@
__version__ = "5.0.0"
__version__ = "5.0.4"
import os
import sys
@ -13,6 +13,10 @@ from .windows.widgets.scaling import ScalingTracker
from .windows.widgets.theme import ThemeManager
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
from .windows.widgets import CTkButton
from .windows.widgets import CTkCheckBox
@ -74,4 +78,4 @@ def set_window_scaling(scaling_value: float):
def deactivate_automatic_dpi_awareness():
""" 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

@ -40,6 +40,14 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
CTkScalingBaseClass.__init__(self, scaling_type="window")
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 = 600 # initial window size, independent of scaling
self._current_height = 500
self._min_width: int = 0

View File

@ -38,6 +38,14 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
CTkScalingBaseClass.__init__(self, scaling_type="window")
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_height = 200
self._min_width: int = 0

View File

@ -1,4 +1,5 @@
import sys
import warnings
import tkinter
import tkinter.ttk as ttk
from typing import Union, Callable, Tuple
@ -158,15 +159,15 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
return font
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"]
elif type(font) == tuple and 2 <= len(font) <= 3:
elif type(font) == tuple and 2 <= len(font) <= 6:
return font
else:
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"font=customtkinter.CTkFont(family='<name>', size=<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):
return image
else:
sys.stderr.write(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")
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")
return image
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),
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):
"""
Place a widget in the parent widget. Use as options:

View File

@ -40,7 +40,7 @@ class CTkButton(CTkBaseClass):
text: str = "CTkButton",
font: Optional[Union[tuple, CTkFont]] = None,
textvariable: Union[tkinter.Variable, None] = None,
image: Union[tkinter.PhotoImage, CTkImage, None] = None,
image: Union[CTkImage, None] = None,
state: str = "normal",
hover: bool = True,
command: Union[Callable[[], None], None] = None,
@ -100,16 +100,38 @@ class CTkButton(CTkBaseClass):
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
# 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
self._create_bindings()
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)
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):
super()._set_scaling(*args, **kwargs)
@ -391,7 +413,7 @@ class CTkButton(CTkBaseClass):
self._image = self._check_image_type(kwargs.pop("image"))
if isinstance(self._image, CTkImage):
self._image.add_configure_callback(self._update_image)
require_redraw = True
self._update_image()
if "state" in kwargs:
self._state = kwargs.pop("state")
@ -532,17 +554,30 @@ class CTkButton(CTkBaseClass):
if self._command is not None:
return self._command()
def bind(self, sequence: str = None, command: Callable = None, add: str = None) -> str:
""" called on the tkinter.Label and tkinter.Canvas """
canvas_bind_return = self._canvas.bind(sequence, command, add)
label_bind_return = self._text_label.bind(sequence, command, add)
return canvas_bind_return + " + " + label_bind_return
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" 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._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 """
canvas_bind_return, label_bind_return = funcid.split(" + ")
self._canvas.unbind(sequence, canvas_bind_return)
self._text_label.unbind(sequence, label_bind_return)
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)
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):
return self._text_label.focus()

View File

@ -103,10 +103,6 @@ class CTkCheckBox(CTkBaseClass):
self._canvas.grid(row=0, column=0, sticky="e")
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,
bd=0,
padx=0,
@ -118,17 +114,26 @@ class CTkCheckBox(CTkBaseClass):
self._text_label.grid(row=0, column=2, sticky="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
if self._variable is not None and self._variable != "":
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._draw() # initial draw
self._create_bindings()
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):
super()._set_scaling(*args, **kwargs)
@ -430,13 +435,21 @@ class CTkCheckBox(CTkBaseClass):
def get(self) -> Union[int, str]:
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 """
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):
""" called on the tkinter.Canvas """
return self._canvas.unbind(sequence, funcid)
def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Label and tkinter.Canvas """
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):
return self._text_label.focus()

View File

@ -1,5 +1,6 @@
import tkinter
import sys
import copy
from typing import Union, Tuple, Callable, List, Optional
from .core_widget_classes import DropdownMenu
@ -102,26 +103,29 @@ class CTkComboBox(CTkBaseClass):
font=self._apply_font_scaling(self._font))
self._create_grid()
# insert default value
if len(self._values) > 0:
self._entry.insert(0, self._values[0])
else:
self._entry.insert(0, "CTkComboBox")
self._create_bindings()
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:
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):
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),
fg=self._apply_appearance_mode(self._text_color),
readonlybackground=self._apply_appearance_mode(self._fg_color),
disabledbackground=self._apply_appearance_mode(self._fg_color),
disabledforeground=self._apply_appearance_mode(self._text_color_disabled),
highlightcolor=self._apply_appearance_mode(self._fg_color),
@ -322,7 +327,7 @@ class CTkComboBox(CTkBaseClass):
elif attribute_name == "dropdown_font":
return self._dropdown_menu.cget("font")
elif attribute_name == "values":
return self._values
return copy.copy(self._values)
elif attribute_name == "state":
return self._state
elif attribute_name == "hover":
@ -391,17 +396,23 @@ class CTkComboBox(CTkBaseClass):
def get(self) -> str:
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:
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 """
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 """
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):
return self._entry.focus()

View File

@ -90,16 +90,20 @@ class CTkEntry(CTkBaseClass):
textvariable=self._textvariable,
**pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes))
self._create_grid()
check_kwargs_empty(kwargs, raise_error=True)
self._entry.bind('<FocusOut>', self._entry_focus_out)
self._entry.bind('<FocusIn>', self._entry_focus_in)
self._create_grid()
self._activate_placeholder()
self._create_bindings()
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):
self._canvas.grid(column=0, row=0, sticky="nswe")
@ -163,6 +167,7 @@ class CTkEntry(CTkBaseClass):
outline=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),
readonlybackground=self._apply_appearance_mode(self._bg_color),
highlightcolor=self._apply_appearance_mode(self._bg_color))
else:
self._canvas.itemconfig("inner_parts",
@ -170,6 +175,7 @@ class CTkEntry(CTkBaseClass):
outline=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),
readonlybackground=self._apply_appearance_mode(self._fg_color),
highlightcolor=self._apply_appearance_mode(self._fg_color))
self._canvas.itemconfig("border_parts",
@ -275,13 +281,19 @@ class CTkEntry(CTkBaseClass):
else:
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 """
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 """
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):
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)
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._entry.config(fg=self._apply_appearance_mode(self._text_color),

View File

@ -182,10 +182,15 @@ class CTkFrame(CTkBaseClass):
else:
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 """
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 """
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",
font: Optional[Union[tuple, CTkFont]] = None,
image: Union[tkinter.PhotoImage, CTkImage, None] = None,
image: Union[CTkImage, None] = None,
compound: str = "center",
anchor: str = "center", # label anchor: center, n, e, s, w
wraplength: int = 0,
@ -247,17 +247,20 @@ class CTkLabel(CTkBaseClass):
else:
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 """
canvas_bind_return = self._canvas.bind(sequence, command, add)
label_bind_return = self._label.bind(sequence, command, add)
return canvas_bind_return + " + " + label_bind_return
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._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 """
canvas_bind_return, label_bind_return = funcid.split(" + ")
self._canvas.unbind(sequence, canvas_bind_return)
self._label.unbind(sequence, label_bind_return)
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._label.unbind(sequence, None)
def focus(self):
return self._label.focus()

View File

@ -1,4 +1,5 @@
import tkinter
import copy
import sys
from typing import Union, Tuple, Callable, Optional
@ -107,10 +108,6 @@ class CTkOptionMenu(CTkBaseClass):
pady=0,
borderwidth=1,
text=self._current_value)
self._create_grid()
if not self._dynamic_resizing:
self.grid_propagate(0)
if self._cursor_manipulation_enabled:
if sys.platform == "darwin":
@ -118,17 +115,11 @@ class CTkOptionMenu(CTkBaseClass):
elif sys.platform.startswith("win"):
self.configure(cursor="hand2")
# 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)
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_grid()
if not self._dynamic_resizing:
self.grid_propagate(0)
self._create_bindings()
self._draw() # initial draw
if self._variable is not None:
@ -136,6 +127,18 @@ class CTkOptionMenu(CTkBaseClass):
self._current_value = self._variable.get()
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):
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"))
require_redraw = True
if "dropdown_color" in kwargs:
self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_color"))
if "dropdown_fg_color" in kwargs:
self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_fg_color"))
if "dropdown_hover_color" in kwargs:
self._dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
@ -326,7 +329,7 @@ class CTkOptionMenu(CTkBaseClass):
elif attribute_name == "dropdown_font":
return self._dropdown_menu.cget("font")
elif attribute_name == "values":
return self._values
return copy.copy(self._values)
elif attribute_name == "variable":
return self._variable
elif attribute_name == "state":
@ -393,17 +396,21 @@ class CTkOptionMenu(CTkBaseClass):
if self._state is not tkinter.DISABLED and len(self._values) > 0:
self._open_dropdown_menu()
def bind(self, sequence: str = None, command: Callable = None, add: str = None) -> str:
""" called on the tkinter.Label and tkinter.Canvas """
canvas_bind_return = self._canvas.bind(sequence, command, add)
label_bind_return = self._text_label.bind(sequence, command, add)
return canvas_bind_return + " + " + label_bind_return
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" 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._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 """
canvas_bind_return, label_bind_return = funcid.split(" + ")
self._canvas.unbind(sequence, canvas_bind_return)
self._text_label.unbind(sequence, label_bind_return)
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):
return self._text_label.focus()

View File

@ -1,6 +1,10 @@
import tkinter
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 .theme import ThemeManager
@ -29,7 +33,7 @@ class CTkProgressBar(CTkBaseClass):
variable: Union[tkinter.Variable, None] = None,
orientation: str = "horizontal",
mode: str = "determinate",
mode: Literal["determinate", "indeterminate"] = "determinate",
determinate_speed: float = 1,
indeterminate_speed: float = 1,
**kwargs):
@ -58,6 +62,7 @@ class CTkProgressBar(CTkBaseClass):
self._variable = variable
self._variable_callback_blocked = False
self._variable_callback_name = None
self._loop_after_id = None
# shape
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
def start(self):
""" start indeterminate mode """
""" start automatic mode """
if not self._loop_running:
self._loop_running = True
self._internal_loop()
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
def _internal_loop(self):
@ -265,13 +272,14 @@ class CTkProgressBar(CTkBaseClass):
if self._determinate_value > 1:
self._determinate_value -= 1
self._draw()
self.after(20, self._internal_loop)
self._loop_after_id = self.after(20, self._internal_loop)
else:
self._indeterminate_value += self._indeterminate_speed
self._draw()
self.after(20, self._internal_loop)
self._loop_after_id = self.after(20, self._internal_loop)
def step(self):
""" increase progress """
if self._mode == "determinate":
self._determinate_value += self._determinate_speed / 50
if self._determinate_value > 1:
@ -281,13 +289,18 @@ class CTkProgressBar(CTkBaseClass):
self._indeterminate_value += self._indeterminate_speed
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 """
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):
""" called on the tkinter.Canvas """
return self._canvas.unbind(sequence, funcid)
def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Label and tkinter.Canvas """
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):
return self._canvas.focus()

View File

@ -99,10 +99,6 @@ class CTkRadioButton(CTkBaseClass):
self._canvas.grid(row=0, column=0)
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,
bd=0,
padx=0,
@ -114,16 +110,25 @@ class CTkRadioButton(CTkBaseClass):
self._text_label.grid(row=0, column=2, sticky="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:
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._draw() # initial draw
self._create_bindings()
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):
super()._set_scaling(*args, **kwargs)
@ -377,8 +382,8 @@ class CTkRadioButton(CTkBaseClass):
self._check_state = True
self.select()
if self._command is not None:
self._command()
if self._command is not None:
self._command()
def select(self, from_variable_callback=False):
self._check_state = True
@ -398,13 +403,21 @@ class CTkRadioButton(CTkBaseClass):
self._variable.set("")
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 """
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):
""" called on the tkinter.Canvas """
return self._canvas.unbind(sequence, funcid)
def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Label and tkinter.Canvas """
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):
return self._text_label.focus()

View File

@ -49,8 +49,8 @@ class CTkScrollbar(CTkBaseClass):
# color
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_hover_color = ThemeManager.theme["CTkScrollbar"]["scrollbar_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_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"]["button_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
# shape
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._draw_engine = DrawEngine(self._canvas)
self._canvas.bind("<Enter>", self._on_enter)
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._create_bindings()
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):
super()._set_scaling(*args, **kwargs)
@ -249,13 +257,19 @@ class CTkScrollbar(CTkBaseClass):
def get(self):
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 """
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):
""" called on the tkinter.Canvas """
return self._canvas.unbind(sequence, funcid)
def unbind(self, sequence=None, funcid=None):
""" called on the tkinter.Canvas, restores internal callbacks """
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):
return self._canvas.focus()

View File

@ -1,5 +1,10 @@
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 .font import CTkFont
@ -317,7 +322,7 @@ class CTkSegmentedButton(CTkFrame):
elif attribute_name == "font":
return self._font
elif attribute_name == "values":
return self._value_list
return copy.copy(self._value_list)
elif attribute_name == "variable":
return self._variable
elif attribute_name == "dynamic_resizing":
@ -408,3 +413,9 @@ class CTkSegmentedButton(CTkFrame):
else:
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._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._clicked)
self._canvas.bind("<B1-Motion>", self._clicked)
self._create_bindings()
self._set_cursor()
self._draw() # initial draw
@ -110,6 +106,17 @@ class CTkSlider(CTkBaseClass):
self.set(self._variable.get(), from_variable_callback=True)
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):
super()._set_scaling(*args, **kwargs)
@ -366,13 +373,19 @@ class CTkSlider(CTkBaseClass):
if not self._variable_callback_blocked:
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 """
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):
""" called on the tkinter.Canvas """
return self._canvas.unbind(sequence, funcid)
def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Label and tkinter.Canvas """
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):
return self._canvas.focus()

View File

@ -106,10 +106,6 @@ class CTkSwitch(CTkBaseClass):
self._canvas.grid(row=0, column=0, sticky="")
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,
bd=0,
padx=0,
@ -121,16 +117,25 @@ class CTkSwitch(CTkBaseClass):
self._text_label.grid(row=0, column=2, sticky="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 != "":
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._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):
super()._set_scaling(*args, **kwargs)
@ -437,13 +442,21 @@ class CTkSwitch(CTkBaseClass):
elif self._variable.get() == self._offvalue:
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 """
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):
""" called on the tkinter.Canvas """
return self._canvas.unbind(sequence, funcid)
def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Label and tkinter.Canvas """
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):
return self._text_label.focus()

View File

@ -1,5 +1,5 @@
import tkinter
from typing import Union, Tuple, Optional
from typing import Union, Tuple, Optional, Callable
from .core_rendering import CTkCanvas
from .ctk_scrollbar import CTkScrollbar
@ -86,7 +86,6 @@ class CTkTextbox(CTkBaseClass):
highlightthickness=0,
relief="flat",
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))
check_kwargs_empty(kwargs, raise_error=True)
@ -227,10 +226,10 @@ class CTkTextbox(CTkBaseClass):
self._textbox.configure(fg=self._apply_appearance_mode(self._text_color),
bg=self._apply_appearance_mode(self._bg_color),
insertbackground=self._apply_appearance_mode(self._text_color))
self._x_scrollbar.configure(fg_color=self._bg_color, scrollbar_color=self._scrollbar_button_color,
scrollbar_hover_color=self._scrollbar_button_hover_color)
self._y_scrollbar.configure(fg_color=self._bg_color, scrollbar_color=self._scrollbar_button_color,
scrollbar_hover_color=self._scrollbar_button_hover_color)
self._x_scrollbar.configure(fg_color=self._bg_color, button_color=self._scrollbar_button_color,
button_hover_color=self._scrollbar_button_hover_color)
self._y_scrollbar.configure(fg_color=self._bg_color, button_color=self._scrollbar_button_color,
button_hover_color=self._scrollbar_button_hover_color)
else:
self._canvas.itemconfig("inner_parts",
fill=self._apply_appearance_mode(self._fg_color),
@ -327,18 +326,18 @@ class CTkTextbox(CTkBaseClass):
else:
return super().cget(attribute_name)
def bind(self, sequence=None, command=None, add=None):
""" called on the tkinter.Text """
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
""" 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
if sequence == "<KeyRelease>":
return self._textbox.bind(sequence, command, add="+")
else:
return self._textbox.bind(sequence, command, add)
def unbind(self, sequence, funcid=None):
""" called on the tkinter.Text """
return self._textbox.unbind(sequence, funcid)
def unbind(self, sequence: str = None, funcid: str = None):
""" called on the tkinter.Label and tkinter.Canvas """
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._textbox.unbind(sequence, None)
def focus(self):
return self._textbox.focus()

View File

@ -1,6 +1,10 @@
from tkinter.font import Font
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
@ -51,7 +55,6 @@ class CTkFont(Font):
self._size_configure_callback_list.remove(callback)
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 self._family, round(-abs(self._size) * font_scaling), self._tuple_style_string

View File

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

View File

@ -82,8 +82,8 @@ class CTkScalingBaseClass:
return font
elif len(font) == 2:
return font[0], -abs(round(font[1] * self.__widget_scaling))
elif len(font) == 3:
return font[0], -abs(round(font[1] * self.__widget_scaling)), font[2]
elif 3 <= len(font) <= 6:
return font[0], -abs(round(font[1] * self.__widget_scaling)), font[2:]
else:
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
customtkinter.set_appearance_mode("dark") # Modes: "System" (standard), "Dark", "Light"
@ -8,7 +7,6 @@ app = customtkinter.CTk()
app.geometry("400x780")
app.title("CustomTkinter simple_example.py")
def button_callback():
print("Button click", combobox_1.get())
@ -20,7 +18,7 @@ def slider_callback(value):
frame_1 = customtkinter.CTkFrame(master=app)
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)
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.pack(pady=10, padx=10)
optionmenu_1.set("CTkComboBox")
combobox_1.set("CTkComboBox")
checkbox_1 = customtkinter.CTkCheckBox(master=frame_1)
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.pack(pady=10, padx=10)

View File

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

View File

@ -1,8 +1,8 @@
[metadata]
name = customtkinter
version = 5.0.0
version = 5.0.4
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
url = https://github.com/TomSchimansky/CustomTkinter
author = Tom Schimansky
@ -17,7 +17,6 @@ classifiers =
python_requires = >=3.7
packages =
customtkinter
customtkinter.utility
customtkinter.windows
customtkinter.windows.widgets
customtkinter.windows.widgets.appearance_mode
@ -27,6 +26,7 @@ packages =
customtkinter.windows.widgets.image
customtkinter.windows.widgets.scaling
customtkinter.windows.widgets.theme
customtkinter.windows.widgets.utility
install_requires =
darkdetect
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