49 Commits

Author SHA1 Message Date
d719950f80 fixed tabview initial fg_color, removed tests from simple_example.py 2023-07-27 14:40:19 +02:00
16990a566f added anchor to tabview #1766, fixed tabview fg_color update #1803, added index method to tabview and segmented button, added rename method to tabview #1192, fixed license in Readme.md 2023-07-27 14:06:13 +02:00
f629e4c5cb fix simple example 2023-07-08 21:32:20 +02:00
9cfd5f4515 added missing arguments to CTkSLider configure #1790 2023-07-08 21:24:59 +02:00
e116faf910 add font option to input dialog #838 2023-06-20 13:38:47 +02:00
b8f1eea411 fix license classifier 2023-06-19 14:01:01 +02:00
8b7386bc64 fix license classifier 2023-06-19 13:57:11 +02:00
8fc2d31584 Bump to 5.2.0 2023-06-19 13:54:51 +02:00
290cafbc39 Merge pull request #1399 from Sahil481/customtkinter-sahil-contribute
Fix configuring anchor on CTkButton #1394 #1393
2023-06-19 13:19:50 +02:00
9bd6d7bdde Merge pull request #1729 from dishb/fix-cancel-event
Fix incorrect event in input dialog
2023-06-19 13:00:37 +02:00
6841f94a99 🛠️ [fix] fix issue in #1631 2023-06-16 12:54:16 -07:00
a0c0da6c9a Merge pull request #1721 from dishb/fix-readme-1
README has unnecessary tkinter import
2023-06-16 13:17:47 +02:00
10d5cd4c2a 🛠️ [fix] small mistake in the README example code 2023-06-15 16:07:18 -07:00
abe33f7e81 fix error in CTkFont with multiple destroy calls #1692 2023-06-15 12:49:07 +02:00
37b375d58b fixed wrong json class names and added fix for backwards compatibility in ThemeManager #1538 2023-05-27 13:25:30 +02:00
cd85a1c782 added text_color_disabled to CTkLabel #1557, change license in setup.cfg 2023-05-27 12:48:46 +02:00
1960c0b1e0 added text_color_disabled to configure of CTkOptionMenu #1559 2023-05-08 19:11:22 +02:00
6d2f31c23c added border_width, corner_radius to configure of CTkSegmentedButton, removed border_color because has no effect #1562 2023-05-08 19:06:39 +02:00
c6b16ce815 added checkmark_color and text_color_disabled to CTkCheckbox configure method #1586 2023-05-08 13:08:44 +02:00
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
9f653fab1d Fixed a bug
Fixed #1394
2023-03-29 22:14:43 +05:30
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
37 changed files with 957 additions and 319 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 Permission is hereby granted, free of charge, to any person obtaining a copy
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN of this software and associated documentation files (the "Software"), to deal
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS in the Software without restriction, including without limitation the rights
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS copies of the Software, and to permit persons to whom the Software is
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM furnished to do so, subject to the following conditions:
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
exclusive Copyright and Related Rights (defined below) upon the creator IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
and subsequent owner(s) (each and all, an "owner") of an original work of FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
authorship and/or a database (each, a "Work"). AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
Certain owners wish to permanently relinquish those rights to a Work for OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
the purpose of contributing to a commons of creative, cultural and SOFTWARE.
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.

View File

