diff --git a/Readme.md b/Readme.md index d27cba6..0de3def 100644 --- a/Readme.md +++ b/Readme.md @@ -35,7 +35,7 @@ To test customtkinter you can try this simple example with only a single button: import tkinter import customtkinter # <- import the CustomTkinter module -root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (you can also use normal tkinter.Tk window) +root_tk = customtkinter.CTk() # create CTk window like you do with the Tk window (tkinter.Tk has less functionality) root_tk.geometry("400x240") root_tk.title("CustomTkinter Test") @@ -108,7 +108,7 @@ With macOS dark-mode turned on, it looks like this: But you can also customize it by yourself. Here I changed the main colors and removed the round corners, and added a border to the buttons: -![](documentation_images/complex_example_other_style.png) +![](documentation_images/complex_example_custom_colors.png) ### Default color themes @@ -138,7 +138,7 @@ Example 1:```examples/complex_example.py``` ![](documentation_images/Windows_light.png) -Example 2: ```examples/complex_example_other_style.py``` +Example 2: ```examples/complex_example_custom_colors.py``` ![](documentation_images/Windows_dark.png) @@ -180,7 +180,7 @@ root_tk = customtkinter.CTk() root_tk.mainloop() ```
-Show all arguments: +Show all arguments and methods: argument | value --- | --- @@ -206,7 +206,7 @@ frame = customtkinter.CTkFrame(master=root_tk, frame.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) ```
-Show all arguments: +Show all arguments and methods: argument | value --- | --- @@ -233,13 +233,14 @@ button = customtkinter.CTkButton(master=root_tk, button.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) ```
-Show all arguments: +Show all arguments and methods: argument | value --- | --- master | root, tkinter.Frame or CTkFrame -text | string command | callback function +textvariable | tkinter.StringVar object to change text of button +text | string width | button width in px height | button height in px corner_radius | corner radius in px @@ -283,11 +284,12 @@ label = customtkinter.CTkLabel(master=root_tk, label.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) ```
-Show all arguments: +Show all arguments and methods: argument | value --- | --- master | root, tkinter.Frame or CTkFrame +variable | tkinter.StringVar object text | string width | label width in px height | label height in px @@ -320,11 +322,12 @@ entry.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) text = entry.get() ```
-Show all arguments: +Show all arguments and methods: argument | value --- | --- master | root, tkinter.Frame or CTkFrame +variable | tkinter.StringVar object width | entry width in px height | entry height in px corner_radius | corner radius in px @@ -352,7 +355,7 @@ checkbox = customtkinter.CTkCheckBox(master=root_tk, checkbox.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) ```
-Show all arguments: +Show all arguments and methods: argument | value --- | --- @@ -405,12 +408,13 @@ slider = customtkinter.CTkSlider(master=root_tk, slider.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) ```
-Show all arguments: +Show all arguments and methods: argument | value --- | --- master | root, tkinter.Frame or CTkFrame command | callback function, gest called when slider gets changed +variable | tkinter.IntVar or tkinter.DoubleVar object width | slider width in px height | slider height in px from_ | lower slider value @@ -444,7 +448,7 @@ progressbar.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) progressbar.set(value) ```
-Show all arguments: +Show all arguments and methods: argument | value --- | --- diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 364d52e..112c346 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -10,7 +10,7 @@ from .customtkinter_dialog import CTkDialog from .customtkinter_checkbox import CTkCheckBox from .customtkinter_tk import CTk -from .appearance_mode_tracker import AppearanceModeTracker#, SystemAppearanceModeListenerNoThread +from .appearance_mode_tracker import AppearanceModeTracker from .customtkinter_color_manager import CTkColorManager from distutils.version import StrictVersion as Version diff --git a/customtkinter/customtkinter_button.py b/customtkinter/customtkinter_button.py index 9c5c839..0ee4e1e 100644 --- a/customtkinter/customtkinter_button.py +++ b/customtkinter/customtkinter_button.py @@ -11,13 +11,14 @@ from .customtkinter_color_manager import CTkColorManager class CTkButton(tkinter.Frame): """ tkinter custom button with border, rounded corners and hover effect """ - def __init__(self, + def __init__(self, *args, bg_color=None, fg_color="CTkColorManager", hover_color="CTkColorManager", border_color=None, border_width=0, command=None, + textvariable=None, width=120, height=30, corner_radius=8, @@ -28,7 +29,7 @@ class CTkButton(tkinter.Frame): image=None, compound=tkinter.LEFT, state=tkinter.NORMAL, - *args, **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 @@ -92,6 +93,7 @@ class CTkButton(tkinter.Frame): self.text_font = text_font self.function = command + self.textvariable = textvariable self.state = state self.hover = hover self.image = image @@ -209,7 +211,7 @@ class CTkButton(tkinter.Frame): if self.text is not None and self.text != "": if self.text_label is None: - self.text_label = tkinter.Label(master=self, font=self.text_font) + self.text_label = tkinter.Label(master=self, font=self.text_font, textvariable=self.textvariable) self.text_label.bind("", self.on_enter) self.text_label.bind("", self.on_leave) @@ -490,6 +492,12 @@ class CTkButton(tkinter.Frame): self.function = kwargs["command"] del kwargs["command"] + if "textvariable" in kwargs: + self.textvariable = kwargs["textvariable"] + if self.text_label is not None: + self.text_label.configure(textvariable=self.textvariable) + del kwargs["textvariable"] + super().configure(*args, **kwargs) if require_redraw: diff --git a/customtkinter/customtkinter_checkbox.py b/customtkinter/customtkinter_checkbox.py index 117b8f2..26ed176 100644 --- a/customtkinter/customtkinter_checkbox.py +++ b/customtkinter/customtkinter_checkbox.py @@ -10,7 +10,7 @@ from .customtkinter_color_manager import CTkColorManager class CTkCheckBox(tkinter.Frame): """ tkinter custom checkbox with border, rounded corners and hover effect """ - def __init__(self, + def __init__(self, *args, bg_color=None, fg_color="CTkColorManager", hover_color="CTkColorManager", @@ -25,7 +25,11 @@ class CTkCheckBox(tkinter.Frame): hover=True, command=None, state=tkinter.NORMAL, - *args, **kwargs): + onvalue=1, + offvalue=0, + variable=None, + textvariable=None, + **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 @@ -89,6 +93,12 @@ class CTkCheckBox(tkinter.Frame): self.state = state self.hover = hover self.check_state = False + self.onvalue = onvalue + self.offvalue = offvalue + self.variable: tkinter.Variable = variable + self.variable_callback_blocked = False + self.textvariable = textvariable + self.variabel_callback_name = None self.canvas = tkinter.Canvas(master=self, highlightthicknes=0, @@ -111,10 +121,21 @@ class CTkCheckBox(tkinter.Frame): self.canvas_check_parts = [] self.text_label = None - self.draw() + self.draw() # initial draw + + if self.variable is not None: + self.variabel_callback_name = self.variable.trace_add("write", self.variable_callback) + if self.variable.get() == self.onvalue: + self.select(from_variable_callback=True) + elif self.variable.get() == self.offvalue: + self.deselect(from_variable_callback=True) def destroy(self): AppearanceModeTracker.remove(self.set_appearance_mode) + + if self.variable is not None: + self.variable.trace_remove("write", self.variabel_callback_name) + super().destroy() def detect_color_of_master(self): @@ -300,6 +321,23 @@ class CTkCheckBox(tkinter.Frame): self.function = kwargs["command"] del kwargs["command"] + if "variable" in kwargs: + if self.variable is not None: + self.variable.trace_remove("write", self.variabel_callback_name) + + self.variable = kwargs["variable"] + + if self.variable is not None and self.variable != "": + self.variabel_callback_name = self.variable.trace_add("write", self.variable_callback) + if self.variable.get() == self.onvalue: + self.select(from_variable_callback=True) + elif self.variable.get() == self.offvalue: + self.deselect(from_variable_callback=True) + else: + self.variable = None + + del kwargs["variable"] + super().configure(*args, **kwargs) if require_redraw: @@ -350,6 +388,13 @@ class CTkCheckBox(tkinter.Frame): else: self.canvas.itemconfig(part, fill=self.bg_color, width=0) + def variable_callback(self, var_name, index, mode): + if not self.variable_callback_blocked: + if self.variable.get() == self.onvalue: + self.select(from_variable_callback=True) + elif self.variable.get() == self.offvalue: + self.deselect(from_variable_callback=True) + def toggle(self, event=0): if self.state == tkinter.NORMAL: if self.check_state is True: @@ -370,7 +415,12 @@ class CTkCheckBox(tkinter.Frame): if self.function is not None: self.function() - def select(self): + if self.variable is not None: + self.variable_callback_blocked = True + self.variable.set(self.onvalue if self.check_state is True else self.offvalue) + self.variable_callback_blocked = False + + def select(self, from_variable_callback=False): self.check_state = True for part in self.canvas_fg_parts: if type(self.fg_color) == tuple and len(self.fg_color) == 2: @@ -381,7 +431,12 @@ class CTkCheckBox(tkinter.Frame): if self.function is not None: self.function() - def deselect(self): + if self.variable is not None and not from_variable_callback: + self.variable_callback_blocked = True + self.variable.set(self.onvalue) + self.variable_callback_blocked = False + + def deselect(self, from_variable_callback=False): self.check_state = False for part in self.canvas_fg_parts: if type(self.bg_color) == tuple and len(self.bg_color) == 2: @@ -392,8 +447,13 @@ class CTkCheckBox(tkinter.Frame): if self.function is not None: self.function() + if self.variable is not None and not from_variable_callback: + self.variable_callback_blocked = True + self.variable.set(self.offvalue) + self.variable_callback_blocked = False + def get(self): - return 1 if self.check_state is True else 0 + return self.onvalue if self.check_state is True else self.offvalue def set_appearance_mode(self, mode_string): if mode_string.lower() == "dark": diff --git a/customtkinter/customtkinter_entry.py b/customtkinter/customtkinter_entry.py index b99344b..7827028 100644 --- a/customtkinter/customtkinter_entry.py +++ b/customtkinter/customtkinter_entry.py @@ -8,7 +8,7 @@ from .customtkinter_color_manager import CTkColorManager class CTkEntry(tkinter.Frame): - def __init__(self, + def __init__(self, *args, master=None, bg_color=None, fg_color="CTkColorManager", @@ -16,9 +16,11 @@ class CTkEntry(tkinter.Frame): corner_radius=8, width=120, height=30, - *args, **kwargs): - super().__init__(master=master) + if master is None: + super().__init__(*args) + else: + super().__init__(*args, master=master) # 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)): @@ -58,7 +60,7 @@ class CTkEntry(tkinter.Frame): elif self.corner_radius*2 > self.width: self.corner_radius = self.width/2 - self.configure(width=self.width, height=self.height) + super().configure(width=self.width, height=self.height) self.canvas = tkinter.Canvas(master=self, highlightthicknes=0, @@ -69,7 +71,7 @@ class CTkEntry(tkinter.Frame): self.entry = tkinter.Entry(master=self, bd=0, highlightthicknes=0, - *args, **kwargs) + **kwargs) self.entry.place(relx=0.5, rely=0.5, relwidth=0.8, anchor=tkinter.CENTER) self.fg_parts = [] @@ -184,7 +186,7 @@ class CTkEntry(tkinter.Frame): del kwargs["corner_radius"] require_redraw = True - super().configure(*args, **kwargs) + self.entry.configure(*args, **kwargs) if require_redraw is True: self.draw() diff --git a/customtkinter/customtkinter_label.py b/customtkinter/customtkinter_label.py index 9fa3d92..f67c600 100644 --- a/customtkinter/customtkinter_label.py +++ b/customtkinter/customtkinter_label.py @@ -8,7 +8,7 @@ from .customtkinter_color_manager import CTkColorManager class CTkLabel(tkinter.Frame): - def __init__(self, + def __init__(self, *args, master=None, bg_color=None, fg_color="CTkColorManager", @@ -18,9 +18,11 @@ class CTkLabel(tkinter.Frame): height=25, text="CTkLabel", text_font=None, - *args, **kwargs): - super().__init__(master=master) + if master is None: + super().__init__(*args) + else: + super().__init__(*args, master=master) # 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)): @@ -71,7 +73,6 @@ class CTkLabel(tkinter.Frame): self.text_font = ("TkDefaultFont", 10) else: self.text_font = text_font - self.configure(width=self.width, height=self.height) self.canvas = tkinter.Canvas(master=self, highlightthicknes=0, @@ -84,11 +85,13 @@ class CTkLabel(tkinter.Frame): bd=0, text=self.text, font=self.text_font, - *args, **kwargs) + **kwargs) self.text_label.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) self.fg_parts = [] + super().configure(width=self.width, height=self.height) + self.draw() def destroy(self): @@ -202,7 +205,7 @@ class CTkLabel(tkinter.Frame): require_redraw = True del kwargs["text_color"] - super().configure(*args, **kwargs) + self.text_label.configure(*args, **kwargs) if require_redraw: self.draw() diff --git a/customtkinter/customtkinter_progressbar.py b/customtkinter/customtkinter_progressbar.py index 432caa2..a184d9e 100644 --- a/customtkinter/customtkinter_progressbar.py +++ b/customtkinter/customtkinter_progressbar.py @@ -10,7 +10,8 @@ from .customtkinter_color_manager import CTkColorManager class CTkProgressBar(tkinter.Frame): """ tkinter custom progressbar, always horizontal, values are from 0 to 1 """ - def __init__(self, + def __init__(self, *args, + variable=None, bg_color=None, border_color="CTkColorManager", fg_color="CTkColorManager", @@ -18,7 +19,7 @@ class CTkProgressBar(tkinter.Frame): width=160, height=10, border_width=0, - *args, **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 @@ -50,6 +51,10 @@ class CTkProgressBar(tkinter.Frame): self.fg_color = CTkColorManager.PROGRESS_BG if fg_color == "CTkColorManager" else fg_color self.progress_color = CTkColorManager.MAIN if progress_color == "CTkColorManager" else progress_color + self.variable = variable + self.variable_callback_blocked = False + self.variabel_callback_name = None + self.width = width self.height = self.calc_optimal_height(height) self.border_width = round(border_width) @@ -63,13 +68,20 @@ class CTkProgressBar(tkinter.Frame): height=self.height) self.canvas.place(x=0, y=0) - self.draw() + self.draw() # initial draw - # set progress - self.set(self.value) + if self.variable is not None: + self.variabel_callback_name = self.variable.trace_add("write", self.variable_callback) + self.variable_callback_blocked = True + self.set(self.variable.get(), from_variable_callback=True) + self.variable_callback_blocked = False def destroy(self): AppearanceModeTracker.remove(self.change_appearance_mode) + + if self.variable is not None: + self.variable.trace_remove("write", self.variabel_callback_name) + super().destroy() def detect_color_of_master(self): @@ -252,12 +264,30 @@ class CTkProgressBar(tkinter.Frame): del kwargs["border_width"] require_redraw = True + if "variable" in kwargs: + if self.variable is not None: + self.variable.trace_remove("write", self.variabel_callback_name) + + self.variable = kwargs["variable"] + + if self.variable is not None and self.variable != "": + self.variabel_callback_name = self.variable.trace_add("write", self.variable_callback) + self.set(self.variable.get(), from_variable_callback=True) + else: + self.variable = None + + del kwargs["variable"] + super().configure(*args, **kwargs) if require_redraw is True: self.draw() - def set(self, value): + def variable_callback(self, var_name, index, mode): + if not self.variable_callback_blocked: + self.set(self.variable.get(), from_variable_callback=True) + + def set(self, value, from_variable_callback=False): self.value = value if self.value > 1: @@ -267,6 +297,11 @@ class CTkProgressBar(tkinter.Frame): self.draw(no_color_updates=True) + if self.variable is not None and not from_variable_callback: + self.variable_callback_blocked = True + self.variable.set(round(self.value) if isinstance(self.variable, tkinter.IntVar) else self.value) + self.variable_callback_blocked = False + def change_appearance_mode(self, mode_string): if mode_string.lower() == "dark": self.appearance_mode = 1 diff --git a/customtkinter/customtkinter_slider.py b/customtkinter/customtkinter_slider.py index 9ffa20d..d44f6c6 100644 --- a/customtkinter/customtkinter_slider.py +++ b/customtkinter/customtkinter_slider.py @@ -10,7 +10,7 @@ from .customtkinter_color_manager import CTkColorManager class CTkSlider(tkinter.Frame): """ tkinter custom slider, always horizontal """ - def __init__(self, + def __init__(self, *args, bg_color=None, border_color=None, fg_color="CTkColorManager", @@ -24,7 +24,8 @@ class CTkSlider(tkinter.Frame): height=16, border_width=5, command=None, - *args, **kwargs): + variable=None, + **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 @@ -56,12 +57,11 @@ class CTkSlider(tkinter.Frame): self.fg_color = CTkColorManager.SLIDER_BG if fg_color == "CTkColorManager" else fg_color self.progress_color = CTkColorManager.SLIDER_PROGRESS if progress_color == "CTkColorManager" else progress_color self.button_color = CTkColorManager.MAIN if button_color == "CTkColorManager" else button_color - self.button_hover_color = CTkColorManager.MAIN if button_hover_color == "CTkColorManager" else button_hover_color + self.button_hover_color = CTkColorManager.MAIN_HOVER if button_hover_color == "CTkColorManager" else button_hover_color self.width = width self.height = self.calc_optimal_height(height) self.border_width = round(border_width) - self.callback_function = command self.value = 0.5 # initial value of slider in percent self.hover_state = False self.from_ = from_ @@ -69,6 +69,11 @@ class CTkSlider(tkinter.Frame): self.number_of_steps = number_of_steps self.output_value = self.from_ + (self.value * (self.to - self.from_)) + self.callback_function = command + self.variable: tkinter.Variable = variable + self.variable_callback_blocked = False + self.variabel_callback_name = None + self.configure(width=self.width, height=self.height) if sys.platform == "darwin": self.configure(cursor="pointinghand") @@ -84,10 +89,22 @@ class CTkSlider(tkinter.Frame): self.canvas.bind("", self.clicked) self.canvas.bind("", self.clicked) - self.draw() + self.draw() # initial draw + + if self.variable is not None: + self.variabel_callback_name = self.variable.trace_add("write", self.variable_callback) + self.variable_callback_blocked = True + self.set(self.variable.get(), from_variable_callback=True) + self.variable_callback_blocked = False def destroy(self): + # remove change_appearance_mode function from callback list of AppearanceModeTracker AppearanceModeTracker.remove(self.change_appearance_mode) + + # remove variabel_callback from variable callbacks if variable exists + if self.variable is not None: + self.variable.trace_remove("write", self.variabel_callback_name) + super().destroy() def detect_color_of_master(self): @@ -292,6 +309,11 @@ class CTkSlider(tkinter.Frame): if self.callback_function is not None: self.callback_function(self.output_value) + if self.variable is not None: + self.variable_callback_blocked = True + self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value) + self.variable_callback_blocked = False + def on_enter(self, event=0): self.hover_state = True self.canvas.itemconfig("button_parts", fill=CTkColorManager.single_color(self.button_hover_color, self.appearance_mode)) @@ -311,7 +333,7 @@ class CTkSlider(tkinter.Frame): def get(self): return self.output_value - def set(self, output_value): + def set(self, output_value, from_variable_callback=False): if output_value > self.to: output_value = self.to elif output_value < self.from_: @@ -325,6 +347,15 @@ class CTkSlider(tkinter.Frame): if self.callback_function is not None: self.callback_function(self.output_value) + if self.variable is not None and not from_variable_callback: + self.variable_callback_blocked = True + self.variable.set(round(self.output_value) if isinstance(self.variable, tkinter.IntVar) else self.output_value) + self.variable_callback_blocked = False + + def variable_callback(self, var_name, index, mode): + if not self.variable_callback_blocked: + self.set(self.variable.get(), from_variable_callback=True) + def config(self, *args, **kwargs): self.configure(*args, **kwargs) @@ -388,6 +419,20 @@ class CTkSlider(tkinter.Frame): self.callback_function = kwargs["command"] del kwargs["command"] + if "variable" in kwargs: + if self.variable is not None: + self.variable.trace_remove("write", self.variabel_callback_name) + + self.variable = kwargs["variable"] + + if self.variable is not None and self.variable != "": + self.variabel_callback_name = self.variable.trace_add("write", self.variable_callback) + self.set(self.variable.get(), from_variable_callback=True) + else: + self.variable = None + + del kwargs["variable"] + super().configure(*args, **kwargs) if require_redraw: diff --git a/documentation_images/complex_example_other_style.png b/documentation_images/complex_example_custom_colors.png similarity index 100% rename from documentation_images/complex_example_other_style.png rename to documentation_images/complex_example_custom_colors.png diff --git a/examples/complex_example.py b/examples/complex_example.py index 0505982..0c17229 100644 --- a/examples/complex_example.py +++ b/examples/complex_example.py @@ -4,7 +4,7 @@ import customtkinter import sys customtkinter.set_appearance_mode("System") # Other: "Light", "Dark" -customtkinter.set_default_color_theme("green") # Themes: "blue" (standard), "green", "dark-blue" +customtkinter.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue" class App(customtkinter.CTk): diff --git a/examples/complex_example_other_style.py b/examples/complex_example_custom_colors.py similarity index 100% rename from examples/complex_example_other_style.py rename to examples/complex_example_custom_colors.py