moved base functionality of widgets to widget_base_class.py

This commit is contained in:
Tom Schimansky 2022-04-20 22:50:57 +02:00
parent 5ede252b2f
commit 992d0cbff0
18 changed files with 378 additions and 985 deletions

View File

@ -1,18 +1,18 @@
__version__ = "3.12" __version__ = "3.12"
from .widgets.customtkinter_input_dialog import CTkInputDialog from .widgets.ctk_input_dialog import CTkInputDialog
from .widgets.customtkinter_button import CTkButton from .widgets.ctk_button import CTkButton
from .widgets.customtkinter_slider import CTkSlider from .widgets.ctk_slider import CTkSlider
from .widgets.customtkinter_frame import CTkFrame from .widgets.ctk_frame import CTkFrame
from .widgets.customtkinter_progressbar import CTkProgressBar from .widgets.ctk_progressbar import CTkProgressBar
from .widgets.customtkinter_label import CTkLabel from .widgets.ctk_label import CTkLabel
from .widgets.customtkinter_entry import CTkEntry from .widgets.ctk_entry import CTkEntry
from .widgets.customtkinter_checkbox import CTkCheckBox from .widgets.ctk_checkbox import CTkCheckBox
from .widgets.customtkinter_radiobutton import CTkRadioButton from .widgets.ctk_radiobutton import CTkRadioButton
from .widgets.customtkinter_tk import CTk from .widgets.ctk_tk import CTk
from .widgets.customtkinter_canvas import CTkCanvas from .widgets.ctk_canvas import CTkCanvas
from .widgets.customtkinter_switch import CTkSwitch from .widgets.ctk_switch import CTkSwitch
from .widgets.customtkinter_toplevel import CTkToplevel from .widgets.ctk_toplevel import CTkToplevel
from .customtkinter_settings import CTkSettings from .customtkinter_settings import CTkSettings
from .appearance_mode_tracker import AppearanceModeTracker from .appearance_mode_tracker import AppearanceModeTracker

View File

@ -2,7 +2,7 @@ import sys
import tkinter import tkinter
from typing import Union from typing import Union
from .widgets.customtkinter_canvas import CTkCanvas from .widgets.ctk_canvas import CTkCanvas
class CTkDrawEngine: class CTkDrawEngine:

View File

