finished textbox, combined CTkTextbox and CTkScrolledTextbox into one class, enhanced test_text.py

This commit is contained in:
Tom Schimansky 2022-10-05 18:39:45 +02:00
parent 14b39aa7b4
commit 0cbec6fea1
5 changed files with 208 additions and 484 deletions

View File

@ -64,7 +64,6 @@ from .widgets.ctk_optionmenu import CTkOptionMenu
from .widgets.ctk_combobox import CTkComboBox from .widgets.ctk_combobox import CTkComboBox
from .widgets.ctk_scrollbar import CTkScrollbar from .widgets.ctk_scrollbar import CTkScrollbar
from .widgets.ctk_textbox import CTkTextbox from .widgets.ctk_textbox import CTkTextbox
from .widgets.ctk_scrollbar_textbox import CTkScrolledTextbox
# import windows # import windows
from .windows.ctk_tk import CTk from .windows.ctk_tk import CTk

View File

@ -1,452 +0,0 @@
import time
import tkinter
from typing import Union, Tuple
from .ctk_canvas import CTkCanvas
from .ctk_scrollbar import CTkScrollbar
from ..theme_manager import ThemeManager
from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass
from .widget_helper_functions import pop_from_dict_by_set
class CTkScrolledTextbox(CTkBaseClass):
"""
Textbox with x and y scrollbars, rounded corners, and all text features of tkinter.Text widget.
Scrollbars only appear when they are needed. Text is wrapped on line end by default,
set wrap='none' to disable automatic line wrapping.
For detailed information check out the documentation.
Detailed methods and parameters of the underlaying tkinter.Text widget can be found here:
https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/text.html
(most of them are implemented here too)
"""
_scrollbar_update_time = 200 # interval in ms, to check if scrollbars are needed
_scrollbars_activated = True
# attributes that are passed to and managed by the tkinter textbox only:
_valid_tk_text_attributes = {"autoseparators", "cursor", "exportselection",
"insertborderwidth", "insertofftime", "insertontime", "insertwidth",
"maxundo", "padx", "pady", "selectborderwidth", "spacing1",
"spacing2", "spacing3", "state", "tabs", "takefocus", "undo", "wrap",
"xscrollcommand", "yscrollcommand"}
def __init__(self, *args,
width: int = 200,
height: int = 200,
corner_radius: Union[int, str] = "default_theme",
border_width: Union[int, str] = "default_theme",
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",
text_color: Union[str, str] = "default_theme",
scrollbar_color: Union[str, Tuple[str, str]] = "default_theme",
scrollbar_hover_color: Union[str, Tuple[str, str]] = "default_theme",
font: any = "default_theme",
**kwargs):
# 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.pop("master"))
else:
super().__init__(*args, bg_color=bg_color, width=width, height=height)
# color
self._fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
self._border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self._scrollbar_color = ThemeManager.theme["color"]["scrollbar_button"] if scrollbar_color == "default_theme" else scrollbar_color
self._scrollbar_hover_color = ThemeManager.theme["color"]["scrollbar_button_hover"] if scrollbar_hover_color == "default_theme" else scrollbar_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
# text
self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font
self._canvas = CTkCanvas(master=self,
highlightthickness=0,
width=self._apply_widget_scaling(self._current_width),
height=self._apply_widget_scaling(self._current_height))
self._canvas.grid(row=0, column=0, padx=0, pady=0, rowspan=2, columnspan=2, sticky="nsew")
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
self._draw_engine = DrawEngine(self._canvas)
self._textbox = tkinter.Text(self,
fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
width=0,
height=0,
font=self._apply_font_scaling(self._font),
highlightthickness=0,
relief="flat",
insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode),
bg=ThemeManager.single_color(self._fg_color, self._appearance_mode),
**pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes))
self._check_kwargs_empty(kwargs, raise_error=True)
self._y_scrollbar = CTkScrollbar(self,
width=8,
border_spacing=0,
fg_color=self._fg_color,
scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color,
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(3 + self._border_width)),
pady=(self._apply_widget_scaling(self._corner_radius + self._border_width), 0))
self._x_scrollbar = CTkScrollbar(self,
height=8,
border_spacing=0,
fg_color=self._fg_color,
scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color,
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(3 + self._border_width)),
padx=(self._apply_widget_scaling(self._corner_radius + self._border_width), 0))
self._hide_x_scrollbar = True
self._hide_y_scrollbar = True
self._manage_grid_commands(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
self.after(500, self._check_if_scrollbars_needed)
super().bind('<Configure>', self._update_dimensions_event)
self._draw()
def _manage_grid_commands(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False):
# configure 2x2 grid
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(max(self._corner_radius, self._border_width)))
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(max(self._corner_radius, self._border_width)))
if re_grid_textbox:
self._textbox.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew",
padx=(self._apply_widget_scaling(max(self._corner_radius, self._border_width)), 0),
pady=(self._apply_widget_scaling(max(self._corner_radius, self._border_width)), 0))
if re_grid_x_scrollbar:
if not self._hide_x_scrollbar:
self._x_scrollbar.grid(row=1, column=0, rowspan=1, columnspan=1, sticky="ew",
pady=(3, 3 + self._border_width),
padx=(max(self._corner_radius, self._border_width), 0)) # scrollbar grid method without scaling
else:
self._x_scrollbar.grid_forget()
if re_grid_y_scrollbar:
if not self._hide_y_scrollbar:
self._y_scrollbar.grid(row=0, column=1, rowspan=1, columnspan=1, sticky="ns",
padx=(3, 3 + self._border_width),
pady=(max(self._corner_radius, self._border_width), 0)) # scrollbar grid method without scaling
else:
self._y_scrollbar.grid_forget()
def _check_if_scrollbars_needed(self, event=None):
""" Method hides or places the scrollbars if they are needed on key release event of tkinter.text widget """
if self._textbox.xview() != (0.0, 1.0) and not self._x_scrollbar.winfo_ismapped(): # x scrollbar needed
self._hide_x_scrollbar = False
self._manage_grid_commands(re_grid_x_scrollbar=True)
elif self._textbox.xview() == (0.0, 1.0) and self._x_scrollbar.winfo_ismapped(): # x scrollbar not needed
self._hide_x_scrollbar = True
self._manage_grid_commands(re_grid_x_scrollbar=True)
if self._textbox.yview() != (0.0, 1.0) and not self._y_scrollbar.winfo_ismapped(): # y scrollbar needed
self._hide_y_scrollbar = False
self._manage_grid_commands(re_grid_y_scrollbar=True)
elif self._textbox.yview() == (0.0, 1.0) and self._y_scrollbar.winfo_ismapped(): # y scrollbar not needed
self._hide_y_scrollbar = True
self._manage_grid_commands(re_grid_y_scrollbar=True)
if self._textbox.winfo_exists():
self.after(200, self._check_if_scrollbars_needed)
def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs)
self._textbox.configure(font=self._apply_font_scaling(self._font))
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
self._manage_grid_commands(re_grid_textbox=False, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
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._draw()
def _draw(self, no_color_updates=False):
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),
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))
self._textbox.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
bg=ThemeManager.single_color(self._bg_color, self._appearance_mode),
insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode))
self._x_scrollbar.configure(fg_color=self._bg_color, scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color)
self._y_scrollbar.configure(fg_color=self._bg_color, scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color)
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._textbox.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
bg=ThemeManager.single_color(self._fg_color, self._appearance_mode),
insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode))
self._x_scrollbar.configure(fg_color=self._fg_color, scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color)
self._y_scrollbar.configure(fg_color=self._fg_color, scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color)
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))
self._canvas.tag_lower("inner_parts")
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
for child in self.winfo_children():
if isinstance(child, CTkBaseClass) and hasattr(child, "_fg_color"):
child.configure(bg_color=self._fg_color)
if "border_color" in kwargs:
self._border_color = kwargs.pop("border_color")
require_redraw = True
if "text_color" in kwargs:
self._text_color = kwargs.pop("text_color")
require_redraw = True
if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius")
self._manage_grid_commands(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
require_redraw = True
if "border_width" in kwargs:
self._border_width = kwargs.pop("border_width")
self._manage_grid_commands(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"))
if "height" in kwargs:
self._set_dimensions(height=kwargs.pop("height"))
if "font" in kwargs:
self._font = kwargs.pop("font")
self._textbox.configure(font=self._apply_font_scaling(self._font))
self._textbox.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes))
super().configure(require_redraw=require_redraw, **kwargs)
def cget(self, attribute_name: str) -> any:
if attribute_name == "corner_radius":
return self._corner_radius
elif attribute_name == "border_width":
return self._border_width
elif attribute_name == "fg_color":
return self._fg_color
elif attribute_name == "border_color":
return self._border_color
elif attribute_name == "text_color":
return self._text_color
elif attribute_name == "font":
return self._font
else:
return super().cget(attribute_name)
def bind(self, sequence=None, command=None, add=None):
""" called on the tkinter.Text """
# if sequence is <KeyRelease>, allow only to add the binding to keep the _textbox_modified_event() being called
if sequence == "<KeyRelease>":
return self._textbox.bind(sequence, command, add="+")
else:
return self._textbox.bind(sequence, command, add)
def unbind(self, sequence, funcid=None):
""" called on the tkinter.Text """
return self._textbox.unbind(sequence, funcid)
def insert(self, index, text, tags=None):
self._check_if_scrollbars_needed()
return self._textbox.insert(index, text, tags)
def get(self, index1, index2=None):
return self._textbox.get(index1, index2)
def bbox(self, index):
return self._textbox.bbox(index)
def compare(self, index, op, index2):
return self._textbox.compare(index, op, index2)
def dlineinfo(self, index):
return self._textbox.dlineinfo(index)
def edit_modified(self, arg=None):
return self._textbox.edit_modified(arg)
def edit_redo(self):
self._check_if_scrollbars_needed()
return self._textbox.edit_redo()
def edit_reset(self):
return self._textbox.edit_reset()
def edit_separator(self):
return self._textbox.edit_separator()
def edit_undo(self):
self._check_if_scrollbars_needed()
return self._textbox.edit_undo()
def image_create(self, index, **kwargs):
raise AttributeError("embedding images is forbidden, because would be incompatible with scaling")
def image_cget(self, index, option):
raise AttributeError("embedding images is forbidden, because would be incompatible with scaling")
def image_configure(self, index):
raise AttributeError("embedding images is forbidden, because would be incompatible with scaling")
def image_names(self):
raise AttributeError("embedding images is forbidden, because would be incompatible with scaling")
def index(self, i):
return self._textbox.index(i)
def mark_gravity(self, mark, gravity=None):
return self._textbox.mark_gravity(mark, gravity)
def mark_names(self):
return self._textbox.mark_names()
def mark_next(self, index):
return self._textbox.mark_next(index)
def mark_previous(self, index):
return self._textbox.mark_previous(index)
def mark_set(self, mark, index):
return self._textbox.mark_set(mark, index)
def mark_unset(self, mark):
return self._textbox.mark_unset(mark)
def scan_dragto(self, x, y):
return self._textbox.scan_dragto(x, y)
def scan_mark(self, x, y):
return self._textbox.scan_mark(x, y)
def search(self, pattern, index, *args, **kwargs):
return self._textbox.search(pattern, index, *args, **kwargs)
def see(self, index):
return self._textbox.see(index)
def tag_add(self, tagName, index1, index2=None):
return self._textbox.tag_add(tagName, index1, index2)
def tag_bind(self, tagName, sequence, func, add=None):
return self._textbox.tag_bind(tagName, sequence, func, add)
def tag_cget(self, tagName, option):
return self._textbox.tag_cget(tagName, option)
def tag_config(self, tagName, **kwargs):
if "font" in kwargs:
raise AttributeError("'font' option forbidden, because would be incompatible with scaling")
return self._textbox.tag_config(tagName, **kwargs)
def tag_delete(self, *tagName):
return self._textbox.tag_delete(*tagName)
def tag_lower(self, tagName, belowThis=None):
return self._textbox.tag_lower(tagName, belowThis)
def tag_names(self, index=None):
return self._textbox.tag_names(index)
def tag_nextrange(self, tagName, index1, index2=None):
return self._textbox.tag_nextrange(tagName, index1, index2)
def tag_prevrange(self, tagName, index1, index2=None):
return self._textbox.tag_prevrange(tagName, index1, index2)
def tag_raise(self, tagName, aboveThis=None):
return self._textbox.tag_raise(tagName, aboveThis)
def tag_ranges(self, tagName):
return self._textbox.tag_ranges(tagName)
def tag_remove(self, tagName, index1, index2=None):
return self._textbox.tag_remove(tagName, index1, index2)
def tag_unbind(self, tagName, sequence, funcid=None):
return self._textbox.tag_unbind(tagName, sequence, funcid)
def window_cget(self, index, option):
raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
def window_configure(self, index, option):
raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
def window_create(self, index, **kwargs):
raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
def window_names(self):
raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
def xview(self, *args):
return self._textbox.xview(*args)
def xview_moveto(self, fraction):
return self._textbox.xview_moveto(fraction)
def xview_scroll(self, n, what):
return self._textbox.xview_scroll(n, what)
def yview(self, *args):
return self._textbox.yview(*args)
def yview_moveto(self, fraction):
return self._textbox.yview_moveto(fraction)
def yview_scroll(self, n, what):
return self._textbox.yview_scroll(n, what)

