fixed radiobutton disabled command call bug #677, fixed key error for theme in scrollbar #711, removed bind_all and unbind_all from baseclass, added CTkCanvas and CTkBaseClass for top level import

This commit is contained in:
Tom Schimansky 2022-12-06 11:09:34 +01:00
parent f4af512290
commit 2c7b2c5030
11 changed files with 97 additions and 38 deletions

View File

@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
ToDo:
- cursor configuring
- overwrite winfo methods
- set icon (self.call("wm", "iconphoto", self._w, tkinter.PhotoImage(file="test_images/CustomTkinter_logo_single.png")))
- add option to change label position for checkbox, switch, radiobutton #628
## [5.0.0] - 2022-11-13

View File

@ -11,6 +11,7 @@
![PyPI - Downloads](https://img.shields.io/pypi/dm/customtkinter?color=green&label=downloads)
![Downloads](https://static.pepy.tech/personalized-badge/customtkinter?period=total&units=international_system&left_color=grey&right_color=green&left_text=downloads)
![PyPI - License](https://img.shields.io/pypi/l/customtkinter)
![](https://tokei.rs/b1/github/tomschimansky/customtkinter)
![Total lines](https://img.shields.io/tokei/lines/github.com/tomschimansky/customtkinter?color=green&label=total%20lines)
</div>

View File

@ -13,6 +13,10 @@ from .windows.widgets.scaling import ScalingTracker
from .windows.widgets.theme import ThemeManager
from .windows.widgets.core_rendering import DrawEngine
# import base widgets
from .windows.widgets.core_rendering import CTkCanvas
from .windows.widgets.core_widget_classes import CTkBaseClass
# import widgets
from .windows.widgets import CTkButton
from .windows.widgets import CTkCheckBox

View File

@ -240,6 +240,12 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
super().configure(width=self._apply_widget_scaling(self._desired_width),
height=self._apply_widget_scaling(self._desired_height))
def unbind_all(self, sequence):
raise AttributeError("'unbind_all' is not allowed, because it would delete necessary internal callbacks for all widgets")
def bind_all(self, sequence=None, func=None, add=None):
raise AttributeError("'bind_all' is not allowed, could result in undefined behavior")
def place(self, **kwargs):
"""
Place a widget in the parent widget. Use as options:

View File

@ -100,16 +100,22 @@ class CTkButton(CTkBaseClass):
self._draw_engine = DrawEngine(self._canvas)
self._draw_engine.set_round_to_even_numbers(self._round_width_to_even_numbers, self._round_height_to_even_numbers) # rendering options
# canvas event bindings
self._canvas.bind("<Enter>", self._on_enter)
self._canvas.bind("<Leave>", self._on_leave)
self._canvas.bind("<Button-1>", self._clicked)
self._canvas.bind("<Button-1>", self._clicked)
# configure cursor and initial draw
self._create_bindings()
self._set_cursor()
self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter)
if sequence is None or sequence == "<Leave>":
self._canvas.bind("<Leave>", self._on_leave)
if sequence is None or sequence == "<Button-1>":
self._canvas.bind("<Button-1>", self._clicked)
if sequence is None or sequence == "<Button-1>":
self._canvas.bind("<Button-1>", self._clicked)
def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs)
@ -532,17 +538,22 @@ class CTkButton(CTkBaseClass):
if self._command is not None:
return self._command()
def bind(self, sequence: str = None, command: Callable = None, add: str = None) -> str:
""" called on the tkinter.Label and tkinter.Canvas """
canvas_bind_return = self._canvas.bind(sequence, command, add)
label_bind_return = self._text_label.bind(sequence, command, add)
def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = "+") -> str:
""" called on the tkinter.Canvas """
if add != "+" or add is not True:
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
canvas_bind_return = self._canvas.bind(sequence, command, add="+")
label_bind_return = self._text_label.bind(sequence, command, add="+")
return canvas_bind_return + " + " + label_bind_return
def unbind(self, sequence: str, funcid: str = None):
""" called on the tkinter.Label and tkinter.Canvas """
canvas_bind_return, label_bind_return = funcid.split(" + ")
self._canvas.unbind(sequence, canvas_bind_return)
self._text_label.unbind(sequence, label_bind_return)
if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None)
self._text_label.unbind(sequence, None)
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self):
return self._text_label.focus()

View File

@ -118,17 +118,23 @@ class CTkCheckBox(CTkBaseClass):
self._text_label.grid(row=0, column=2, sticky="w")
self._text_label["anchor"] = "w"
self._text_label.bind("<Enter>", self._on_enter)
self._text_label.bind("<Leave>", self._on_leave)
self._text_label.bind("<Button-1>", self.toggle)
# register variable callback and set state according to variable
if self._variable is not None and self._variable != "":
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
self._check_state = True if self._variable.get() == self._onvalue else False
self._draw() # initial draw
self._create_bindings()
self._set_cursor()
self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter)
if sequence is None or sequence == "<Leave>":
self._canvas.bind("<Leave>", self._on_leave)
if sequence is None or sequence == "<Button-1>":
self._canvas.bind("<Button-1>", self.toggle)
def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs)
@ -430,13 +436,19 @@ class CTkCheckBox(CTkBaseClass):
def get(self) -> Union[int, str]:
return self._onvalue if self._check_state is True else self._offvalue
def bind(self, sequence=None, command=None, add=None):
def bind(self, sequence=None, command=None, add="+"):
""" called on the tkinter.Canvas """
return self._canvas.bind(sequence, command, add)
if add != "+" or add is not True:
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
return self._canvas.bind(sequence, command, add="+")
def unbind(self, sequence, funcid=None):
""" called on the tkinter.Canvas """
return self._canvas.unbind(sequence, funcid)
""" called on the tkinter.Canvas, restores internal callbacks """
if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None) # unbind all callbacks for sequence
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self):
return self._text_label.focus()

View File

@ -1,6 +1,6 @@
import tkinter
import math
from typing import Union, Tuple, Optional
from typing import Union, Tuple, Optional, Literal
from .core_rendering import CTkCanvas
from .theme import ThemeManager
@ -29,7 +29,7 @@ class CTkProgressBar(CTkBaseClass):
variable: Union[tkinter.Variable, None] = None,
orientation: str = "horizontal",
mode: str = "determinate",
mode: Literal["determinate", "indeterminate"] = "determinate",
determinate_speed: float = 1,
indeterminate_speed: float = 1,
**kwargs):

View File

@ -377,8 +377,8 @@ class CTkRadioButton(CTkBaseClass):
self._check_state = True
self.select()
if self._command is not None:
self._command()
if self._command is not None:
self._command()
def select(self, from_variable_callback=False):
self._check_state = True

View File

@ -49,8 +49,8 @@ class CTkScrollbar(CTkBaseClass):
# color
self._fg_color = ThemeManager.theme["CTkScrollbar"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
self._button_color = ThemeManager.theme["CTkScrollbar"]["scrollbar_color"] if button_color is None else self._check_color_type(button_color)
self._button_hover_color = ThemeManager.theme["CTkScrollbar"]["scrollbar_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
self._button_color = ThemeManager.theme["CTkScrollbar"]["button_color"] if button_color is None else self._check_color_type(button_color)
self._button_hover_color = ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
# shape
self._corner_radius = ThemeManager.theme["CTkScrollbar"]["corner_radius"] if corner_radius is None else corner_radius
@ -71,14 +71,22 @@ class CTkScrollbar(CTkBaseClass):
self._canvas.place(x=0, y=0, relwidth=1, relheight=1)
self._draw_engine = DrawEngine(self._canvas)
self._canvas.bind("<Enter>", self._on_enter)
self._canvas.bind("<Leave>", self._on_leave)
self._canvas.tag_bind("border_parts", "<Button-1>", self._clicked)
self._canvas.bind("<B1-Motion>", self._clicked)
self._canvas.bind("<MouseWheel>", self._mouse_scroll_event)
self._create_bindings()
self._draw()
def _create_bindings(self, sequence: Optional[str] = None):
""" set necessary bindings for functionality of widget, will overwrite other bindings """
if sequence is None:
self._canvas.tag_bind("border_parts", "<Button-1>", self._clicked)
if sequence is None or sequence == "<Enter>":
self._canvas.bind("<Enter>", self._on_enter)
if sequence is None or sequence == "<Leave>":
self._canvas.bind("<Leave>", self._on_leave)
if sequence is None or sequence == "<B1-Motion>":
self._canvas.bind("<B1-Motion>", self._clicked)
if sequence is None or sequence == "<MouseWheel>":
self._canvas.bind("<MouseWheel>", self._mouse_scroll_event)
def _set_scaling(self, *args, **kwargs):
super()._set_scaling(*args, **kwargs)
@ -249,13 +257,19 @@ class CTkScrollbar(CTkBaseClass):
def get(self):
return self._start_value, self._end_value
def bind(self, sequence=None, command=None, add=None):
def bind(self, sequence=None, command=None, add="+"):
""" called on the tkinter.Canvas """
return self._canvas.bind(sequence, command, add)
if add != "+" or add is not True:
raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
return self._canvas.bind(sequence, command, add="+")
def unbind(self, sequence, funcid=None):
""" called on the tkinter.Canvas """
return self._canvas.unbind(sequence, funcid)
""" called on the tkinter.Canvas, restores internal callbacks """
if funcid is not None:
raise ValueError("'funcid' argument can only be None, because there is a bug in" +
" tkinter and its not clear whether the internal callbacks will be unbinded or not")
self._canvas.unbind(sequence, None) # unbind all callbacks for sequence
self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
def focus(self):
return self._canvas.focus()

View File

@ -1,3 +1,5 @@
import tkinter
import customtkinter
import os
from PIL import Image

View File

@ -70,4 +70,11 @@ tabview_1.pack(pady=10, padx=10)
tabview_1.add("CTkTabview")
tabview_1.add("Tab 2")
progressbar_1.configure(mode="indeterminate")
progressbar_1.start()
#progressbar_1.stop()
#progressbar_1.start()
#progressbar_1.stop()
#progressbar_1.start()
app.mainloop()