worked on CTkTabview, fixed small issues

This commit is contained in:
Tom Schimansky
2022-10-10 00:48:08 +02:00
parent dbf5577cf0
commit 466ba7747e
16 changed files with 375 additions and 178 deletions

View File

@ -4,6 +4,7 @@ import os
import sys
from tkinter.constants import *
from tkinter import StringVar, IntVar, DoubleVar, BooleanVar
from tkinter import filedialog
# import manager classes
from .settings import Settings

View File

@ -14,9 +14,9 @@
"frame_high": ["#C0C2C5", "#343638"],
"label": [null, null],
"text": ["gray10", "#DCE4EE"],
"text_disabled": ["gray60", "#777B80"],
"text_disabled": ["gray60", "gray45"],
"text_button": ["#DCE4EE", "#DCE4EE"],
"text_button_disabled": ["gray74", "gray74"],
"text_button_disabled": ["gray74", "gray60"],
"progressbar": ["#939BA2", "#4A4D50"],
"progressbar_progress": ["#3B8ED0", "#1F6AA5"],
"progressbar_border": ["gray", "gray"],

View File

@ -37,7 +37,7 @@ class CTkEntry(CTkBaseClass):
textvariable: tkinter.Variable = None,
placeholder_text: str = None,
font: Union[str, Tuple[str, str]] = "default_theme",
font: Union[str, Tuple] = "default_theme",
state: str = tkinter.NORMAL,
**kwargs):

View File

@ -24,6 +24,7 @@ class CTkFrame(CTkBaseClass):
bg_color: Union[str, Tuple[str, str], None] = None,
fg_color: Union[str, Tuple[str, str], None] = "default_theme",
border_color: Union[str, Tuple[str, str]] = "default_theme",
background_corner_colors: Tuple[Union[str, Tuple[str, str]]] = None,
overwrite_preferred_drawing_method: str = None,
**kwargs):
@ -46,6 +47,8 @@ class CTkFrame(CTkBaseClass):
else:
self._fg_color = fg_color
self._background_corner_colors = background_corner_colors # rendering options for DrawEngine
# shape
self._corner_radius = ThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
self._border_width = ThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
@ -90,10 +93,19 @@ class CTkFrame(CTkBaseClass):
self._draw()
def _draw(self, no_color_updates=False):
if not self._canvas.winfo_exists():
return
if self._background_corner_colors is not None:
self._draw_engine.draw_background_corners(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height))
self._canvas.itemconfig("background_corner_top_left", fill=ThemeManager.single_color(self._background_corner_colors[0], self._appearance_mode))
self._canvas.itemconfig("background_corner_top_right", fill=ThemeManager.single_color(self._background_corner_colors[1], self._appearance_mode))
self._canvas.itemconfig("background_corner_bottom_right", fill=ThemeManager.single_color(self._background_corner_colors[2], self._appearance_mode))
self._canvas.itemconfig("background_corner_bottom_left", fill=ThemeManager.single_color(self._background_corner_colors[3], self._appearance_mode))
else:
self._canvas.delete("background_parts")
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height),
self._apply_widget_scaling(self._corner_radius),
@ -115,29 +127,34 @@ class CTkFrame(CTkBaseClass):
outline=ThemeManager.single_color(self._border_color, self._appearance_mode))
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
self._canvas.tag_lower("inner_parts")
self._canvas.tag_lower("border_parts")
# self._canvas.tag_lower("inner_parts") # maybe unnecessary, I don't know ???
# self._canvas.tag_lower("border_parts")
def configure(self, require_redraw=False, **kwargs):
if "fg_color" in kwargs:
self._fg_color = kwargs.pop("fg_color")
require_redraw = True
# check if CTk widgets are children of the frame and change their _bg_color to new frame 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)
# only workaround, to enable one layer of passing new bg_color for children with fg_color=None,
# but needs to be abstracted to n-layers somehow
if isinstance(child, CTkFrame) and child.cget("fg_color") is None:
for childrens_child in child.winfo_children():
childrens_child.configure(bg_color=self._fg_color)
if "bg_color" in kwargs:
# pass bg_color change to children if fg_color is None
if self._fg_color is None:
for child in self.winfo_children():
if isinstance(child, CTkBaseClass):
child.configure(bg_color=self._fg_color)
if "border_color" in kwargs:
self._border_color = kwargs.pop("border_color")
require_redraw = True
if "background_corner_colors" in kwargs:
self._background_corner_colors = kwargs.pop("background_corner_colors")
require_redraw = True
if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius")
require_redraw = True
@ -164,6 +181,8 @@ class CTkFrame(CTkBaseClass):
return self._fg_color
elif attribute_name == "border_color":
return self._border_color
elif attribute_name == "background_corner_colors":
return self._background_corner_colors
else:
return super().cget(attribute_name)

