29 Commits

Author SHA1 Message Date
72157e8d26 Bump to 5.1.3 2023-05-05 18:55:53 +02:00
ccf9fdfc9a add new website links 2023-05-05 18:54:32 +02:00
d3883012b7 change tabview select method #1508 2023-04-29 01:38:55 +02:00
86ba99c2ab fixed scrollbar colors for scrollable frame #1509 2023-04-26 10:53:08 +02:00
b60859dfd6 added text color configuration for switch #1497 2023-04-24 13:45:16 +02:00
121f5713a8 fixed platform independant path for theme loading #1498 2023-04-24 13:37:17 +02:00
2ee496ed28 fixed fg_color key for CTkSwitch #1482 2023-04-23 11:36:50 +02:00
838bc7885b update Readme.md 2023-04-22 17:33:22 +02:00
d23d99deb0 update Readme.md 2023-04-22 17:33:04 +02:00
fe5ace8ab7 update Readme.md 2023-04-22 16:54:24 +02:00
ffbf851a92 update Readme.md 2023-04-22 16:41:18 +02:00
220bfea1a6 added 'master' o valid CTkToplevel arguments #1468 2023-04-21 01:47:19 +02:00
09e584634c Bump to 5.1.2 2023-02-06 20:07:47 +01:00
bc5d527d68 added scrollable frame to other theme files #1193 2023-02-06 20:07:29 +01:00
6e9258a444 Bump to 5.1.1 2023-02-06 12:55:59 +01:00
f47cf024b2 fixed CTkScrollableFrame and example 2023-02-06 12:55:46 +01:00
59df37e920 Bump to 5.1.0 2023-02-05 22:58:13 +01:00
b177f85328 scrollable frame fix 2023-02-05 22:39:07 +01:00
786a5148de finished scrollable frame, added example and test for scrollable frame 2023-02-05 21:41:23 +01:00
2359a6ce39 progress on scrollable frame 2023-02-05 03:02:21 +01:00
110e9bbcbf Merge remote-tracking branch 'origin/master' 2023-02-04 16:54:12 +01:00
a478334fb7 start working on scrollable frame 2023-02-04 16:53:48 +01:00
4d52febd99 Change license to MIT 2023-02-04 14:52:17 +01:00
9e2584c958 override iconbitmap #1106 2023-01-24 12:45:29 +01:00
9fcd963fd2 prevent width and height args in place method #1094 2023-01-22 22:00:18 +01:00
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
28 changed files with 700 additions and 215 deletions

134
LICENSE
View File

