added CTkCheckBox

This commit is contained in:
Tom Schimansky 2021-08-25 17:02:16 +02:00
parent ee5a19e55d
commit ccd1040329
12 changed files with 405 additions and 35 deletions

View File

@ -228,6 +228,53 @@ text_color | entry text color, tuple: (light_color, dark_color) or single color
text_font | entry text font, tuple: (font_name, size)
</details>
### CTkCheckBox
Examle Code:
```python
checkbox = customtkinter.CTkCheckBox(master=root_tk,
text="CTkCheckBox")
checkbox.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
```
<details>
<summary>Show all arguments:</summary>
argument | value
--- | ---
master | root, tkinter.Frame or CTkFrame
text | string
width | box width in px
height | box height in px
corner_radius | corner radius in px
border_width | box border width in px
fg_color | forground (inside) color, tuple: (light_color, dark_color) or single color
bg_color | background color, tuple: (light_color, dark_color) or single color
border_color | border color, tuple: (light_color, dark_color) or single color
hover_color | hover color, tuple: (light_color, dark_color) or single color
text_color | text color, tuple: (light_color, dark_color) or single color
text_font | button text font, tuple: (font_name, size)
hover | enable/disable hover effect: True, False
state | tkinter.NORMAL (standard) or tkinter.DISABLED (not clickable, darker color)
CTkCheckBox Methods:
```python
CTkCheckBox.get() # 1 or 0 (checked or not checked)
CTkCheckBox.set_text(new_text)
CTkCheckBox.select() # turns on checkbox
CTkCheckBox.deselect() # turns off checkbox
CTkCheckBox.toggle() # change check state of checkbox
CTkCheckBox.configure_color(bg_color=new_bg_color,
fg_color=new_fg_color,
hover_color=new_hover_color,
text_color=new_text_color)
CTkCheckBox.configure(state=tkinter.DISABLED)
CTkCheckBox.configure(state=tkinter.NORMAL)
checkbox_state = CTkCheckBox.state
```
</details>
### CTkSlider
Example Code:
```python

View File

@ -7,6 +7,7 @@ from .customtkinter_progressbar import CTkProgressBar
from .customtkinter_label import CTkLabel
from .customtkinter_entry import CTkEntry
from .customtkinter_dialog import CTkDialog
from .customtkinter_checkbox import CTkCheckBox
from .appearance_mode_tracker import AppearanceModeTracker, SystemAppearanceModeListenerNoThread
from .customtkinter_color_manager import CTkColorManager
@ -53,10 +54,6 @@ def get_appearance_mode():
return "Dark"
def set_theme(main_color):
CTkColorManager.set_theme(main_color)
def deactivate_threading():
AppearanceModeTracker.init_listener_function(no_thread=True)
sys.stderr.write("WARNING (customtkinter.deactivate_threading): Automatic threaded search for a change of the " +

View File

@ -38,11 +38,11 @@ class CTkButton(tkinter.Frame):
AppearanceModeTracker.add(self.set_appearance_mode)
if fg_color is None:
self.fg_color = self.bg_color
else:
self.fg_color = fg_color
self.hover_color = hover_color
self.fg_color = self.bg_color if fg_color is None else fg_color
self.hover_color = self.bg_color if hover_color is None else hover_color
self.fg_color = self.bg_color if self.fg_color is None else self.fg_color
self.border_color = border_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"

View File

@ -0,0 +1,322 @@
import tkinter
import sys
from .customtkinter_frame import CTkFrame
from .appearance_mode_tracker import AppearanceModeTracker
from .customtkinter_color_manager import CTkColorManager
class CTkCheckBox(tkinter.Frame):
""" tkinter custom checkbox with border, rounded corners and hover effect """
def __init__(self,
bg_color=None,
fg_color=CTkColorManager.MAIN,
hover_color=CTkColorManager.MAIN_HOVER,
border_color=CTkColorManager.CHECKBOX_LINES,
border_width=3,
width=24,
height=24,
corner_radius=5,
text_font=None,
text_color=CTkColorManager.TEXT,
text="CTkCheckBox",
hover=True,
state=tkinter.NORMAL,
*args, **kwargs):
super().__init__(*args, **kwargs)
if bg_color is None:
if isinstance(self.master, CTkFrame):
self.bg_color = self.master.fg_color
else:
self.bg_color = self.master.cget("bg")
else:
self.bg_color = bg_color
AppearanceModeTracker.add(self.set_appearance_mode)
self.fg_color = CTkColorManager.MAIN if fg_color is None else fg_color
self.hover_color = CTkColorManager.MAIN_HOVER if hover_color is None else hover_color
self.border_color = border_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.width = width
self.height = height
if corner_radius*2 > self.height:
self.corner_radius = self.height/2
elif corner_radius*2 > self.width:
self.corner_radius = self.width/2
else:
self.corner_radius = corner_radius
self.border_width = border_width
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_color = text_color
if text_font is None:
if sys.platform == "darwin": # macOS
self.text_font = ("Avenir", 13)
elif "win" in sys.platform: # Windows
self.text_font = ("Century Gothic", 11)
else:
self.text_font = ("TkDefaultFont")
else:
self.text_font = text_font
self.state = state
self.hover = hover
self.check_state = False
self.canvas = tkinter.Canvas(master=self,
highlightthicknes=0,
width=self.width,
height=self.height,
cursor="pointinghand")
self.canvas.pack(side='left')
if self.hover is True:
self.canvas.bind("<Enter>", self.on_enter)
self.canvas.bind("<Leave>", self.on_leave)
self.canvas.bind("<Button-1>", self.toggle)
self.canvas.bind("<Button-1>", self.toggle)
self.canvas_fg_parts = []
self.canvas_border_parts = []
self.canvas_check_parts = []
self.text_label = None
self.draw()
def draw(self):
self.canvas.delete("all")
self.canvas_fg_parts = []
self.canvas_border_parts = []
self.canvas_check_parts = []
if type(self.bg_color) == tuple and len(self.bg_color) == 2:
self.canvas.configure(bg=self.bg_color[self.appearance_mode])
else:
self.canvas.configure(bg=self.bg_color)
# border button parts
if self.border_width > 0:
if self.corner_radius > 0:
self.canvas_border_parts.append(self.canvas.create_oval(0,
0,
self.corner_radius * 2,
self.corner_radius * 2))
self.canvas_border_parts.append(self.canvas.create_oval(self.width - self.corner_radius * 2,
0,
self.width,
self.corner_radius * 2))
self.canvas_border_parts.append(self.canvas.create_oval(0,
self.height - self.corner_radius * 2,
self.corner_radius * 2,
self.height))
self.canvas_border_parts.append(self.canvas.create_oval(self.width - self.corner_radius * 2,
self.height - self.corner_radius * 2,
self.width,
self.height))
self.canvas_border_parts.append(self.canvas.create_rectangle(0,
self.corner_radius,
self.width,
self.height - self.corner_radius))
self.canvas_border_parts.append(self.canvas.create_rectangle(self.corner_radius,
0,
self.width - self.corner_radius,
self.height))
# inner button parts
if self.corner_radius > 0:
self.canvas_fg_parts.append(self.canvas.create_oval(self.border_width,
self.border_width,
self.border_width + self.inner_corner_radius * 2,
self.border_width + self.inner_corner_radius * 2))
self.canvas_fg_parts.append(self.canvas.create_oval(self.width - self.border_width - self.inner_corner_radius * 2,
self.border_width,
self.width - self.border_width,
self.border_width + self.inner_corner_radius * 2))
self.canvas_fg_parts.append(self.canvas.create_oval(self.border_width,
self.height - self.border_width - self.inner_corner_radius * 2,
self.border_width + self.inner_corner_radius * 2,
self.height-self.border_width))
self.canvas_fg_parts.append(self.canvas.create_oval(self.width - self.border_width - self.inner_corner_radius * 2,
self.height - self.border_width - self.inner_corner_radius * 2,
self.width - self.border_width,
self.height - self.border_width))
self.canvas_fg_parts.append(self.canvas.create_rectangle(self.border_width + self.inner_corner_radius,
self.border_width,
self.width - self.border_width - self.inner_corner_radius,
self.height - self.border_width))
self.canvas_fg_parts.append(self.canvas.create_rectangle(self.border_width,
self.border_width + self.inner_corner_radius,
self.width - self.border_width,
self.height - self.inner_corner_radius - self.border_width))
for part in self.canvas_fg_parts:
if type(self.bg_color) == tuple and len(self.bg_color) == 2:
self.canvas.itemconfig(part, fill=self.bg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.bg_color, outline=self.bg_color, width=0)
for part in self.canvas_border_parts:
if type(self.border_color) == tuple and len(self.border_color) == 2:
self.canvas.itemconfig(part, fill=self.border_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.border_color, outline=self.border_color, width=0)
if self.text_label is not None:
self.text_label.pack_forget()
self.text_label = tkinter.Label(master=self,
text=self.text,
font=self.text_font)
self.text_label.pack(side='right', padx="4")
if type(self.text_color) == tuple and len(self.text_color) == 2:
self.text_label.configure(fg=self.text_color[self.appearance_mode])
else:
self.text_label.configure(fg=self.text_color)
if type(self.bg_color) == tuple and len(self.text_color) == 2:
self.configure(bg=self.bg_color[self.appearance_mode])
self.text_label.configure(bg=self.bg_color[self.appearance_mode])
else:
self.configure(bg=self.bg_color)
self.text_label.configure(bg=self.bg_color)
self.set_text(self.text)
def configure_color(self, bg_color=None, fg_color=None, hover_color=None, text_color=None):
if bg_color is not None:
self.bg_color = bg_color
else:
self.bg_color = self.master.cget("bg")
if fg_color is not None:
self.fg_color = fg_color
if hover_color is not None:
self.hover_color = hover_color
if text_color is not None:
self.text_color = text_color
self.draw()
def config(self, *args, **kwargs):
self.configure(*args, **kwargs)
def configure(self, *args, **kwargs):
if "text" in kwargs:
self.set_text(kwargs["text"])
del kwargs["text"]
if "state" in kwargs:
self.set_state(kwargs["state"])
del kwargs["state"]
super().configure(*args, **kwargs)
def set_state(self, state):
self.state = state
if self.state == tkinter.DISABLED:
self.hover = False
if sys.platform == "darwin":
self.canvas.configure(cursor="arrow")
elif self.state == tkinter.NORMAL:
self.hover = True
if sys.platform == "darwin":
self.canvas.configure(cursor="pointinghand")
self.draw()
def set_text(self, text):
self.text = text
if self.text_label is not None:
self.text_label.configure(text=self.text, width=len(self.text))
else:
sys.stderr.write("ERROR (CTkButton): Cant change text because button has no text.")
def on_enter(self, event=0):
if self.hover is True:
for part in self.canvas_fg_parts:
if type(self.hover_color) == tuple and len(self.hover_color) == 2:
self.canvas.itemconfig(part, fill=self.hover_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.hover_color, width=0)
def on_leave(self, event=0):
if self.hover is True:
if self.check_state == True:
for part in self.canvas_fg_parts:
if type(self.fg_color) == tuple and len(self.fg_color) == 2:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, width=0)
else:
for part in self.canvas_fg_parts:
if type(self.bg_color) == tuple and len(self.bg_color) == 2:
self.canvas.itemconfig(part, fill=self.bg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.bg_color, width=0)
def toggle(self, event=0):
if self.state == tkinter.NORMAL:
if self.check_state is True:
self.check_state = False
for part in self.canvas_fg_parts:
if type(self.bg_color) == tuple and len(self.bg_color) == 2:
self.canvas.itemconfig(part, fill=self.bg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.bg_color, width=0)
else:
self.check_state = True
for part in self.canvas_fg_parts:
if type(self.fg_color) == tuple and len(self.fg_color) == 2:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, width=0)
def select(self):
self.check_state = True
for part in self.canvas_fg_parts:
if type(self.fg_color) == tuple and len(self.fg_color) == 2:
self.canvas.itemconfig(part, fill=self.fg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.fg_color, width=0)
def deselect(self):
self.check_state = False
for part in self.canvas_fg_parts:
if type(self.bg_color) == tuple and len(self.bg_color) == 2:
self.canvas.itemconfig(part, fill=self.bg_color[self.appearance_mode], width=0)
else:
self.canvas.itemconfig(part, fill=self.bg_color, width=0)
def get(self):
return 1 if self.check_state is True else 0
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
self.draw()

View File

@ -10,6 +10,7 @@ class CTkColorManager:
PROGRESS_BG = ("#6B6B6B", "#222222")
FRAME = ("#D4D5D6", "#3F3F3F")
FRAME_2 = ("#BFBEC1", "#505050")
CHECKBOX_LINES = ("black", "#ededed")
DARKEN_COLOR_FACTOR = 0.8 # used for generate color for disabled button
@ -34,8 +35,8 @@ class CTkColorManager:
@classmethod
def set_theme_color(cls, hex_color, hex_color_hover):
cls.MAIN = (hex_color, hex_color)
cls.MAIN_HOVER = (hex_color_hover, hex_color_hover)
cls.MAIN = hex_color
cls.MAIN_HOVER = hex_color_hover
@classmethod
def set_theme(cls, main_color):

View File

@ -20,8 +20,9 @@ class CTkDialog:
self.running = False
self.height = len(text.split("\n"))*20 + 150
self.fg_color = fg_color
self.hover_color = hover_color
self.fg_color = CTkColorManager.MAIN if fg_color is None else fg_color
self.hover_color = CTkColorManager.MAIN_HOVER if hover_color is None else hover_color
self.top = tkinter.Toplevel()
self.top.geometry("300x{}".format(self.height))

View File

@ -31,10 +31,7 @@ class CTkLabel(tkinter.Frame):
else:
self.bg_color = bg_color
if fg_color is None:
self.fg_color = self.bg_color
else:
self.fg_color = fg_color
self.fg_color = self.bg_color if fg_color is None else fg_color
self.text_color = text_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"

View File

@ -29,7 +29,8 @@ class CTkProgressBar(tkinter.Frame):
self.border_color = border_color
self.fg_color = fg_color
self.progress_color = progress_color
self.progress_color = CTkColorManager.MAIN if progress_color is None else progress_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.width = width

View File

@ -32,8 +32,9 @@ class CTkSlider(tkinter.Frame):
self.border_color = border_color
self.fg_color = fg_color
self.button_color = button_color
self.button_hover_color = button_hover_color
self.button_color = self.bg_color if button_color is None else button_color
self.button_hover_color = self.bg_color if button_hover_color is None else button_hover_color
self.appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
self.width = width

View File

@ -3,21 +3,22 @@ import customtkinter # <- import the CustomTkinter module
customtkinter.enable_macos_darkmode()
customtkinter.set_appearance_mode("System") # Other: "Dark", "Light"
# customtkinter.set_theme_color(("red2", "red3"), ("red3", "red4"))
root_tk = tkinter.Tk() # create the Tk window like you normally do
root_tk.geometry("400x240")
root_tk.geometry("400x300")
root_tk.title("CustomTkinter Test")
def button_function():
print("button pressed")
print("CheckBox value:", checkbox_1.get())
def slider_function(value):
progressbar_1.set(value)
frame_1 = customtkinter.CTkFrame(master=root_tk, width=300, height=200, corner_radius=15)
frame_1 = customtkinter.CTkFrame(master=root_tk, width=300, height=260, corner_radius=15)
frame_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
label_1 = customtkinter.CTkLabel(master=frame_1)
@ -27,18 +28,17 @@ progressbar_1 = customtkinter.CTkProgressBar(master=frame_1)
progressbar_1.place(relx=0.5, rely=0.25, anchor=tkinter.CENTER)
button_1 = customtkinter.CTkButton(master=frame_1, corner_radius=10, command=button_function)
button_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
button_1.place(relx=0.5, rely=0.4, anchor=tkinter.CENTER)
# button_1.configure(state="disabled")
for child in button_1.winfo_children():
child.configure(state='disable')
slider_1 = customtkinter.CTkSlider(master=frame_1, command=slider_function)
slider_1.place(relx=0.5, rely=0.7, anchor=tkinter.CENTER)
slider_1.place(relx=0.5, rely=0.55, anchor=tkinter.CENTER)
entry_1 = customtkinter.CTkEntry(master=frame_1)
entry_1.place(relx=0.5, rely=0.85, anchor=tkinter.CENTER)
entry_1.place(relx=0.5, rely=0.75, anchor=tkinter.CENTER)
checkbox_1 = customtkinter.CTkCheckBox(master=frame_1)
checkbox_1.place(relx=0.5, rely=0.9, anchor=tkinter.CENTER)
root_tk.mainloop()
customtkinter.disable_macos_darkmode()

View File

@ -1,6 +1,9 @@
import tkinter
import customtkinter # <- import the CustomTkinter module
from PIL import Image, ImageTk # <- import PIL for the images
import os
PATH = os.path.dirname(os.path.realpath(__file__))
customtkinter.enable_macos_darkmode()
customtkinter.set_appearance_mode("System") # Other: "Dark", "Light"
@ -15,8 +18,8 @@ def button_function():
# load images as PhotoImage
settings_image = ImageTk.PhotoImage(Image.open("test_images/settings.png").resize((40, 40)))
bell_image = ImageTk.PhotoImage(Image.open("test_images/bell.png").resize((40, 40)))
settings_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/settings.png").resize((40, 40)))
bell_image = ImageTk.PhotoImage(Image.open(PATH + "/test_images/bell.png").resize((40, 40)))
frame_1 = customtkinter.CTkFrame(master=root_tk, width=300, height=200, corner_radius=15)
frame_1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)

View File

@ -10,7 +10,7 @@ def read(filename):
setup(name="customtkinter",
version="1.1",
version="1.2",
author="Tom Schimansky",
license="Creative Commons Zero v1.0 Universal",
url="https://github.com/TomSchimansky/CustomTkinter",