@ -1,17 +1,14 @@
import tkinter import tkinter
import tkinter.ttk as ttk
import sys import sys
from .customtkinter_tk import CTk from .ctk_canvas import CTkCanvas
from .customtkinter_frame import CTkFrame
from .customtkinter_canvas import CTkCanvas
from customtkinter.appearance_mode_tracker import AppearanceModeTracker
from ..customtkinter_theme_manager import CTkThemeManager from ..customtkinter_theme_manager import CTkThemeManager
from ..customtkinter_settings import CTkSettings from ..customtkinter_settings import CTkSettings
from ..customtkinter_draw_engine import CTkDrawEngine from ..customtkinter_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass
class CTkButton(tkinter.Frame): class CTkButton(CTkBaseClass):
""" tkinter custom button with border, rounded corners and hover effect """ """ tkinter custom button with border, rounded corners and hover effect """
def __init__(self, *args, def __init__(self, *args,
@ -34,45 +31,18 @@ class CTkButton(tkinter.Frame):
compound=tkinter.LEFT, compound=tkinter.LEFT,
state=tkinter.NORMAL, state=tkinter.NORMAL,
**kwargs): **kwargs):
super().__init__(*args, **kwargs)
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)): super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
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
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.set_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.configure_basic_grid() self.configure_basic_grid()
# color variables # color variables
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
self.border_color = CTkThemeManager.theme["color"]["button_border"] if border_color == "default_theme" else border_color self.border_color = CTkThemeManager.theme["color"]["button_border"] if border_color == "default_theme" else border_color
# shape and size # shape
self.width = width
self.height = height
self.configure(width=self.width, height=self.height)
self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = CTkThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width self.border_width = CTkThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width
@ -98,7 +68,6 @@ class CTkButton(tkinter.Frame):
width=self.width, width=self.width,
height=self.height) height=self.height)
self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew") self.canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew")
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
# event bindings # event bindings
@ -113,45 +82,13 @@ class CTkButton(tkinter.Frame):
self.set_cursor() self.set_cursor()
self.draw() # initial draw self.draw() # initial draw
def destroy(self):
AppearanceModeTracker.remove(self.set_appearance_mode)
super().destroy()
def configure_basic_grid(self): def configure_basic_grid(self):
# Configuration of a basic grid (2x2) in which all elements of CTkButtons are centered on one row and one column # Configuration of a grid system (2x2) in which all parts of CTkButton are centered on one row and one column
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=1) self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(1, weight=1) self.grid_columnconfigure(1, weight=1)
def update_dimensions(self, event):
# only redraw if dimensions changed (for performance)
if self.width != event.width or self.height != event.height:
self.width = event.width
self.height = event.height
# self.canvas.config(width=self.width, height=self.height)
self.draw(no_color_updates=True) # fast drawing without color changes
def detect_color_of_master(self):
""" detect color of self.master widget to set correct bg_color """
if isinstance(self.master, CTkFrame): # master is CTkFrame
return self.master.fg_color
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try:
ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background')
except Exception:
return "#FFFFFF", "#000000"
else: # master is normal tkinter widget
try:
return self.master.cget("bg") # try to get bg color by .cget() method
except Exception:
return "#FFFFFF", "#000000"
def draw(self, no_color_updates=False): def draw(self, no_color_updates=False):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width) requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
@ -256,9 +193,6 @@ class CTkButton(tkinter.Frame):
self.image_label.grid(row=1, column=0, padx=max(self.corner_radius, self.border_width), sticky="n", columnspan=2, rowspan=1, pady=(2, self.border_width)) self.image_label.grid(row=1, column=0, padx=max(self.corner_radius, self.border_width), sticky="n", columnspan=2, rowspan=1, pady=(2, self.border_width))
self.text_label.grid(row=0, column=0, padx=max(self.corner_radius, self.border_width), sticky="s", columnspan=2, rowspan=1, pady=(self.border_width, 2)) self.text_label.grid(row=0, column=0, padx=max(self.corner_radius, self.border_width), sticky="s", columnspan=2, rowspan=1, pady=(self.border_width, 2))
def config(self, *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 require_redraw = False # some attribute changes require a call of self.draw() at the end
@ -401,16 +335,3 @@ class CTkButton(tkinter.Frame):
self.after(100, self.click_animation) self.after(100, self.click_animation)
self.function() self.function()
def set_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()

View File

@ -1,17 +1,14 @@
import tkinter import tkinter
import tkinter.ttk as ttk
import sys import sys
from .customtkinter_tk import CTk from .ctk_canvas import CTkCanvas
from .customtkinter_frame import CTkFrame
from .customtkinter_canvas import CTkCanvas
from ..appearance_mode_tracker import AppearanceModeTracker
from ..customtkinter_theme_manager import CTkThemeManager from ..customtkinter_theme_manager import CTkThemeManager
from ..customtkinter_settings import CTkSettings from ..customtkinter_settings import CTkSettings
from ..customtkinter_draw_engine import CTkDrawEngine from ..customtkinter_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass
class CTkCheckBox(tkinter.Frame): class CTkCheckBox(CTkBaseClass):
""" tkinter custom checkbox with border, rounded corners and hover effect """ """ tkinter custom checkbox with border, rounded corners and hover effect """
def __init__(self, *args, def __init__(self, *args,
@ -36,59 +33,28 @@ class CTkCheckBox(tkinter.Frame):
variable=None, variable=None,
textvariable=None, textvariable=None,
**kwargs): **kwargs):
super().__init__(*args, **kwargs)
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)): super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
master_old_configure = self.master.config
def new_configure(*args, **kwargs): # color
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
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.set_appearance_mode, self)
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.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
self.border_color = CTkThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color self.border_color = CTkThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color
self.checkmark_color = CTkThemeManager.theme["color"]["checkmark"] if checkmark_color == "default_theme" else checkmark_color self.checkmark_color = CTkThemeManager.theme["color"]["checkmark"] if checkmark_color == "default_theme" else checkmark_color
self.width = width # shape
self.height = height
self.corner_radius = CTkThemeManager.theme["shape"]["checkbox_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = CTkThemeManager.theme["shape"]["checkbox_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = CTkThemeManager.theme["shape"]["checkbox_border_width"] if border_width == "default_theme" else border_width self.border_width = CTkThemeManager.theme["shape"]["checkbox_border_width"] if border_width == "default_theme" else border_width
if self.corner_radius*2 > self.height: # text
self.corner_radius = self.height/2
elif self.corner_radius*2 > self.width:
self.corner_radius = self.width/2
if self.corner_radius >= self.border_width:
self.inner_corner_radius = self.corner_radius - self.border_width
else:
self.inner_corner_radius = 0
self.text = text self.text = text
self.text_label = None
self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self.text_color_disabled = CTkThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled self.text_color_disabled = CTkThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
# callback and hover functionality
self.function = command self.function = command
self.state = state self.state = state
self.hover = hover self.hover = hover
@ -100,7 +66,7 @@ class CTkCheckBox(tkinter.Frame):
self.textvariable = textvariable self.textvariable = textvariable
self.variable_callback_name = None self.variable_callback_name = None
# configure grid system # configure grid system (1x3)
self.grid_columnconfigure(0, weight=0) self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=0, minsize=6) self.grid_columnconfigure(1, weight=0, minsize=6)
self.grid_columnconfigure(2, weight=1) self.grid_columnconfigure(2, weight=1)
@ -110,7 +76,6 @@ class CTkCheckBox(tkinter.Frame):
width=self.width, width=self.width,
height=self.height) height=self.height)
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1) self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1)
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
if self.hover is True: if self.hover is True:
@ -120,11 +85,7 @@ class CTkCheckBox(tkinter.Frame):
self.canvas.bind("<Button-1>", self.toggle) self.canvas.bind("<Button-1>", self.toggle)
self.canvas.bind("<Button-1>", self.toggle) self.canvas.bind("<Button-1>", self.toggle)
self.text_label = None # set select state according to variable
self.set_cursor()
self.draw() # initial draw
if self.variable is not None: if self.variable is not None:
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback) self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
if self.variable.get() == self.onvalue: if self.variable.get() == self.onvalue:
@ -132,34 +93,16 @@ class CTkCheckBox(tkinter.Frame):
elif self.variable.get() == self.offvalue: elif self.variable.get() == self.offvalue:
self.deselect(from_variable_callback=True) self.deselect(from_variable_callback=True)
def destroy(self): self.set_cursor()
AppearanceModeTracker.remove(self.set_appearance_mode) self.draw() # initial draw
def destroy(self):
if self.variable is not None: if self.variable is not None:
self.variable.trace_remove("write", self.variable_callback_name) self.variable.trace_remove("write", self.variable_callback_name)
super().destroy() super().destroy()
def detect_color_of_master(self): def draw(self, no_color_updates=False):
""" detect color of self.master widget to set correct bg_color """
if isinstance(self.master, CTkFrame): # master is CTkFrame
return self.master.fg_color
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try:
ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background')
except Exception:
return "#FFFFFF", "#000000"
else: # master is normal tkinter widget
try:
return self.master.cget("bg") # try to get bg color by .cget() method
except Exception:
return "#FFFFFF", "#000000"
def draw(self):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width) requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
if self.check_state is True: if self.check_state is True:
@ -207,9 +150,6 @@ class CTkCheckBox(tkinter.Frame):
self.set_text(self.text) self.set_text(self.text)
def config(self, *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() require_redraw = False # some attribute changes require a call of self.draw()
@ -378,16 +318,3 @@ class CTkCheckBox(tkinter.Frame):
def get(self): def get(self):
return self.onvalue if self.check_state is True else self.offvalue return self.onvalue if self.check_state is True else self.offvalue
def set_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()

View File

@ -1,18 +1,14 @@
import tkinter import tkinter
import tkinter.ttk as ttk
from .customtkinter_tk import CTk from .ctk_canvas import CTkCanvas
from .customtkinter_frame import CTkFrame
from .customtkinter_canvas import CTkCanvas
from ..appearance_mode_tracker import AppearanceModeTracker
from ..customtkinter_theme_manager import CTkThemeManager from ..customtkinter_theme_manager import CTkThemeManager
from ..customtkinter_settings import CTkSettings from ..customtkinter_settings import CTkSettings
from ..customtkinter_draw_engine import CTkDrawEngine from ..customtkinter_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass
class CTkEntry(tkinter.Frame): class CTkEntry(CTkBaseClass):
def __init__(self, *args, def __init__(self, *args,
master=None,
bg_color=None, bg_color=None,
fg_color="default_theme", fg_color="default_theme",
text_color="default_theme", text_color="default_theme",
@ -25,39 +21,16 @@ class CTkEntry(tkinter.Frame):
width=120, width=120,
height=30, height=30,
**kwargs): **kwargs):
if master is None:
super().__init__(*args) # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
if "master" in kwargs:
super().__init__(*args, bg_color=bg_color, width=width, height=height, master=kwargs["master"])
del kwargs["master"]
else: else:
super().__init__(*args, master=master) super().__init__(*args, bg_color=bg_color, width=width, height=height)
# 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
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.change_appearance_mode, self)
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.fg_color = CTkThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color self.fg_color = CTkThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self.placeholder_text_color = CTkThemeManager.theme["color"]["entry_placeholder_text"] if placeholder_text_color == "default_theme" else placeholder_text_color self.placeholder_text_color = CTkThemeManager.theme["color"]["entry_placeholder_text"] if placeholder_text_color == "default_theme" else placeholder_text_color
@ -68,23 +41,15 @@ class CTkEntry(tkinter.Frame):
self.placeholder_text_active = False self.placeholder_text_active = False
self.pre_placeholder_arguments = {} # some set arguments of the entry will be changed for placeholder and then set back self.pre_placeholder_arguments = {} # some set arguments of the entry will be changed for placeholder and then set back
self.width = width
self.height = height
self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = CTkThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = CTkThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width self.border_width = CTkThemeManager.theme["shape"]["entry_border_width"] if border_width == "default_theme" else border_width
if self.corner_radius*2 > self.height:
self.corner_radius = self.height/2
elif self.corner_radius*2 > self.width:
self.corner_radius = self.width/2
super().configure(width=self.width, height=self.height)
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.width, width=self.width,
height=self.height) height=self.height)
self.canvas.grid(column=0, row=0, sticky="we") self.canvas.grid(column=0, row=0, sticky="we")
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
self.entry = tkinter.Entry(master=self, self.entry = tkinter.Entry(master=self,
bd=0, bd=0,
@ -94,8 +59,6 @@ class CTkEntry(tkinter.Frame):
**kwargs) **kwargs)
self.entry.grid(column=0, row=0, sticky="we", padx=self.corner_radius if self.corner_radius >= 6 else 6) self.entry.grid(column=0, row=0, sticky="we", padx=self.corner_radius if self.corner_radius >= 6 else 6)
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
super().bind('<Configure>', self.update_dimensions) super().bind('<Configure>', self.update_dimensions)
self.entry.bind('<FocusOut>', self.set_placeholder) self.entry.bind('<FocusOut>', self.set_placeholder)
self.entry.bind('<FocusIn>', self.clear_placeholder) self.entry.bind('<FocusIn>', self.clear_placeholder)
@ -103,42 +66,10 @@ class CTkEntry(tkinter.Frame):
self.draw() self.draw()
self.set_placeholder() self.set_placeholder()
def destroy(self):
AppearanceModeTracker.remove(self.change_appearance_mode)
super().destroy()
def configure_basic_grid(self): def configure_basic_grid(self):
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
def detect_color_of_master(self):
""" detect color of self.master widget to set correct bg_color """
if isinstance(self.master, CTkFrame): # master is CTkFrame
return self.master.fg_color
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try:
ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background')
except Exception:
return "#FFFFFF", "#000000"
else: # master is normal tkinter widget
try:
return self.master.cget("bg") # try to get bg color by .cget() method
except Exception:
return "#FFFFFF", "#000000"
def update_dimensions(self, event):
# only redraw if dimensions changed (for performance)
if self.width != event.width or self.height != event.height:
# print(event.x, event.width, self.width)
self.width = event.width
self.height = event.height
self.draw()
def set_placeholder(self, event=None): def set_placeholder(self, event=None):
if self.placeholder_text is not None: if self.placeholder_text is not None:
if not self.placeholder_text_active and self.entry.get() == "": if not self.placeholder_text_active and self.entry.get() == "":
@ -156,7 +87,7 @@ class CTkEntry(tkinter.Frame):
for argument, value in self.pre_placeholder_arguments.items(): for argument, value in self.pre_placeholder_arguments.items():
self.entry[argument] = value self.entry[argument] = value
def draw(self): def draw(self, no_color_updates=False):
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width) requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
@ -188,16 +119,16 @@ class CTkEntry(tkinter.Frame):
def bind(self, *args, **kwargs): def bind(self, *args, **kwargs):
self.entry.bind(*args, **kwargs) self.entry.bind(*args, **kwargs)
def config(self, *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 require_redraw = False # some attribute changes require a call of self.draw() at the end
if "bg_color" in kwargs: if "bg_color" in kwargs:
self.bg_color = kwargs["bg_color"] if kwargs["bg_color"] is None:
del kwargs["bg_color"] self.bg_color = self.detect_color_of_master()
else:
self.bg_color = kwargs["bg_color"]
require_redraw = True require_redraw = True
del kwargs["bg_color"]
if "fg_color" in kwargs: if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"] self.fg_color = kwargs["fg_color"]
@ -232,7 +163,6 @@ class CTkEntry(tkinter.Frame):
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
self.entry.delete(*args, **kwargs) self.entry.delete(*args, **kwargs)
self.set_placeholder() self.set_placeholder()
return
def insert(self, *args, **kwargs): def insert(self, *args, **kwargs):
self.clear_placeholder() self.clear_placeholder()
@ -243,16 +173,3 @@ class CTkEntry(tkinter.Frame):
return "" return ""
else: else:
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()

View File

@ -0,0 +1,111 @@
import tkinter
from .ctk_canvas import CTkCanvas
from ..customtkinter_theme_manager import CTkThemeManager
from ..customtkinter_settings import CTkSettings
from ..customtkinter_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass
class CTkFrame(CTkBaseClass):
def __init__(self, *args,
bg_color=None,
fg_color="default_theme",
border_color="default_theme",
border_width="default_theme",
corner_radius="default_theme",
width=200,
height=200,
**kwargs):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
# color
self.border_color = CTkThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
if fg_color == "default_theme":
if isinstance(self.master, CTkFrame):
if self.master.fg_color == CTkThemeManager.theme["color"]["frame_low"]:
self.fg_color = CTkThemeManager.theme["color"]["frame_high"]
else:
self.fg_color = CTkThemeManager.theme["color"]["frame_low"]
else:
self.fg_color = CTkThemeManager.theme["color"]["frame_low"]
else:
self.fg_color = fg_color
# shape
self.corner_radius = CTkThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = CTkThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
self.canvas = CTkCanvas(master=self,
highlightthickness=0,
width=self.width,
height=self.height)
self.canvas.place(x=0, y=0, relwidth=1, relheight=1)
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
self.bind('<Configure>', self.update_dimensions)
self.draw()
def winfo_children(self):
""" winfo_children of CTkFrame without self.canvas widget,
because it's not a child but part of the CTkFrame itself """
child_widgets = super().winfo_children()
try:
child_widgets.remove(self.canvas)
return child_widgets
except ValueError:
return child_widgets
def draw(self, no_color_updates=False):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
if no_color_updates is False or requires_recoloring:
self.canvas.itemconfig("inner_parts",
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
self.canvas.itemconfig("border_parts",
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
self.canvas.tag_lower("inner_parts")
self.canvas.tag_lower("border_parts")
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
for child in self.winfo_children():
if isinstance(child, CTkBaseClass):
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()

View File

@ -1,11 +1,11 @@
import tkinter import tkinter
import time import time
from .customtkinter_label import CTkLabel from .ctk_label import CTkLabel
from .customtkinter_entry import CTkEntry from .ctk_entry import CTkEntry
from .customtkinter_frame import CTkFrame from .ctk_frame import CTkFrame
from .customtkinter_toplevel import CTkToplevel from .ctk_toplevel import CTkToplevel
from .customtkinter_button import CTkButton from .ctk_button import CTkButton
from ..appearance_mode_tracker import AppearanceModeTracker from ..appearance_mode_tracker import AppearanceModeTracker
from ..customtkinter_theme_manager import CTkThemeManager from ..customtkinter_theme_manager import CTkThemeManager

View File

@ -1,18 +1,14 @@
import tkinter import tkinter
import tkinter.ttk as ttk
from .customtkinter_tk import CTk from .ctk_canvas import CTkCanvas
from .customtkinter_frame import CTkFrame
from .customtkinter_canvas import CTkCanvas
from ..appearance_mode_tracker import AppearanceModeTracker
from ..customtkinter_theme_manager import CTkThemeManager from ..customtkinter_theme_manager import CTkThemeManager
from ..customtkinter_settings import CTkSettings from ..customtkinter_settings import CTkSettings
from ..customtkinter_draw_engine import CTkDrawEngine from ..customtkinter_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass
class CTkLabel(tkinter.Frame): class CTkLabel(CTkBaseClass):
def __init__(self, *args, def __init__(self, *args,
master=None,
bg_color=None, bg_color=None,
fg_color="default_theme", fg_color="default_theme",
text_color="default_theme", text_color="default_theme",
@ -22,54 +18,28 @@ class CTkLabel(tkinter.Frame):
text="CTkLabel", text="CTkLabel",
text_font="default_theme", text_font="default_theme",
**kwargs): **kwargs):
if master is None:
super().__init__(*args) # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
if "master" in kwargs:
super().__init__(*args, bg_color=bg_color, width=width, height=height, master=kwargs["master"])
del kwargs["master"]
else: else:
super().__init__(*args, master=master) super().__init__(*args, bg_color=bg_color, width=width, height=height)
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too # color
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
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.change_appearance_mode, self)
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.fg_color = CTkThemeManager.theme["color"]["label"] if fg_color == "default_theme" else fg_color self.fg_color = CTkThemeManager.theme["color"]["label"] if fg_color == "default_theme" else fg_color
if self.fg_color is None: if self.fg_color is None:
self.fg_color = self.bg_color self.fg_color = self.bg_color
self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self.width = width # shape
self.height = height
self.corner_radius = CTkThemeManager.theme["shape"]["label_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = CTkThemeManager.theme["shape"]["label_corner_radius"] if corner_radius == "default_theme" else corner_radius
if self.corner_radius * 2 > self.height: # text
self.corner_radius = self.height / 2
elif self.corner_radius * 2 > self.width:
self.corner_radius = self.width / 2
self.text = text self.text = text
self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
# configure grid system (1x1)
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
@ -78,6 +48,7 @@ class CTkLabel(tkinter.Frame):
width=self.width, width=self.width,
height=self.height) height=self.height)
self.canvas.grid(row=0, column=0, sticky="nswe") self.canvas.grid(row=0, column=0, sticky="nswe")
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
self.text_label = tkinter.Label(master=self, self.text_label = tkinter.Label(master=self,
highlightthickness=0, highlightthickness=0,
@ -87,46 +58,10 @@ class CTkLabel(tkinter.Frame):
**kwargs) **kwargs)
self.text_label.grid(row=0, column=0, padx=self.corner_radius) self.text_label.grid(row=0, column=0, padx=self.corner_radius)
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
super().configure(width=self.width, height=self.height)
self.bind('<Configure>', self.update_dimensions) self.bind('<Configure>', self.update_dimensions)
self.draw() self.draw()
def destroy(self): def draw(self, no_color_updates=False):
AppearanceModeTracker.remove(self.change_appearance_mode)
super().destroy()
def detect_color_of_master(self):
""" detect color of self.master widget to set correct bg_color """
if isinstance(self.master, CTkFrame): # master is CTkFrame
return self.master.fg_color
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try:
ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background')
except Exception:
return "#FFFFFF", "#000000"
else: # master is normal tkinter widget
try:
return self.master.cget("bg") # try to get bg color by .cget() method
except Exception:
return "#FFFFFF", "#000000"
def update_dimensions(self, event):
# only redraw if dimensions changed (for performance)
if self.width != event.width or self.height != event.height:
self.width = event.width
self.height = event.height
# self.canvas.config(width=self.width, height=self.height)
self.draw()
def draw(self):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, 0) requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, 0)
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
@ -146,9 +81,6 @@ class CTkLabel(tkinter.Frame):
self.text_label.configure(fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode), self.text_label.configure(fg=CTkThemeManager.single_color(self.text_color, self.appearance_mode),
bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
def config(self, *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 require_redraw = False # some attribute changes require a call of self.draw() at the end
@ -182,16 +114,3 @@ class CTkLabel(tkinter.Frame):
def set_text(self, text): def set_text(self, text):
self.text = text self.text = text
self.text_label.configure(text=self.text, width=len(self.text)) self.text_label.configure(text=self.text, width=len(self.text))
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()

View File

@ -1,17 +1,13 @@
import sys
import tkinter import tkinter
import tkinter.ttk as ttk
from .customtkinter_tk import CTk from .ctk_canvas import CTkCanvas
from .customtkinter_frame import CTkFrame
from .customtkinter_canvas import CTkCanvas
from ..appearance_mode_tracker import AppearanceModeTracker
from ..customtkinter_theme_manager import CTkThemeManager from ..customtkinter_theme_manager import CTkThemeManager
from ..customtkinter_draw_engine import CTkDrawEngine from ..customtkinter_draw_engine import CTkDrawEngine
from ..customtkinter_settings import CTkSettings from ..customtkinter_settings import CTkSettings
from .widget_base_class import CTkBaseClass
class CTkProgressBar(tkinter.Frame): class CTkProgressBar(CTkBaseClass):
""" tkinter custom progressbar, always horizontal, values are from 0 to 1 """ """ tkinter custom progressbar, always horizontal, values are from 0 to 1 """
def __init__(self, *args, def __init__(self, *args,
@ -25,56 +21,30 @@ class CTkProgressBar(tkinter.Frame):
height=8, height=8,
border_width="default_theme", border_width="default_theme",
**kwargs): **kwargs):
super().__init__(*args, **kwargs)
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)): super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
master_old_configure = self.master.config
def new_configure(*args, **kwargs): # color
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
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.change_appearance_mode, self)
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.border_color = CTkThemeManager.theme["color"]["progressbar_border"] if border_color == "default_theme" else border_color self.border_color = CTkThemeManager.theme["color"]["progressbar_border"] if border_color == "default_theme" else border_color
self.fg_color = CTkThemeManager.theme["color"]["progressbar"] if fg_color == "default_theme" else fg_color self.fg_color = CTkThemeManager.theme["color"]["progressbar"] if fg_color == "default_theme" else fg_color
self.progress_color = CTkThemeManager.theme["color"]["progressbar_progress"] if progress_color == "default_theme" else progress_color self.progress_color = CTkThemeManager.theme["color"]["progressbar_progress"] if progress_color == "default_theme" else progress_color
# control variable
self.variable = variable self.variable = variable
self.variable_callback_blocked = False self.variable_callback_blocked = False
self.variable_callback_name = None self.variable_callback_name = None
self.width = width # shape
self.height = height
self.corner_radius = CTkThemeManager.theme["shape"]["progressbar_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = CTkThemeManager.theme["shape"]["progressbar_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = CTkThemeManager.theme["shape"]["progressbar_border_width"] if border_width == "default_theme" else border_width self.border_width = CTkThemeManager.theme["shape"]["progressbar_border_width"] if border_width == "default_theme" else border_width
self.value = 0.5 self.value = 0.5
self.configure(width=self.width, height=self.height)
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.width, width=self.width,
height=self.height) height=self.height)
self.canvas.place(x=0, y=0, relwidth=1, relheight=1) self.canvas.place(x=0, y=0, relwidth=1, relheight=1)
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
# Each time an item is resized due to pack position mode, the binding Configure is called on the widget # Each time an item is resized due to pack position mode, the binding Configure is called on the widget
@ -89,53 +59,11 @@ class CTkProgressBar(tkinter.Frame):
self.variable_callback_blocked = False self.variable_callback_blocked = False
def destroy(self): def destroy(self):
AppearanceModeTracker.remove(self.change_appearance_mode)
if self.variable is not None: if self.variable is not None:
self.variable.trace_remove("write", self.variable_callback_name) self.variable.trace_remove("write", self.variable_callback_name)
super().destroy() super().destroy()
def detect_color_of_master(self):
""" detect color of self.master widget to set correct bg_color """
if isinstance(self.master, CTkFrame): # master is CTkFrame
return self.master.fg_color
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try:
ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background')
except Exception:
return "#FFFFFF", "#000000"
else: # master is normal tkinter widget
try:
return self.master.cget("bg") # try to get bg color by .cget() method
except Exception:
return "#FFFFFF", "#000000"
@staticmethod
def calc_optimal_height(user_height):
if sys.platform == "darwin":
return user_height # on macOS just use given value (canvas has Antialiasing)
else:
# make sure the value is always with uneven for better rendering of the ovals
if user_height == 0:
return 0
elif user_height % 2 == 0:
return user_height + 1
else:
return user_height
def update_dimensions(self, event):
# only redraw if dimensions changed (for performance)
if self.width != event.width or self.height != event.height:
self.width = event.width
self.height = event.height
self.draw()
def draw(self, no_color_updates=False): def draw(self, no_color_updates=False):
requires_recoloring = self.draw_engine.draw_rounded_progress_bar_with_border(self.width, self.height, self.corner_radius, self.border_width, self.value, "w") requires_recoloring = self.draw_engine.draw_rounded_progress_bar_with_border(self.width, self.height, self.corner_radius, self.border_width, self.value, "w")
@ -156,9 +84,12 @@ class CTkProgressBar(tkinter.Frame):
require_redraw = False # some attribute changes require a call of self.draw() at the end require_redraw = False # some attribute changes require a call of self.draw() at the end
if "bg_color" in kwargs: if "bg_color" in kwargs:
self.bg_color = kwargs["bg_color"] if kwargs["bg_color"] is None:
del kwargs["bg_color"] self.bg_color = self.detect_color_of_master()
else:
self.bg_color = kwargs["bg_color"]
require_redraw = True require_redraw = True
del kwargs["bg_color"]
if "fg_color" in kwargs: if "fg_color" in kwargs:
self.fg_color = kwargs["fg_color"] self.fg_color = kwargs["fg_color"]
@ -217,16 +148,3 @@ class CTkProgressBar(tkinter.Frame):
self.variable_callback_blocked = True self.variable_callback_blocked = True
self.variable.set(round(self.value) if isinstance(self.variable, tkinter.IntVar) else self.value) self.variable.set(round(self.value) if isinstance(self.variable, tkinter.IntVar) else self.value)
self.variable_callback_blocked = False self.variable_callback_blocked = False
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):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()

