diff --git a/CHANGELOG.md b/CHANGELOG.md index 83291c0..ec0332f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Readme.md b/Readme.md index f2936b1..8c2815f 100644 --- a/Readme.md +++ b/Readme.md @@ -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) diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 7bd17ed..01620f3 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -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 diff --git a/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py b/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py index 491a253..a8766ef 100644 --- a/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py +++ b/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py @@ -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: diff --git a/customtkinter/windows/widgets/ctk_button.py b/customtkinter/windows/widgets/ctk_button.py index 595e703..0c65506 100644 --- a/customtkinter/windows/widgets/ctk_button.py +++ b/customtkinter/windows/widgets/ctk_button.py @@ -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("", self._on_enter) - self._canvas.bind("", self._on_leave) - self._canvas.bind("", self._clicked) - self._canvas.bind("", 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 == "": + self._canvas.bind("", self._on_enter) + if sequence is None or sequence == "": + self._canvas.bind("", self._on_leave) + if sequence is None or sequence == "": + self._canvas.bind("", self._clicked) + if sequence is None or sequence == "": + self._canvas.bind("", 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() diff --git a/customtkinter/windows/widgets/ctk_checkbox.py b/customtkinter/windows/widgets/ctk_checkbox.py index fb8a7e2..0789786 100644 --- a/customtkinter/windows/widgets/ctk_checkbox.py +++ b/customtkinter/windows/widgets/ctk_checkbox.py @@ -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("", self._on_enter) - self._text_label.bind("", self._on_leave) - self._text_label.bind("", 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 == "": + self._canvas.bind("", self._on_enter) + if sequence is None or sequence == "": + self._canvas.bind("", self._on_leave) + if sequence is None or sequence == "": + self._canvas.bind("", 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() diff --git a/customtkinter/windows/widgets/ctk_progressbar.py b/customtkinter/windows/widgets/ctk_progressbar.py index 2859ff4..13cbb11 100644 --- a/customtkinter/windows/widgets/ctk_progressbar.py +++ b/customtkinter/windows/widgets/ctk_progressbar.py @@ -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): diff --git a/customtkinter/windows/widgets/ctk_radiobutton.py b/customtkinter/windows/widgets/ctk_radiobutton.py index 9ba3a29..7fcfe62 100644 --- a/customtkinter/windows/widgets/ctk_radiobutton.py +++ b/customtkinter/windows/widgets/ctk_radiobutton.py @@ -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 diff --git a/customtkinter/windows/widgets/ctk_scrollbar.py b/customtkinter/windows/widgets/ctk_scrollbar.py index 99134ff..b53e63b 100644 --- a/customtkinter/windows/widgets/ctk_scrollbar.py +++ b/customtkinter/windows/widgets/ctk_scrollbar.py @@ -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("", self._on_enter) - self._canvas.bind("", self._on_leave) - self._canvas.tag_bind("border_parts", "", self._clicked) - self._canvas.bind("", self._clicked) - self._canvas.bind("", 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", "", self._clicked) + if sequence is None or sequence == "": + self._canvas.bind("", self._on_enter) + if sequence is None or sequence == "": + self._canvas.bind("", self._on_leave) + if sequence is None or sequence == "": + self._canvas.bind("", self._clicked) + if sequence is None or sequence == "": + self._canvas.bind("", 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() diff --git a/examples/image_example.py b/examples/image_example.py index c4064b8..da10b81 100644 --- a/examples/image_example.py +++ b/examples/image_example.py @@ -1,3 +1,5 @@ +import tkinter + import customtkinter import os from PIL import Image diff --git a/examples/simple_example.py b/examples/simple_example.py index 26d900f..da4695c 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -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()