fixed numerous bg_color bugs, added complete_test.py for color testing, changed AppearanceModeTracker to non-threaded approach with tk.after()

This commit is contained in:
Tom Schimansky 2022-01-07 20:54:45 +01:00
parent 12b44c1095
commit 2fa7a519ea
19 changed files with 752 additions and 252 deletions

View File

@ -461,13 +461,8 @@ print(customtkinter.get_appearance_mode())
Use macOS darkmode window style without using the `customtkinter.Ctk` class: Use macOS darkmode window style without using the `customtkinter.Ctk` class:
```python ```python
customtkinter.enable_macos_darkmode() # get darkmode window style customtkinter.enable_macos_darkmode() # get darkmode window style
customtkinter.disable_macos_darkmode() # disable darkmode (important!)
```
If you dont use ``root_tk.mainloop()``, then you have to deactivate ... program ...
the threaded search for a change of the system appearance mode, and
do it yourself in your main loop where you call ``root_tk.update()``. customtkinter.disable_macos_darkmode() # disable darkmode (very important!)
```python
customtkinter.deactivate_threading() # call this at the beginning
customtkinter.update_appearance_mode() # then call this in the loop
``` ```

View File

@ -10,7 +10,7 @@ from .customtkinter_dialog import CTkDialog
from .customtkinter_checkbox import CTkCheckBox from .customtkinter_checkbox import CTkCheckBox
from .customtkinter_tk import CTk from .customtkinter_tk import CTk
from .appearance_mode_tracker import AppearanceModeTracker, SystemAppearanceModeListenerNoThread from .appearance_mode_tracker import AppearanceModeTracker#, SystemAppearanceModeListenerNoThread
from .customtkinter_color_manager import CTkColorManager from .customtkinter_color_manager import CTkColorManager
from distutils.version import StrictVersion as Version from distutils.version import StrictVersion as Version

View File