View File

@ -2,6 +2,7 @@ import tkinter
from typing import Union, Tuple from typing import Union, Tuple
from .ctk_canvas import CTkCanvas from .ctk_canvas import CTkCanvas
from .ctk_scrollbar import CTkScrollbar
from ..theme_manager import ThemeManager from ..theme_manager import ThemeManager
from ..draw_engine import DrawEngine from ..draw_engine import DrawEngine
from .widget_base_class import CTkBaseClass from .widget_base_class import CTkBaseClass
@ -11,7 +12,9 @@ from .widget_helper_functions import pop_from_dict_by_set
class CTkTextbox(CTkBaseClass): class CTkTextbox(CTkBaseClass):
""" """
Textbox with rounded corners, and all text features of tkinter.Text widget. Textbox with x and y scrollbars, rounded corners, and all text features of tkinter.Text widget.
Scrollbars only appear when they are needed. Text is wrapped on line end by default,
set wrap='none' to disable automatic line wrapping.
For detailed information check out the documentation. For detailed information check out the documentation.
Detailed methods and parameters of the underlaying tkinter.Text widget can be found here: Detailed methods and parameters of the underlaying tkinter.Text widget can be found here:
@ -19,6 +22,8 @@ class CTkTextbox(CTkBaseClass):
(most of them are implemented here too) (most of them are implemented here too)
""" """
_scrollbar_update_time = 200 # interval in ms, to check if scrollbars are needed
# attributes that are passed to and managed by the tkinter textbox only: # attributes that are passed to and managed by the tkinter textbox only:
_valid_tk_text_attributes = {"autoseparators", "cursor", "exportselection", _valid_tk_text_attributes = {"autoseparators", "cursor", "exportselection",
"insertborderwidth", "insertofftime", "insertontime", "insertwidth", "insertborderwidth", "insertofftime", "insertontime", "insertwidth",
@ -31,13 +36,17 @@ class CTkTextbox(CTkBaseClass):
height: int = 200, height: int = 200,
corner_radius: Union[int, str] = "default_theme", corner_radius: Union[int, str] = "default_theme",
border_width: Union[int, str] = "default_theme", border_width: Union[int, str] = "default_theme",
border_spacing: int = 3,
bg_color: Union[str, Tuple[str, str], None] = None, bg_color: Union[str, Tuple[str, str], None] = None,
fg_color: Union[str, Tuple[str, str], None] = "default_theme", fg_color: Union[str, Tuple[str, str], None] = "default_theme",
border_color: Union[str, Tuple[str, str]] = "default_theme", border_color: Union[str, Tuple[str, str]] = "default_theme",
text_color: Union[str, str] = "default_theme", text_color: Union[str, str] = "default_theme",
scrollbar_color: Union[str, Tuple[str, str]] = "default_theme",
scrollbar_hover_color: Union[str, Tuple[str, str]] = "default_theme",
font: any = "default_theme", font: any = "default_theme",
activate_scrollbars: bool = True,
**kwargs): **kwargs):
# transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass
@ -50,23 +59,22 @@ class CTkTextbox(CTkBaseClass):
self._fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color self._fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
self._border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color self._border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
self._scrollbar_color = ThemeManager.theme["color"]["scrollbar_button"] if scrollbar_color == "default_theme" else scrollbar_color
self._scrollbar_hover_color = ThemeManager.theme["color"]["scrollbar_button_hover"] if scrollbar_hover_color == "default_theme" else scrollbar_hover_color
# shape # shape
self._corner_radius = ThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius 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._border_width = ThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
self._border_spacing = border_spacing
# text # text
self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font
# configure 1x1 grid
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self._canvas = CTkCanvas(master=self, self._canvas = CTkCanvas(master=self,
highlightthickness=0, highlightthickness=0,
width=self._apply_widget_scaling(self._current_width), width=self._apply_widget_scaling(self._current_width),
height=self._apply_widget_scaling(self._current_height)) height=self._apply_widget_scaling(self._current_height))
self._canvas.grid(row=0, column=0, padx=0, pady=0, rowspan=1, columnspan=1, sticky="nsew") self._canvas.grid(row=0, column=0, padx=0, pady=0, rowspan=2, columnspan=2, sticky="nsew")
self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode)) self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
self._draw_engine = DrawEngine(self._canvas) self._draw_engine = DrawEngine(self._canvas)
@ -74,31 +82,116 @@ class CTkTextbox(CTkBaseClass):
fg=ThemeManager.single_color(self._text_color, self._appearance_mode), fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
width=0, width=0,
height=0, height=0,
font=self._font, font=self._apply_font_scaling(self._font),
highlightthickness=0, highlightthickness=0,
relief="flat", relief="flat",
insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode), insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode),
bg=ThemeManager.single_color(self._fg_color, self._appearance_mode), bg=ThemeManager.single_color(self._fg_color, self._appearance_mode),
**pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes)) **pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes))
self._textbox.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew",
padx=self._apply_widget_scaling(self._corner_radius),
pady=self._apply_widget_scaling(self._corner_radius))
self._check_kwargs_empty(kwargs, raise_error=True) self._check_kwargs_empty(kwargs, raise_error=True)
# scrollbars
self._scrollbars_activated = activate_scrollbars
self._hide_x_scrollbar = True
self._hide_y_scrollbar = True
self._y_scrollbar = CTkScrollbar(self,
width=8,
height=0,
border_spacing=0,
fg_color=self._fg_color,
scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color,
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._x_scrollbar = CTkScrollbar(self,
height=8,
width=0,
border_spacing=0,
fg_color=self._fg_color,
scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color,
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._manage_grid_commands(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
self.after(500, self._check_if_scrollbars_needed)
super().bind('<Configure>', self._update_dimensions_event) super().bind('<Configure>', self._update_dimensions_event)
self._draw() self._draw()
def _manage_grid_commands(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False):
# configure 2x2 grid
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(max(self._corner_radius, self._border_width + self._border_spacing)))
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(max(self._corner_radius, self._border_width + self._border_spacing)))
if re_grid_textbox:
self._textbox.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew",
padx=(self._apply_widget_scaling(max(self._corner_radius, self._border_width + self._border_spacing)), 0),
pady=(self._apply_widget_scaling(max(self._corner_radius, self._border_width + self._border_spacing)), 0))
if re_grid_x_scrollbar:
if not self._hide_x_scrollbar and self._scrollbars_activated:
self._x_scrollbar.grid(row=1, column=0, rowspan=1, columnspan=1, sticky="ewn",
pady=(3, self._border_spacing + self._border_width),
padx=(max(self._corner_radius, self._border_width + self._border_spacing), self._border_spacing)) # scrollbar grid method without scaling
else:
self._x_scrollbar.grid_forget()
if re_grid_y_scrollbar:
if not self._hide_y_scrollbar and self._scrollbars_activated:
self._y_scrollbar.grid(row=0, column=1, rowspan=1, columnspan=1, sticky="nsw",
padx=(3, self._border_spacing + self._border_width),
pady=(max(self._corner_radius, self._border_width + self._border_spacing), self._border_spacing)) # scrollbar grid method without scaling
else:
self._y_scrollbar.grid_forget()
def _check_if_scrollbars_needed(self, event=None):
""" Method hides or places the scrollbars if they are needed on key release event of tkinter.text widget """
if self._scrollbars_activated:
if self._textbox.xview() != (0.0, 1.0) and not self._x_scrollbar.winfo_ismapped(): # x scrollbar needed
self._hide_x_scrollbar = False
self._manage_grid_commands(re_grid_x_scrollbar=True)
elif self._textbox.xview() == (0.0, 1.0) and self._x_scrollbar.winfo_ismapped(): # x scrollbar not needed
self._hide_x_scrollbar = True
self._manage_grid_commands(re_grid_x_scrollbar=True)
if self._textbox.yview() != (0.0, 1.0) and not self._y_scrollbar.winfo_ismapped(): # y scrollbar needed
self._hide_y_scrollbar = False
self._manage_grid_commands(re_grid_y_scrollbar=True)
elif self._textbox.yview() == (0.0, 1.0) and self._y_scrollbar.winfo_ismapped(): # y scrollbar not needed
self._hide_y_scrollbar = True
self._manage_grid_commands(re_grid_y_scrollbar=True)
else:
self._hide_x_scrollbar = False
self._hide_x_scrollbar = False
self._manage_grid_commands(re_grid_y_scrollbar=True)
if self._textbox.winfo_exists():
self.after(200, self._check_if_scrollbars_needed)
def _set_scaling(self, *args, **kwargs): def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs) super()._set_scaling(*args, **kwargs)
self._textbox.configure(font=self._apply_font_scaling(self._font)) self._textbox.configure(font=self._apply_font_scaling(self._font))
self._textbox.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew",
padx=self._apply_widget_scaling(self._corner_radius),
pady=self._apply_widget_scaling(self._corner_radius))
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height)) height=self._apply_widget_scaling(self._desired_height))
self._manage_grid_commands(re_grid_textbox=False, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
self._draw() self._draw()
def _set_dimensions(self, width=None, height=None): def _set_dimensions(self, width=None, height=None):
@ -120,20 +213,30 @@ class CTkTextbox(CTkBaseClass):
self._canvas.itemconfig("inner_parts", self._canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self._bg_color, self._appearance_mode), fill=ThemeManager.single_color(self._bg_color, self._appearance_mode),
outline=ThemeManager.single_color(self._bg_color, self._appearance_mode)) outline=ThemeManager.single_color(self._bg_color, self._appearance_mode))
self._textbox.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
bg=ThemeManager.single_color(self._bg_color, self._appearance_mode),
insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode))
self._x_scrollbar.configure(fg_color=self._bg_color, scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color)
self._y_scrollbar.configure(fg_color=self._bg_color, scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color)
else: else:
self._canvas.itemconfig("inner_parts", self._canvas.itemconfig("inner_parts",
fill=ThemeManager.single_color(self._fg_color, self._appearance_mode), fill=ThemeManager.single_color(self._fg_color, self._appearance_mode),
outline=ThemeManager.single_color(self._fg_color, self._appearance_mode)) outline=ThemeManager.single_color(self._fg_color, self._appearance_mode))
self._textbox.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
bg=ThemeManager.single_color(self._fg_color, self._appearance_mode),
insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode))
self._x_scrollbar.configure(fg_color=self._fg_color, scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color)
self._y_scrollbar.configure(fg_color=self._fg_color, scrollbar_color=self._scrollbar_color,
scrollbar_hover_color=self._scrollbar_hover_color)
self._canvas.itemconfig("border_parts", self._canvas.itemconfig("border_parts",
fill=ThemeManager.single_color(self._border_color, self._appearance_mode), fill=ThemeManager.single_color(self._border_color, self._appearance_mode),
outline=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)) self._canvas.configure(bg=ThemeManager.single_color(self._bg_color, self._appearance_mode))
self._textbox.configure(fg=ThemeManager.single_color(self._text_color, self._appearance_mode),
bg=ThemeManager.single_color(self._fg_color, self._appearance_mode),
insertbackground=ThemeManager.single_color(self._text_color, self._appearance_mode))
self._canvas.tag_lower("inner_parts") self._canvas.tag_lower("inner_parts")
self._canvas.tag_lower("border_parts") self._canvas.tag_lower("border_parts")
@ -157,13 +260,12 @@ class CTkTextbox(CTkBaseClass):
if "corner_radius" in kwargs: if "corner_radius" in kwargs:
self._corner_radius = kwargs.pop("corner_radius") self._corner_radius = kwargs.pop("corner_radius")
self._textbox.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew", self._manage_grid_commands(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
padx=self._apply_widget_scaling(self._corner_radius),
pady=self._apply_widget_scaling(self._corner_radius))
require_redraw = True require_redraw = True
if "border_width" in kwargs: if "border_width" in kwargs:
self._border_width = kwargs.pop("border_width") self._border_width = kwargs.pop("border_width")
self._manage_grid_commands(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
require_redraw = True require_redraw = True
if "width" in kwargs: if "width" in kwargs:
@ -200,6 +302,11 @@ class CTkTextbox(CTkBaseClass):
def bind(self, sequence=None, command=None, add=None): def bind(self, sequence=None, command=None, add=None):
""" called on the tkinter.Text """ """ called on the tkinter.Text """
# if sequence is <KeyRelease>, allow only to add the binding to keep the _textbox_modified_event() being called
if sequence == "<KeyRelease>":
return self._textbox.bind(sequence, command, add="+")
else:
return self._textbox.bind(sequence, command, add) return self._textbox.bind(sequence, command, add)
def unbind(self, sequence, funcid=None): def unbind(self, sequence, funcid=None):
@ -207,6 +314,7 @@ class CTkTextbox(CTkBaseClass):
return self._textbox.unbind(sequence, funcid) return self._textbox.unbind(sequence, funcid)
def insert(self, index, text, tags=None): def insert(self, index, text, tags=None):
self._check_if_scrollbars_needed()
return self._textbox.insert(index, text, tags) return self._textbox.insert(index, text, tags)
def get(self, index1, index2=None): def get(self, index1, index2=None):
@ -225,6 +333,7 @@ class CTkTextbox(CTkBaseClass):
return self._textbox.edit_modified(arg) return self._textbox.edit_modified(arg)
def edit_redo(self): def edit_redo(self):
self._check_if_scrollbars_needed()
return self._textbox.edit_redo() return self._textbox.edit_redo()
def edit_reset(self): def edit_reset(self):
@ -234,6 +343,7 @@ class CTkTextbox(CTkBaseClass):
return self._textbox.edit_separator() return self._textbox.edit_separator()
def edit_undo(self): def edit_undo(self):
self._check_if_scrollbars_needed()
return self._textbox.edit_undo() return self._textbox.edit_undo()
def image_create(self, index, **kwargs): def image_create(self, index, **kwargs):

View File

@ -52,7 +52,7 @@ class App(customtkinter.CTk):
self.main_button_1 = customtkinter.CTkButton(self, fg_color=None, border_width=2) self.main_button_1 = customtkinter.CTkButton(self, fg_color=None, border_width=2)
self.main_button_1.grid(row=3, column=3, padx=(10, 20), pady=(10, 20), sticky="nsew") self.main_button_1.grid(row=3, column=3, padx=(10, 20), pady=(10, 20), sticky="nsew")
self.textbox = customtkinter.CTkScrolledTextbox(self) self.textbox = customtkinter.CTkTextbox(self)
self.textbox.grid(row=0, column=1, padx=(20, 10), pady=(20, 10), sticky="nsew") self.textbox.grid(row=0, column=1, padx=(20, 10), pady=(20, 10), sticky="nsew")
# create radiobutton frame # create radiobutton frame
@ -127,7 +127,7 @@ class App(customtkinter.CTk):
self.progressbar_1.start() self.progressbar_1.start()
self.textbox.insert("1.0", "CTkTextbox\n\n" + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20) self.textbox.insert("1.0", "CTkTextbox\n\n" + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
self.textbox.configure(border_width=5, corner_radius=20, wrap="none") self.textbox.configure(border_width=5, corner_radius=5, wrap="none")
self.radiobutton_frame.configure(border_width=3) self.radiobutton_frame.configure(border_width=3)
def open_input_dialog(self): def open_input_dialog(self):

View File

@ -1,28 +1,95 @@
import customtkinter import customtkinter
#customtkinter.set_widget_scaling(2) customtkinter.set_widget_scaling(0.9)
#customtkinter.set_window_scaling(2) customtkinter.set_window_scaling(0.9)
#customtkinter.set_spacing_scaling(2) customtkinter.set_spacing_scaling(0.9)
customtkinter.set_appearance_mode("dark") customtkinter.set_appearance_mode("dark")
app = customtkinter.CTk() app = customtkinter.CTk()
app.title("test_scrollbar.py") app.title("test_scrollbar.py")
app.geometry("800x1200")
app.grid_rowconfigure(0, weight=1) app.grid_rowconfigure(0, weight=1)
app.grid_columnconfigure((0, 1), weight=1) app.grid_columnconfigure((0, 1, 2, 3), weight=1)
textbox_1 = customtkinter.CTkScrolledTextbox(app, fg_color=None, corner_radius=0) textbox_1 = customtkinter.CTkTextbox(app, fg_color=None, corner_radius=0, border_spacing=0)
textbox_1.grid(row=0, column=0, sticky="nsew") textbox_1.grid(row=0, column=0, sticky="nsew")
textbox_1.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20) textbox_1.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
frame_1 = customtkinter.CTkFrame(app, corner_radius=0) frame_1 = customtkinter.CTkFrame(app, corner_radius=0)
frame_1.grid(row=0, column=1, sticky="nsew") frame_1.grid(row=0, column=1, sticky="nsew")
frame_1.grid_rowconfigure((0, 1), weight=1) frame_1.grid_rowconfigure((0, 1, 2, 3, 4), weight=1)
frame_1.grid_columnconfigure(0, weight=1) frame_1.grid_columnconfigure(0, weight=1)
textbox_2 = customtkinter.CTkScrolledTextbox(frame_1, wrap="none") textbox_2 = customtkinter.CTkTextbox(frame_1, wrap="none")
textbox_2.grid(row=0, column=0, sticky="nsew", padx=20, pady=20) textbox_2.grid(row=0, column=0, sticky="nsew", padx=20, pady=20)
textbox_2.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20) textbox_2.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_2 = customtkinter.CTkTextbox(frame_1, wrap="none", corner_radius=30)
textbox_2.grid(row=1, column=0, sticky="nsew", padx=20, pady=20)
textbox_2.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_2 = customtkinter.CTkTextbox(frame_1, wrap="none", corner_radius=0, border_width=30)
textbox_2.grid(row=2, column=0, sticky="nsew", padx=20, pady=20)
textbox_2.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_2 = customtkinter.CTkTextbox(frame_1, wrap="none", corner_radius=60, border_width=15)
textbox_2.grid(row=3, column=0, sticky="nsew", padx=20, pady=20)
textbox_2.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_2 = customtkinter.CTkTextbox(frame_1, wrap="none", corner_radius=0, border_width=0)
textbox_2.grid(row=4, column=0, sticky="nsew", padx=20, pady=20)
textbox_2.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
frame_2 = customtkinter.CTkFrame(app, corner_radius=0, fg_color=None)
frame_2.grid(row=0, column=2, sticky="nsew")
frame_2.grid_rowconfigure((0, 1, 2, 3, 4), weight=1)
frame_2.grid_columnconfigure(0, weight=1)
textbox_3 = customtkinter.CTkTextbox(frame_2)
textbox_3.grid(row=0, column=0, sticky="nsew", padx=20, pady=20)
textbox_3.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_3 = customtkinter.CTkTextbox(frame_2, corner_radius=30)
textbox_3.grid(row=1, column=0, sticky="nsew", padx=20, pady=20)
textbox_3.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_3 = customtkinter.CTkTextbox(frame_2, corner_radius=0, border_width=30)
textbox_3.grid(row=2, column=0, sticky="nsew", padx=20, pady=20)
textbox_3.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_3 = customtkinter.CTkTextbox(frame_2, corner_radius=60, border_width=15)
textbox_3.grid(row=3, column=0, sticky="nsew", padx=20, pady=20)
textbox_3.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_3 = customtkinter.CTkTextbox(frame_2, corner_radius=0, border_width=0, border_spacing=20)
textbox_3.grid(row=4, column=0, sticky="nsew", padx=20, pady=20)
textbox_3.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
frame_3 = customtkinter.CTkFrame(app, corner_radius=0, fg_color=None)
frame_3.grid(row=0, column=3, sticky="nsew")
frame_3.grid_rowconfigure((0, 1, 2, 3, 4), weight=1)
frame_3.grid_columnconfigure(0, weight=1)
textbox_3 = customtkinter.CTkTextbox(frame_3, activate_scrollbars=False)
textbox_3.grid(row=0, column=0, sticky="nsew", padx=20, pady=20)
textbox_3.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_3 = customtkinter.CTkTextbox(frame_3, corner_radius=10, border_width=2, activate_scrollbars=False)
textbox_3.grid(row=1, column=0, sticky="nsew", padx=20, pady=20)
textbox_3.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_3 = customtkinter.CTkTextbox(frame_3, corner_radius=0, border_width=2, activate_scrollbars=False)
textbox_3.grid(row=2, column=0, sticky="nsew", padx=20, pady=20)
textbox_3.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_3 = customtkinter.CTkTextbox(frame_3, corner_radius=0, border_width=2, activate_scrollbars=False)
textbox_3.grid(row=3, column=0, sticky="nsew", padx=20, pady=20)
textbox_3.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
textbox_3 = customtkinter.CTkTextbox(frame_3, corner_radius=0, border_width=0, activate_scrollbars=False, border_spacing=10)
textbox_3.grid(row=4, column=0, sticky="nsew", padx=20, pady=20)
textbox_3.insert("0.0", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.\n\n" * 20)
app.after(3000, lambda: customtkinter.set_appearance_mode("light"))
app.mainloop() app.mainloop()