View File

@ -1,17 +1,14 @@
import tkinter import tkinter
import tkinter.ttk as ttk
import sys import sys
from .customtkinter_tk import CTk from .ctk_canvas import CTkCanvas
from .customtkinter_frame import CTkFrame
from .customtkinter_canvas import CTkCanvas
from ..appearance_mode_tracker import AppearanceModeTracker
from ..customtkinter_theme_manager import CTkThemeManager from ..customtkinter_theme_manager import CTkThemeManager
from ..customtkinter_settings import CTkSettings from ..customtkinter_settings import CTkSettings
from ..customtkinter_draw_engine import CTkDrawEngine from ..customtkinter_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass
class CTkRadioButton(tkinter.Frame): class CTkRadioButton(CTkBaseClass):
def __init__(self, *args, def __init__(self, *args,
bg_color=None, bg_color=None,
fg_color="default_theme", fg_color="default_theme",
@ -33,60 +30,29 @@ class CTkRadioButton(tkinter.Frame):
variable=None, variable=None,
textvariable=None, textvariable=None,
**kwargs): **kwargs):
super().__init__(*args, **kwargs)
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)): super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
master_old_configure = self.master.config
def new_configure(*args, **kwargs): # color
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
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.set_appearance_mode, self)
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.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color self.fg_color = CTkThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color self.hover_color = CTkThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
self.border_color = CTkThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color self.border_color = CTkThemeManager.theme["color"]["checkbox_border"] if border_color == "default_theme" else border_color
self.width = width # shape
self.height = height
self.corner_radius = CTkThemeManager.theme["shape"]["radiobutton_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = CTkThemeManager.theme["shape"]["radiobutton_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width_unchecked = CTkThemeManager.theme["shape"]["radiobutton_border_width_unchecked"] if border_width_unchecked == "default_theme" else border_width_unchecked self.border_width_unchecked = CTkThemeManager.theme["shape"]["radiobutton_border_width_unchecked"] if border_width_unchecked == "default_theme" else border_width_unchecked
self.border_width_checked = CTkThemeManager.theme["shape"]["radiobutton_border_width_checked"] if border_width_checked == "default_theme" else border_width_checked self.border_width_checked = CTkThemeManager.theme["shape"]["radiobutton_border_width_checked"] if border_width_checked == "default_theme" else border_width_checked
self.border_width = self.border_width_unchecked self.border_width = self.border_width_unchecked
if self.corner_radius*2 > self.height: # text
self.corner_radius = self.height/2
elif self.corner_radius*2 > self.width:
self.corner_radius = self.width/2
if self.corner_radius >= self.border_width:
self.inner_corner_radius = self.corner_radius - self.border_width
else:
self.inner_corner_radius = 0
self.text = text self.text = text
self.text_label = None
self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self.text_color_disabled = CTkThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled self.text_color_disabled = CTkThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
# callback and control variables
self.function = command self.function = command
self.state = state self.state = state
self.hover = hover self.hover = hover
@ -97,7 +63,7 @@ class CTkRadioButton(tkinter.Frame):
self.textvariable = textvariable self.textvariable = textvariable
self.variable_callback_name = None self.variable_callback_name = None
# configure grid system # configure grid system (3x1)
self.grid_columnconfigure(0, weight=0) self.grid_columnconfigure(0, weight=0)
self.grid_columnconfigure(1, weight=0, minsize=6) self.grid_columnconfigure(1, weight=0, minsize=6)
self.grid_columnconfigure(2, weight=1) self.grid_columnconfigure(2, weight=1)
@ -107,7 +73,6 @@ class CTkRadioButton(tkinter.Frame):
width=self.width, width=self.width,
height=self.height) height=self.height)
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1) self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1)
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
self.canvas.bind("<Enter>", self.on_enter) self.canvas.bind("<Enter>", self.on_enter)
@ -115,8 +80,6 @@ class CTkRadioButton(tkinter.Frame):
self.canvas.bind("<Button-1>", self.invoke) self.canvas.bind("<Button-1>", self.invoke)
self.canvas.bind("<Button-1>", self.invoke) self.canvas.bind("<Button-1>", self.invoke)
self.text_label = None
self.set_cursor() self.set_cursor()
self.draw() # initial draw self.draw() # initial draw
@ -128,33 +91,12 @@ class CTkRadioButton(tkinter.Frame):
self.deselect(from_variable_callback=True) self.deselect(from_variable_callback=True)
def destroy(self): def destroy(self):
AppearanceModeTracker.remove(self.set_appearance_mode)
if self.variable is not None: if self.variable is not None:
self.variable.trace_remove("write", self.variable_callback_name) self.variable.trace_remove("write", self.variable_callback_name)
super().destroy() super().destroy()
def detect_color_of_master(self): def draw(self, no_color_updates=False):
""" detect color of self.master widget to set correct bg_color """
if isinstance(self.master, CTkFrame): # master is CTkFrame
return self.master.fg_color
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try:
ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background')
except Exception:
return "#FFFFFF", "#000000"
else: # master is normal tkinter widget
try:
return self.master.cget("bg") # try to get bg color by .cget() method
except Exception:
return "#FFFFFF", "#000000"
def draw(self):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width) requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
@ -191,9 +133,6 @@ class CTkRadioButton(tkinter.Frame):
self.set_text(self.text) self.set_text(self.text)
def config(self, *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() require_redraw = False # some attribute changes require a call of self.draw()
@ -338,16 +277,3 @@ class CTkRadioButton(tkinter.Frame):
self.variable_callback_blocked = True self.variable_callback_blocked = True
self.variable.set("") self.variable.set("")
self.variable_callback_blocked = False self.variable_callback_blocked = False
def set_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()

View File

@ -1,17 +1,14 @@
import tkinter import tkinter
import tkinter.ttk as ttk
import sys import sys
from .customtkinter_tk import CTk from .ctk_canvas import CTkCanvas
from .customtkinter_frame import CTkFrame
from .customtkinter_canvas import CTkCanvas
from ..appearance_mode_tracker import AppearanceModeTracker
from ..customtkinter_theme_manager import CTkThemeManager from ..customtkinter_theme_manager import CTkThemeManager
from ..customtkinter_settings import CTkSettings from ..customtkinter_settings import CTkSettings
from ..customtkinter_draw_engine import CTkDrawEngine from ..customtkinter_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass
class CTkSlider(tkinter.Frame): class CTkSlider(CTkBaseClass):
""" tkinter custom slider, always horizontal """ """ tkinter custom slider, always horizontal """
def __init__(self, *args, def __init__(self, *args,
@ -33,44 +30,18 @@ class CTkSlider(tkinter.Frame):
command=None, command=None,
variable=None, variable=None,
**kwargs): **kwargs):
super().__init__(*args, **kwargs)
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)): super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
master_old_configure = self.master.config
def new_configure(*args, **kwargs): # color
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
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.change_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.configure_basic_grid()
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
self.border_color = border_color self.border_color = border_color
self.fg_color = CTkThemeManager.theme["color"]["slider"] if fg_color == "default_theme" else fg_color self.fg_color = CTkThemeManager.theme["color"]["slider"] if fg_color == "default_theme" else fg_color
self.progress_color = CTkThemeManager.theme["color"]["slider_progress"] if progress_color == "default_theme" else progress_color self.progress_color = CTkThemeManager.theme["color"]["slider_progress"] if progress_color == "default_theme" else progress_color
self.button_color = CTkThemeManager.theme["color"]["slider_button"] if button_color == "default_theme" else button_color self.button_color = CTkThemeManager.theme["color"]["slider_button"] if button_color == "default_theme" else button_color
self.button_hover_color = CTkThemeManager.theme["color"]["slider_button_hover"] if button_hover_color == "default_theme" else button_hover_color self.button_hover_color = CTkThemeManager.theme["color"]["slider_button_hover"] if button_hover_color == "default_theme" else button_hover_color
self.width = width # shape
self.height = height
self.corner_radius = CTkThemeManager.theme["shape"]["slider_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = CTkThemeManager.theme["shape"]["slider_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.button_corner_radius = CTkThemeManager.theme["shape"]["slider_button_corner_radius"] if button_corner_radius == "default_theme" else button_corner_radius self.button_corner_radius = CTkThemeManager.theme["shape"]["slider_button_corner_radius"] if button_corner_radius == "default_theme" else button_corner_radius
self.border_width = CTkThemeManager.theme["shape"]["slider_border_width"] if border_width == "default_theme" else border_width self.border_width = CTkThemeManager.theme["shape"]["slider_border_width"] if border_width == "default_theme" else border_width
@ -85,23 +56,17 @@ class CTkSlider(tkinter.Frame):
if self.corner_radius < self.button_corner_radius: if self.corner_radius < self.button_corner_radius:
self.corner_radius = self.button_corner_radius self.corner_radius = self.button_corner_radius
# callback and control variables
self.callback_function = command self.callback_function = command
self.variable: tkinter.Variable = variable self.variable: tkinter.Variable = variable
self.variable_callback_blocked = False self.variable_callback_blocked = False
self.variable_callback_name = None self.variable_callback_name = None
self.configure(width=self.width, height=self.height)
if sys.platform == "darwin":
self.configure(cursor="pointinghand")
elif sys.platform.startswith("win"):
self.configure(cursor="hand2")
self.canvas = CTkCanvas(master=self, self.canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self.width, width=self.width,
height=self.height) height=self.height)
self.canvas.grid(column=0, row=0, sticky="nswe") self.canvas.grid(column=0, row=0, sticky="nswe")
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
self.canvas.bind("<Enter>", self.on_enter) self.canvas.bind("<Enter>", self.on_enter)
@ -112,6 +77,7 @@ class CTkSlider(tkinter.Frame):
# Each time an item is resized due to pack position mode, the binding Configure is called on the widget # Each time an item is resized due to pack position mode, the binding Configure is called on the widget
self.bind('<Configure>', self.update_dimensions) self.bind('<Configure>', self.update_dimensions)
self.set_cursor()
self.draw() # initial draw self.draw() # initial draw
if self.variable is not None: if self.variable is not None:
@ -121,9 +87,6 @@ class CTkSlider(tkinter.Frame):
self.variable_callback_blocked = False self.variable_callback_blocked = False
def destroy(self): def destroy(self):
# remove change_appearance_mode function from callback list of AppearanceModeTracker
AppearanceModeTracker.remove(self.change_appearance_mode)
# remove variable_callback from variable callbacks if variable exists # remove variable_callback from variable callbacks if variable exists
if self.variable is not None: if self.variable is not None:
self.variable.trace_remove("write", self.variable_callback_name) self.variable.trace_remove("write", self.variable_callback_name)
@ -134,52 +97,17 @@ class CTkSlider(tkinter.Frame):
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
def detect_color_of_master(self): def set_cursor(self):
""" detect color of self.master widget to set correct bg_color """
if isinstance(self.master, CTkFrame): # master is CTkFrame
return self.master.fg_color
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try:
ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background')
except Exception:
return "#FFFFFF", "#000000"
else: # master is normal tkinter widget
try:
return self.master.cget("bg") # try to get bg color by .cget() method
except Exception:
return "#FFFFFF", "#000000"
@staticmethod
def calc_optimal_height(user_height):
if sys.platform == "darwin": if sys.platform == "darwin":
return user_height # on macOS just use given value (canvas has Antialiasing) self.configure(cursor="pointinghand")
else: elif sys.platform.startswith("win"):
# make sure the value is always with uneven for better rendering of the ovals self.configure(cursor="hand2")
if user_height == 0:
return 0
elif user_height % 2 == 0:
return user_height + 1
else:
return user_height
def update_dimensions(self, event):
# only redraw if dimensions changed (for performance)
if self.width != event.width or self.height != event.height:
self.width = event.width
self.height = event.height
self.draw()
def draw(self, color_updates=True):
def draw(self, no_color_updates=False):
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.width, self.height, self.corner_radius, self.border_width, requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.width, self.height, self.corner_radius, self.border_width,
self.button_length, self.button_corner_radius, self.value, "w") self.button_length, self.button_corner_radius, self.value, "w")
if color_updates or requires_recoloring: if no_color_updates is False or requires_recoloring:
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
if self.border_color is None: if self.border_color is None:
@ -213,7 +141,7 @@ class CTkSlider(tkinter.Frame):
self.output_value = self.round_to_step_size(self.from_ + (self.value * (self.to - self.from_))) self.output_value = self.round_to_step_size(self.from_ + (self.value * (self.to - self.from_)))
self.value = (self.output_value - self.from_) / (self.to - self.from_) self.value = (self.output_value - self.from_) / (self.to - self.from_)
self.draw(color_updates=False) self.draw(no_color_updates=False)
if self.callback_function is not None: if self.callback_function is not None:
self.callback_function(self.output_value) self.callback_function(self.output_value)
@ -259,7 +187,7 @@ class CTkSlider(tkinter.Frame):
self.output_value = self.round_to_step_size(output_value) self.output_value = self.round_to_step_size(output_value)
self.value = (self.output_value - self.from_) / (self.to - self.from_) self.value = (self.output_value - self.from_) / (self.to - self.from_)
self.draw(color_updates=False) self.draw(no_color_updates=False)
if self.callback_function is not None: if self.callback_function is not None:
self.callback_function(self.output_value) self.callback_function(self.output_value)
@ -273,9 +201,6 @@ class CTkSlider(tkinter.Frame):
if not self.variable_callback_blocked: if not self.variable_callback_blocked:
self.set(self.variable.get(), from_variable_callback=True) self.set(self.variable.get(), from_variable_callback=True)
def config(self, *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 require_redraw = False # some attribute changes require a call of self.draw() at the end
@ -354,16 +279,3 @@ class CTkSlider(tkinter.Frame):
if require_redraw: if require_redraw:
self.draw() self.draw()
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):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()