@ -10,13 +10,19 @@
![PyPI](https://img.shields.io/pypi/v/customtkinter) ![PyPI](https://img.shields.io/pypi/v/customtkinter)
![PyPI - Downloads](https://img.shields.io/pypi/dm/customtkinter?color=green&label=downloads) ![PyPI - Downloads](https://img.shields.io/pypi/dm/customtkinter?color=green&label=downloads)
![Downloads](https://static.pepy.tech/personalized-badge/customtkinter?period=total&units=international_system&left_color=grey&right_color=green&left_text=downloads) ![Downloads](https://static.pepy.tech/personalized-badge/customtkinter?period=total&units=international_system&left_color=grey&right_color=green&left_text=downloads)
![PyPI - License](https://img.shields.io/pypi/l/customtkinter) ![PyPI - License](https://img.shields.io/badge/license_MIT)
![](https://tokei.rs/b1/github/tomschimansky/customtkinter) ![](https://tokei.rs/b1/github/tomschimansky/customtkinter)
</div> </div>
--- ---
<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 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 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 can also be used in combination with normal Tkinter elements. The widgets
@ -43,14 +49,13 @@ pip3 install customtkinter
## Documentation ## 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 ## Example Program
To test customtkinter you can try this simple example with only a single button: To test customtkinter you can try this simple example with only a single button:
```python ```python
import tkinter
import customtkinter import customtkinter
customtkinter.set_appearance_mode("System") # Modes: system (default), light, dark customtkinter.set_appearance_mode("System") # Modes: system (default), light, dark
@ -64,7 +69,7 @@ def button_function():
# Use CTkButton instead of tkinter Button # Use CTkButton instead of tkinter Button
button = customtkinter.CTkButton(master=app, text="CTkButton", command=button_function) button = customtkinter.CTkButton(master=app, text="CTkButton", command=button_function)
button.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) button.place(relx=0.5, rely=0.5, anchor=customtkinter.CENTER)
app.mainloop() app.mainloop()
``` ```
@ -105,6 +110,12 @@ how to position the text and image at once with the ``compound`` option:
| _`image_example.py` on Windows 11_ | _`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 ### Integration of TkinterMapView widget
In the following example I used a TkinterMapView which integrates In the following example I used a TkinterMapView which integrates
well with a CustomTkinter program. It's a tile based map widget which displays 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.2.0"
import os import os
import sys import sys
@ -33,6 +33,7 @@ from .windows.widgets import CTkSlider
from .windows.widgets import CTkSwitch from .windows.widgets import CTkSwitch
from .windows.widgets import CTkTabview from .windows.widgets import CTkTabview
from .windows.widgets import CTkTextbox from .windows.widgets import CTkTextbox
from .windows.widgets import CTkScrollableFrame
# import windows # import windows
from .windows import CTk from .windows import CTk

View File

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

View File

@ -34,7 +34,7 @@
"text_color": ["gray14", "gray84"], "text_color": ["gray14", "gray84"],
"placeholder_text_color": ["gray52", "gray62"] "placeholder_text_color": ["gray52", "gray62"]
}, },
"CTkCheckbox": { "CTkCheckBox": {
"corner_radius": 6, "corner_radius": 6,
"border_width": 3, "border_width": 3,
"fg_color": ["#3a7ebf", "#1f538d"], "fg_color": ["#3a7ebf", "#1f538d"],
@ -48,14 +48,14 @@
"corner_radius": 1000, "corner_radius": 1000,
"border_width": 3, "border_width": 3,
"button_length": 0, "button_length": 0,
"fg_Color": ["#939BA2", "#4A4D50"], "fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["#3a7ebf", "#1f538d"], "progress_color": ["#3a7ebf", "#1f538d"],
"button_color": ["gray36", "#D5D9DE"], "button_color": ["gray36", "#D5D9DE"],
"button_hover_color": ["gray20", "gray100"], "button_hover_color": ["gray20", "gray100"],
"text_color": ["gray14", "gray84"], "text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"] "text_color_disabled": ["gray60", "gray45"]
}, },
"CTkRadiobutton": { "CTkRadioButton": {
"corner_radius": 1000, "corner_radius": 1000,
"border_width_checked": 6, "border_width_checked": 6,
"border_width_unchecked": 3, "border_width_unchecked": 3,
@ -127,6 +127,9 @@
"scrollbar_button_color": ["gray55", "gray41"], "scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"] "scrollbar_button_hover_color": ["gray40", "gray53"]
}, },
"CTkScrollableFrame": {
"label_fg_color": ["gray80", "gray21"]
},
"DropdownMenu": { "DropdownMenu": {
"fg_color": ["gray90", "gray20"], "fg_color": ["gray90", "gray20"],
"hover_color": ["gray75", "gray28"], "hover_color": ["gray75", "gray28"],

View File

@ -34,7 +34,7 @@
"text_color":["gray10", "#DCE4EE"], "text_color":["gray10", "#DCE4EE"],
"placeholder_text_color": ["gray52", "gray62"] "placeholder_text_color": ["gray52", "gray62"]
}, },
"CTkCheckbox": { "CTkCheckBox": {
"corner_radius": 6, "corner_radius": 6,
"border_width": 3, "border_width": 3,
"fg_color": ["#2CC985", "#2FA572"], "fg_color": ["#2CC985", "#2FA572"],
@ -48,14 +48,14 @@
"corner_radius": 1000, "corner_radius": 1000,
"border_width": 3, "border_width": 3,
"button_length": 0, "button_length": 0,
"fg_Color": ["#939BA2", "#4A4D50"], "fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["#2CC985", "#2FA572"], "progress_color": ["#2CC985", "#2FA572"],
"button_color": ["gray36", "#D5D9DE"], "button_color": ["gray36", "#D5D9DE"],
"button_hover_color": ["gray20", "gray100"], "button_hover_color": ["gray20", "gray100"],
"text_color": ["gray10", "#DCE4EE"], "text_color": ["gray10", "#DCE4EE"],
"text_color_disabled": ["gray60", "gray45"] "text_color_disabled": ["gray60", "gray45"]
}, },
"CTkRadiobutton": { "CTkRadioButton": {
"corner_radius": 1000, "corner_radius": 1000,
"border_width_checked": 6, "border_width_checked": 6,
"border_width_unchecked": 3, "border_width_unchecked": 3,
@ -127,6 +127,9 @@
"scrollbar_button_color": ["gray55", "gray41"], "scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"] "scrollbar_button_hover_color": ["gray40", "gray53"]
}, },
"CTkScrollableFrame": {
"label_fg_color": ["gray78", "gray23"]
},
"DropdownMenu": { "DropdownMenu": {
"fg_color": ["gray90", "gray20"], "fg_color": ["gray90", "gray20"],
"hover_color": ["gray75", "gray28"], "hover_color": ["gray75", "gray28"],

View File

@ -5,6 +5,7 @@ from .widgets import CTkEntry
from .widgets import CTkButton from .widgets import CTkButton
from .widgets.theme import ThemeManager from .widgets.theme import ThemeManager
from .ctk_toplevel import CTkToplevel from .ctk_toplevel import CTkToplevel
from .widgets.font import CTkFont
class CTkInputDialog(CTkToplevel): class CTkInputDialog(CTkToplevel):
@ -24,6 +25,7 @@ class CTkInputDialog(CTkToplevel):
entry_text_color: Optional[Union[str, Tuple[str, str]]] = None, entry_text_color: Optional[Union[str, Tuple[str, str]]] = None,
title: str = "CTkDialog", title: str = "CTkDialog",
font: Optional[Union[tuple, CTkFont]] = None,
text: str = "CTkDialog"): text: str = "CTkDialog"):
super().__init__(fg_color=fg_color) super().__init__(fg_color=fg_color)
@ -39,9 +41,11 @@ class CTkInputDialog(CTkToplevel):
self._user_input: Union[str, None] = None self._user_input: Union[str, None] = None
self._running: bool = False self._running: bool = False
self._title = title
self._text = text self._text = text
self._font = font
self.title(title) self.title(self._title)
self.lift() # lift window on top self.lift() # lift window on top
self.attributes("-topmost", True) # stay on top self.attributes("-topmost", True) # stay on top
self.protocol("WM_DELETE_WINDOW", self._on_closing) self.protocol("WM_DELETE_WINDOW", self._on_closing)
@ -50,7 +54,6 @@ class CTkInputDialog(CTkToplevel):
self.grab_set() # make other windows not clickable self.grab_set() # make other windows not clickable
def _create_widgets(self): def _create_widgets(self):
self.grid_columnconfigure((0, 1), weight=1) self.grid_columnconfigure((0, 1), weight=1)
self.rowconfigure(0, weight=1) self.rowconfigure(0, weight=1)
@ -59,14 +62,16 @@ class CTkInputDialog(CTkToplevel):
wraplength=300, wraplength=300,
fg_color="transparent", fg_color="transparent",
text_color=self._text_color, text_color=self._text_color,
text=self._text,) text=self._text,
font=self._font)
self._label.grid(row=0, column=0, columnspan=2, padx=20, pady=20, sticky="ew") self._label.grid(row=0, column=0, columnspan=2, padx=20, pady=20, sticky="ew")
self._entry = CTkEntry(master=self, self._entry = CTkEntry(master=self,
width=230, width=230,
fg_color=self._entry_fg_color, fg_color=self._entry_fg_color,
border_color=self._entry_border_color, border_color=self._entry_border_color,
text_color=self._entry_text_color) text_color=self._entry_text_color,
font=self._font)
self._entry.grid(row=1, column=0, columnspan=2, padx=20, pady=(0, 20), sticky="ew") self._entry.grid(row=1, column=0, columnspan=2, padx=20, pady=(0, 20), sticky="ew")
self._ok_button = CTkButton(master=self, self._ok_button = CTkButton(master=self,
@ -76,6 +81,7 @@ class CTkInputDialog(CTkToplevel):
hover_color=self._button_hover_color, hover_color=self._button_hover_color,
text_color=self._button_text_color, text_color=self._button_text_color,
text='Ok', text='Ok',
font=self._font,
command=self._ok_event) command=self._ok_event)
self._ok_button.grid(row=2, column=0, columnspan=1, padx=(20, 10), pady=(0, 20), sticky="ew") self._ok_button.grid(row=2, column=0, columnspan=1, padx=(20, 10), pady=(0, 20), sticky="ew")
@ -86,7 +92,8 @@ class CTkInputDialog(CTkToplevel):
hover_color=self._button_hover_color, hover_color=self._button_hover_color,
text_color=self._button_text_color, text_color=self._button_text_color,
text='Cancel', text='Cancel',
command=self._ok_event) font=self._font,
command=self._cancel_event)
self._cancel_button.grid(row=2, column=1, columnspan=1, padx=(10, 20), pady=(0, 20), sticky="ew") self._cancel_button.grid(row=2, column=1, columnspan=1, padx=(10, 20), pady=(0, 20), sticky="ew")
self.after(150, lambda: self._entry.focus()) # set focus to entry with slight delay, otherwise it won't work self.after(150, lambda: self._entry.focus()) # set focus to entry with slight delay, otherwise it won't work

View File

@ -1,4 +1,5 @@
import tkinter import tkinter
import tkinterDnD
from distutils.version import StrictVersion as Version from distutils.version import StrictVersion as Version
import sys import sys
import os import os
@ -12,8 +13,10 @@ from .widgets.appearance_mode import CTkAppearanceModeBaseClass
from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
TK_CLASS = tkinterDnD.Tk
class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
class CTk(TK_CLASS, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
""" """
Main app window with dark titlebar on Windows and macOS. Main app window with dark titlebar on Windows and macOS.
For detailed information check out the documentation. For detailed information check out the documentation.
@ -35,19 +38,11 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
self._enable_macos_dark_title_bar() self._enable_macos_dark_title_bar()
# call init methods of super classes # call init methods of super classes
tkinter.Tk.__init__(self, **pop_from_dict_by_set(kwargs, self._valid_tk_constructor_arguments)) TK_CLASS.__init__(self, **pop_from_dict_by_set(kwargs, self._valid_tk_constructor_arguments))
CTkAppearanceModeBaseClass.__init__(self) CTkAppearanceModeBaseClass.__init__(self)
CTkScalingBaseClass.__init__(self, scaling_type="window") CTkScalingBaseClass.__init__(self, scaling_type="window")
check_kwargs_empty(kwargs, raise_error=True) check_kwargs_empty(kwargs, raise_error=True)
try:
# Set Windows titlebar icon
if sys.platform.startswith("win"):
customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.after(200, lambda: self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico")))
except Exception:
pass
self._current_width = 600 # initial window size, independent of scaling self._current_width = 600 # initial window size, independent of scaling
self._current_height = 500 self._current_height = 500
self._min_width: int = 0 self._min_width: int = 0
@ -61,23 +56,31 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
# set bg of tkinter.Tk # set bg of tkinter.Tk
super().configure(bg=self._apply_appearance_mode(self._fg_color)) super().configure(bg=self._apply_appearance_mode(self._fg_color))
# set title and initial geometry # set title
self.title("CTk") self.title("CTk")
# self.geometry(f"{self._current_width}x{self._current_height}")
# indicator variables
self._iconbitmap_method_called = False # indicates if wm_iconbitmap method got called
self._state_before_windows_set_titlebar_color = None self._state_before_windows_set_titlebar_color = None
self._window_exists = False # indicates if the window is already shown through update() or mainloop() after init self._window_exists = False # indicates if the window is already shown through update() or mainloop() after init
self._withdraw_called_before_window_exists = False # indicates if withdraw() was called before window is first shown through update() or mainloop() self._withdraw_called_before_window_exists = False # indicates if withdraw() was called before window is first shown through update() or mainloop()
self._iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop() self._iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop()
self._block_update_dimensions_event = False
# 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"): if sys.platform.startswith("win"):
self._windows_set_titlebar_color(self._get_appearance_mode()) self._windows_set_titlebar_color(self._get_appearance_mode())
self.bind('<Configure>', self._update_dimensions_event) self.bind('<Configure>', self._update_dimensions_event)
self.bind('<FocusIn>', self._focus_in_event) self.bind('<FocusIn>', self._focus_in_event)
self._block_update_dimensions_event = False
def destroy(self): def destroy(self):
self._disable_macos_dark_title_bar() self._disable_macos_dark_title_bar()
@ -140,24 +143,26 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
def update(self): def update(self):
if self._window_exists is False: if self._window_exists is False:
self._window_exists = True
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists: if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists:
# print("window dont exists -> deiconify in update") # print("window dont exists -> deiconify in update")
self.deiconify() self.deiconify()
self._window_exists = True
super().update() super().update()
def mainloop(self, *args, **kwargs): def mainloop(self, *args, **kwargs):
if not self._window_exists: if not self._window_exists:
self._window_exists = True
if sys.platform.startswith("win"): 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: if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists:
# print("window dont exists -> deiconify in mainloop") # print("window dont exists -> deiconify in mainloop")
self.deiconify() self.deiconify()
self._window_exists = True
super().mainloop(*args, **kwargs) super().mainloop(*args, **kwargs)
def resizable(self, width: bool = None, height: bool = None): def resizable(self, width: bool = None, height: bool = None):
@ -219,6 +224,23 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
else: else:
return super().cget(attribute_name) return super().cget(attribute_name)
def wm_iconbitmap(self, bitmap=None, default=None):
self._iconbitmap_method_called = True
super().wm_iconbitmap(bitmap, default)
def iconbitmap(self, bitmap=None, default=None):
self._iconbitmap_method_called = True
super().wm_iconbitmap(bitmap, default)
def _windows_set_titlebar_icon(self):
try:
# if not the user already called iconbitmap method, set icon
if not self._iconbitmap_method_called:
customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico"))
except Exception:
pass
@classmethod @classmethod
def _enable_macos_dark_title_bar(cls): def _enable_macos_dark_title_bar(cls):
if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS
@ -253,9 +275,11 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
# print("window_exists -> state_before_windows_set_titlebar_color: ", self.state_before_windows_set_titlebar_color) # 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": 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 super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
else: else:
# print("window dont exists -> withdraw and update") # print("window dont exists -> withdraw and update")
self.focused_widget_before_widthdraw = self.focus_get()
super().withdraw() super().withdraw()
super().update() super().update()
@ -284,7 +308,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
except Exception as err: except Exception as err:
print(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) # print("window_exists -> return to original state: ", self.state_before_windows_set_titlebar_color)
if self._state_before_windows_set_titlebar_color == "normal": if self._state_before_windows_set_titlebar_color == "normal":
self.deiconify() self.deiconify()
@ -297,6 +321,10 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
else: else:
pass # wait for update or mainloop to be called pass # wait for update or mainloop to be called
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): def _set_appearance_mode(self, mode_string: str):
super()._set_appearance_mode(mode_string) 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. 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", "highlightbackground", "highlightthickness", "menu", "relief",
"screen", "takefocus", "use", "visual", "width"} "screen", "takefocus", "use", "visual", "width"}
@ -62,19 +62,28 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
# set title of tkinter.Toplevel # set title of tkinter.Toplevel
super().title("CTkToplevel") super().title("CTkToplevel")
# indicator variables
self._iconbitmap_method_called = True
self._state_before_windows_set_titlebar_color = None self._state_before_windows_set_titlebar_color = None
self._windows_set_titlebar_color_called = False # indicates if windows_set_titlebar_color was called, stays True until revert_withdraw_after_windows_set_titlebar_color is called self._windows_set_titlebar_color_called = False # indicates if windows_set_titlebar_color was called, stays True until revert_withdraw_after_windows_set_titlebar_color is called
self._withdraw_called_after_windows_set_titlebar_color = False # indicates if withdraw() was called after windows_set_titlebar_color self._withdraw_called_after_windows_set_titlebar_color = False # indicates if withdraw() was called after windows_set_titlebar_color
self._iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color self._iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color
self._block_update_dimensions_event = False
# 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"): if sys.platform.startswith("win"):
self._windows_set_titlebar_color(self._get_appearance_mode()) self._windows_set_titlebar_color(self._get_appearance_mode())
self.bind('<Configure>', self._update_dimensions_event) self.bind('<Configure>', self._update_dimensions_event)
self.bind('<FocusIn>', self._focus_in_event) self.bind('<FocusIn>', self._focus_in_event)
self._block_update_dimensions_event = False
def destroy(self): def destroy(self):
self._disable_macos_dark_title_bar() self._disable_macos_dark_title_bar()
@ -190,6 +199,19 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
else: else:
return super().cget(attribute_name) return super().cget(attribute_name)
def wm_iconbitmap(self, bitmap=None, default=None):
self._iconbitmap_method_called = True
super().wm_iconbitmap(bitmap, default)
def _windows_set_titlebar_icon(self):
try:
# if not the user already called iconbitmap method, set icon
if not self._iconbitmap_method_called:
customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico"))
except Exception:
pass
@classmethod @classmethod
def _enable_macos_dark_title_bar(cls): def _enable_macos_dark_title_bar(cls):
if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS
@ -219,6 +241,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
if sys.platform.startswith("win") and not self._deactivate_windows_window_header_manipulation: if sys.platform.startswith("win") and not self._deactivate_windows_window_header_manipulation:
self._state_before_windows_set_titlebar_color = self.state() 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().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
super().update() super().update()
@ -249,6 +272,10 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
self._windows_set_titlebar_color_called = True self._windows_set_titlebar_color_called = True
self.after(5, self._revert_withdraw_after_windows_set_titlebar_color) 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): def _revert_withdraw_after_windows_set_titlebar_color(self):
""" if in a short time (5ms) after """ """ if in a short time (5ms) after """
if self._windows_set_titlebar_color_called: 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_switch import CTkSwitch
from .ctk_tabview import CTkTabview from .ctk_tabview import CTkTabview
from .ctk_textbox import CTkTextbox 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 - destroy() must be called when sub-class is destroyed
- _set_appearance_mode() abstractmethod, gets called when appearance mode changes, must be overridden - _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): def __init__(self):

View File

@ -9,9 +9,6 @@ try:
except ImportError: except ImportError:
from typing_extensions import TypedDict 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 .... import windows # import windows for isinstance checks
from ..theme import ThemeManager from ..theme import ThemeManager
@ -74,7 +71,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
super().bind('<Configure>', self._update_dimensions_event) 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 # 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 master_old_configure = self.master.config
def new_configure(*args, **kwargs): def new_configure(*args, **kwargs):
@ -179,8 +176,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
elif isinstance(image, CTkImage): elif isinstance(image, CTkImage):
return image return image
else: else:
warnings.warn(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. " + warnings.warn(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. Image can not be scaled on HighDPI displays, use CTkImage instead.\n")
f"Image can not be scaled on HighDPI displays, use CTkImage instead.\n")
return image return image
def _update_dimensions_event(self, event): def _update_dimensions_event(self, event):
@ -197,12 +193,15 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
if master_widget is None: if master_widget is None:
master_widget = self.master 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": if master_widget.cget("fg_color") is not None and master_widget.cget("fg_color") != "transparent":
return master_widget.cget("fg_color") 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 # 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) 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 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) 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 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} self._last_geometry_manager_call = {"function": super().place, "kwargs": kwargs}
return super().place(**self._apply_argument_scaling(kwargs)) return super().place(**self._apply_argument_scaling(kwargs))

View File

@ -40,7 +40,7 @@ class CTkButton(CTkBaseClass):
text: str = "CTkButton", text: str = "CTkButton",
font: Optional[Union[tuple, CTkFont]] = None, font: Optional[Union[tuple, CTkFont]] = None,
textvariable: Union[tkinter.Variable, None] = None, textvariable: Union[tkinter.Variable, None] = None,
image: Union[CTkImage, None] = None, image: Union[CTkImage, "ImageTk.PhotoImage", None] = None,
state: str = "normal", state: str = "normal",
hover: bool = True, hover: bool = True,
command: Union[Callable[[], None], None] = None, command: Union[Callable[[], None], None] = None,
@ -169,8 +169,11 @@ class CTkButton(CTkBaseClass):
def _update_image(self): def _update_image(self):
if self._image_label is not None: if self._image_label is not None:
self._image_label.configure(image=self._image.create_scaled_photo_image(self._get_widget_scaling(), if isinstance(self._image, CTkImage):
self._get_appearance_mode())) 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): def destroy(self):
if isinstance(self._font, CTkFont): if isinstance(self._font, CTkFont):
@ -425,6 +428,7 @@ class CTkButton(CTkBaseClass):
if "command" in kwargs: if "command" in kwargs:
self._command = kwargs.pop("command") self._command = kwargs.pop("command")
self._set_cursor()
if "compound" in kwargs: if "compound" in kwargs:
self._compound = kwargs.pop("compound") self._compound = kwargs.pop("compound")
@ -432,6 +436,7 @@ class CTkButton(CTkBaseClass):
if "anchor" in kwargs: if "anchor" in kwargs:
self._anchor = kwargs.pop("anchor") self._anchor = kwargs.pop("anchor")
self._create_grid()
require_redraw = True require_redraw = True
super().configure(require_redraw=require_redraw, **kwargs) super().configure(require_redraw=require_redraw, **kwargs)

View File

@ -51,20 +51,20 @@ class CTkCheckBox(CTkBaseClass):
self._checkbox_height = checkbox_height self._checkbox_height = checkbox_height
# color # color
self._fg_color = ThemeManager.theme["CTkCheckbox"]["fg_color"] if fg_color is None else self._check_color_type(fg_color) self._fg_color = ThemeManager.theme["CTkCheckBox"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
self._hover_color = ThemeManager.theme["CTkCheckbox"]["hover_color"] if hover_color is None else self._check_color_type(hover_color) self._hover_color = ThemeManager.theme["CTkCheckBox"]["hover_color"] if hover_color is None else self._check_color_type(hover_color)
self._border_color = ThemeManager.theme["CTkCheckbox"]["border_color"] if border_color is None else self._check_color_type(border_color) self._border_color = ThemeManager.theme["CTkCheckBox"]["border_color"] if border_color is None else self._check_color_type(border_color)
self._checkmark_color = ThemeManager.theme["CTkCheckbox"]["checkmark_color"] if checkmark_color is None else self._check_color_type(checkmark_color) self._checkmark_color = ThemeManager.theme["CTkCheckBox"]["checkmark_color"] if checkmark_color is None else self._check_color_type(checkmark_color)
# shape # shape
self._corner_radius = ThemeManager.theme["CTkCheckbox"]["corner_radius"] if corner_radius is None else corner_radius self._corner_radius = ThemeManager.theme["CTkCheckBox"]["corner_radius"] if corner_radius is None else corner_radius
self._border_width = ThemeManager.theme["CTkCheckbox"]["border_width"] if border_width is None else border_width self._border_width = ThemeManager.theme["CTkCheckBox"]["border_width"] if border_width is None else border_width
# text # text
self._text = text self._text = text
self._text_label: Union[tkinter.Label, None] = None self._text_label: Union[tkinter.Label, None] = None
self._text_color = ThemeManager.theme["CTkCheckbox"]["text_color"] if text_color is None else self._check_color_type(text_color) self._text_color = ThemeManager.theme["CTkCheckBox"]["text_color"] if text_color is None else self._check_color_type(text_color)
self._text_color_disabled = ThemeManager.theme["CTkCheckbox"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled) self._text_color_disabled = ThemeManager.theme["CTkCheckBox"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
# font # font
self._font = CTkFont() if font is None else self._check_font_type(font) self._font = CTkFont() if font is None else self._check_font_type(font)
@ -265,12 +265,20 @@ class CTkCheckBox(CTkBaseClass):
self._hover_color = self._check_color_type(kwargs.pop("hover_color")) self._hover_color = self._check_color_type(kwargs.pop("hover_color"))
require_redraw = True require_redraw = True
if "border_color" in kwargs:
self._border_color = self._check_color_type(kwargs.pop("border_color"))
require_redraw = True
if "checkmark_color" in kwargs:
self._checkmark_color = self._check_color_type(kwargs.pop("checkmark_color"))
require_redraw = True
if "text_color" in kwargs: if "text_color" in kwargs:
self._text_color = self._check_color_type(kwargs.pop("text_color")) self._text_color = self._check_color_type(kwargs.pop("text_color"))
require_redraw = True require_redraw = True
if "border_color" in kwargs: if "text_color_disabled" in kwargs:
self._border_color = self._check_color_type(kwargs.pop("border_color")) self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
require_redraw = True require_redraw = True
if "hover" in kwargs: if "hover" in kwargs:

View File

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

View File

@ -24,8 +24,8 @@ class CTkFrame(CTkBaseClass):
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: Optional[Union[str, Tuple[str, str]]] = None,
border_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, overwrite_preferred_drawing_method: Union[str, None] = None,
**kwargs): **kwargs):

View File

@ -14,6 +14,8 @@ class CTkLabel(CTkBaseClass):
""" """
Label with rounded corners. Default is fg_color=None (transparent fg_color). Label with rounded corners. Default is fg_color=None (transparent fg_color).
For detailed information check out the documentation. 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: # attributes that are passed to and managed by the tkinter entry only:
@ -29,6 +31,7 @@ class CTkLabel(CTkBaseClass):
bg_color: Union[str, Tuple[str, str]] = "transparent", bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None, fg_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None, text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
text: str = "CTkLabel", text: str = "CTkLabel",
font: Optional[Union[tuple, CTkFont]] = None, font: Optional[Union[tuple, CTkFont]] = None,
@ -45,6 +48,14 @@ class CTkLabel(CTkBaseClass):
self._fg_color = ThemeManager.theme["CTkLabel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True) self._fg_color = ThemeManager.theme["CTkLabel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
self._text_color = ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else self._check_color_type(text_color) self._text_color = ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else self._check_color_type(text_color)
if text_color_disabled is None:
if "text_color_disabled" in ThemeManager.theme["CTkLabel"]:
self._text_color_disabled = ThemeManager.theme["CTkLabel"]["text_color"]
else:
self._text_color_disabled = self._text_color
else:
self._text_color_disabled = self._check_color_type(text_color_disabled)
# shape # shape
self._corner_radius = ThemeManager.theme["CTkLabel"]["corner_radius"] if corner_radius is None else corner_radius self._corner_radius = ThemeManager.theme["CTkLabel"]["corner_radius"] if corner_radius is None else corner_radius
@ -159,6 +170,7 @@ class CTkLabel(CTkBaseClass):
outline=self._apply_appearance_mode(self._bg_color)) outline=self._apply_appearance_mode(self._bg_color))
self._label.configure(fg=self._apply_appearance_mode(self._text_color), self._label.configure(fg=self._apply_appearance_mode(self._text_color),
disabledforeground=self._apply_appearance_mode(self._text_color_disabled),
bg=self._apply_appearance_mode(self._bg_color)) bg=self._apply_appearance_mode(self._bg_color))
else: else:
self._canvas.itemconfig("inner_parts", self._canvas.itemconfig("inner_parts",
@ -166,6 +178,7 @@ class CTkLabel(CTkBaseClass):
outline=self._apply_appearance_mode(self._fg_color)) outline=self._apply_appearance_mode(self._fg_color))
self._label.configure(fg=self._apply_appearance_mode(self._text_color), self._label.configure(fg=self._apply_appearance_mode(self._text_color),
disabledforeground=self._apply_appearance_mode(self._text_color_disabled),
bg=self._apply_appearance_mode(self._fg_color)) bg=self._apply_appearance_mode(self._fg_color))
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color)) self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
@ -184,6 +197,10 @@ class CTkLabel(CTkBaseClass):
self._text_color = self._check_color_type(kwargs.pop("text_color")) self._text_color = self._check_color_type(kwargs.pop("text_color"))
require_redraw = True require_redraw = True
if "text_color_disabled" in kwargs:
self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
require_redraw = True
if "text" in kwargs: if "text" in kwargs:
self._text = kwargs.pop("text") self._text = kwargs.pop("text")
self._label.configure(text=self._text) self._label.configure(text=self._text)
@ -228,6 +245,8 @@ class CTkLabel(CTkBaseClass):
return self._fg_color return self._fg_color
elif attribute_name == "text_color": elif attribute_name == "text_color":
return self._text_color return self._text_color
elif attribute_name == "text_color_disabled":
return self._text_color_disabled
elif attribute_name == "text": elif attribute_name == "text":
return self._text return self._text

View File

@ -243,6 +243,10 @@ class CTkOptionMenu(CTkBaseClass):
self._text_color = self._check_color_type(kwargs.pop("text_color")) self._text_color = self._check_color_type(kwargs.pop("text_color"))
require_redraw = True require_redraw = True
if "text_color_disabled" in kwargs:
self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
require_redraw = True
if "dropdown_fg_color" in kwargs: if "dropdown_fg_color" in kwargs:
self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_fg_color")) self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_fg_color"))
@ -261,8 +265,12 @@ class CTkOptionMenu(CTkBaseClass):
self._update_font() self._update_font()
if "command" in kwargs: if "dropdown_font" in kwargs:
self._command = kwargs.pop("command") self._dropdown_menu.configure(font=kwargs.pop("dropdown_font"))
if "values" in kwargs:
self._values = kwargs.pop("values")
self._dropdown_menu.configure(values=self._values)
if "variable" in kwargs: if "variable" in kwargs:
if self._variable is not None: # remove old callback if self._variable is not None: # remove old callback
@ -277,19 +285,15 @@ class CTkOptionMenu(CTkBaseClass):
else: else:
self._variable = None self._variable = None
if "values" in kwargs: if "state" in kwargs:
self._values = kwargs.pop("values") self._state = kwargs.pop("state")
self._dropdown_menu.configure(values=self._values) require_redraw = True
if "dropdown_font" in kwargs:
self._dropdown_menu.configure(font=kwargs.pop("dropdown_font"))
if "hover" in kwargs: if "hover" in kwargs:
self._hover = kwargs.pop("hover") self._hover = kwargs.pop("hover")
if "state" in kwargs: if "command" in kwargs:
self._state = kwargs.pop("state") self._command = kwargs.pop("command")
require_redraw = True
if "dynamic_resizing" in kwargs: if "dynamic_resizing" in kwargs:
self._dynamic_resizing = kwargs.pop("dynamic_resizing") self._dynamic_resizing = kwargs.pop("dynamic_resizing")

View File

@ -50,20 +50,20 @@ class CTkRadioButton(CTkBaseClass):
self._radiobutton_height = radiobutton_height self._radiobutton_height = radiobutton_height
# color # color
self._fg_color = ThemeManager.theme["CTkRadiobutton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color) self._fg_color = ThemeManager.theme["CTkRadioButton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
self._hover_color = ThemeManager.theme["CTkRadiobutton"]["hover_color"] if hover_color is None else self._check_color_type(hover_color) self._hover_color = ThemeManager.theme["CTkRadioButton"]["hover_color"] if hover_color is None else self._check_color_type(hover_color)
self._border_color = ThemeManager.theme["CTkRadiobutton"]["border_color"] if border_color is None else self._check_color_type(border_color) self._border_color = ThemeManager.theme["CTkRadioButton"]["border_color"] if border_color is None else self._check_color_type(border_color)
# shape # shape
self._corner_radius = ThemeManager.theme["CTkRadiobutton"]["corner_radius"] if corner_radius is None else corner_radius self._corner_radius = ThemeManager.theme["CTkRadioButton"]["corner_radius"] if corner_radius is None else corner_radius
self._border_width_unchecked = ThemeManager.theme["CTkRadiobutton"]["border_width_unchecked"] if border_width_unchecked is None else border_width_unchecked self._border_width_unchecked = ThemeManager.theme["CTkRadioButton"]["border_width_unchecked"] if border_width_unchecked is None else border_width_unchecked
self._border_width_checked = ThemeManager.theme["CTkRadiobutton"]["border_width_checked"] if border_width_checked is None else border_width_checked self._border_width_checked = ThemeManager.theme["CTkRadioButton"]["border_width_checked"] if border_width_checked is None else border_width_checked
# text # text
self._text = text self._text = text
self._text_label: Union[tkinter.Label, None] = None self._text_label: Union[tkinter.Label, None] = None
self._text_color = ThemeManager.theme["CTkRadiobutton"]["text_color"] if text_color is None else self._check_color_type(text_color) self._text_color = ThemeManager.theme["CTkRadioButton"]["text_color"] if text_color is None else self._check_color_type(text_color)
self._text_color_disabled = ThemeManager.theme["CTkRadiobutton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled) self._text_color_disabled = ThemeManager.theme["CTkRadioButton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
# font # font
self._font = CTkFont() if font is None else self._check_font_type(font) self._font = CTkFont() if font is None else self._check_font_type(font)
@ -85,6 +85,7 @@ class CTkRadioButton(CTkBaseClass):
self.grid_columnconfigure(0, weight=0) self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6)) self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
self.grid_columnconfigure(2, weight=1) self.grid_columnconfigure(2, weight=1)
self.grid_rowconfigure(0, weight=1)
self._bg_canvas = CTkCanvas(master=self, self._bg_canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,

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

@ -10,6 +10,7 @@ from .theme import ThemeManager
from .font import CTkFont from .font import CTkFont
from .ctk_button import CTkButton from .ctk_button import CTkButton
from .ctk_frame import CTkFrame from .ctk_frame import CTkFrame
from .utility import check_kwargs_empty
class CTkSegmentedButton(CTkFrame): class CTkSegmentedButton(CTkFrame):
@ -40,10 +41,9 @@ class CTkSegmentedButton(CTkFrame):
variable: Union[tkinter.Variable, None] = None, variable: Union[tkinter.Variable, None] = None,
dynamic_resizing: bool = True, dynamic_resizing: bool = True,
command: Union[Callable[[str], None], None] = None, command: Union[Callable[[str], None], None] = None,
state: str = "normal", state: str = "normal"):
**kwargs):
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) super().__init__(master=master, bg_color=bg_color, width=width, height=height)
self._sb_fg_color = ThemeManager.theme["CTkSegmentedButton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color) self._sb_fg_color = ThemeManager.theme["CTkSegmentedButton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
@ -186,7 +186,7 @@ class CTkSegmentedButton(CTkFrame):
for index, value in enumerate(self._value_list): for index, value in enumerate(self._value_list):
self.grid_columnconfigure(index, weight=1, minsize=self._current_height) self.grid_columnconfigure(index, weight=1, minsize=self._current_height)
self._buttons_dict[value].grid(row=0, column=index, sticky="ew") self._buttons_dict[value].grid(row=0, column=index, sticky="nsew")
def _create_buttons_from_values(self): def _create_buttons_from_values(self):
assert len(self._buttons_dict) == 0 assert len(self._buttons_dict) == 0
@ -197,6 +197,23 @@ class CTkSegmentedButton(CTkFrame):
self._configure_button_corners_for_index(index) self._configure_button_corners_for_index(index)
def configure(self, **kwargs): def configure(self, **kwargs):
if "width" in kwargs:
super().configure(width=kwargs.pop("width"))
if "height" in kwargs:
super().configure(height=kwargs.pop("height"))
if "corner_radius" in kwargs:
self._sb_corner_radius = kwargs.pop("corner_radius")
super().configure(corner_radius=self._sb_corner_radius)
for button in self._buttons_dict.values():
button.configure(corner_radius=self._sb_corner_radius)
if "border_width" in kwargs:
self._sb_border_width = kwargs.pop("border_width")
for button in self._buttons_dict.values():
button.configure(border_width=self._sb_border_width)
if "bg_color" in kwargs: if "bg_color" in kwargs:
super().configure(bg_color=kwargs.pop("bg_color")) super().configure(bg_color=kwargs.pop("bg_color"))
@ -296,14 +313,20 @@ class CTkSegmentedButton(CTkFrame):
for button in self._buttons_dict.values(): for button in self._buttons_dict.values():
button.configure(state=self._state) button.configure(state=self._state)
super().configure(**kwargs) check_kwargs_empty(kwargs, raise_error=True)
def cget(self, attribute_name: str) -> any: def cget(self, attribute_name: str) -> any:
if attribute_name == "corner_radius": if attribute_name == "width":
return super().cget(attribute_name)
elif attribute_name == "height":
return super().cget(attribute_name)
elif attribute_name == "corner_radius":
return self._sb_corner_radius return self._sb_corner_radius
elif attribute_name == "border_width": elif attribute_name == "border_width":
return self._sb_border_width return self._sb_border_width
elif attribute_name == "bg_color":
return super().cget(attribute_name)
elif attribute_name == "fg_color": elif attribute_name == "fg_color":
return self._sb_fg_color return self._sb_fg_color
elif attribute_name == "selected_color": elif attribute_name == "selected_color":
@ -331,7 +354,7 @@ class CTkSegmentedButton(CTkFrame):
return self._command return self._command
else: else:
return super().cget(attribute_name) raise ValueError(f"'{attribute_name}' is not a supported argument. Look at the documentation for supported arguments.")
def set(self, value: str, from_variable_callback: bool = False, from_button_callback: bool = False): def set(self, value: str, from_variable_callback: bool = False, from_button_callback: bool = False):
if value == self._current_value: if value == self._current_value:
@ -360,6 +383,9 @@ class CTkSegmentedButton(CTkFrame):
def get(self) -> str: def get(self) -> str:
return self._current_value return self._current_value
def index(self, value: str) -> int:
return self._value_list.index(value)
def insert(self, index: int, value: str): def insert(self, index: int, value: str):
if value not in self._buttons_dict: if value not in self._buttons_dict:
if value != "": if value != "":

View File

@ -199,15 +199,30 @@ class CTkSlider(CTkBaseClass):
outline=self._apply_appearance_mode(self._button_color)) outline=self._apply_appearance_mode(self._button_color))
def configure(self, require_redraw=False, **kwargs): def configure(self, require_redraw=False, **kwargs):
if "state" in kwargs: if "corner_radius" in kwargs:
self._state = kwargs.pop("state") self._corner_radius = kwargs.pop("corner_radius")
self._set_cursor() require_redraw = True
if "button_corner_radius" in kwargs:
self._button_corner_radius = kwargs.pop("button_corner_radius")
require_redraw = True
if "border_width" in kwargs:
self._border_width = kwargs.pop("border_width")
require_redraw = True
if "button_length" in kwargs:
self._button_length = kwargs.pop("button_length")
require_redraw = True require_redraw = True
if "fg_color" in kwargs: if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color")) self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
require_redraw = True 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: if "progress_color" in kwargs:
self._progress_color = self._check_color_type(kwargs.pop("progress_color"), transparency=True) self._progress_color = self._check_color_type(kwargs.pop("progress_color"), transparency=True)
require_redraw = True require_redraw = True
@ -220,20 +235,17 @@ class CTkSlider(CTkBaseClass):
self._button_hover_color = self._check_color_type(kwargs.pop("button_hover_color")) self._button_hover_color = self._check_color_type(kwargs.pop("button_hover_color"))
require_redraw = True 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 "border_width" in kwargs:
self._border_width = kwargs.pop("border_width")
require_redraw = True
if "from_" in kwargs: if "from_" in kwargs:
self._from_ = kwargs.pop("from_") self._from_ = kwargs.pop("from_")
if "to" in kwargs: if "to" in kwargs:
self._to = kwargs.pop("to") self._to = kwargs.pop("to")
if "state" in kwargs:
self._state = kwargs.pop("state")
self._set_cursor()
require_redraw = True
if "number_of_steps" in kwargs: if "number_of_steps" in kwargs:
self._number_of_steps = kwargs.pop("number_of_steps") self._number_of_steps = kwargs.pop("number_of_steps")
@ -255,6 +267,10 @@ class CTkSlider(CTkBaseClass):
else: else:
self._variable = None self._variable = None
if "orientation" in kwargs:
self._orientation = kwargs.pop("orientation")
require_redraw = True
super().configure(require_redraw=require_redraw, **kwargs) super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any: def cget(self, attribute_name: str) -> any:

View File

@ -54,7 +54,7 @@ class CTkSwitch(CTkBaseClass):
# color # color
self._border_color = self._check_color_type(border_color, transparency=True) 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._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_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) 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(0, weight=0)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6)) self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
self.grid_columnconfigure(2, weight=1) self.grid_columnconfigure(2, weight=1)
self.grid_rowconfigure(0, weight=1)
self._bg_canvas = CTkCanvas(master=self, self._bg_canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
@ -221,23 +222,29 @@ class CTkSwitch(CTkBaseClass):
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color)) self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
if self._border_color == "transparent": if self._border_color == "transparent":
self._canvas.itemconfig("border_parts", fill=self._apply_appearance_mode(self._bg_color), self._canvas.itemconfig("border_parts",
fill=self._apply_appearance_mode(self._bg_color),
outline=self._apply_appearance_mode(self._bg_color)) outline=self._apply_appearance_mode(self._bg_color))
else: else:
self._canvas.itemconfig("border_parts", fill=self._apply_appearance_mode(self._border_color), self._canvas.itemconfig("border_parts",
fill=self._apply_appearance_mode(self._border_color),
outline=self._apply_appearance_mode(self._border_color)) outline=self._apply_appearance_mode(self._border_color))
self._canvas.itemconfig("inner_parts", fill=self._apply_appearance_mode(self._fg_color), self._canvas.itemconfig("inner_parts",
fill=self._apply_appearance_mode(self._fg_color),
outline=self._apply_appearance_mode(self._fg_color)) outline=self._apply_appearance_mode(self._fg_color))
if self._progress_color == "transparent": if self._progress_color == "transparent":
self._canvas.itemconfig("progress_parts", fill=self._apply_appearance_mode(self._fg_color), self._canvas.itemconfig("progress_parts",
fill=self._apply_appearance_mode(self._fg_color),
outline=self._apply_appearance_mode(self._fg_color)) outline=self._apply_appearance_mode(self._fg_color))
else: else:
self._canvas.itemconfig("progress_parts", fill=self._apply_appearance_mode(self._progress_color), self._canvas.itemconfig("progress_parts",
fill=self._apply_appearance_mode(self._progress_color),
outline=self._apply_appearance_mode(self._progress_color)) outline=self._apply_appearance_mode(self._progress_color))
self._canvas.itemconfig("slider_parts", fill=self._apply_appearance_mode(self._button_color), self._canvas.itemconfig("slider_parts",
fill=self._apply_appearance_mode(self._button_color),
outline=self._apply_appearance_mode(self._button_color)) outline=self._apply_appearance_mode(self._button_color))
if self._state == tkinter.DISABLED: if self._state == tkinter.DISABLED:
@ -292,6 +299,10 @@ class CTkSwitch(CTkBaseClass):
self._fg_color = self._check_color_type(kwargs.pop("fg_color")) self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
require_redraw = True 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: if "progress_color" in kwargs:
self._progress_color = self._check_color_type(kwargs.pop("progress_color"), transparency=True) self._progress_color = self._check_color_type(kwargs.pop("progress_color"), transparency=True)
require_redraw = True require_redraw = True
@ -304,8 +315,12 @@ class CTkSwitch(CTkBaseClass):
self._button_hover_color = self._check_color_type(kwargs.pop("button_hover_color")) self._button_hover_color = self._check_color_type(kwargs.pop("button_hover_color"))
require_redraw = True require_redraw = True
if "border_color" in kwargs: if "text_color" in kwargs:
self._border_color = self._check_color_type(kwargs.pop("border_color"), transparency=True) 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 require_redraw = True
if "hover" in kwargs: if "hover" in kwargs:

View File

@ -15,8 +15,8 @@ class CTkTabview(CTkBaseClass):
For detailed information check out the documentation. For detailed information check out the documentation.
""" """
_top_spacing: int = 10 # px on top of the buttons _outer_spacing: int = 10 # px on top or below the button
_top_button_overhang: int = 8 # px _outer_button_overhang: int = 8 # px
_button_height: int = 26 _button_height: int = 26
_segmented_button_border_width: int = 3 _segmented_button_border_width: int = 3
@ -41,6 +41,7 @@ class CTkTabview(CTkBaseClass):
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
command: Union[Callable, None] = None, command: Union[Callable, None] = None,
anchor: str = "center",
state: str = "normal", state: str = "normal",
**kwargs): **kwargs):
@ -65,12 +66,13 @@ class CTkTabview(CTkBaseClass):
# shape # shape
self._corner_radius = ThemeManager.theme["CTkFrame"]["corner_radius"] if corner_radius is None else corner_radius self._corner_radius = ThemeManager.theme["CTkFrame"]["corner_radius"] if corner_radius is None else corner_radius
self._border_width = ThemeManager.theme["CTkFrame"]["border_width"] if border_width is None else border_width self._border_width = ThemeManager.theme["CTkFrame"]["border_width"] if border_width is None else border_width
self._anchor = anchor
self._canvas = CTkCanvas(master=self, self._canvas = CTkCanvas(master=self,
bg=self._apply_appearance_mode(self._bg_color), bg=self._apply_appearance_mode(self._bg_color),
highlightthickness=0, highlightthickness=0,
width=self._apply_widget_scaling(self._desired_width), width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height - self._top_spacing - self._top_button_overhang)) height=self._apply_widget_scaling(self._desired_height - self._outer_spacing - self._outer_button_overhang))
self._draw_engine = DrawEngine(self._canvas) self._draw_engine = DrawEngine(self._canvas)
self._segmented_button = CTkSegmentedButton(self, self._segmented_button = CTkSegmentedButton(self,
@ -99,9 +101,9 @@ class CTkTabview(CTkBaseClass):
self._draw() self._draw()
def _segmented_button_callback(self, selected_name): def _segmented_button_callback(self, selected_name):
self._tab_dict[self._current_name].grid_forget()
self._current_name = selected_name self._current_name = selected_name
self._grid_forget_all_tabs() self._set_grid_current_tab()
self._set_grid_tab_by_name(self._current_name)
if self._command is not None: if self._command is not None:
self._command() self._command()
@ -124,7 +126,7 @@ class CTkTabview(CTkBaseClass):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height - self._top_spacing - self._top_button_overhang)) height=self._apply_widget_scaling(self._desired_height - self._outer_spacing - self._outer_button_overhang))
self._configure_grid() self._configure_grid()
self._draw(no_color_updates=True) self._draw(no_color_updates=True)
@ -132,56 +134,82 @@ class CTkTabview(CTkBaseClass):
super()._set_dimensions(width, height) super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height - self._top_spacing - self._top_button_overhang)) height=self._apply_widget_scaling(self._desired_height - self._outer_spacing - self._outer_button_overhang))
self._draw() self._draw()
def _configure_segmented_button_background_corners(self): def _configure_segmented_button_background_corners(self):
""" needs to be called for changes in fg_color, bg_color """ """ needs to be called for changes in fg_color, bg_color """
if self._fg_color is not None: if self._fg_color == "transparent":
self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._fg_color, self._fg_color))
else:
self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color)) self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color))
else:
def _configure_tab_background_corners_by_name(self, name: str): if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"):
""" needs to be called for changes in fg_color, bg_color, border_width """ self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._fg_color, self._fg_color))
else:
self._tab_dict[name].configure(background_corner_colors=None) self._segmented_button.configure(background_corner_colors=(self._fg_color, self._fg_color, self._bg_color, self._bg_color))
def _configure_grid(self): def _configure_grid(self):
""" create 3 x 4 grid system """ """ create 3 x 4 grid system """
self.grid_rowconfigure(0, weight=0, minsize=self._apply_widget_scaling(self._top_spacing)) if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"):
self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(self._top_button_overhang)) self.grid_rowconfigure(0, weight=0, minsize=self._apply_widget_scaling(self._outer_spacing))
self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._button_height - self._top_button_overhang)) self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(self._outer_button_overhang))
self.grid_rowconfigure(3, weight=1) self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._button_height - self._outer_button_overhang))
self.grid_rowconfigure(3, weight=1)
else:
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(self._button_height - self._outer_button_overhang))
self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._outer_button_overhang))
self.grid_rowconfigure(3, weight=0, minsize=self._apply_widget_scaling(self._outer_spacing))
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
def _set_grid_canvas(self): def _set_grid_canvas(self):
self._canvas.grid(row=2, rowspan=2, column=0, columnspan=1, sticky="nsew") if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"):
self._canvas.grid(row=2, rowspan=2, column=0, columnspan=1, sticky="nsew")
else:
self._canvas.grid(row=0, rowspan=2, column=0, columnspan=1, sticky="nsew")
def _set_grid_segmented_button(self): def _set_grid_segmented_button(self):
""" needs to be called for changes in corner_radius """ """ needs to be called for changes in corner_radius, anchor """
self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="ns")
def _set_grid_tab_by_name(self, name: str): if self._anchor.lower() in ("center", "n", "s"):
self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="ns")
elif self._anchor.lower() in ("nw", "w", "sw"):
self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="nsw")
elif self._anchor.lower() in ("ne", "e", "se"):
self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="nse")
def _set_grid_current_tab(self):
""" needs to be called for changes in corner_radius, border_width """ """ needs to be called for changes in corner_radius, border_width """
self._tab_dict[name].grid(row=3, column=0, sticky="nsew", if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"):
padx=self._apply_widget_scaling(max(self._corner_radius, self._border_width)), self._tab_dict[self._current_name].grid(row=3, column=0, sticky="nsew",
pady=self._apply_widget_scaling(max(self._corner_radius, self._border_width))) padx=self._apply_widget_scaling(max(self._corner_radius, self._border_width)),
pady=self._apply_widget_scaling(max(self._corner_radius, self._border_width)))
else:
self._tab_dict[self._current_name].grid(row=0, column=0, sticky="nsew",
padx=self._apply_widget_scaling(max(self._corner_radius, self._border_width)),
pady=self._apply_widget_scaling(max(self._corner_radius, self._border_width)))
def _grid_forget_all_tabs(self): def _grid_forget_all_tabs(self, exclude_name=None):
for frame in self._tab_dict.values(): for name, frame in self._tab_dict.items():
frame.grid_forget() if name != exclude_name:
frame.grid_forget()
def _create_tab(self) -> CTkFrame: def _create_tab(self) -> CTkFrame:
new_tab = CTkFrame(self, new_tab = CTkFrame(self,
height=0, height=0,
width=0, width=0,
fg_color=self._fg_color,
border_width=0, border_width=0,
corner_radius=self._corner_radius) corner_radius=0)
if self._fg_color == "transparent":
new_tab.configure(fg_color=self._apply_appearance_mode(self._bg_color),
bg_color=self._apply_appearance_mode(self._bg_color))
else:
new_tab.configure(fg_color=self._apply_appearance_mode(self._fg_color),
bg_color=self._apply_appearance_mode(self._fg_color))
return new_tab return new_tab
def _draw(self, no_color_updates: bool = False): def _draw(self, no_color_updates: bool = False):
@ -191,7 +219,7 @@ class CTkTabview(CTkBaseClass):
return return
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width), requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height - self._top_spacing - self._top_button_overhang), self._apply_widget_scaling(self._current_height - self._outer_spacing - self._outer_button_overhang),
self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._corner_radius),
self._apply_widget_scaling(self._border_width)) self._apply_widget_scaling(self._border_width))
@ -200,27 +228,37 @@ class CTkTabview(CTkBaseClass):
self._canvas.itemconfig("inner_parts", self._canvas.itemconfig("inner_parts",
fill=self._apply_appearance_mode(self._bg_color), fill=self._apply_appearance_mode(self._bg_color),
outline=self._apply_appearance_mode(self._bg_color)) outline=self._apply_appearance_mode(self._bg_color))
for tab in self._tab_dict.values():
tab.configure(fg_color=self._apply_appearance_mode(self._bg_color),
bg_color=self._apply_appearance_mode(self._bg_color))
else: else:
self._canvas.itemconfig("inner_parts", self._canvas.itemconfig("inner_parts",
fill=self._apply_appearance_mode(self._fg_color), fill=self._apply_appearance_mode(self._fg_color),
outline=self._apply_appearance_mode(self._fg_color)) outline=self._apply_appearance_mode(self._fg_color))
for tab in self._tab_dict.values():
tab.configure(fg_color=self._apply_appearance_mode(self._fg_color),
bg_color=self._apply_appearance_mode(self._fg_color))
self._canvas.itemconfig("border_parts", self._canvas.itemconfig("border_parts",
fill=self._apply_appearance_mode(self._border_color), fill=self._apply_appearance_mode(self._border_color),
outline=self._apply_appearance_mode(self._border_color)) outline=self._apply_appearance_mode(self._border_color))
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color)) self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._bg_color)) # configure bg color of tkinter.Frame, cuase canvas does not fill frame tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._bg_color)) # configure bg color of tkinter.Frame, cause canvas does not fill frame
def configure(self, require_redraw=False, **kwargs): def configure(self, require_redraw=False, **kwargs):
if "corner_radius" in kwargs: if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius") self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True self._set_grid_segmented_button()
self._set_grid_current_tab()
self._set_grid_canvas()
self._configure_segmented_button_background_corners()
self._segmented_button.configure(corner_radius=self._corner_radius)
if "border_width" in kwargs: if "border_width" in kwargs:
self._border_width = kwargs.pop("border_width") self._border_width = kwargs.pop("border_width")
require_redraw = True require_redraw = True
if "fg_color" in kwargs: if "fg_color" in kwargs:
self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True) self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
self._configure_segmented_button_background_corners()
require_redraw = True require_redraw = True
if "border_color" in kwargs: if "border_color" in kwargs:
self._border_color = self._check_color_type(kwargs.pop("border_color")) self._border_color = self._check_color_type(kwargs.pop("border_color"))
@ -242,6 +280,10 @@ class CTkTabview(CTkBaseClass):
if "command" in kwargs: if "command" in kwargs:
self._command = kwargs.pop("command") self._command = kwargs.pop("command")
if "anchor" in kwargs:
self._anchor = kwargs.pop("anchor")
self._configure_grid()
self._set_grid_segmented_button()
if "state" in kwargs: if "state" in kwargs:
self._segmented_button.configure(state=kwargs.pop("state")) self._segmented_button.configure(state=kwargs.pop("state"))
@ -274,6 +316,8 @@ class CTkTabview(CTkBaseClass):
elif attribute_name == "command": elif attribute_name == "command":
return self._command return self._command
elif attribute_name == "anchor":
return self._anchor
elif attribute_name == "state": elif attribute_name == "state":
return self._segmented_button.cget(attribute_name) return self._segmented_button.cget(attribute_name)
@ -296,17 +340,16 @@ class CTkTabview(CTkBaseClass):
if len(self._tab_dict) == 0: if len(self._tab_dict) == 0:
self._set_grid_segmented_button() self._set_grid_segmented_button()
self._name_list.insert(index, name) self._name_list.append(name)
self._tab_dict[name] = self._create_tab() self._tab_dict[name] = self._create_tab()
self._segmented_button.insert(index, name) self._segmented_button.insert(index, name)
self._configure_tab_background_corners_by_name(name)
# if created tab is only tab select this tab # if created tab is only tab select this tab
if len(self._tab_dict) == 1: if len(self._tab_dict) == 1:
self._current_name = name self._current_name = name
self._segmented_button.set(self._current_name) self._segmented_button.set(self._current_name)
self._grid_forget_all_tabs() self._grid_forget_all_tabs()
self._set_grid_tab_by_name(self._current_name) self._set_grid_current_tab()
return self._tab_dict[name] return self._tab_dict[name]
else: else:
@ -316,6 +359,10 @@ class CTkTabview(CTkBaseClass):
""" appends new tab with given name """ """ appends new tab with given name """
return self.insert(len(self._tab_dict), name) return self.insert(len(self._tab_dict), name)
def index(self, name) -> int:
""" get index of tab with given name """
return self._segmented_button.index(name)
def move(self, new_index: int, name: str): def move(self, new_index: int, name: str):
if 0 <= new_index < len(self._name_list): if 0 <= new_index < len(self._name_list):
if name in self._tab_dict: if name in self._tab_dict:
@ -325,6 +372,22 @@ class CTkTabview(CTkBaseClass):
else: else:
raise ValueError(f"CTkTabview new_index {new_index} not in range of name list with len {len(self._name_list)}") raise ValueError(f"CTkTabview new_index {new_index} not in range of name list with len {len(self._name_list)}")
def rename(self, old_name: str, new_name: str):
if new_name in self._name_list:
raise ValueError(f"new_name '{new_name}' already exists")
# segmented button
old_index = self._segmented_button.index(old_name)
self._segmented_button.delete(old_name)
self._segmented_button.insert(old_index, new_name)
# name list
self._name_list.remove(old_name)
self._name_list.append(new_name)
# tab dictionary
self._tab_dict[new_name] = self._tab_dict.pop(old_name)
def delete(self, name: str): def delete(self, name: str):
""" delete tab by name """ """ delete tab by name """
@ -344,7 +407,7 @@ class CTkTabview(CTkBaseClass):
self._current_name = self._name_list[0] self._current_name = self._name_list[0]
self._segmented_button.set(self._current_name) self._segmented_button.set(self._current_name)
self._grid_forget_all_tabs() self._grid_forget_all_tabs()
self._set_grid_tab_by_name(self._current_name) self._set_grid_current_tab()
# more tabs are left # more tabs are left
else: else:
@ -360,8 +423,8 @@ class CTkTabview(CTkBaseClass):
if name in self._tab_dict: if name in self._tab_dict:
self._current_name = name self._current_name = name
self._segmented_button.set(name) self._segmented_button.set(name)
self._grid_forget_all_tabs() self._set_grid_current_tab()
self._set_grid_tab_by_name(name) self.after(100, lambda: self._grid_forget_all_tabs(exclude_name=name))
else: else:
raise ValueError(f"CTkTabview has no tab named '{name}'") raise ValueError(f"CTkTabview has no tab named '{name}'")

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._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
self.after(50, self._check_if_scrollbars_needed) self.after(50, self._check_if_scrollbars_needed, None, True)
self._draw() self._draw()
def _create_grid_for_text_and_scrollbars(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False): def _create_grid_for_text_and_scrollbars(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False):
@ -151,7 +151,7 @@ class CTkTextbox(CTkBaseClass):
else: else:
self._y_scrollbar.grid_forget() self._y_scrollbar.grid_forget()
def _check_if_scrollbars_needed(self, event=None, continue_loop: bool = True): def _check_if_scrollbars_needed(self, event=None, continue_loop: bool = False):
""" Method hides or places the scrollbars if they are needed on key release event of tkinter.text widget """ """ Method hides or places the scrollbars if they are needed on key release event of tkinter.text widget """
if self._scrollbars_activated: if self._scrollbars_activated:
@ -349,7 +349,6 @@ class CTkTextbox(CTkBaseClass):
return self._textbox.focus_force() return self._textbox.focus_force()
def insert(self, index, text, tags=None): def insert(self, index, text, tags=None):
self._check_if_scrollbars_needed()
return self._textbox.insert(index, text, tags) return self._textbox.insert(index, text, tags)
def get(self, index1, index2=None): def get(self, index1, index2=None):

View File

@ -52,7 +52,10 @@ class CTkFont(Font):
def remove_size_configure_callback(self, callback: Callable): def remove_size_configure_callback(self, callback: Callable):
""" remove function, that gets called when font got configured """ """ remove function, that gets called when font got configured """
self._size_configure_callback_list.remove(callback) try:
self._size_configure_callback_list.remove(callback)
except ValueError:
pass
def create_scaled_tuple(self, font_scaling: float) -> Tuple[str, int, str]: def create_scaled_tuple(self, font_scaling: float) -> Tuple[str, int, str]:
""" return scaled tuple representation of font in the form (family: str, size: int, style: str)""" """ return scaled tuple representation of font in the form (family: str, size: int, style: str)"""

View File

@ -4,6 +4,6 @@ from .theme_manager import ThemeManager
try: try:
ThemeManager.load_theme("blue") ThemeManager.load_theme("blue")
except FileNotFoundError as err: 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"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") f"https://github.com/TomSchimansky/CustomTkinter/wiki/Packaging#windows-pyinstaller-auto-py-to-exe")

View File

@ -1,5 +1,6 @@
import sys import sys
import os import os
import pathlib
import json import json
from typing import List, Union from typing import List, Union
@ -15,7 +16,8 @@ class ThemeManager:
script_directory = os.path.dirname(os.path.abspath(__file__)) script_directory = os.path.dirname(os.path.abspath(__file__))
if theme_name_or_path in cls._built_in_themes: 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) cls.theme = json.load(f)
else: else:
with open(theme_name_or_path, "r") as f: with open(theme_name_or_path, "r") as f:
@ -35,6 +37,12 @@ class ThemeManager:
else: else:
cls.theme[key] = cls.theme[key]["Linux"] cls.theme[key] = cls.theme[key]["Linux"]
# fix name inconsistencies
if "CTkCheckbox" in cls.theme.keys():
cls.theme["CTkCheckBox"] = cls.theme.pop("CTkCheckbox")
if "CTkRadiobutton" in cls.theme.keys():
cls.theme["CTkRadioButton"] = cls.theme.pop("CTkRadiobutton")
@classmethod @classmethod
def save_theme(cls): def save_theme(cls):
if cls._currently_loaded_theme is not None: if cls._currently_loaded_theme is not None:

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 = 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") 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 # create slider and progressbar frame
self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color="transparent") 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_columnconfigure(0, weight=1)
self.slider_progressbar_frame.grid_rowconfigure(4, weight=1) self.slider_progressbar_frame.grid_rowconfigure(4, weight=1)
self.seg_button_1 = customtkinter.CTkSegmentedButton(self.slider_progressbar_frame) self.seg_button_1 = 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 = 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") 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 # set default values
self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton") self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton")
self.checkbox_2.configure(state="disabled") self.checkbox_3.configure(state="disabled")
self.switch_2.configure(state="disabled")
self.checkbox_1.select() 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.radio_button_3.configure(state="disabled")
self.appearance_mode_optionemenu.set("Dark") self.appearance_mode_optionemenu.set("Dark")
self.scaling_optionemenu.set("100%") 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

@ -63,7 +63,7 @@ text_1.insert("0.0", "CTkTextbox\n\n\n\n")
segmented_button_1 = customtkinter.CTkSegmentedButton(master=frame_1, values=["CTkSegmentedButton", "Value 2"]) segmented_button_1 = customtkinter.CTkSegmentedButton(master=frame_1, values=["CTkSegmentedButton", "Value 2"])
segmented_button_1.pack(pady=10, padx=10) segmented_button_1.pack(pady=10, padx=10)
tabview_1 = customtkinter.CTkTabview(master=frame_1, width=200, height=70) tabview_1 = customtkinter.CTkTabview(master=frame_1, width=300)
tabview_1.pack(pady=10, padx=10) tabview_1.pack(pady=10, padx=10)
tabview_1.add("CTkTabview") tabview_1.add("CTkTabview")
tabview_1.add("Tab 2") tabview_1.add("Tab 2")

View File

@ -3,11 +3,10 @@ requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.tbump] [tool.tbump]
# Uncomment this if your project is hosted on GitHub:
github_url = "https://github.com/TomSchimansky/CustomTkinter" github_url = "https://github.com/TomSchimansky/CustomTkinter"
[tool.tbump.version] [tool.tbump.version]
current = "5.0.4" current = "5.2.0"
# Example of a semver regexp. # Example of a semver regexp.
# Make sure this matches current_version before # Make sure this matches current_version before
@ -33,17 +32,3 @@ src = "setup.cfg"
[[tool.tbump.file]] [[tool.tbump.file]]
src = "customtkinter/__init__.py" src = "customtkinter/__init__.py"
search = "__version__ = \"{current_version}\"" 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,18 +1,23 @@
[metadata] [metadata]
name = customtkinter name = customtkinter
version = 5.0.4 version = 5.2.0
description = Create modern looking GUIs with Python 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 long_description_content_type = text/markdown
url = https://github.com/TomSchimansky/CustomTkinter url = https://customtkinter.tomschimansky.com
author = Tom Schimansky author = Tom Schimansky
license = Creative Commons Zero v1.0 Universal license = Creative Commons Zero v1.0 Universal
license_file = LICENSE license_file = LICENSE
classifiers = classifiers =
License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication License :: OSI Approved :: MIT License
Operating System :: OS Independent Operating System :: OS Independent
Programming Language :: Python :: 3 :: Only 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] [options]
python_requires = >=3.7 python_requires = >=3.7
packages = packages =

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

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()