@ -1,6 +1,6 @@
from threading import Thread import time
from time import sleep
import sys import sys
import tkinter
from distutils.version import StrictVersion as Version from distutils.version import StrictVersion as Version
import darkdetect import darkdetect
@ -10,115 +10,91 @@ if Version(darkdetect.__version__) < Version("0.3.1"):
exit() exit()
class SystemAppearanceModeListener(Thread):
""" This class checks for a system appearance change
in a loop, and if a change is detected, than the
callback function gets called. Either 'Light' or
'Dark' is passed in the callback function. """
def __init__(self, callback, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setDaemon(True)
self.appearance_mode = self.detect_appearance_mode()
self.callback_function = callback
self.activated = True
def activate(self):
self.activated = True
def deactivate(self):
self.activated = False
def get_mode(self):
return self.appearance_mode
@staticmethod
def detect_appearance_mode():
try:
if darkdetect.theme() == "Dark":
return 1 # Dark
else:
return 0 # Light
except NameError:
return 0 # Light
def run(self):
while True:
if self.activated:
detected_mode = self.detect_appearance_mode()
if detected_mode != self.appearance_mode:
self.appearance_mode = detected_mode
if self.appearance_mode == 0:
self.callback_function("Light", from_listener=True)
else:
self.callback_function("Dark", from_listener=True)
sleep(0.5)
class SystemAppearanceModeListenerNoThread:
def __init__(self, callback):
self.appearance_mode = self.detect_appearance_mode()
self.callback_function = callback
self.activated = True
def get_mode(self):
return self.appearance_mode
@staticmethod
def detect_appearance_mode():
try:
if darkdetect.theme() == "Dark":
return 1 # Dark
else:
return 0 # Light
except NameError:
return 0 # Light
def update(self):
detected_mode = self.detect_appearance_mode()
if detected_mode != self.appearance_mode:
self.appearance_mode = detected_mode
if self.appearance_mode == 0:
self.callback_function("Light", from_listener=True)
else:
self.callback_function("Dark", from_listener=True)
class AppearanceModeTracker(): class AppearanceModeTracker():
""" This class holds a list with callback functions
of every customtkinter object that gets created.
And when either the SystemAppearanceModeListener
or the user changes the appearance_mode, all
callbacks in the list get called and the
new appearance_mode is passed over to the
customtkinter objects """
callback_list = [] callback_list = []
root_tk_list = []
update_loop_running = False
appearance_mode_set_by = "system"
appearance_mode = 0 # Light (standard) appearance_mode = 0 # Light (standard)
system_mode_listener = None
@classmethod @classmethod
def init_listener_function(cls, no_thread=False): def init_appearance_mode(cls):
if isinstance(cls.system_mode_listener, SystemAppearanceModeListener): if cls.appearance_mode_set_by == "system":
cls.system_mode_listener.deactivate() new_appearance_mode = cls.detect_appearance_mode()
if no_thread is True: if new_appearance_mode != cls.appearance_mode:
cls.system_mode_listener = SystemAppearanceModeListenerNoThread(cls.set_appearance_mode) cls.appearance_mode = new_appearance_mode
cls.appearance_mode = cls.system_mode_listener.get_mode() cls.update_callbacks()
else:
cls.system_mode_listener = SystemAppearanceModeListener(cls.set_appearance_mode)
cls.system_mode_listener.start()
cls.appearance_mode = cls.system_mode_listener.get_mode()
@classmethod @classmethod
def add(cls, callback): def get_tk_root_of_widget(cls, widget):
current_widget = widget
while isinstance(current_widget, tkinter.Tk) is False:
current_widget = current_widget.master
return current_widget
@classmethod
def add(cls, callback, widget=None):
cls.callback_list.append(callback) cls.callback_list.append(callback)
if widget is not None:
root_tk = cls.get_tk_root_of_widget(widget)
if root_tk not in cls.root_tk_list:
cls.root_tk_list.append(root_tk)
if not cls.update_loop_running:
root_tk.after(500, cls.update)
cls.update_loop_running = True
@staticmethod
def detect_appearance_mode():
try:
if darkdetect.theme() == "Dark":
return 1 # Dark
else:
return 0 # Light
except NameError:
return 0 # Light
@classmethod
def update_callbacks(cls):
if cls.appearance_mode == 0:
for callback in cls.callback_list:
try:
callback("Light")
except Exception:
continue
elif cls.appearance_mode == 1:
for callback in cls.callback_list:
try:
callback("Dark")
except Exception:
continue
@classmethod
def update(cls):
if cls.appearance_mode_set_by == "system":
new_appearance_mode = cls.detect_appearance_mode()
if new_appearance_mode != cls.appearance_mode:
cls.appearance_mode = new_appearance_mode
cls.update_callbacks()
# find an existing tkinter.Tk object for the next call of .after()
for root_tk in cls.root_tk_list:
try:
root_tk.after(200, cls.update)
return
except Exception:
continue
cls.update_loop_running = False
@classmethod @classmethod
def remove(cls, callback): def remove(cls, callback):
cls.callback_list.remove(callback) cls.callback_list.remove(callback)
@ -128,36 +104,25 @@ class AppearanceModeTracker():
return cls.appearance_mode return cls.appearance_mode
@classmethod @classmethod
def set_appearance_mode(cls, mode_string, from_listener=False): def set_appearance_mode(cls, mode_string):
if mode_string.lower() == "dark": if mode_string.lower() == "dark":
cls.appearance_mode = 1 cls.appearance_mode_set_by = "user"
new_appearance_mode = 1
if not from_listener: if new_appearance_mode != cls.appearance_mode:
cls.system_mode_listener.deactivate() cls.appearance_mode = new_appearance_mode
cls.update_callbacks()
elif mode_string.lower() == "light": elif mode_string.lower() == "light":
cls.appearance_mode = 0 cls.appearance_mode_set_by = "user"
if not from_listener: new_appearance_mode = 0
cls.system_mode_listener.deactivate()
if new_appearance_mode != cls.appearance_mode:
cls.appearance_mode = new_appearance_mode
cls.update_callbacks()
elif mode_string.lower() == "system": elif mode_string.lower() == "system":
cls.system_mode_listener.activate() cls.appearance_mode_set_by = "system"
if cls.appearance_mode == 0:
for callback in cls.callback_list:
try:
callback("Light")
except Exception:
print("error callback")
continue
elif cls.appearance_mode == 1:
for callback in cls.callback_list:
try:
callback("Dark")
except Exception:
print("error callback")
continue
AppearanceModeTracker.init_listener_function() AppearanceModeTracker.init_appearance_mode()

View File

@ -0,0 +1,172 @@
from threading import Thread
import time
import sys
from distutils.version import StrictVersion as Version
import darkdetect
if Version(darkdetect.__version__) < Version("0.3.1"):
sys.stderr.write("WARNING: You have to update the darkdetect library: pip3 install --upgrade darkdetect\n")
if sys.platform != "darwin":
exit()
class SystemAppearanceModeListener(Thread):
""" This class checks for a system appearance change
in a loop, and if a change is detected, than the
callback function gets called. Either 'Light' or
'Dark' is passed in the callback function. """
def __init__(self, callback, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setDaemon(True)
self.appearance_mode = self.detect_appearance_mode()
self.callback_function = callback
self.activated = True
def activate(self):
self.activated = True
def deactivate(self):
self.activated = False
def get_mode(self):
return self.appearance_mode
def set_mode(self, appearance_mode):
self.appearance_mode = appearance_mode
@staticmethod
def detect_appearance_mode():
try:
if darkdetect.theme() == "Dark":
return 1 # Dark
else:
return 0 # Light
except NameError:
return 0 # Light
def run(self):
while True:
if self.activated:
detected_mode = self.detect_appearance_mode()
if detected_mode != self.appearance_mode:
self.appearance_mode = detected_mode
if self.appearance_mode == 0:
self.callback_function("Light", from_listener=True)
else:
self.callback_function("Dark", from_listener=True)
time.sleep(0.5)
else:
while self.activated is False:
time.sleep(0.05)
class SystemAppearanceModeListenerNoThread:
def __init__(self, callback):
self.appearance_mode = self.detect_appearance_mode()
self.callback_function = callback
self.activated = True
def get_mode(self):
return self.appearance_mode
@staticmethod
def detect_appearance_mode():
try:
if darkdetect.theme() == "Dark":
return 1 # Dark
else:
return 0 # Light
except NameError:
return 0 # Light
def update(self):
detected_mode = self.detect_appearance_mode()
if detected_mode != self.appearance_mode:
self.appearance_mode = detected_mode
if self.appearance_mode == 0:
self.callback_function("Light", from_listener=True)
else:
self.callback_function("Dark", from_listener=True)
class AppearanceModeTracker():
""" This class holds a list with callback functions
of every customtkinter object that gets created.
And when either the SystemAppearanceModeListener
or the user changes the appearance_mode, all
callbacks in the list get called and the
new appearance_mode is passed over to the
customtkinter objects """
callback_list = []
appearance_mode = 0 # Light (standard)
system_mode_listener = None
@classmethod
def init_listener_function(cls, no_thread=False):
if isinstance(cls.system_mode_listener, SystemAppearanceModeListener):
cls.system_mode_listener.deactivate()
if no_thread is True:
cls.system_mode_listener = SystemAppearanceModeListenerNoThread(cls.set_appearance_mode)
cls.appearance_mode = cls.system_mode_listener.get_mode()
else:
cls.system_mode_listener = SystemAppearanceModeListener(cls.set_appearance_mode)
cls.system_mode_listener.start()
cls.appearance_mode = cls.system_mode_listener.get_mode()
@classmethod
def add(cls, callback):
cls.callback_list.append(callback)
@classmethod
def remove(cls, callback):
cls.callback_list.remove(callback)
@classmethod
def get_mode(cls):
return cls.appearance_mode
@classmethod
def set_appearance_mode(cls, mode_string, from_listener=False):
if mode_string.lower() == "dark":
cls.appearance_mode = 1
cls.system_mode_listener.set_mode(1)
if not from_listener:
cls.system_mode_listener.deactivate()
elif mode_string.lower() == "light":
cls.appearance_mode = 0
cls.system_mode_listener.set_mode(0)
if not from_listener:
cls.system_mode_listener.deactivate()
elif mode_string.lower() == "system":
cls.system_mode_listener.activate()
if cls.appearance_mode == 0:
for callback in cls.callback_list:
try:
callback("Light")
except Exception:
print("error callback")
continue
elif cls.appearance_mode == 1:
for callback in cls.callback_list:
try:
callback("Dark")
except Exception:
print("error callback")
continue
AppearanceModeTracker.init_listener_function()

View File

@ -2,6 +2,7 @@ import tkinter
import sys import sys
from math import ceil, floor from math import ceil, floor
from .customtkinter_tk import CTk
from .customtkinter_frame import CTkFrame from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager from .customtkinter_color_manager import CTkColorManager
@ -18,7 +19,7 @@ class CTkButton(tkinter.Frame):
border_width=0, border_width=0,
command=None, command=None,
width=120, width=120,
height=32, height=30,
corner_radius=8, corner_radius=8,
text_font=None, text_font=None,
text_color=CTkColorManager.TEXT, text_color=CTkColorManager.TEXT,
@ -30,14 +31,34 @@ class CTkButton(tkinter.Frame):
*args, **kwargs): *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
AppearanceModeTracker.add(self.set_appearance_mode) # overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
master_old_configure = self.master.config
def new_configure(*args, **kwargs):
if "bg" in kwargs:
self.configure(bg_color=kwargs["bg"])
elif "background" in kwargs:
self.configure(bg_color=kwargs["background"])
# args[0] is dict when attribute gets changed by widget[<attribute>] syntax
elif len(args) > 0 and type(args[0]) == dict:
if "bg" in args[0]:
self.configure(bg_color=args[0]["bg"])
elif "background" in args[0]:
self.configure(bg_color=args[0]["background"])
master_old_configure(*args, **kwargs)
self.master.config = new_configure
self.master.configure = new_configure
AppearanceModeTracker.add(self.set_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.configure_basic_grid() self.configure_basic_grid()
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
self.fg_color = self.bg_color if fg_color is None else fg_color self.fg_color = fg_color
self.fg_color = self.bg_color if self.fg_color is None else self.fg_color
self.hover_color = self.fg_color if hover_color is None else hover_color self.hover_color = self.fg_color if hover_color is None else hover_color
self.border_color = border_color self.border_color = border_color
@ -163,14 +184,26 @@ class CTkButton(tkinter.Frame):
# set color for inner button parts (depends on button state) # set color for inner button parts (depends on button state)
if self.state == tkinter.DISABLED: if self.state == tkinter.DISABLED:
self.canvas.itemconfig("inner_parts", if self.fg_color is None:
outline=CTkColorManager.darken_hex_color(CTkColorManager.single_color(self.fg_color, self.appearance_mode)), self.canvas.itemconfig("inner_parts",
fill=CTkColorManager.darken_hex_color(CTkColorManager.single_color(self.fg_color, self.appearance_mode))) outline=CTkColorManager.darken_hex_color(CTkColorManager.single_color(self.bg_color, self.appearance_mode)),
fill=CTkColorManager.darken_hex_color(CTkColorManager.single_color(self.bg_color, self.appearance_mode)))
else:
self.canvas.itemconfig("inner_parts",
outline=CTkColorManager.darken_hex_color(
CTkColorManager.single_color(self.fg_color, self.appearance_mode)),
fill=CTkColorManager.darken_hex_color(
CTkColorManager.single_color(self.fg_color, self.appearance_mode)))
else: else:
self.canvas.itemconfig("inner_parts", if self.fg_color is None:
outline=CTkColorManager.single_color(self.fg_color, self.appearance_mode), self.canvas.itemconfig("inner_parts",
fill=CTkColorManager.single_color(self.fg_color, self.appearance_mode)) outline=CTkColorManager.single_color(self.bg_color, self.appearance_mode),
fill=CTkColorManager.single_color(self.bg_color, self.appearance_mode))
else:
self.canvas.itemconfig("inner_parts",
outline=CTkColorManager.single_color(self.fg_color, self.appearance_mode),
fill=CTkColorManager.single_color(self.fg_color, self.appearance_mode))
# create text label if text given # create text label if text given
if self.text is not None and self.text != "": if self.text is not None and self.text != "":
@ -189,9 +222,17 @@ class CTkButton(tkinter.Frame):
# set text_label bg color (label color) # set text_label bg color (label color)
if self.state == tkinter.DISABLED: if self.state == tkinter.DISABLED:
self.text_label.configure(bg=CTkColorManager.darken_hex_color(CTkColorManager.single_color(self.fg_color, self.appearance_mode))) if self.fg_color is None:
self.text_label.configure(
bg=CTkColorManager.darken_hex_color(CTkColorManager.single_color(self.bg_color, self.appearance_mode)))
else:
self.text_label.configure(
bg=CTkColorManager.darken_hex_color(CTkColorManager.single_color(self.fg_color, self.appearance_mode)))
else: else:
self.text_label.configure(bg=CTkColorManager.single_color(self.fg_color, self.appearance_mode)) if self.fg_color is None:
self.text_label.configure(bg=CTkColorManager.single_color(self.bg_color, self.appearance_mode))
else:
self.text_label.configure(bg=CTkColorManager.single_color(self.fg_color, self.appearance_mode))
self.text_label.configure(text=self.text) # set text self.text_label.configure(text=self.text) # set text
@ -422,6 +463,11 @@ class CTkButton(tkinter.Frame):
require_redraw = True require_redraw = True
del kwargs["fg_color"] del kwargs["fg_color"]
if "border_color" in kwargs:
self.border_color = kwargs["border_color"]
require_redraw = True
del kwargs["border_color"]
if "bg_color" in kwargs: if "bg_color" in kwargs:
if kwargs["bg_color"] is None: if kwargs["bg_color"] is None:
self.bg_color = self.detect_color_of_master() self.bg_color = self.detect_color_of_master()
@ -485,18 +531,23 @@ class CTkButton(tkinter.Frame):
def on_leave(self, event=0): def on_leave(self, event=0):
if self.hover is True: if self.hover is True:
if self.fg_color is None:
inner_parts_color = self.bg_color
else:
inner_parts_color = self.fg_color
# set color of inner button parts # set color of inner button parts
self.canvas.itemconfig("inner_parts", self.canvas.itemconfig("inner_parts",
outline=CTkColorManager.single_color(self.fg_color, self.appearance_mode), outline=CTkColorManager.single_color(inner_parts_color, self.appearance_mode),
fill=CTkColorManager.single_color(self.fg_color, self.appearance_mode)) fill=CTkColorManager.single_color(inner_parts_color, self.appearance_mode))
# set text_label bg color (label color) # set text_label bg color (label color)
if self.text_label is not None: if self.text_label is not None:
self.text_label.configure(bg=CTkColorManager.single_color(self.fg_color, self.appearance_mode)) self.text_label.configure(bg=CTkColorManager.single_color(inner_parts_color, self.appearance_mode))
# set image_label bg color (image bg color) # set image_label bg color (image bg color)
if self.image_label is not None: if self.image_label is not None:
self.image_label.configure(bg=CTkColorManager.single_color(self.fg_color, self.appearance_mode)) self.image_label.configure(bg=CTkColorManager.single_color(inner_parts_color, self.appearance_mode))
def clicked(self, event=0): def clicked(self, event=0):
if self.function is not None: if self.function is not None:
@ -510,4 +561,10 @@ class CTkButton(tkinter.Frame):
elif mode_string.lower() == "light": elif mode_string.lower() == "light":
self.appearance_mode = 0 self.appearance_mode = 0
if isinstance(self.master, (CTkFrame, CTk)):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw() self.draw()
self.update_idletasks()

View File

@ -1,6 +1,7 @@
import tkinter import tkinter
import sys import sys
from .customtkinter_tk import CTk
from .customtkinter_frame import CTkFrame from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager from .customtkinter_color_manager import CTkColorManager
@ -27,7 +28,28 @@ class CTkCheckBox(tkinter.Frame):
*args, **kwargs): *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
AppearanceModeTracker.add(self.set_appearance_mode) # overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
master_old_configure = self.master.config
def new_configure(*args, **kwargs):
if "bg" in kwargs:
self.configure(bg_color=kwargs["bg"])
elif "background" in kwargs:
self.configure(bg_color=kwargs["background"])
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
elif len(args) > 0 and type(args[0]) == dict:
if "bg" in args[0]:
self.configure(bg_color=args[0]["bg"])
elif "background" in args[0]:
self.configure(bg_color=args[0]["background"])
master_old_configure(*args, **kwargs)
self.master.config = new_configure
self.master.configure = new_configure
AppearanceModeTracker.add(self.set_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
@ -191,11 +213,18 @@ class CTkCheckBox(tkinter.Frame):
self.width - self.border_width, self.width - self.border_width,
self.height - self.inner_corner_radius - self.border_width)) self.height - self.inner_corner_radius - self.border_width))
for part in self.canvas_fg_parts: if self.check_state is False:
if type(self.bg_color) == tuple and len(self.bg_color) == 2: for part in self.canvas_fg_parts:
self.canvas.itemconfig(part, fill=self.bg_color[self.appearance_mode], width=0) if type(self.bg_color) == tuple and len(self.bg_color) == 2:
else: self.canvas.itemconfig(part, fill=self.bg_color[self.appearance_mode], width=0)
self.canvas.itemconfig(part, fill=self.bg_color, outline=self.bg_color, width=0) else:
self.canvas.itemconfig(part, fill=self.bg_color, outline=self.bg_color, width=0)
else:
for part in self.canvas_fg_parts:
if type(self.fg_color) == tuple and len(self.fg_color) == 2:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, outline=self.bg_color, width=0)
for part in self.canvas_border_parts: for part in self.canvas_border_parts:
if type(self.border_color) == tuple and len(self.border_color) == 2: if type(self.border_color) == tuple and len(self.border_color) == 2:
@ -368,5 +397,10 @@ class CTkCheckBox(tkinter.Frame):
elif mode_string.lower() == "light": elif mode_string.lower() == "light":
self.appearance_mode = 0 self.appearance_mode = 0
self.draw() if isinstance(self.master, (CTkFrame, CTk)):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()
self.update_idletasks()