@ -1,121 +1,21 @@
Creative Commons Legal Code
MIT License
CC0 1.0 Universal
Copyright (c) 2023 Tom Schimansky
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Statement of Purpose
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -17,6 +17,12 @@
---
<div align="center">
<h3>
Official website: https://customtkinter.tomschimansky.com
</h3>
</div>
CustomTkinter is a python UI-library based on Tkinter, which provides new, modern and
fully customizable widgets. They are created and used like normal Tkinter widgets and
can also be used in combination with normal Tkinter elements. The widgets
@ -43,9 +49,9 @@ pip3 install customtkinter
## Documentation
The **official** documentation can be found in the Wiki Tab here:
The **official** documentation can be found here:
**--> [Documentation](https://github.com/TomSchimansky/CustomTkinter/wiki)**.
**➡️ https://customtkinter.tomschimansky.com/documentation**.
## Example Program
To test customtkinter you can try this simple example with only a single button:
@ -105,6 +111,12 @@ how to position the text and image at once with the ``compound`` option:
| _`image_example.py` on Windows 11_
###
### Scrollable Frames
Scrollable frames are possible in vertical or horizontal orientation and can be combined
with any other widgets.
![](documentation_images/scrollable_frame_example_Windows.png)
| _`scrollable_frame_example.py` on Windows 11_
### Integration of TkinterMapView widget
In the following example I used a TkinterMapView which integrates
well with a CustomTkinter program. It's a tile based map widget which displays

View File

@ -1,4 +1,4 @@
__version__ = "5.0.4"
__version__ = "5.1.3"
import os
import sys
@ -33,6 +33,7 @@ from .windows.widgets import CTkSlider
from .windows.widgets import CTkSwitch
from .windows.widgets import CTkTabview
from .windows.widgets import CTkTextbox
from .windows.widgets import CTkScrollableFrame
# import windows
from .windows import CTk

View File

@ -48,7 +48,7 @@
"corner_radius": 1000,
"border_width": 3,
"button_length": 0,
"fg_Color": ["#939BA2", "#4A4D50"],
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["#3B8ED0", "#1F6AA5"],
"button_color": ["gray36", "#D5D9DE"],
"button_hover_color": ["gray20", "gray100"],
@ -121,12 +121,15 @@
"CTkTextbox": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["#F9F9FA", "gray23"],
"fg_color": ["#F9F9FA", "#1D1E1E"],
"border_color": ["#979DA2", "#565B5E"],
"text_color":["gray10", "#DCE4EE"],
"scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"]
},
"CTkScrollableFrame": {
"label_fg_color": ["gray78", "gray23"]
},
"DropdownMenu": {
"fg_color": ["gray90", "gray20"],
"hover_color": ["gray75", "gray28"],

View File

@ -48,7 +48,7 @@
"corner_radius": 1000,
"border_width": 3,
"button_length": 0,
"fg_Color": ["#939BA2", "#4A4D50"],
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["#3a7ebf", "#1f538d"],
"button_color": ["gray36", "#D5D9DE"],
"button_hover_color": ["gray20", "gray100"],
@ -127,6 +127,9 @@
"scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"]
},
"CTkScrollableFrame": {
"label_fg_color": ["gray80", "gray21"]
},
"DropdownMenu": {
"fg_color": ["gray90", "gray20"],
"hover_color": ["gray75", "gray28"],

View File

@ -48,7 +48,7 @@
"corner_radius": 1000,
"border_width": 3,
"button_length": 0,
"fg_Color": ["#939BA2", "#4A4D50"],
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["#2CC985", "#2FA572"],
"button_color": ["gray36", "#D5D9DE"],
"button_hover_color": ["gray20", "gray100"],
@ -127,6 +127,9 @@
"scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"]
},
"CTkScrollableFrame": {
"label_fg_color": ["gray78", "gray23"]
},
"DropdownMenu": {
"fg_color": ["gray90", "gray20"],
"hover_color": ["gray75", "gray28"],

View File

@ -40,14 +40,6 @@ 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
@ -61,23 +53,31 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
# set bg of tkinter.Tk
super().configure(bg=self._apply_appearance_mode(self._fg_color))
# set title and initial geometry
# set title
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._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._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
# save focus before calling withdraw
self.focused_widget_before_widthdraw = None
# 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"):
self._windows_set_titlebar_color(self._get_appearance_mode())
self.bind('<Configure>', self._update_dimensions_event)
self.bind('<FocusIn>', self._focus_in_event)
self._block_update_dimensions_event = False
def destroy(self):
self._disable_macos_dark_title_bar()
@ -140,24 +140,26 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
def update(self):
if self._window_exists is False:
self._window_exists = True
if sys.platform.startswith("win"):
if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists:
# print("window dont exists -> deiconify in update")
self.deiconify()
self._window_exists = True
super().update()
def mainloop(self, *args, **kwargs):
if not self._window_exists:
self._window_exists = True
if sys.platform.startswith("win"):
self._windows_set_titlebar_color(self._get_appearance_mode())
if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists:
# print("window dont exists -> deiconify in mainloop")
self.deiconify()
self._window_exists = True
super().mainloop(*args, **kwargs)
def resizable(self, width: bool = None, height: bool = None):
@ -219,6 +221,23 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
else:
return super().cget(attribute_name)
def wm_iconbitmap(self, bitmap=None, default=None):
self._iconbitmap_method_called = True
super().wm_iconbitmap(bitmap, default)
def 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
def _enable_macos_dark_title_bar(cls):
if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS
@ -253,9 +272,11 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
# print("window_exists -> state_before_windows_set_titlebar_color: ", self.state_before_windows_set_titlebar_color)
if self._state_before_windows_set_titlebar_color != "iconic" or self._state_before_windows_set_titlebar_color != "withdrawn":
self.focused_widget_before_widthdraw = self.focus_get()
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
else:
# print("window dont exists -> withdraw and update")
self.focused_widget_before_widthdraw = self.focus_get()
super().withdraw()
super().update()
@ -284,7 +305,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
except Exception as err:
print(err)
if self._window_exists:
if self._window_exists or True:
# print("window_exists -> return to original state: ", self.state_before_windows_set_titlebar_color)
if self._state_before_windows_set_titlebar_color == "normal":
self.deiconify()
@ -297,6 +318,10 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
else:
pass # wait for update or mainloop to be called
if self.focused_widget_before_widthdraw is not None:
self.after(1, self.focused_widget_before_widthdraw.focus)
self.focused_widget_before_widthdraw = None
def _set_appearance_mode(self, mode_string: str):
super()._set_appearance_mode(mode_string)

View File

@ -19,7 +19,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
For detailed information check out the documentation.
"""
_valid_tk_toplevel_arguments: set = {"bd", "borderwidth", "class", "container", "cursor", "height",
_valid_tk_toplevel_arguments: set = {"master", "bd", "borderwidth", "class", "container", "cursor", "height",
"highlightbackground", "highlightthickness", "menu", "relief",
"screen", "takefocus", "use", "visual", "width"}
@ -62,19 +62,28 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
# set title of tkinter.Toplevel
super().title("CTkToplevel")
# indicator variables
self._iconbitmap_method_called = True
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._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._block_update_dimensions_event = False
# save focus before calling withdraw
self.focused_widget_before_widthdraw = None
# 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"):
self._windows_set_titlebar_color(self._get_appearance_mode())
self.bind('<Configure>', self._update_dimensions_event)
self.bind('<FocusIn>', self._focus_in_event)
self._block_update_dimensions_event = False
def destroy(self):
self._disable_macos_dark_title_bar()
@ -190,6 +199,19 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
else:
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
def _enable_macos_dark_title_bar(cls):
if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS
@ -219,6 +241,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
if sys.platform.startswith("win") and not self._deactivate_windows_window_header_manipulation:
self._state_before_windows_set_titlebar_color = self.state()
self.focused_widget_before_widthdraw = self.focus_get()
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
super().update()
@ -249,6 +272,10 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._windows_set_titlebar_color_called = True
self.after(5, self._revert_withdraw_after_windows_set_titlebar_color)
if self.focused_widget_before_widthdraw is not None:
self.after(10, self.focused_widget_before_widthdraw.focus)
self.focused_widget_before_widthdraw = None
def _revert_withdraw_after_windows_set_titlebar_color(self):
""" if in a short time (5ms) after """
if self._windows_set_titlebar_color_called:

View File

@ -13,3 +13,4 @@ from .ctk_slider import CTkSlider
from .ctk_switch import CTkSwitch
from .ctk_tabview import CTkTabview
from .ctk_textbox import CTkTextbox
from .ctk_scrollable_frame import CTkScrollableFrame

View File

@ -9,7 +9,7 @@ class CTkAppearanceModeBaseClass:
- destroy() must be called when sub-class is destroyed
- _set_appearance_mode() abstractmethod, gets called when appearance mode changes, must be overridden
- _apply_appearance_mode()
- _apply_appearance_mode() to convert tuple color
"""
def __init__(self):

View File

@ -9,9 +9,6 @@ try:
except ImportError:
from typing_extensions import TypedDict
# removed due to circular import
# from ...ctk_tk import CTk
# from ...ctk_toplevel import CTkToplevel
from .... import windows # import windows for isinstance checks
from ..theme import ThemeManager
@ -74,7 +71,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
super().bind('<Configure>', self._update_dimensions_event)
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget as well
if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame, tkinter.LabelFrame, ttk.Frame, ttk.LabelFrame, ttk.Notebook)) and not isinstance(self.master, CTkBaseClass):
if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame, tkinter.LabelFrame, ttk.Frame, ttk.LabelFrame, ttk.Notebook)) and not isinstance(self.master, (CTkBaseClass, CTkAppearanceModeBaseClass)):
master_old_configure = self.master.config
def new_configure(*args, **kwargs):
@ -179,8 +176,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
elif isinstance(image, CTkImage):
return image
else:
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")
warnings.warn(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. Image can not be scaled on HighDPI displays, use CTkImage instead.\n")
return image
def _update_dimensions_event(self, event):
@ -197,12 +193,15 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
if master_widget is None:
master_widget = self.master
if isinstance(master_widget, (windows.widgets.core_widget_classes.CTkBaseClass, windows.CTk, windows.CTkToplevel)):
if isinstance(master_widget, (windows.widgets.core_widget_classes.CTkBaseClass, windows.CTk, windows.CTkToplevel, windows.widgets.ctk_scrollable_frame.CTkScrollableFrame)):
if master_widget.cget("fg_color") is not None and master_widget.cget("fg_color") != "transparent":
return master_widget.cget("fg_color")
elif isinstance(master_widget, windows.widgets.ctk_scrollable_frame.CTkScrollableFrame):
return self._detect_color_of_master(master_widget.master.master.master)
# if fg_color of master is None, try to retrieve fg_color from master of master
elif hasattr(master_widget.master, "master"):
elif hasattr(master_widget, "master"):
return self._detect_color_of_master(master_widget.master)
elif isinstance(master_widget, (ttk.Frame, ttk.LabelFrame, ttk.Notebook, ttk.Label)): # master is ttk widget
@ -269,6 +268,8 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
relheight=amount - height of this widget between 0.0 and 1.0 relative to height of master (1.0 is the same height as the master)
bordermode="inside" or "outside" - whether to take border width of master widget into account
"""
if "width" in kwargs or "height" in kwargs:
raise ValueError("'width' and 'height' arguments must be passed to the constructor of the widget, not the place method")
self._last_geometry_manager_call = {"function": super().place, "kwargs": kwargs}
return super().place(**self._apply_argument_scaling(kwargs))

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[CTkImage, None] = None,
image: Union[CTkImage, "ImageTk.PhotoImage", None] = None,
state: str = "normal",
hover: bool = True,
command: Union[Callable[[], None], None] = None,
@ -169,8 +169,11 @@ class CTkButton(CTkBaseClass):
def _update_image(self):
if self._image_label is not None:
self._image_label.configure(image=self._image.create_scaled_photo_image(self._get_widget_scaling(),
self._get_appearance_mode()))
if isinstance(self._image, CTkImage):
self._image_label.configure(image=self._image.create_scaled_photo_image(self._get_widget_scaling(),
self._get_appearance_mode()))
elif self._image is not None:
self._image_label.configure(image=self._image)
def destroy(self):
if isinstance(self._font, CTkFont):
@ -425,6 +428,7 @@ class CTkButton(CTkBaseClass):
if "command" in kwargs:
self._command = kwargs.pop("command")
self._set_cursor()
if "compound" in kwargs:
self._compound = kwargs.pop("compound")

View File

@ -133,7 +133,7 @@ class CTkEntry(CTkBaseClass):
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
self._draw()
self._draw(no_color_updates=True)
def _update_font(self):
""" pass font to tkinter widgets with applied font scaling and update grid with workaround """
@ -153,14 +153,14 @@ class CTkEntry(CTkBaseClass):
def _draw(self, no_color_updates=False):
super()._draw(no_color_updates)
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height),
self._apply_widget_scaling(self._corner_radius),
self._apply_widget_scaling(self._border_width))
if requires_recoloring or no_color_updates is False:
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
if self._apply_appearance_mode(self._fg_color) == "transparent":
self._canvas.itemconfig("inner_parts",
fill=self._apply_appearance_mode(self._bg_color),
@ -342,13 +342,13 @@ class CTkEntry(CTkBaseClass):
return self._entry.get()
def focus(self):
return self._entry.focus()
self._entry.focus()
def focus_set(self):
return self._entry.focus_set()
self._entry.focus_set()
def focus_force(self):
return self._entry.focus_force()
self._entry.focus_force()
def index(self, index):
return self._entry.index(index)

View File

@ -24,8 +24,8 @@ class CTkFrame(CTkBaseClass):
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None,
background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None,
overwrite_preferred_drawing_method: Union[str, None] = None,
**kwargs):

View File

@ -14,6 +14,8 @@ class CTkLabel(CTkBaseClass):
"""
Label with rounded corners. Default is fg_color=None (transparent fg_color).
For detailed information check out the documentation.
state argument will probably be removed because it has no effect
"""
# attributes that are passed to and managed by the tkinter entry only:

View File

@ -85,6 +85,7 @@ class CTkRadioButton(CTkBaseClass):
self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
self.grid_columnconfigure(2, weight=1)
self.grid_rowconfigure(0, weight=1)
self._bg_canvas = CTkCanvas(master=self,
highlightthickness=0,

View File

@ -0,0 +1,316 @@
from typing import Union, Tuple, Optional
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
import tkinter
import sys
from .ctk_frame import CTkFrame
from .ctk_scrollbar import CTkScrollbar
from .appearance_mode import CTkAppearanceModeBaseClass
from .scaling import CTkScalingBaseClass
from .core_widget_classes import CTkBaseClass
from .ctk_label import CTkLabel
from .font import CTkFont
from .theme import ThemeManager
class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
def __init__(self,
master: any,
width: int = 200,
height: int = 200,
corner_radius: Optional[Union[int, str]] = None,
border_width: Optional[Union[int, str]] = None,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
scrollbar_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
scrollbar_button_color: Optional[Union[str, Tuple[str, str]]] = None,
scrollbar_button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
label_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
label_text_color: Optional[Union[str, Tuple[str, str]]] = None,
label_text: str = "",
label_font: Optional[Union[tuple, CTkFont]] = None,
label_anchor: str = "center",
orientation: Literal["vertical", "horizontal"] = "vertical"):
self._orientation = orientation
# dimensions independent of scaling
self._desired_width = width # _desired_width and _desired_height, represent desired size set by width and height
self._desired_height = height
self._parent_frame = CTkFrame(master=master, width=0, height=0, corner_radius=corner_radius,
border_width=border_width, bg_color=bg_color, fg_color=fg_color, border_color=border_color)
self._parent_canvas = tkinter.Canvas(master=self._parent_frame, highlightthickness=0)
self._set_scroll_increments()
if self._orientation == "horizontal":
self._scrollbar = CTkScrollbar(master=self._parent_frame, orientation="horizontal", command=self._parent_canvas.xview,
fg_color=scrollbar_fg_color, button_color=scrollbar_button_color, button_hover_color=scrollbar_button_hover_color)
self._parent_canvas.configure(xscrollcommand=self._scrollbar.set)
elif self._orientation == "vertical":
self._scrollbar = CTkScrollbar(master=self._parent_frame, orientation="vertical", command=self._parent_canvas.yview,
fg_color=scrollbar_fg_color, button_color=scrollbar_button_color, button_hover_color=scrollbar_button_hover_color)
self._parent_canvas.configure(yscrollcommand=self._scrollbar.set)
self._label_text = label_text
self._label = CTkLabel(self._parent_frame, text=label_text, anchor=label_anchor, font=label_font,
corner_radius=self._parent_frame.cget("corner_radius"), text_color=label_text_color,
fg_color=ThemeManager.theme["CTkScrollableFrame"]["label_fg_color"] if label_fg_color is None else label_fg_color)
tkinter.Frame.__init__(self, master=self._parent_canvas, highlightthickness=0)
CTkAppearanceModeBaseClass.__init__(self)
CTkScalingBaseClass.__init__(self, scaling_type="widget")
self._create_grid()
self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
self.bind("<Configure>", lambda e: self._parent_canvas.configure(scrollregion=self._parent_canvas.bbox("all")))
self._parent_canvas.bind("<Configure>", self._fit_frame_dimensions_to_canvas)
self.bind_all("<MouseWheel>", self._mouse_wheel_all, add="+")
self.bind_all("<KeyPress-Shift_L>", self._keyboard_shift_press_all, add="+")
self.bind_all("<KeyPress-Shift_R>", self._keyboard_shift_press_all, add="+")
self.bind_all("<KeyRelease-Shift_L>", self._keyboard_shift_release_all, add="+")
self.bind_all("<KeyRelease-Shift_R>", self._keyboard_shift_release_all, add="+")
self._create_window_id = self._parent_canvas.create_window(0, 0, window=self, anchor="nw")
if self._parent_frame.cget("fg_color") == "transparent":
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
else:
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
self._shift_pressed = False
def destroy(self):
tkinter.Frame.destroy(self)
CTkAppearanceModeBaseClass.destroy(self)
CTkScalingBaseClass.destroy(self)
def _create_grid(self):
border_spacing = self._apply_widget_scaling(self._parent_frame.cget("corner_radius") + self._parent_frame.cget("border_width"))
if self._orientation == "horizontal":
self._parent_frame.grid_columnconfigure(0, weight=1)
self._parent_frame.grid_rowconfigure(1, weight=1)
self._parent_canvas.grid(row=1, column=0, sticky="nsew", padx=border_spacing, pady=(border_spacing, 0))
self._scrollbar.grid(row=2, column=0, sticky="nsew", padx=border_spacing)
if self._label_text is not None and self._label_text != "":
self._label.grid(row=0, column=0, sticky="ew", padx=border_spacing, pady=border_spacing)
else:
self._label.grid_forget()
elif self._orientation == "vertical":
self._parent_frame.grid_columnconfigure(0, weight=1)
self._parent_frame.grid_rowconfigure(1, weight=1)
self._parent_canvas.grid(row=1, column=0, sticky="nsew", padx=(border_spacing, 0), pady=border_spacing)
self._scrollbar.grid(row=1, column=1, sticky="nsew", pady=border_spacing)
if self._label_text is not None and self._label_text != "":
self._label.grid(row=0, column=0, columnspan=2, sticky="ew", padx=border_spacing, pady=border_spacing)
else:
self._label.grid_forget()
def _set_appearance_mode(self, mode_string):
super()._set_appearance_mode(mode_string)
if self._parent_frame.cget("fg_color") == "transparent":
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
else:
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
def _set_scaling(self, new_widget_scaling, new_window_scaling):
super()._set_scaling(new_widget_scaling, new_window_scaling)
self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
def _set_dimensions(self, width=None, height=None):
if width is not None:
self._desired_width = width
if height is not None:
self._desired_height = height
self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
def configure(self, **kwargs):
if "width" in kwargs:
self._set_dimensions(width=kwargs.pop("width"))
if "height" in kwargs:
self._set_dimensions(height=kwargs.pop("height"))
if "corner_radius" in kwargs:
new_corner_radius = kwargs.pop("corner_radius")
self._parent_frame.configure(corner_radius=new_corner_radius)
if self._label is not None:
self._label.configure(corner_radius=new_corner_radius)
self._create_grid()
if "border_width" in kwargs:
self._parent_frame.configure(border_width=kwargs.pop("border_width"))
self._create_grid()
if "fg_color" in kwargs:
self._parent_frame.configure(fg_color=kwargs.pop("fg_color"))
if self._parent_frame.cget("fg_color") == "transparent":
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
else:
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
for child in self.winfo_children():
if isinstance(child, CTkBaseClass):
child.configure(bg_color=self._parent_frame.cget("fg_color"))
if "scrollbar_fg_color" in kwargs:
self._scrollbar.configure(fg_color=kwargs.pop("scrollbar_fg_color"))
if "scrollbar_button_color" in kwargs:
self._scrollbar.configure(button_color=kwargs.pop("scrollbar_button_color"))
if "scrollbar_button_hover_color" in kwargs:
self._scrollbar.configure(button_hover_color=kwargs.pop("scrollbar_button_hover_color"))
if "label_text" in kwargs:
self._label_text = kwargs.pop("label_text")
self._label.configure(text=self._label_text)
self._create_grid()
if "label_font" in kwargs:
self._label.configure(font=kwargs.pop("label_font"))
if "label_text_color" in kwargs:
self._label.configure(text_color=kwargs.pop("label_text_color"))
if "label_fg_color" in kwargs:
self._label.configure(fg_color=kwargs.pop("label_fg_color"))
if "label_anchor" in kwargs:
self._label.configure(anchor=kwargs.pop("label_anchor"))
self._parent_frame.configure(**kwargs)
def cget(self, attribute_name: str):
if attribute_name == "width":
return self._desired_width
elif attribute_name == "height":
return self._desired_height
elif attribute_name == "label_text":
return self._label_text
elif attribute_name == "label_font":
return self._label.cget("font")
elif attribute_name == "label_text_color":
return self._label.cget("_text_color")
elif attribute_name == "label_fg_color":
return self._label.cget("fg_color")
elif attribute_name == "label_anchor":
return self._label.cget("anchor")
elif attribute_name.startswith("scrollbar_fg_color"):
return self._scrollbar.cget("fg_color")
elif attribute_name.startswith("scrollbar_button_color"):
return self._scrollbar.cget("button_color")
elif attribute_name.startswith("scrollbar_button_hover_color"):
return self._scrollbar.cget("button_hover_color")
else:
return self._parent_frame.cget(attribute_name)
def _fit_frame_dimensions_to_canvas(self, event):
if self._orientation == "horizontal":
self._parent_canvas.itemconfigure(self._create_window_id, height=self._parent_canvas.winfo_height())
elif self._orientation == "vertical":
self._parent_canvas.itemconfigure(self._create_window_id, width=self._parent_canvas.winfo_width())
def _set_scroll_increments(self):
if sys.platform.startswith("win"):
self._parent_canvas.configure(xscrollincrement=1, yscrollincrement=1)
elif sys.platform == "darwin":
self._parent_canvas.configure(xscrollincrement=4, yscrollincrement=8)
def _mouse_wheel_all(self, event):
if self.check_if_master_is_canvas(event.widget):
if sys.platform.startswith("win"):
if self._shift_pressed:
if self._parent_canvas.xview() != (0.0, 1.0):
self._parent_canvas.xview("scroll", -int(event.delta / 6), "units")
else:
if self._parent_canvas.yview() != (0.0, 1.0):
self._parent_canvas.yview("scroll", -int(event.delta / 6), "units")
elif sys.platform == "darwin":
if self._shift_pressed:
if self._parent_canvas.xview() != (0.0, 1.0):
self._parent_canvas.xview("scroll", -event.delta, "units")
else:
if self._parent_canvas.yview() != (0.0, 1.0):
self._parent_canvas.yview("scroll", -event.delta, "units")
else:
if self._shift_pressed:
if self._parent_canvas.xview() != (0.0, 1.0):
self._parent_canvas.xview("scroll", -event.delta, "units")
else:
if self._parent_canvas.yview() != (0.0, 1.0):
self._parent_canvas.yview("scroll", -event.delta, "units")
def _keyboard_shift_press_all(self, event):
self._shift_pressed = True
def _keyboard_shift_release_all(self, event):
self._shift_pressed = False
def check_if_master_is_canvas(self, widget):
if widget == self._parent_canvas:
return True
elif widget.master is not None:
return self.check_if_master_is_canvas(widget.master)
else:
return False
def pack(self, **kwargs):
self._parent_frame.pack(**kwargs)
def place(self, **kwargs):
self._parent_frame.place(**kwargs)
def grid(self, **kwargs):
self._parent_frame.grid(**kwargs)
def pack_forget(self):
self._parent_frame.pack_forget()
def place_forget(self, **kwargs):
self._parent_frame.place_forget()
def grid_forget(self, **kwargs):
self._parent_frame.grid_forget()
def grid_remove(self, **kwargs):
self._parent_frame.grid_remove()
def grid_propagate(self, **kwargs):
self._parent_frame.grid_propagate()
def grid_info(self, **kwargs):
return self._parent_frame.grid_info()
def lift(self, aboveThis=None):
self._parent_frame.lift(aboveThis)
def lower(self, belowThis=None):
self._parent_frame.lower(belowThis)

View File

@ -54,7 +54,7 @@ class CTkSwitch(CTkBaseClass):
# color
self._border_color = self._check_color_type(border_color, transparency=True)
self._fg_color = ThemeManager.theme["CTkSwitch"]["fg_Color"] if fg_color is None else self._check_color_type(fg_color)
self._fg_color = ThemeManager.theme["CTkSwitch"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
self._progress_color = ThemeManager.theme["CTkSwitch"]["progress_color"] if progress_color is None else self._check_color_type(progress_color, transparency=True)
self._button_color = ThemeManager.theme["CTkSwitch"]["button_color"] if button_color is None else self._check_color_type(button_color)
self._button_hover_color = ThemeManager.theme["CTkSwitch"]["button_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
@ -92,6 +92,7 @@ class CTkSwitch(CTkBaseClass):
self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
self.grid_columnconfigure(2, weight=1)
self.grid_rowconfigure(0, weight=1)
self._bg_canvas = CTkCanvas(master=self,
highlightthickness=0,
@ -221,23 +222,29 @@ class CTkSwitch(CTkBaseClass):
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
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))
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))
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))
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))
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))
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))
if self._state == tkinter.DISABLED:
@ -292,6 +299,10 @@ class CTkSwitch(CTkBaseClass):
self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
require_redraw = True
if "border_color" in kwargs:
self._border_color = self._check_color_type(kwargs.pop("border_color"), transparency=True)
require_redraw = True
if "progress_color" in kwargs:
self._progress_color = self._check_color_type(kwargs.pop("progress_color"), transparency=True)
require_redraw = True
@ -304,8 +315,12 @@ class CTkSwitch(CTkBaseClass):
self._button_hover_color = self._check_color_type(kwargs.pop("button_hover_color"))
require_redraw = True
if "border_color" in kwargs:
self._border_color = self._check_color_type(kwargs.pop("border_color"), transparency=True)
if "text_color" in kwargs:
self._text_color = self._check_color_type(kwargs.pop("text_color"))
require_redraw = True
if "text_color_disabled" in kwargs:
self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
require_redraw = True
if "hover" in kwargs:

View File

@ -99,9 +99,9 @@ class CTkTabview(CTkBaseClass):
self._draw()
def _segmented_button_callback(self, selected_name):
self._set_grid_tab_by_name(selected_name)
self._tab_dict[self._current_name].grid_forget()
self._current_name = selected_name
self._grid_forget_all_tabs()
self._set_grid_tab_by_name(self._current_name)
if self._command is not None:
self._command()

View File

@ -119,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.after(50, self._check_if_scrollbars_needed)
self.after(50, self._check_if_scrollbars_needed, None, True)
self._draw()
def _create_grid_for_text_and_scrollbars(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False):
@ -151,7 +151,7 @@ class CTkTextbox(CTkBaseClass):
else:
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 """
if self._scrollbars_activated:
@ -349,7 +349,6 @@ class CTkTextbox(CTkBaseClass):
return self._textbox.focus_force()
def insert(self, index, text, tags=None):
self._check_if_scrollbars_needed()
return self._textbox.insert(index, text, tags)
def get(self, index1, index2=None):

View File

@ -4,6 +4,6 @@ from .theme_manager import ThemeManager
try:
ThemeManager.load_theme("blue")
except FileNotFoundError as err:
raise FileNotFoundError(f"{err}\n\nThe .json theme file for CustomTkinter could not be found.\n" +
raise FileNotFoundError(f"{err}\nThe .json theme file for CustomTkinter could not be found.\n" +
f"If packaging with pyinstaller was used, have a look at the wiki:\n" +
f"https://github.com/TomSchimansky/CustomTkinter/wiki/Packaging#windows-pyinstaller-auto-py-to-exe")

View File

@ -1,5 +1,6 @@
import sys
import os
import pathlib
import json
from typing import List, Union
@ -15,7 +16,8 @@ class ThemeManager:
script_directory = os.path.dirname(os.path.abspath(__file__))
if theme_name_or_path in cls._built_in_themes:
with open(os.path.join(script_directory, "../../../assets", "themes", f"{theme_name_or_path}.json"), "r") as f:
customtkinter_path = pathlib.Path(script_directory).parent.parent.parent
with open(os.path.join(customtkinter_path, "assets", "themes", f"{theme_name_or_path}.json"), "r") as f:
cls.theme = json.load(f)
else:
with open(theme_name_or_path, "r") as f:

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -87,21 +87,9 @@ class App(customtkinter.CTk):
self.radio_button_3 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=2)
self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n")
# create checkbox and switch frame
self.checkbox_slider_frame = customtkinter.CTkFrame(self)
self.checkbox_slider_frame.grid(row=1, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew")
self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_1.grid(row=1, column=0, pady=(20, 10), padx=20, sticky="n")
self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_2.grid(row=2, column=0, pady=10, padx=20, sticky="n")
self.switch_1 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame, command=lambda: print("switch 1 toggle"))
self.switch_1.grid(row=3, column=0, pady=10, padx=20, sticky="n")
self.switch_2 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame)
self.switch_2.grid(row=4, column=0, pady=(10, 20), padx=20, sticky="n")
# create slider and progressbar frame
self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color="transparent")
self.slider_progressbar_frame.grid(row=1, column=1, columnspan=2, padx=(20, 0), pady=(20, 0), sticky="nsew")
self.slider_progressbar_frame.grid(row=1, column=1, padx=(20, 0), pady=(20, 0), sticky="nsew")
self.slider_progressbar_frame.grid_columnconfigure(0, weight=1)
self.slider_progressbar_frame.grid_rowconfigure(4, weight=1)
self.seg_button_1 = customtkinter.CTkSegmentedButton(self.slider_progressbar_frame)
@ -117,12 +105,32 @@ class App(customtkinter.CTk):
self.progressbar_3 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orientation="vertical")
self.progressbar_3.grid(row=0, column=2, rowspan=5, padx=(10, 20), pady=(10, 10), sticky="ns")
# create scrollable frame
self.scrollable_frame = customtkinter.CTkScrollableFrame(self, label_text="CTkScrollableFrame")
self.scrollable_frame.grid(row=1, column=2, padx=(20, 0), pady=(20, 0), sticky="nsew")
self.scrollable_frame.grid_columnconfigure(0, weight=1)
self.scrollable_frame_switches = []
for i in range(100):
switch = customtkinter.CTkSwitch(master=self.scrollable_frame, text=f"CTkSwitch {i}")
switch.grid(row=i, column=0, padx=10, pady=(0, 20))
self.scrollable_frame_switches.append(switch)
# create checkbox and switch frame
self.checkbox_slider_frame = customtkinter.CTkFrame(self)
self.checkbox_slider_frame.grid(row=1, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew")
self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_1.grid(row=1, column=0, pady=(20, 0), padx=20, sticky="n")
self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_2.grid(row=2, column=0, pady=(20, 0), padx=20, sticky="n")
self.checkbox_3 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame)
self.checkbox_3.grid(row=3, column=0, pady=20, padx=20, sticky="n")
# set default values
self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton")
self.checkbox_2.configure(state="disabled")
self.switch_2.configure(state="disabled")
self.checkbox_3.configure(state="disabled")
self.checkbox_1.select()
self.switch_1.select()
self.scrollable_frame_switches[0].select()
self.scrollable_frame_switches[4].select()
self.radio_button_3.configure(state="disabled")
self.appearance_mode_optionemenu.set("Dark")
self.scaling_optionemenu.set("100%")