View File

@ -38,7 +38,7 @@ class CTkOptionMenu(CTkBaseClass):
variable: tkinter.Variable = None,
state: str = tkinter.NORMAL,
hover: bool = True,
command: Callable = None,
command: Callable[[str], None] = None,
dynamic_resizing: bool = True,
**kwargs):

View File

@ -1,5 +1,5 @@
import tkinter
from typing import Union, Tuple, List, Dict
from typing import Union, Tuple, List, Dict, Callable
from ..theme_manager import ThemeManager
from .ctk_button import CTkButton
@ -27,10 +27,14 @@ class CTkSegmentedButton(CTkFrame):
unselected_hover_color: Union[str, Tuple[str, str]] = "default_theme",
text_color: Union[str, Tuple[str, str]] = "default_theme",
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
background_corner_colors: Tuple[Union[str, Tuple[str, str]]] = None,
font: any = "default_theme",
values: list = None,
variable: tkinter.Variable = None,
dynamic_resizing: bool = True,
command: Callable[[str], None] = None,
state: str = "normal",
**kwargs):
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
@ -49,11 +53,15 @@ class CTkSegmentedButton(CTkFrame):
self._sb_corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
self._sb_border_width = ThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width
self._background_corner_colors = background_corner_colors # rendering options for DrawEngine
self._command: Callable[[str], None] = command
self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font
self._state = state
self._buttons_dict: Dict[str, CTkButton] = {} # mapped from value to button object
if values is None:
self._value_list: List[str] = ["CTkSegmentedButton"]
elif len(values) == 0:
raise ValueError("values of CTkSegmentedButton can not be empty")
else:
self._value_list: List[str] = values # Values ordered like buttons rendered on widget
@ -63,8 +71,9 @@ class CTkSegmentedButton(CTkFrame):
self._check_unique_values(self._value_list)
self._current_value: str = ""
self._create_buttons_from_values()
self._create_button_grid()
if len(self._value_list) > 0:
self._create_buttons_from_values()
self._create_button_grid()
self._variable = variable
self._variable_callback_blocked: bool = False
@ -74,7 +83,7 @@ class CTkSegmentedButton(CTkFrame):
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
self.set(self._variable.get(), from_variable_callback=True)
super().configure(corner_radius=self._sb_corner_radius)
super().configure(corner_radius=self._sb_corner_radius, fg_color=None)
def destroy(self):
if self._variable is not None: # remove old callback
@ -95,11 +104,23 @@ class CTkSegmentedButton(CTkFrame):
def _configure_button_corners_for_index(self, index: int):
if index == 0 and len(self._value_list) == 1:
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color))
if self._background_corner_colors is None:
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color))
else:
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=self._background_corner_colors)
elif index == 0:
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._bg_color, self._sb_fg_color, self._sb_fg_color, self._bg_color))
if self._background_corner_colors is None:
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._bg_color, self._sb_fg_color, self._sb_fg_color, self._bg_color))
else:
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._background_corner_colors[0], self._sb_fg_color, self._sb_fg_color, self._background_corner_colors[3]))
elif index == len(self._value_list) - 1:
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._sb_fg_color, self._bg_color, self._bg_color, self._sb_fg_color))
if self._background_corner_colors is None:
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._sb_fg_color, self._bg_color, self._bg_color, self._sb_fg_color))
else:
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._sb_fg_color, self._background_corner_colors[1], self._background_corner_colors[2], self._sb_fg_color))
else:
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._sb_fg_color, self._sb_fg_color, self._sb_fg_color, self._sb_fg_color))
@ -129,7 +150,9 @@ class CTkSegmentedButton(CTkFrame):
hover_color=self._sb_unselected_hover_color,
text_color=self._sb_text_color,
text_color_disabled=self._sb_text_color_disabled,
command=lambda v=value: self.set(v),
font=self._font,
state=self._state,
command=lambda v=value: self.set(v, from_button_callback=True),
background_corner_colors=None,
round_width_to_even_numbers=False) # DrawEngine rendering option (so that theres no gap between buttons)
@ -212,19 +235,30 @@ class CTkSegmentedButton(CTkFrame):
for button in self._buttons_dict.values():
button.configure(text_color_disabled=self._sb_text_color_disabled)
if "background_corner_colors" in kwargs:
self._background_corner_colors = kwargs.pop("background_corner_colors")
for i in range(len(self._buttons_dict)):
self._configure_button_corners_for_index(i)
if "font" in kwargs:
self._font = kwargs.pop("font")
for button in self._buttons_dict.values():
button.configure(font=self._font)
if "values" in kwargs:
for button in self._buttons_dict.values():
button.destroy()
self._buttons_dict.clear()
self._value_list = kwargs.pop("values")
self._current_value = ""
if len(self._value_list) == 0:
raise ValueError("len() of values of CTkSegmentedButton can not be zero")
self._check_unique_values(self._value_list)
self._create_buttons_from_values()
self._create_button_grid()
if len(self._value_list) > 0:
self._create_buttons_from_values()
self._create_button_grid()
if self._current_value in self._value_list:
self._select_button_by_value(self._current_value)
if "variable" in kwargs:
if self._variable is not None: # remove old callback
@ -245,6 +279,14 @@ class CTkSegmentedButton(CTkFrame):
else:
self.grid_propagate(True)
if "command" in kwargs:
self._command = kwargs.pop("command")
if "state" in kwargs:
self._state = kwargs.pop("state")
for button in self._buttons_dict.values():
button.configure(state=self._state)
super().configure(**kwargs)
def cget(self, attribute_name: str) -> any:
@ -268,17 +310,25 @@ class CTkSegmentedButton(CTkFrame):
elif attribute_name == "text_color_disabled":
return self._sb_text_color_disabled
elif attribute_name == "font":
return self._font
elif attribute_name == "values":
return self._value_list
elif attribute_name == "variable":
return self._variable
elif attribute_name == "dynamic_resizing":
return self._dynamic_resizing
elif attribute_name == "command":
return self._command
else:
return super().cget(attribute_name)
def set(self, value: str, from_variable_callback: bool = False):
def set(self, value: str, from_variable_callback: bool = False, from_button_callback: bool = False):
if from_button_callback:
if self._command is not None:
self._command(self._current_value)
if value == self._current_value:
return
elif value in self._buttons_dict:
@ -301,7 +351,7 @@ class CTkSegmentedButton(CTkFrame):
def get(self) -> str:
return self._current_value
def insert_value(self, index: int, value: str):
def insert(self, index: int, value: str):
if value not in self._buttons_dict:
self._value_list.insert(index, value)
self._buttons_dict[value] = self._create_button(index, value)
@ -319,7 +369,7 @@ class CTkSegmentedButton(CTkFrame):
else:
raise ValueError(f"CTkSegmentedButton can not insert value '{value}', already part of the values")
def remove_value(self, value: str):
def delete(self, value: str):
if value in self._buttons_dict:
self._buttons_dict[value].destroy()
self._buttons_dict.pop(value)