View File

@ -14,7 +14,7 @@ class CTkColorManager:
FRAME_2 = ("#BFBEC1", "#505050") FRAME_2 = ("#BFBEC1", "#505050")
CHECKBOX_LINES = ("black", "#ededed") CHECKBOX_LINES = ("black", "#ededed")
DARKEN_COLOR_FACTOR = 0.8 # used for generate color for disabled button DARKEN_COLOR_FACTOR = 0.8 # used to generate color for disabled button
@staticmethod @staticmethod
def single_color(color, appearance_mode: int) -> str: def single_color(color, appearance_mode: int) -> str:

View File

@ -25,9 +25,12 @@ class CTkDialog:
self.hover_color = CTkColorManager.MAIN_HOVER if hover_color is None else hover_color self.hover_color = CTkColorManager.MAIN_HOVER if hover_color is None else hover_color
self.top = tkinter.Toplevel() self.top = tkinter.Toplevel()
self.top.geometry("300x{}".format(self.height)) self.top.geometry(f"280x{self.height}")
self.top.resizable(False, False) self.top.resizable(False, False)
self.top.title(title) self.top.title(title)
self.top.lift()
self.top.focus_force()
self.top.grab_set()
self.label_frame = tkinter.Frame(master=self.top, self.label_frame = tkinter.Frame(master=self.top,
width=300, width=300,
@ -46,22 +49,24 @@ class CTkDialog:
self.myLabel.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) self.myLabel.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
self.entry = CTkEntry(master=self.button_and_entry_frame, self.entry = CTkEntry(master=self.button_and_entry_frame,
width=180) width=230)
self.entry.place(relx=0.5, rely=0.15, anchor=tkinter.CENTER) self.entry.place(relx=0.5, rely=0.15, anchor=tkinter.CENTER)
self.ok_button = CTkButton(master=self.button_and_entry_frame, self.ok_button = CTkButton(master=self.button_and_entry_frame,
text='Ok', text='Ok',
width=100,
command=self.ok_event, command=self.ok_event,
fg_color=self.fg_color, fg_color=self.fg_color,
hover_color=self.hover_color) hover_color=self.hover_color)
self.ok_button.place(relx=0.25, rely=0.75, anchor=tkinter.CENTER) self.ok_button.place(relx=0.28, rely=0.65, anchor=tkinter.CENTER)
self.cancel_button = CTkButton(master=self.button_and_entry_frame, self.cancel_button = CTkButton(master=self.button_and_entry_frame,
text='Cancel', text='Cancel',
width=100,
command=self.cancel_event, command=self.cancel_event,
fg_color=self.fg_color, fg_color=self.fg_color,
hover_color=self.hover_color) hover_color=self.hover_color)
self.cancel_button.place(relx=0.75, rely=0.75, anchor=tkinter.CENTER) self.cancel_button.place(relx=0.72, rely=0.65, anchor=tkinter.CENTER)
def ok_event(self): def ok_event(self):
self.user_input = self.entry.get() self.user_input = self.entry.get()

View File

@ -1,6 +1,7 @@
import tkinter import tkinter
import sys import sys
from .customtkinter_tk import CTk
from .customtkinter_frame import CTkFrame from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager from .customtkinter_color_manager import CTkColorManager
@ -12,14 +13,35 @@ class CTkEntry(tkinter.Frame):
bg_color=None, bg_color=None,
fg_color=CTkColorManager.ENTRY, fg_color=CTkColorManager.ENTRY,
text_color=CTkColorManager.TEXT, text_color=CTkColorManager.TEXT,
corner_radius=10, corner_radius=8,
width=120, width=120,
height=25, height=30,
*args, *args,
**kwargs): **kwargs):
super().__init__(master=master) super().__init__(master=master)
AppearanceModeTracker.add(self.change_appearance_mode) # overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
master_old_configure = self.master.config
def new_configure(*args, **kwargs):
if "bg" in kwargs:
self.configure(bg_color=kwargs["bg"])
elif "background" in kwargs:
self.configure(bg_color=kwargs["background"])
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
elif len(args) > 0 and type(args[0]) == dict:
if "bg" in args[0]:
self.configure(bg_color=args[0]["bg"])
elif "background" in args[0]:
self.configure(bg_color=args[0]["background"])
master_old_configure(*args, **kwargs)
self.master.config = new_configure
self.master.configure = new_configure
AppearanceModeTracker.add(self.change_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
@ -140,20 +162,32 @@ class CTkEntry(tkinter.Frame):
self.configure(*args, **kwargs) self.configure(*args, **kwargs)
def configure(self, *args, **kwargs): def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end
if "bg_color" in kwargs:
self.bg_color = kwargs["bg_color"]
del kwargs["bg_color"]
require_redraw = True
if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"]
del kwargs["fg_color"]
require_redraw = True
if "text_color" in kwargs:
self.text_color = kwargs["text_color"]
del kwargs["text_color"]
require_redraw = True
if "corner_radius" in kwargs:
self.corner_radius = kwargs["corner_radius"]
del kwargs["corner_radius"]
require_redraw = True
super().configure(*args, **kwargs) super().configure(*args, **kwargs)
def change_appearance_mode(self, mode_string): if require_redraw is True:
if mode_string.lower() == "dark": self.draw()
self.appearance_mode = 1
elif mode_string.lower() == "light":
self.appearance_mode = 0
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
return self.entry.delete(*args, **kwargs) return self.entry.delete(*args, **kwargs)
@ -164,3 +198,17 @@ class CTkEntry(tkinter.Frame):
def get(self): def get(self):
return self.entry.get() return self.entry.get()
def change_appearance_mode(self, mode_string):
if mode_string.lower() == "dark":
self.appearance_mode = 1
elif mode_string.lower() == "light":
self.appearance_mode = 0
if isinstance(self.master, (CTkFrame, CTk)):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()
self.update_idletasks()

View File

@ -1,6 +1,7 @@
import tkinter import tkinter
import sys import sys
from .customtkinter_tk import CTk
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager from .customtkinter_color_manager import CTkColorManager
@ -15,7 +16,28 @@ class CTkFrame(tkinter.Frame):
**kwargs): **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
AppearanceModeTracker.add(self.change_appearance_mode) # overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
master_old_configure = self.master.config
def new_configure(*args, **kwargs):
if "bg" in kwargs:
self.configure(bg_color=kwargs["bg"])
elif "background" in kwargs:
self.configure(bg_color=kwargs["background"])
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
elif len(args) > 0 and type(args[0]) == dict:
if "bg" in args[0]:
self.configure(bg_color=args[0]["bg"])
elif "background" in args[0]:
self.configure(bg_color=args[0]["background"])
master_old_configure(*args, **kwargs)
self.master.config = new_configure
self.master.configure = new_configure
AppearanceModeTracker.add(self.change_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
@ -130,6 +152,48 @@ class CTkFrame(tkinter.Frame):
for part in self.fg_parts: for part in self.fg_parts:
self.canvas.tag_lower(part) self.canvas.tag_lower(part)
def config(self, *args, **kwargs):
self.configure(*args, **kwargs)
def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end
if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"]
require_redraw = True
del kwargs["fg_color"]
# check if CTk widgets are children of the frame and change their bg_color to new frame fg_color
from .customtkinter_slider import CTkSlider
from .customtkinter_progressbar import CTkProgressBar
from .customtkinter_label import CTkLabel
from .customtkinter_entry import CTkEntry
from .customtkinter_checkbox import CTkCheckBox
from .customtkinter_button import CTkButton
for child in self.winfo_children():
if isinstance(child, (CTkButton, CTkLabel, CTkSlider, CTkCheckBox, CTkEntry, CTkProgressBar, CTkFrame)):
child.configure(bg_color=self.fg_color)
if "bg_color" in kwargs:
if kwargs["bg_color"] is None:
self.bg_color = self.detect_color_of_master()
else:
self.bg_color = kwargs["bg_color"]
require_redraw = True
del kwargs["bg_color"]
if "corner_radius" in kwargs:
self.corner_radius = kwargs["corner_radius"]
require_redraw = True
del kwargs["corner_radius"]
super().configure(*args, **kwargs)
if require_redraw:
self.draw()
def change_appearance_mode(self, mode_string): def change_appearance_mode(self, mode_string):
if mode_string.lower() == "dark": if mode_string.lower() == "dark":
self.appearance_mode = 1 self.appearance_mode = 1
@ -142,4 +206,3 @@ class CTkFrame(tkinter.Frame):
self.bg_color = self.master.cget("bg") self.bg_color = self.master.cget("bg")
self.draw() self.draw()

View File

@ -1,6 +1,7 @@
import tkinter import tkinter
import sys import sys
from .customtkinter_tk import CTk
from .customtkinter_frame import CTkFrame from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager from .customtkinter_color_manager import CTkColorManager
@ -21,7 +22,28 @@ class CTkLabel(tkinter.Frame):
**kwargs): **kwargs):
super().__init__(master=master) super().__init__(master=master)
AppearanceModeTracker.add(self.change_appearance_mode) # overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
master_old_configure = self.master.config
def new_configure(*args, **kwargs):
if "bg" in kwargs:
self.configure(bg_color=kwargs["bg"])
elif "background" in kwargs:
self.configure(bg_color=kwargs["background"])
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
elif len(args) > 0 and type(args[0]) == dict:
if "bg" in args[0]:
self.configure(bg_color=args[0]["bg"])
elif "background" in args[0]:
self.configure(bg_color=args[0]["background"])
master_old_configure(*args, **kwargs)
self.master.config = new_configure
self.master.configure = new_configure
AppearanceModeTracker.add(self.change_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
@ -190,9 +212,10 @@ class CTkLabel(tkinter.Frame):
elif mode_string.lower() == "light": elif mode_string.lower() == "light":
self.appearance_mode = 0 self.appearance_mode = 0
if isinstance(self.master, CTkFrame): if isinstance(self.master, (CTkFrame, CTk)):
self.bg_color = self.master.fg_color self.bg_color = self.master.fg_color
else: else:
self.bg_color = self.master.cget("bg") self.bg_color = self.master.cget("bg")
self.draw() self.draw()
self.update_idletasks()

View File

@ -1,6 +1,7 @@
import sys import sys
import tkinter import tkinter
from .customtkinter_tk import CTk
from .customtkinter_frame import CTkFrame from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager from .customtkinter_color_manager import CTkColorManager
@ -20,7 +21,28 @@ class CTkProgressBar(tkinter.Frame):
*args, **kwargs): *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
AppearanceModeTracker.add(self.change_appearance_mode) # overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
master_old_configure = self.master.config
def new_configure(*args, **kwargs):
if "bg" in kwargs:
self.configure(bg_color=kwargs["bg"])
elif "background" in kwargs:
self.configure(bg_color=kwargs["background"])
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
elif len(args) > 0 and type(args[0]) == dict:
if "bg" in args[0]:
self.configure(bg_color=args[0]["bg"])
elif "background" in args[0]:
self.configure(bg_color=args[0]["background"])
master_old_configure(*args, **kwargs)
self.master.config = new_configure
self.master.configure = new_configure
AppearanceModeTracker.add(self.change_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
@ -104,6 +126,7 @@ class CTkProgressBar(tkinter.Frame):
self.canvas.itemconfig("border_line_1", self.canvas.itemconfig("border_line_1",
capstyle=tkinter.ROUND, capstyle=tkinter.ROUND,
width=self.height + width_reduced) width=self.height + width_reduced)
self.canvas.lower("border_parts")
# create inner button parts # create inner button parts
if not self.canvas.find_withtag("inner_parts"): if not self.canvas.find_withtag("inner_parts"):
@ -201,6 +224,39 @@ class CTkProgressBar(tkinter.Frame):
self.height / 2 + (self.width - self.height) * self.value + self.height / 2 - self.border_width + oval_bottom_right_shift, self.height / 2 + (self.width - self.height) * self.value + self.height / 2 - self.border_width + oval_bottom_right_shift,
self.height - self.border_width + oval_bottom_right_shift)) self.height - self.border_width + oval_bottom_right_shift))
def configure(self, *args, **kwargs):
require_redraw = False # some attribute changes require a call of self.draw() at the end
if "bg_color" in kwargs:
self.bg_color = kwargs["bg_color"]
del kwargs["bg_color"]
require_redraw = True
if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"]
del kwargs["fg_color"]
require_redraw = True
if "border_color" in kwargs:
self.border_color = kwargs["border_color"]
del kwargs["border_color"]
require_redraw = True
if "progress_color" in kwargs:
self.progress_color = kwargs["progress_color"]
del kwargs["progress_color"]
require_redraw = True
if "border_width" in kwargs:
self.border_width = kwargs["border_width"]
del kwargs["border_width"]
require_redraw = True
super().configure(*args, **kwargs)
if require_redraw is True:
self.draw()
def set(self, value): def set(self, value):
self.value = value self.value = value
@ -223,3 +279,4 @@ class CTkProgressBar(tkinter.Frame):
self.bg_color = self.master.cget("bg") self.bg_color = self.master.cget("bg")
self.draw() self.draw()
self.update_idletasks()