View File

@ -1,17 +1,14 @@
import tkinter import tkinter
import tkinter.ttk as ttk
import sys import sys
from .customtkinter_tk import CTk from .ctk_canvas import CTkCanvas
from .customtkinter_frame import CTkFrame
from .customtkinter_canvas import CTkCanvas
from ..appearance_mode_tracker import AppearanceModeTracker
from ..customtkinter_theme_manager import CTkThemeManager from ..customtkinter_theme_manager import CTkThemeManager
from ..customtkinter_settings import CTkSettings from ..customtkinter_settings import CTkSettings
from ..customtkinter_draw_engine import CTkDrawEngine from ..customtkinter_draw_engine import CTkDrawEngine
from .widget_base_class import CTkBaseClass
class CTkSwitch(tkinter.Frame): class CTkSwitch(CTkBaseClass):
def __init__(self, *args, def __init__(self, *args,
text="CTkSwitch", text="CTkSwitch",
text_font="default_theme", text_font="default_theme",
@ -34,34 +31,11 @@ class CTkSwitch(tkinter.Frame):
variable=None, variable=None,
textvariable=None, textvariable=None,
**kwargs): **kwargs):
super().__init__(*args, **kwargs)
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget too # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
if isinstance(self.master, (tkinter.Tk, tkinter.Frame)) and not isinstance(self.master, (CTk, CTkFrame)): super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs)
master_old_configure = self.master.config
def new_configure(*args, **kwargs): # color
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
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.change_appearance_mode, self)
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.border_color = border_color self.border_color = border_color
self.fg_color = CTkThemeManager.theme["color"]["switch"] if fg_color == "default_theme" else fg_color self.fg_color = CTkThemeManager.theme["color"]["switch"] if fg_color == "default_theme" else fg_color
self.progress_color = CTkThemeManager.theme["color"]["switch_progress"] if progress_color == "default_theme" else progress_color self.progress_color = CTkThemeManager.theme["color"]["switch_progress"] if progress_color == "default_theme" else progress_color
@ -69,10 +43,12 @@ class CTkSwitch(tkinter.Frame):
self.button_hover_color = CTkThemeManager.theme["color"]["switch_button_hover"] if button_hover_color == "default_theme" else button_hover_color self.button_hover_color = CTkThemeManager.theme["color"]["switch_button_hover"] if button_hover_color == "default_theme" else button_hover_color
self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self.text_color = CTkThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
# text
self.text = text self.text = text
self.text_label = None
self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font self.text_font = (CTkThemeManager.theme["text"]["font"], CTkThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
self.width = width
self.height = height # shape
self.corner_radius = CTkThemeManager.theme["shape"]["switch_corner_radius"] if corner_radius == "default_theme" else corner_radius self.corner_radius = CTkThemeManager.theme["shape"]["switch_corner_radius"] if corner_radius == "default_theme" else corner_radius
# self.button_corner_radius = CTkThemeManager.theme["shape"]["switch_button_corner_radius"] if button_corner_radius == "default_theme" else button_corner_radius # self.button_corner_radius = CTkThemeManager.theme["shape"]["switch_button_corner_radius"] if button_corner_radius == "default_theme" else button_corner_radius
self.border_width = CTkThemeManager.theme["shape"]["switch_border_width"] if border_width == "default_theme" else border_width self.border_width = CTkThemeManager.theme["shape"]["switch_border_width"] if border_width == "default_theme" else border_width
@ -82,16 +58,17 @@ class CTkSwitch(tkinter.Frame):
self.onvalue = onvalue self.onvalue = onvalue
self.offvalue = offvalue self.offvalue = offvalue
#if self.corner_radius < self.button_corner_radius: # if self.corner_radius < self.button_corner_radius:
# self.corner_radius = self.button_corner_radius # self.corner_radius = self.button_corner_radius
# callback and control variables
self.callback_function = command self.callback_function = command
self.variable: tkinter.Variable = variable self.variable: tkinter.Variable = variable
self.variable_callback_blocked = False self.variable_callback_blocked = False
self.variable_callback_name = None self.variable_callback_name = None
self.textvariable = textvariable self.textvariable = textvariable
# configure grid system # configure grid system (3x1)
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=0, minsize=6) self.grid_columnconfigure(1, weight=0, minsize=6)
self.grid_columnconfigure(2, weight=0) self.grid_columnconfigure(2, weight=0)
@ -101,20 +78,13 @@ class CTkSwitch(tkinter.Frame):
width=self.width, width=self.width,
height=self.height) height=self.height)
self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, sticky="nswe") self.canvas.grid(row=0, column=0, padx=0, pady=0, columnspan=1, sticky="nswe")
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method) self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
if sys.platform == "darwin" and CTkSettings.hand_cursor_enabled:
self.canvas.configure(cursor="pointinghand")
elif sys.platform.startswith("win") and CTkSettings.hand_cursor_enabled:
self.canvas.configure(cursor="hand2")
self.canvas.bind("<Enter>", self.on_enter) self.canvas.bind("<Enter>", self.on_enter)
self.canvas.bind("<Leave>", self.on_leave) self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Button-1>", self.toggle) self.canvas.bind("<Button-1>", self.toggle)
self.text_label = None self.set_cursor()
self.draw() # initial draw self.draw() # initial draw
if self.variable is not None: if self.variable is not None:
@ -125,35 +95,19 @@ class CTkSwitch(tkinter.Frame):
self.deselect(from_variable_callback=True) self.deselect(from_variable_callback=True)
def destroy(self): def destroy(self):
# remove change_appearance_mode function from callback list of AppearanceModeTracker
AppearanceModeTracker.remove(self.change_appearance_mode)
# remove variable_callback from variable callbacks if variable exists # remove variable_callback from variable callbacks if variable exists
if self.variable is not None: if self.variable is not None:
self.variable.trace_remove("write", self.variable_callback_name) self.variable.trace_remove("write", self.variable_callback_name)
super().destroy() super().destroy()
def detect_color_of_master(self): def set_cursor(self):
""" detect color of self.master widget to set correct bg_color """ if sys.platform == "darwin" and CTkSettings.hand_cursor_enabled:
self.canvas.configure(cursor="pointinghand")
elif sys.platform.startswith("win") and CTkSettings.hand_cursor_enabled:
self.canvas.configure(cursor="hand2")
if isinstance(self.master, CTkFrame): # master is CTkFrame def draw(self, no_color_updates=False):
return self.master.fg_color
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try:
ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background')
except Exception:
return "#FFFFFF", "#000000"
else: # master is normal tkinter widget
try:
return self.master.cget("bg") # try to get bg color by .cget() method
except Exception:
return "#FFFFFF", "#000000"
def draw(self, color_updates=True):
if self.check_state is True: if self.check_state is True:
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.width, self.height, self.corner_radius, self.border_width, requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.width, self.height, self.corner_radius, self.border_width,
@ -162,7 +116,7 @@ class CTkSwitch(tkinter.Frame):
requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.width, self.height, self.corner_radius, self.border_width, requires_recoloring = self.draw_engine.draw_rounded_slider_with_border_and_button(self.width, self.height, self.corner_radius, self.border_width,
self.button_length, self.corner_radius, 0, "w") self.button_length, self.corner_radius, 0, "w")
if color_updates or requires_recoloring: if no_color_updates is False or requires_recoloring:
self.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) self.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode)) self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
@ -186,8 +140,6 @@ class CTkSwitch(tkinter.Frame):
self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_color, self.appearance_mode), self.canvas.itemconfig("slider_parts", fill=CTkThemeManager.single_color(self.button_color, self.appearance_mode),
outline=CTkThemeManager.single_color(self.button_color, self.appearance_mode)) outline=CTkThemeManager.single_color(self.button_color, self.appearance_mode))
# self.canvas.configure(bg="red")
if self.text_label is None: if self.text_label is None:
self.text_label = tkinter.Label(master=self, self.text_label = tkinter.Label(master=self,
bd=0, bd=0,
@ -208,8 +160,6 @@ class CTkSwitch(tkinter.Frame):
self.text = text self.text = text
if self.text_label is not None: if self.text_label is not None:
self.text_label.configure(text=self.text) self.text_label.configure(text=self.text)
else:
sys.stderr.write("ERROR (CTkSwitch): Cant change text because checkbox has no text.")
def toggle(self, event=None): def toggle(self, event=None):
if self.check_state is True: if self.check_state is True:
@ -217,7 +167,7 @@ class CTkSwitch(tkinter.Frame):
else: else:
self.check_state = True self.check_state = True
self.draw(color_updates=False) self.draw(no_color_updates=True)
if self.callback_function is not None: if self.callback_function is not None:
self.callback_function() self.callback_function()
@ -230,7 +180,7 @@ class CTkSwitch(tkinter.Frame):
def select(self, from_variable_callback=False): def select(self, from_variable_callback=False):
self.check_state = True self.check_state = True
self.draw(color_updates=False) self.draw(no_color_updates=True)
if self.callback_function is not None: if self.callback_function is not None:
self.callback_function() self.callback_function()
@ -243,7 +193,7 @@ class CTkSwitch(tkinter.Frame):
def deselect(self, from_variable_callback=False): def deselect(self, from_variable_callback=False):
self.check_state = False self.check_state = False
self.draw(color_updates=False) self.draw(no_color_updates=True)
if self.callback_function is not None: if self.callback_function is not None:
self.callback_function() self.callback_function()
@ -273,9 +223,6 @@ class CTkSwitch(tkinter.Frame):
elif self.variable.get() == self.offvalue: elif self.variable.get() == self.offvalue:
self.deselect(from_variable_callback=True) self.deselect(from_variable_callback=True)
def config(self, *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 require_redraw = False # some attribute changes require a call of self.draw() at the end
@ -349,16 +296,3 @@ class CTkSwitch(tkinter.Frame):
if require_redraw: if require_redraw:
self.draw() self.draw()
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):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()

View File

@ -97,13 +97,13 @@ class CTk(tkinter.Tk):
args[0]["background"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode) args[0]["background"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
if bg_changed: if bg_changed:
from .customtkinter_slider import CTkSlider from .ctk_slider import CTkSlider
from .customtkinter_progressbar import CTkProgressBar from .ctk_progressbar import CTkProgressBar
from .customtkinter_label import CTkLabel from .ctk_label import CTkLabel
from .customtkinter_frame import CTkFrame from .ctk_frame import CTkFrame
from .customtkinter_entry import CTkEntry from .ctk_entry import CTkEntry
from customtkinter.widgets.customtkinter_checkbox import CTkCheckBox from customtkinter.widgets.ctk_checkbox import CTkCheckBox
from customtkinter.widgets.customtkinter_button import CTkButton from customtkinter.widgets.ctk_button import CTkButton
for child in self.winfo_children(): for child in self.winfo_children():
if isinstance(child, (CTkFrame, CTkButton, CTkLabel, CTkSlider, CTkCheckBox, CTkEntry, CTkProgressBar)): if isinstance(child, (CTkFrame, CTkButton, CTkLabel, CTkSlider, CTkCheckBox, CTkEntry, CTkProgressBar)):

View File

@ -83,13 +83,13 @@ class CTkToplevel(tkinter.Toplevel):
args[0]["background"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode) args[0]["background"] = CTkThemeManager.single_color(self.fg_color, self.appearance_mode)
if bg_changed: if bg_changed:
from .customtkinter_slider import CTkSlider from .ctk_slider import CTkSlider
from .customtkinter_progressbar import CTkProgressBar from .ctk_progressbar import CTkProgressBar
from .customtkinter_label import CTkLabel from .ctk_label import CTkLabel
from .customtkinter_frame import CTkFrame from .ctk_frame import CTkFrame
from .customtkinter_entry import CTkEntry from .ctk_entry import CTkEntry
from customtkinter.widgets.customtkinter_checkbox import CTkCheckBox from customtkinter.widgets.ctk_checkbox import CTkCheckBox
from customtkinter.widgets.customtkinter_button import CTkButton from customtkinter.widgets.ctk_button import CTkButton
for child in self.winfo_children(): for child in self.winfo_children():
if isinstance(child, (CTkFrame, CTkButton, CTkLabel, CTkSlider, CTkCheckBox, CTkEntry, CTkProgressBar)): if isinstance(child, (CTkFrame, CTkButton, CTkLabel, CTkSlider, CTkCheckBox, CTkEntry, CTkProgressBar)):

View File

@ -1,203 +0,0 @@
import tkinter
import tkinter.ttk as ttk
from .customtkinter_tk import CTk
from .customtkinter_canvas import CTkCanvas
from ..appearance_mode_tracker import AppearanceModeTracker
from ..customtkinter_theme_manager import CTkThemeManager
from ..customtkinter_settings import CTkSettings
from ..customtkinter_draw_engine import CTkDrawEngine
class CTkFrame(tkinter.Frame):
def __init__(self, *args,
bg_color=None,
fg_color="default_theme",
border_color="default_theme",
border_width="default_theme",
corner_radius="default_theme",
width=200,
height=200,
**kwargs):
super().__init__(*args, **kwargs)
# 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
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.change_appearance_mode, self)
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.border_color = CTkThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
if fg_color == "default_theme":
if isinstance(self.master, CTkFrame):
if self.master.fg_color == CTkThemeManager.theme["color"]["frame_low"]:
self.fg_color = CTkThemeManager.theme["color"]["frame_high"]
else:
self.fg_color = CTkThemeManager.theme["color"]["frame_low"]
else:
self.fg_color = CTkThemeManager.theme["color"]["frame_low"]
else:
self.fg_color = fg_color
self.width = width
self.height = height
self.configure(width=self.width, height=self.height)
self.corner_radius = CTkThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
self.border_width = CTkThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
if self.corner_radius * 2 > self.height:
self.corner_radius = self.height / 2
elif self.corner_radius * 2 > self.width:
self.corner_radius = self.width / 2
if self.corner_radius >= self.border_width:
self.inner_corner_radius = self.corner_radius - self.border_width
else:
self.inner_corner_radius = 0
self.canvas = CTkCanvas(master=self,
highlightthickness=0,
width=self.width,
height=self.height)
self.canvas.place(x=0, y=0, relwidth=1, relheight=1)
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
self.draw_engine = CTkDrawEngine(self.canvas, CTkSettings.preferred_drawing_method)
self.bind('<Configure>', self.update_dimensions)
self.draw()
def destroy(self):
AppearanceModeTracker.remove(self.change_appearance_mode)
super().destroy()
def winfo_children(self):
""" winfo_children of CTkFrame without self.canvas widget,
because it's not a child but part of the CTkFrame itself """
child_widgets = super().winfo_children()
try:
child_widgets.remove(self.canvas)
return child_widgets
except ValueError:
return child_widgets
def detect_color_of_master(self):
""" detect color of self.master widget to set correct bg_color """
if isinstance(self.master, CTkFrame): # master is CTkFrame
return self.master.fg_color
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try:
ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background')
except Exception:
return "#FFFFFF", "#000000"
else: # master is normal tkinter widget
try:
return self.master.cget("bg") # try to get bg color by .cget() method
except Exception:
return "#FFFFFF", "#000000"
def update_dimensions(self, event):
# only redraw if dimensions changed (for performance)
if self.width != event.width or self.height != event.height:
self.width = event.width
self.height = event.height
self.draw()
def draw(self):
requires_recoloring = self.draw_engine.draw_rounded_rect_with_border(self.width, self.height, self.corner_radius, self.border_width)
self.canvas.itemconfig("inner_parts",
fill=CTkThemeManager.single_color(self.fg_color, self.appearance_mode),
outline=CTkThemeManager.single_color(self.fg_color, self.appearance_mode))
self.canvas.itemconfig("border_parts",
fill=CTkThemeManager.single_color(self.border_color, self.appearance_mode),
outline=CTkThemeManager.single_color(self.border_color, self.appearance_mode))
self.canvas.configure(bg=CTkThemeManager.single_color(self.bg_color, self.appearance_mode))
self.canvas.tag_lower("inner_parts")
self.canvas.tag_lower("border_parts")
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.widgets.customtkinter_checkbox import CTkCheckBox
from customtkinter.widgets.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):
if mode_string.lower() == "dark":
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()

View File

@ -0,0 +1,111 @@
import tkinter
import tkinter.ttk as ttk
from .ctk_tk import CTk
from .ctk_toplevel import CTkToplevel
from ..appearance_mode_tracker import AppearanceModeTracker
class CTkBaseClass(tkinter.Frame):
def __init__(self, *args, bg_color=None, width, height, **kwargs):
super().__init__(*args, width=width, height=height, **kwargs) # set desired size of underlying tkinter.Frame
self.bg_color = self.detect_color_of_master() if bg_color is None else bg_color
self.width = width # width and height in pixel, represent current size of the widget (not the desired size by init)
self.height = height
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
AppearanceModeTracker.add(self.set_appearance_mode, self)
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
# 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.Toplevel, tkinter.Frame)) and not isinstance(self.master, (CTkBaseClass, CTk, CTkToplevel)):
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
def destroy(self):
AppearanceModeTracker.remove(self.set_appearance_mode)
super().destroy()
def config(self, *args, **kwargs):
self.configure(*args, **kwargs)
def configure(self, *args, **kwargs):
""" basic configure with bg_color support, to be overridden """
require_redraw = False
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"]
super().configure(*args, **kwargs)
if require_redraw:
self.draw()
def update_dimensions(self, event):
# only redraw if dimensions changed (for performance)
if self.width != event.width or self.height != event.height:
self.width = event.width # adjust current size according to new size given by event
self.height = event.height
self.draw(no_color_updates=True) # faster drawing without color changes
def detect_color_of_master(self):
""" detect color of self.master widget to set correct bg_color """
if isinstance(self.master, CTkBaseClass) and hasattr(self.master, "fg_color"): # master is CTkFrame
return self.master.fg_color
elif isinstance(self.master, (ttk.Frame, ttk.LabelFrame, ttk.Notebook)): # master is ttk widget
try:
ttk_style = ttk.Style()
return ttk_style.lookup(self.master.winfo_class(), 'background')
except Exception:
return "#FFFFFF", "#000000"
else: # master is normal tkinter widget
try:
return self.master.cget("bg") # try to get bg color by .cget() method
except Exception:
return "#FFFFFF", "#000000"
def set_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, (CTkBaseClass, CTk)) and hasattr(self.master, "fg_color"):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
self.draw()
def draw(self, no_color_updates=False):
""" abstract of draw method to be overridden """
pass

View File

@ -10,9 +10,9 @@ app.title("CTkDialog Test")
def change_mode(): def change_mode():
if customtkinter.get_appearance_mode().lower() == "dark": if c1.get() == 0:
customtkinter.set_appearance_mode("light") customtkinter.set_appearance_mode("light")
elif customtkinter.get_appearance_mode().lower() == "light": else:
customtkinter.set_appearance_mode("dark") customtkinter.set_appearance_mode("dark")