View File

@ -0,0 +1,133 @@
import customtkinter
import os
from PIL import Image
class ScrollableCheckBoxFrame(customtkinter.CTkScrollableFrame):
def __init__(self, master, item_list, command=None, **kwargs):
super().__init__(master, **kwargs)
self.command = command
self.checkbox_list = []
for i, item in enumerate(item_list):
self.add_item(item)
def add_item(self, item):
checkbox = customtkinter.CTkCheckBox(self, text=item)
if self.command is not None:
checkbox.configure(command=self.command)
checkbox.grid(row=len(self.checkbox_list), column=0, pady=(0, 10))
self.checkbox_list.append(checkbox)
def remove_item(self, item):
for checkbox in self.checkbox_list:
if item == checkbox.cget("text"):
checkbox.destroy()
self.checkbox_list.remove(checkbox)
return
def get_checked_items(self):
return [checkbox.cget("text") for checkbox in self.checkbox_list if checkbox.get() == 1]
class ScrollableRadiobuttonFrame(customtkinter.CTkScrollableFrame):
def __init__(self, master, item_list, command=None, **kwargs):
super().__init__(master, **kwargs)
self.command = command
self.radiobutton_variable = customtkinter.StringVar()
self.radiobutton_list = []
for i, item in enumerate(item_list):
self.add_item(item)
def add_item(self, item):
radiobutton = customtkinter.CTkRadioButton(self, text=item, value=item, variable=self.radiobutton_variable)
if self.command is not None:
radiobutton.configure(command=self.command)
radiobutton.grid(row=len(self.radiobutton_list), column=0, pady=(0, 10))
self.radiobutton_list.append(radiobutton)
def remove_item(self, item):
for radiobutton in self.radiobutton_list:
if item == radiobutton.cget("text"):
radiobutton.destroy()
self.radiobutton_list.remove(radiobutton)
return
def get_checked_item(self):
return self.radiobutton_variable.get()
class ScrollableLabelButtonFrame(customtkinter.CTkScrollableFrame):
def __init__(self, master, command=None, **kwargs):
super().__init__(master, **kwargs)
self.grid_columnconfigure(0, weight=1)
self.command = command
self.radiobutton_variable = customtkinter.StringVar()
self.label_list = []
self.button_list = []
def add_item(self, item, image=None):
label = customtkinter.CTkLabel(self, text=item, image=image, compound="left", padx=5, anchor="w")
button = customtkinter.CTkButton(self, text="Command", width=100, height=24)
if self.command is not None:
button.configure(command=lambda: self.command(item))
label.grid(row=len(self.label_list), column=0, pady=(0, 10), sticky="w")
button.grid(row=len(self.button_list), column=1, pady=(0, 10), padx=5)
self.label_list.append(label)
self.button_list.append(button)
def remove_item(self, item):
for label, button in zip(self.label_list, self.button_list):
if item == label.cget("text"):
label.destroy()
button.destroy()
self.label_list.remove(label)
self.button_list.remove(button)
return
class App(customtkinter.CTk):
def __init__(self):
super().__init__()
self.title("CTkScrollableFrame example")
self.grid_rowconfigure(0, weight=1)
self.columnconfigure(2, weight=1)
# create scrollable checkbox frame
self.scrollable_checkbox_frame = ScrollableCheckBoxFrame(master=self, width=200, command=self.checkbox_frame_event,
item_list=[f"item {i}" for i in range(50)])
self.scrollable_checkbox_frame.grid(row=0, column=0, padx=15, pady=15, sticky="ns")
self.scrollable_checkbox_frame.add_item("new item")
# create scrollable radiobutton frame
self.scrollable_radiobutton_frame = ScrollableRadiobuttonFrame(master=self, width=500, command=self.radiobutton_frame_event,
item_list=[f"item {i}" for i in range(100)],
label_text="ScrollableRadiobuttonFrame")
self.scrollable_radiobutton_frame.grid(row=0, column=1, padx=15, pady=15, sticky="ns")
self.scrollable_radiobutton_frame.configure(width=200)
self.scrollable_radiobutton_frame.remove_item("item 3")
# create scrollable label and button frame
current_dir = os.path.dirname(os.path.abspath(__file__))
self.scrollable_label_button_frame = ScrollableLabelButtonFrame(master=self, width=300, command=self.label_button_frame_event, corner_radius=0)
self.scrollable_label_button_frame.grid(row=0, column=2, padx=0, pady=0, sticky="nsew")
for i in range(20): # add items with images
self.scrollable_label_button_frame.add_item(f"image and item {i}", image=customtkinter.CTkImage(Image.open(os.path.join(current_dir, "test_images", "chat_light.png"))))
def checkbox_frame_event(self):
print(f"checkbox frame modified: {self.scrollable_checkbox_frame.get_checked_items()}")
def radiobutton_frame_event(self):
print(f"radiobutton frame modified: {self.scrollable_radiobutton_frame.get_checked_item()}")
def label_button_frame_event(self, item):
print(f"label button frame clicked: {item}")
if __name__ == "__main__":
customtkinter.set_appearance_mode("dark")
app = App()
app.mainloop()

