added tkinter variables to all possible CTk widgets

This commit is contained in:
Tom Schimansky 2022-01-16 16:54:21 +01:00
parent 587a597e6b
commit 6eb0d92ddd
11 changed files with 204 additions and 47 deletions

View File

@ -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()
```
<details>
<summary>Show all arguments:</summary>
<summary>Show all arguments and methods:</summary>
argument | value
--- | ---
@ -206,7 +206,7 @@ frame = customtkinter.CTkFrame(master=root_tk,
frame.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
```
<details>
<summary>Show all arguments:</summary>
<summary>Show all arguments and methods:</summary>
argument | value
--- | ---
@ -233,13 +233,14 @@ button = customtkinter.CTkButton(master=root_tk,
button.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
```
<details>
<summary>Show all arguments:</summary>
<summary>Show all arguments and methods:</summary>
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)
```
<details>
<summary>Show all arguments:</summary>
<summary>Show all arguments and methods:</summary>
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()
```
<details>
<summary>Show all arguments:</summary>
<summary>Show all arguments and methods:</summary>
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)
```
<details>
<summary>Show all arguments:</summary>
<summary>Show all arguments and methods:</summary>
argument | value
--- | ---
@ -405,12 +408,13 @@ slider = customtkinter.CTkSlider(master=root_tk,
slider.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
```
<details>
<summary>Show all arguments:</summary>
<summary>Show all arguments and methods:</summary>
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)
```
<details>
<summary>Show all arguments:</summary>
<summary>Show all arguments and methods:</summary>
argument | value
--- | ---

View File

@ -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

View File

@ -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("<Enter>", self.on_enter)
self.text_label.bind("<Leave>", 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:

View File

@ -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":

View File

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

View File

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

View File

@ -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

View File

@ -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("<Button-1>", self.clicked)
self.canvas.bind("<B1-Motion>", 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:

View File

Before

Width:  |  Height:  |  Size: 285 KiB

After

Width:  |  Height:  |  Size: 285 KiB

View File

@ -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):