View File

@ -35,7 +35,7 @@ class CTkSlider(CTkBaseClass):
to: int = 1,
state: str = "normal",
number_of_steps: Union[int, None] = None,
command: Callable = None,
command: Callable[[float], None] = None,
variable: tkinter.Variable = None,
orientation: str = "horizontal",
**kwargs):

View File

@ -1,18 +1,11 @@
import tkinter
from typing import Union, Tuple, List
from typing import Union, Tuple, Dict, List
from ..theme_manager import ThemeManager
from .widget_base_class import CTkBaseClass
from .ctk_frame import CTkFrame
class CTkTab:
def __init__(self, master=None, identifier: str = None, text: str = "CTkTab", index: int = 0):
self.text: str = text
self.frame: tkinter.Frame = tkinter.Frame(master, width=0, height=0)
self.identifier = str(id(self.frame)) if identifier is None else identifier
self.visible: bool = True
self.index = index
from .widget_base_class import CTkBaseClass
from .ctk_segmented_button import CTkSegmentedButton
from .ctk_canvas import CTkCanvas
from ..draw_engine import DrawEngine
class CTkTabview(CTkBaseClass):
@ -23,7 +16,7 @@ class CTkTabview(CTkBaseClass):
_top_spacing = 10 # px on top of the buttons
_top_button_overhang = 8 # px
_button_size = 24
_button_height = 10
def __init__(self,
master: any = None,
@ -34,18 +27,26 @@ class CTkTabview(CTkBaseClass):
bg_color: Union[str, Tuple[str, str], None] = None,
fg_color: Union[str, Tuple[str, str], None] = "default_theme",
button_frame_color: Union[str, Tuple[str, str]] = "default_theme",
button_color: Union[str, Tuple[str, str]] = "default_theme",
button_hover_color: Union[str, Tuple[str, str]] = "default_theme",
border_color: Union[str, Tuple[str, str]] = "default_theme",
segmented_button_fg_color: Union[str, Tuple[str, str], None] = "default_theme",
segmented_button_selected_color: Union[str, Tuple[str, str]] = "default_theme",
segmented_button_selected_hover_color: Union[str, Tuple[str, str]] = "default_theme",
segmented_button_unselected_color: Union[str, Tuple[str, str]] = "default_theme",
segmented_button_unselected_hover_color: Union[str, Tuple[str, str]] = "default_theme",
text_color: Union[str, Tuple[str, str]] = "default_theme",
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
**kwargs):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
# transfer some functionality to CTkFrame
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
# determine fg_color
# color
self._border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
# determine fg_color of frame
if fg_color == "default_theme":
if isinstance(self.master, (CTkFrame, CTkTabview)):
if self.master.cget("fg_color") == ThemeManager.theme["color"]["frame_low"]:
@ -57,65 +58,169 @@ class CTkTabview(CTkBaseClass):
else:
self._fg_color = fg_color
self._border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
self._button_frame_color = ThemeManager.theme["color"]["tabview_button_frame"] if button_frame_color == "default_theme" else button_frame_color
self._button_color = ThemeManager.theme["color"]["tabview_button"] if button_color == "default_theme" else button_color
self._button_hover_color = ThemeManager.theme["color"]["tabview_button_hover"] if button_hover_color == "default_theme" else button_hover_color
# shape
self._corner_radius = ThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
self._border_width = ThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
self._main_frame = CTkFrame(self,
width=width,
height=height - (self._top_spacing + self._top_button_overhang),
bg_color=self._bg_color,
fg_color=self._fg_color,
border_color=self._border_color,
border_width=self._border_width)
self._button_frame = CTkFrame(self,
width=0,
height=0,
bg_color=self._fg_color,
fg_color=self._button_frame_color,
border_color=self._border_color,
border_width=self._border_width)
self._create_grid_for_frames()
self._canvas = CTkCanvas(master=self,
bg=ThemeManager.single_color(self._bg_color, self._appearance_mode),
highlightthickness=0,
width=self._apply_widget_scaling(self._current_width - self._top_spacing - self._top_button_overhang),
height=self._apply_widget_scaling(self._current_height))
self._draw_engine = DrawEngine(self._canvas)
self._tab_list: List[CTkTab] = []
self._segmented_button = CTkSegmentedButton(self,
values=[],
height=self._button_height,
fg_color=segmented_button_fg_color,
selected_color=segmented_button_selected_color,
selected_hover_color=segmented_button_selected_hover_color,
unselected_color=segmented_button_unselected_color,
unselected_hover_color=segmented_button_unselected_hover_color,
text_color=text_color,
text_color_disabled=text_color_disabled,
corner_radius=corner_radius,
border_width=self._apply_widget_scaling(3))
self._configure_segmented_button_background_corners()
self._configure_grid()
self._set_grid_canvas()
def _create_grid_for_frames(self):
self._tab_dict: Dict[str, CTkFrame] = {}
self._name_list: List[str] = [] # list of unique tab names in order of tabs
self._current_name: str = ""
super().bind('<Configure>', self._update_dimensions_event)
def winfo_children(self) -> List[any]:
"""
winfo_children of CTkTabview without canvas and segmented button widgets,
because it's not a child but part of the CTkTabview itself
"""
child_widgets = super().winfo_children()
try:
child_widgets.remove(self._canvas)
child_widgets.remove(self._segmented_button)
return child_widgets
except ValueError:
return child_widgets
def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height - self._top_spacing - self._top_button_overhang))
self._draw()
def _set_dimensions(self, width=None, height=None):
super()._set_dimensions(width, height)
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height - self._top_spacing - self._top_button_overhang))
self._draw()
def _configure_segmented_button_background_corners(self):
""" needs to be called for changes in fg_color, bg_color """
if self._fg_color is not None:
self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._fg_color, self._fg_color))
else:
self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color))
def _configure_tab_background_corners_by_name(self, name: str):
""" needs to be called for changes in fg_color, bg_color, border_width """
if self._border_width == 0:
if self._fg_color is not None:
self._tab_dict[name].configure(background_corner_colors=(self._fg_color, self._fg_color, self._bg_color, self._bg_color))
else:
self._tab_dict[name].configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color))
else:
self._tab_dict[name].configure(background_corner_colors=None)
def _configure_grid(self):
""" create 3 x 4 grid system """
self.grid_rowconfigure(0, weight=0, minsize=self._apply_widget_scaling(self._top_spacing))
self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(self._top_button_overhang))
self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._button_size - self._top_button_overhang))
self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._button_height - self._top_button_overhang))
self.grid_rowconfigure(3, weight=1)
self.grid_columnconfigure((0, 2), weight=1, minsize=self._apply_widget_scaling(self._corner_radius))
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(self._corner_radius))
self.grid_columnconfigure(0, weight=1)
self._main_frame.grid(row=2, column=0, rowspan=2, columnspan=3, sticky="nsew")
self._button_frame.grid(row=1, column=1, rowspan=2, columnspan=1, sticky="nsew")
def _set_grid_canvas(self):
self._canvas.grid(row=2, rowspan=2, column=0, columnspan=1, sticky="nsew")
def _get_tab_by_identifier(self, identifier: str):
for tab in self._tab_list:
if tab.identifier == identifier:
return tab
def _set_grid_segmented_button(self):
""" needs to be called for changes in corner_radius """
self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="ns")
def create_tab(self, identifier=None, text="CTkTabview"):
new_tab = CTkTab(master=self, identifier=identifier, text=text)
self._tab_list.append(new_tab)
return new_tab.identifier
def _set_grid_tab_by_name(self, name: str):
""" needs to be called for changes in corner_radius, border_width """
if self._border_width == 0:
self._tab_dict[name].grid(row=3, column=0, sticky="nsew")
else:
self._tab_dict[name].grid(row=3, column=0, sticky="nsew",
padx=self._apply_widget_scaling(max(self._corner_radius, self._border_width)),
pady=self._apply_widget_scaling(max(self._corner_radius, self._border_width)))
def select_tab(self, identifier: str):
selected_tab = self._get_tab_by_identifier(identifier)
for tab in self._tab_list:
if tab != selected_tab:
tab.frame.grid_forget()
def _create_tab(self) -> CTkFrame:
new_tab = CTkFrame(self,
fg_color=self._fg_color,
border_width=0,
corner_radius=self._corner_radius)
return new_tab
selected_tab.frame.grid(row=3, column=0, rowspan=1, columnspan=3, sticky="nsew")
def _draw(self, no_color_updates: bool = False):
if not self._canvas.winfo_exists():
return
def get_tab(self, identifier):
pass
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
self._apply_widget_scaling(self._current_height - self._top_spacing - self._top_button_overhang),
self._apply_widget_scaling(self._corner_radius),
self._apply_widget_scaling(self._border_width))
if no_color_updates is False or requires_recoloring:
if self._fg_color is None:
self._canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode),
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode))
else:
self._canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
self._canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self._border_color, self._appearance_mode),
outline=ThemeManager.single_color(self._border_color, self._appearance_mode))
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
def tab(self, name: str) -> CTkFrame:
""" returns reference to the tab with given name """
if name in self._tab_dict:
return self._tab_dict[name]
else:
raise ValueError(f"CTkTabview has no tab named '{name}'")
def insert(self, index: int, name: str):
""" creates new tab with given name at position index """
if name not in self._tab_dict:
# if no tab exists, set grid for segmented button
if len(self._tab_dict) == 0:
self._set_grid_segmented_button()
self._name_list.insert(index, name)
self._tab_dict[name] = self._create_tab()
self._segmented_button.insert(index, name)
else:
raise ValueError(f"CTkTabview already has tab named '{name}'")
def add(self, name: str):
""" appends new tab with given name """
self.insert(len(self._tab_dict), name)
def delete(self, name: str):
""" deletes tab with given name """
return