View File

@ -1,6 +1,7 @@
import tkinter import tkinter
import sys import sys
from .customtkinter_tk import CTk
from .customtkinter_frame import CTkFrame from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager from .customtkinter_color_manager import CTkColorManager
@ -26,13 +27,34 @@ class CTkSlider(tkinter.Frame):
*args, **kwargs): *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
AppearanceModeTracker.add(self.change_appearance_mode) # overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)):
master_old_configure = self.master.config
def new_configure(*args, **kwargs):
if "bg" in kwargs:
self.configure(bg_color=kwargs["bg"])
elif "background" in kwargs:
self.configure(bg_color=kwargs["background"])
# args[0] is dict when attribute gets changed by widget[<attribut>] syntax
elif len(args) > 0 and type(args[0]) == dict:
if "bg" in args[0]:
self.configure(bg_color=args[0]["bg"])
elif "background" in args[0]:
self.configure(bg_color=args[0]["background"])
master_old_configure(*args, **kwargs)
self.master.config = new_configure
self.master.configure = new_configure
AppearanceModeTracker.add(self.change_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
self.border_color = self.bg_color if border_color is None else border_color self.border_color = border_color
self.fg_color = fg_color self.fg_color = fg_color
self.progress_color = progress_color if progress_color is not None else fg_color self.progress_color = progress_color
self.button_color = self.bg_color if button_color is None else button_color self.button_color = self.bg_color if button_color is None else button_color
self.button_hover_color = self.bg_color if button_hover_color is None else button_hover_color self.button_hover_color = self.bg_color if button_hover_color is None else button_hover_color
@ -99,9 +121,19 @@ class CTkSlider(tkinter.Frame):
if no_color_updates is False: if no_color_updates is False:
self.canvas.configure(bg=CTkColorManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=CTkColorManager.single_color(self.bg_color, self.appearance_mode))
self.canvas.itemconfig("border_parts", fill=CTkColorManager.single_color(self.border_color, self.appearance_mode))
if self.border_color is None:
self.canvas.itemconfig("border_parts", fill=CTkColorManager.single_color(self.bg_color, self.appearance_mode))
else:
self.canvas.itemconfig("border_parts", fill=CTkColorManager.single_color(self.border_color, self.appearance_mode))
self.canvas.itemconfig("inner_parts", fill=CTkColorManager.single_color(self.fg_color, self.appearance_mode)) self.canvas.itemconfig("inner_parts", fill=CTkColorManager.single_color(self.fg_color, self.appearance_mode))
self.canvas.itemconfig("progress_parts", fill=CTkColorManager.single_color(self.progress_color, self.appearance_mode))
if self.progress_color is None:
self.canvas.itemconfig("progress_parts", fill=CTkColorManager.single_color(self.fg_color, self.appearance_mode))
else:
self.canvas.itemconfig("progress_parts", fill=CTkColorManager.single_color(self.progress_color, self.appearance_mode))
self.canvas.itemconfig("button_parts", fill=CTkColorManager.single_color(self.button_color, self.appearance_mode)) self.canvas.itemconfig("button_parts", fill=CTkColorManager.single_color(self.button_color, self.appearance_mode))
def draw_with_polygon_shapes(self): def draw_with_polygon_shapes(self):
@ -136,7 +168,7 @@ class CTkSlider(tkinter.Frame):
self.canvas.delete("progress_parts") self.canvas.delete("progress_parts")
self.canvas.coords("inner_line_1", self.canvas.coords("inner_line_1",
(self.height / 2, (((self.width + coordinate_shift - self.height) * self.value + self.height / 2),
self.height / 2, self.height / 2,
self.width - self.height / 2 + coordinate_shift, self.width - self.height / 2 + coordinate_shift,
self.height / 2)) self.height / 2))
@ -373,4 +405,4 @@ class CTkSlider(tkinter.Frame):
self.bg_color = self.master.cget("bg") self.bg_color = self.master.cget("bg")
self.draw() self.draw()
self.update_idletasks()

View File

@ -10,24 +10,24 @@ from .customtkinter_color_manager import CTkColorManager
class CTk(tkinter.Tk): class CTk(tkinter.Tk):
def __init__(self, *args, def __init__(self, *args,
bg_color=CTkColorManager.WINDOW_BG, fg_color=CTkColorManager.WINDOW_BG,
**kwargs): **kwargs):
self.enable_macos_dark_title_bar() self.enable_macos_dark_title_bar()
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.bg_color = bg_color self.fg_color = fg_color
if "bg" in kwargs: if "bg" in kwargs:
self.bg_color = kwargs["bg"] self.fg_color = kwargs["bg"]
del kwargs["bg"] del kwargs["bg"]
elif "background" in kwargs: elif "background" in kwargs:
self.bg_color = kwargs["background"] self.fg_color = kwargs["background"]
del kwargs["background"] del kwargs["background"]
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
AppearanceModeTracker.add(self.set_appearance_mode) AppearanceModeTracker.add(self.set_appearance_mode, self)
super().configure(bg=CTkColorManager.single_color(self.bg_color, self.appearance_mode)) super().configure(bg=CTkColorManager.single_color(self.fg_color, self.appearance_mode))
def destroy(self): def destroy(self):
AppearanceModeTracker.remove(self.set_appearance_mode) AppearanceModeTracker.remove(self.set_appearance_mode)
@ -38,10 +38,45 @@ class CTk(tkinter.Tk):
self.configure(*args, **kwargs) self.configure(*args, **kwargs)
def configure(self, *args, **kwargs): def configure(self, *args, **kwargs):
bg_changed = False
if "bg" in kwargs: if "bg" in kwargs:
self.bg_color = kwargs["bg"] self.fg_color = kwargs["bg"]
bg_changed = True
kwargs["bg"] = CTkColorManager.single_color(self.fg_color, self.appearance_mode)
elif "background" in kwargs: elif "background" in kwargs:
self.bg_color = kwargs["background"] self.fg_color = kwargs["background"]
bg_changed = True
kwargs["background"] = CTkColorManager.single_color(self.fg_color, self.appearance_mode)
elif "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"]
kwargs["bg"] = CTkColorManager.single_color(self.fg_color, self.appearance_mode)
del kwargs["fg_color"]
bg_changed = True
elif len(args) > 0 and type(args[0]) == dict:
if "bg" in args[0]:
self.fg_color=args[0]["bg"]
bg_changed = True
args[0]["bg"] = CTkColorManager.single_color(self.fg_color, self.appearance_mode)
elif "background" in args[0]:
self.fg_color=args[0]["background"]
bg_changed = True
args[0]["background"] = CTkColorManager.single_color(self.fg_color, self.appearance_mode)
if bg_changed:
from .customtkinter_slider import CTkSlider
from .customtkinter_progressbar import CTkProgressBar
from .customtkinter_label import CTkLabel
from .customtkinter_frame import CTkFrame
from .customtkinter_entry import CTkEntry
from .customtkinter_checkbox import CTkCheckBox
from .customtkinter_button import CTkButton
for child in self.winfo_children():
print("tk change children:", child)
if isinstance(child, (CTkFrame, CTkButton, CTkLabel, CTkSlider, CTkCheckBox, CTkEntry, CTkProgressBar)):
child.configure(bg_color=self.fg_color)
super().configure(*args, **kwargs) super().configure(*args, **kwargs)
@ -66,4 +101,5 @@ class CTk(tkinter.Tk):
elif mode_string.lower() == "light": elif mode_string.lower() == "light":
self.appearance_mode = 0 self.appearance_mode = 0
super().configure(bg=CTkColorManager.single_color(self.bg_color, self.appearance_mode)) super().configure(bg=CTkColorManager.single_color(self.fg_color, self.appearance_mode))
#self.update_idletasks()