View File

@ -3,11 +3,10 @@ requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[tool.tbump]
# Uncomment this if your project is hosted on GitHub:
github_url = "https://github.com/TomSchimansky/CustomTkinter"
[tool.tbump.version]
current = "5.0.4"
current = "5.1.3"
# Example of a semver regexp.
# Make sure this matches current_version before
@ -33,17 +32,3 @@ src = "setup.cfg"
[[tool.tbump.file]]
src = "customtkinter/__init__.py"
search = "__version__ = \"{current_version}\""
# You can specify a list of commands to
# run after the files have been patched
# and before the git commit is made
# [[tool.tbump.before_commit]]
# name = "check changelog"
# cmd = "grep -q {new_version} Changelog.rst"
# Or run some commands after the git tag and the branch
# have been pushed:
# [[tool.tbump.after_push]]
# name = "publish"
# cmd = "./publish.sh"

View File

@ -1,10 +1,10 @@
[metadata]
[project]
name = customtkinter
version = 5.0.4
version = 5.1.3
description = Create modern looking GUIs with Python
long_description = A modern and customizable python UI-library based on Tkinter: https://github.com/TomSchimansky/CustomTkinter
long_description = A modern and customizable python UI-library based on Tkinter: https://customtkinter.tomschimansky.com
long_description_content_type = text/markdown
url = https://github.com/TomSchimansky/CustomTkinter
url = https://customtkinter.tomschimansky.com
author = Tom Schimansky
license = Creative Commons Zero v1.0 Universal
license_file = LICENSE
@ -13,6 +13,11 @@ classifiers =
Operating System :: OS Independent
Programming Language :: Python :: 3 :: Only
[project.urls]
homepage = https://customtkinter.tomschimansky.com
documentation = https://customtkinter.tomschimansky.com/documentation
repository = https://github.com/tomschimansky/customtkinter
[options]
python_requires = >=3.7
packages =

View File

@ -0,0 +1,39 @@
import customtkinter
customtkinter.set_default_color_theme("dark-blue")
app = customtkinter.CTk()
app.grid_columnconfigure(2, weight=1)
app.grid_rowconfigure(1, weight=1)
toplevel = customtkinter.CTkToplevel()
switch = customtkinter.CTkSwitch(toplevel, text="Mode", command=lambda: customtkinter.set_appearance_mode("dark" if switch.get() == 1 else "light"))
switch.grid(row=0, column=0, padx=50, pady=50)
frame_1 = customtkinter.CTkScrollableFrame(app, orientation="vertical", label_text="should not appear", fg_color="transparent")
frame_1.grid(row=0, column=0, padx=20, pady=20)
frame_1.configure(label_text=None)
frame_2 = customtkinter.CTkScrollableFrame(app, orientation="vertical", label_text="CTkScrollableFrame")
frame_2.grid(row=1, column=0, padx=20, pady=20)
frame_3 = customtkinter.CTkScrollableFrame(app, orientation="horizontal")
frame_3.grid(row=0, column=1, padx=20, pady=20)
frame_4 = customtkinter.CTkScrollableFrame(app, orientation="horizontal", label_fg_color="transparent")
frame_4.grid(row=1, column=1, padx=20, pady=20)
frame_4.configure(label_text="CTkScrollableFrame")
frame_5 = customtkinter.CTkScrollableFrame(app, orientation="vertical", label_text="CTkScrollableFrame", corner_radius=0)
frame_5.grid(row=0, column=2, rowspan=2, sticky="nsew")
for i in range(100):
customtkinter.CTkCheckBox(frame_1).grid(row=i, padx=10, pady=10)
customtkinter.CTkCheckBox(frame_2).grid(row=i, padx=10, pady=10)
customtkinter.CTkCheckBox(frame_3).grid(row=0, column=i, padx=10, pady=10)
customtkinter.CTkCheckBox(frame_4).grid(row=0, column=i, padx=10, pady=10)
customtkinter.CTkCheckBox(frame_5).grid(row=i, padx=10, pady=10)
app.mainloop()