View File

@ -104,9 +104,9 @@ class CTkTextbox(CTkBaseClass):
orientation="vertical",
command=self._textbox.yview)
self._textbox.configure(yscrollcommand=self._y_scrollbar.set)
self._y_scrollbar.grid(row=0, column=1, rowspan=1, columnspan=1, sticky="ns",
padx=(self._apply_widget_scaling(3), self._apply_widget_scaling(self._border_spacing + self._border_width)),
pady=(self._apply_widget_scaling(self._corner_radius + self._border_width), 0))
#self._y_scrollbar.grid(row=0, column=1, rowspan=1, columnspan=1, sticky="ns",
# padx=(self._apply_widget_scaling(3), self._apply_widget_scaling(self._border_spacing + self._border_width)),
# pady=(self._apply_widget_scaling(self._corner_radius + self._border_width), 0))
self._x_scrollbar = CTkScrollbar(self,
height=8,
@ -118,9 +118,9 @@ class CTkTextbox(CTkBaseClass):
orientation="horizontal",
command=self._textbox.xview)
self._textbox.configure(xscrollcommand=self._x_scrollbar.set)
self._x_scrollbar.grid(row=1, column=0, rowspan=1, columnspan=1, sticky="ew",
pady=(self._apply_widget_scaling(3), self._apply_widget_scaling(self._border_spacing + self._border_width)),
padx=(self._apply_widget_scaling(self._corner_radius + self._border_width), 0))
#self._x_scrollbar.grid(row=1, column=0, rowspan=1, columnspan=1, sticky="ew",
# pady=(self._apply_widget_scaling(3), self._apply_widget_scaling(self._border_spacing + self._border_width)),
# padx=(self._apply_widget_scaling(self._corner_radius + self._border_width), 0))
self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
@ -269,6 +269,11 @@ class CTkTextbox(CTkBaseClass):
self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
require_redraw = True
if "border_spacing" in kwargs:
self._border_spacing = kwargs.pop("border_spacing")
self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
require_redraw = True
if "width" in kwargs:
self._set_dimensions(width=kwargs.pop("width"))
@ -287,6 +292,8 @@ class CTkTextbox(CTkBaseClass):
return self._corner_radius
elif attribute_name == "border_width":
return self._border_width
elif attribute_name == "border_spacing":
return self._border_spacing
elif attribute_name == "fg_color":
return self._fg_color
@ -336,6 +343,9 @@ class CTkTextbox(CTkBaseClass):
def compare(self, index, op, index2):
return self._textbox.compare(index, op, index2)
def delete(self, index1, index2=None):
return self._textbox.delete(index1, index2)
def dlineinfo(self, index):
return self._textbox.dlineinfo(index)