View File

@ -90,8 +90,7 @@ class App(customtkinter.CTk):
self.progressbar = customtkinter.CTkProgressBar(master=self.frame_info, self.progressbar = customtkinter.CTkProgressBar(master=self.frame_info,
width=250, width=250,
height=15, height=12)
border_width=3)
self.progressbar.place(relx=0.5, rely=0.85, anchor=tkinter.S) self.progressbar.place(relx=0.5, rely=0.85, anchor=tkinter.S)
# from tkintermapview import TkinterMapView # from tkintermapview import TkinterMapView

View File

@ -18,7 +18,7 @@ class App(customtkinter.CTk):
MAIN_HOVER = "#458577" MAIN_HOVER = "#458577"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs) super().__init__(*args, **kwargs)
self.title(App.APP_NAME) self.title(App.APP_NAME)
self.geometry(str(App.WIDTH) + "x" + str(App.HEIGHT)) self.geometry(str(App.WIDTH) + "x" + str(App.HEIGHT))
@ -102,8 +102,7 @@ class App(customtkinter.CTk):
self.progressbar = customtkinter.CTkProgressBar(master=self.frame_info, self.progressbar = customtkinter.CTkProgressBar(master=self.frame_info,
progress_color=App.MAIN_COLOR, progress_color=App.MAIN_COLOR,
width=250, width=250,
height=15, height=12)
border_width=3)
self.progressbar.place(relx=0.5, rely=0.85, anchor=tkinter.S) self.progressbar.place(relx=0.5, rely=0.85, anchor=tkinter.S)
self.progressbar.set(0.65) self.progressbar.set(0.65)

View File

@ -1,33 +0,0 @@
import tkinter
import customtkinter # <- import the CustomTkinter module
customtkinter.set_appearance_mode("System") # Other: "Dark", "Light"
root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
root_tk.geometry("400x240")
root_tk.title("CustomTkinter Test")
def change_button_2_state():
if button_2.state == tkinter.NORMAL:
button_2.configure(state=tkinter.DISABLED)
elif button_2.state == tkinter.DISABLED:
button_2.configure(state=tkinter.NORMAL)
def button_2_click():
print("button_2 clicked")
frame_1 = customtkinter.CTkFrame(master=root_tk, width=300, height=200, corner_radius=15)
frame_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
button_1 = customtkinter.CTkButton(master=frame_1, text="Disable/Enable Button_2",
corner_radius=10, command=change_button_2_state, width=200)
button_1.place(relx=0.5, rely=0.3, anchor=tkinter.CENTER)
button_2 = customtkinter.CTkButton(master=frame_1, text="Button_2",
corner_radius=10, command=button_2_click)
button_2.place(relx=0.5, rely=0.7, anchor=tkinter.CENTER)
root_tk.mainloop()

View File

@ -11,15 +11,15 @@ PATH = os.path.dirname(os.path.realpath(__file__))
class App(customtkinter.CTk): class App(customtkinter.CTk):
APP_NAME = "CustomTkinter background gardient" APP_NAME = "CustomTkinter background gradient image"
WIDTH = 900 WIDTH = 900
HEIGHT = 600 HEIGHT = 600
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs) super().__init__(*args, **kwargs)
self.title(App.APP_NAME) self.title(App.APP_NAME)
self.geometry(str(App.WIDTH) + "x" + str(App.HEIGHT)) self.geometry(f"{App.WIDTH}x{App.HEIGHT}")
self.minsize(App.WIDTH, App.HEIGHT) self.minsize(App.WIDTH, App.HEIGHT)
self.maxsize(App.WIDTH, App.HEIGHT) self.maxsize(App.WIDTH, App.HEIGHT)
@ -28,10 +28,11 @@ class App(customtkinter.CTk):
self.bind("<Command-w>", self.on_closing) self.bind("<Command-w>", self.on_closing)
self.createcommand('tk::mac::Quit', self.on_closing) self.createcommand('tk::mac::Quit', self.on_closing)
self.image = Image.open(PATH + "/test_images/bg_gradient.jpg").resize((self.WIDTH, self.HEIGHT)) # load image with PIL and convert to PhotoImage
self.photo = ImageTk.PhotoImage(self.image) image = Image.open(PATH + "/test_images/bg_gradient.jpg").resize((self.WIDTH, self.HEIGHT))
self.bg_image = ImageTk.PhotoImage(image)
self.image_label = tkinter.Label(master=self, image=self.photo) self.image_label = tkinter.Label(master=self, image=self.bg_image)
self.image_label.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) self.image_label.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
self.frame = customtkinter.CTkFrame(master=self, self.frame = customtkinter.CTkFrame(master=self,
@ -40,16 +41,24 @@ class App(customtkinter.CTk):
corner_radius=0) corner_radius=0)
self.frame.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) self.frame.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
self.button_1 = customtkinter.CTkButton(master=self.frame, text="button 1", self.label_1 = customtkinter.CTkLabel(master=self.frame, corner_radius=10, width=200, height=60,
corner_radius=10, command=self.button_event, width=200) fg_color=("gray70", "gray20"), text="CustomTkinter\ninterface example")
self.button_1.place(relx=0.5, rely=0.6, anchor=tkinter.CENTER) self.label_1.place(relx=0.5, rely=0.3, anchor=tkinter.CENTER)
self.button_2 = customtkinter.CTkButton(master=self.frame, text="button 2", self.entry_1 = customtkinter.CTkEntry(master=self.frame, corner_radius=10, width=200)
self.entry_1.place(relx=0.5, rely=0.52, anchor=tkinter.CENTER)
self.entry_1.insert(0, "username")
self.entry_2 = customtkinter.CTkEntry(master=self.frame, corner_radius=10, width=200, show="*")
self.entry_2.place(relx=0.5, rely=0.6, anchor=tkinter.CENTER)
self.entry_2.insert(0, "password")
self.button_2 = customtkinter.CTkButton(master=self.frame, text="Login",
corner_radius=10, command=self.button_event, width=200) corner_radius=10, command=self.button_event, width=200)
self.button_2.place(relx=0.5, rely=0.7, anchor=tkinter.CENTER) self.button_2.place(relx=0.5, rely=0.7, anchor=tkinter.CENTER)
def button_event(self): def button_event(self):
print("Button pressed") print("Login pressed - username:", self.entry_1.get(), "password:", self.entry_2.get())
def on_closing(self, event=0): def on_closing(self, event=0):
self.destroy() self.destroy()

View File

@ -1,8 +1,47 @@
import tkinter as tk import time
import customtkinter as ctk import tkinter
import customtkinter # <- import the CustomTkinter module
root = tk.Tk() root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window)
root_tk.geometry("400x300")
root_tk.title("CustomTkinter Test")
btn = ctk.CTkButton(master=root, text="EXIT", command=root.destroy).pack() customtkinter.set_appearance_mode("System") # Other: "Dark", "Light"
root.mainloop()
def button_function():
print("Button click")
def slider_function(value):
progressbar_1.set(value)
def check_box_function():
print("checkbox_1:", checkbox_1.get())
frame_1 = customtkinter.CTkFrame(master=root_tk, width=300, height=260, corner_radius=15)
frame_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
label_1 = customtkinter.CTkLabel(master=frame_1)
label_1.place(relx=0.5, rely=0.1, anchor=tkinter.CENTER)
progressbar_1 = customtkinter.CTkProgressBar(master=frame_1)
progressbar_1.place(relx=0.5, rely=0.25, anchor=tkinter.CENTER)
button_1 = customtkinter.CTkButton(master=frame_1, corner_radius=10, command=button_function)
button_1.place(relx=0.5, rely=0.4, anchor=tkinter.CENTER)
# button_1.configure(state="disabled")
slider_1 = customtkinter.CTkSlider(master=frame_1, command=slider_function, from_=0, to=1, progress_color="gray20")
slider_1.place(relx=0.5, rely=0.55, anchor=tkinter.CENTER)
slider_1.set(1.5)
entry_1 = customtkinter.CTkEntry(master=frame_1)
entry_1.place(relx=0.5, rely=0.75, anchor=tkinter.CENTER)
checkbox_1 = customtkinter.CTkCheckBox(master=frame_1, command=check_box_function)
checkbox_1.place(relx=0.5, rely=0.9, anchor=tkinter.CENTER)
root_tk.mainloop()