View File

@ -2,7 +2,7 @@ import tkinter
import tkinter.ttk as ttk
import copy
import re
from typing import Union, Callable
from typing import Union, Callable, Tuple
try:
from typing import TypedDict
@ -30,7 +30,7 @@ class CTkBaseClass(tkinter.Frame):
width: int = 0,
height: int = 0,
bg_color: Union[str, tuple] = None,
bg_color: Union[str, Tuple[str, str], None] = None,
**kwargs):
super().__init__(master=master, width=width, height=height, **pop_from_dict_by_set(kwargs, self._valid_tk_frame_attributes))
@ -64,7 +64,7 @@ class CTkBaseClass(tkinter.Frame):
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
# background color
self._bg_color = self._detect_color_of_master() if bg_color is None else bg_color
self._bg_color: Union[str, Tuple[str, str]] = self._detect_color_of_master() if bg_color is None else bg_color
super().configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
@ -104,6 +104,7 @@ class CTkBaseClass(tkinter.Frame):
def destroy(self):
""" Destroy this and all descendants widgets. """
AppearanceModeTracker.remove(self._set_appearance_mode)
ScalingTracker.remove_widget(self._set_scaling, self)
super().destroy()
def place(self, **kwargs):
@ -240,6 +241,7 @@ class CTkBaseClass(tkinter.Frame):
raise ValueError(f"'{attribute_name}' is not a supported argument. Look at the documentation for supported arguments.")
def _update_dimensions_event(self, event):
print(event)
# only redraw if dimensions changed (for performance), independent of scaling
if round(self._current_width) != round(event.width / self._widget_scaling) or round(self._current_height) != round(event.height / self._widget_scaling):
self._current_width = (event.width / self._widget_scaling) # adjust current size according to new size given by event
@ -247,7 +249,7 @@ class CTkBaseClass(tkinter.Frame):
self._draw(no_color_updates=True) # faster drawing without color changes
def _detect_color_of_master(self, master_widget=None):
def _detect_color_of_master(self, master_widget=None) -> Union[str, Tuple[str, str]]:
""" detect color of self.master widget to set correct _bg_color """
if master_widget is None: