From a478334fb706edbe764c95f14e9df2efe28cf4bc Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Sat, 4 Feb 2023 16:53:48 +0100 Subject: [PATCH] start working on scrollable frame --- customtkinter/__init__.py | 1 + customtkinter/windows/widgets/__init__.py | 1 + .../core_widget_classes/ctk_base_class.py | 3 +- customtkinter/windows/widgets/ctk_button.py | 9 +- customtkinter/windows/widgets/ctk_frame.py | 2 +- .../windows/widgets/ctk_scrollable_frame.py | 98 +++++++++++++++++++ 6 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 customtkinter/windows/widgets/ctk_scrollable_frame.py diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index 3004e4d..3e7f85c 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -33,6 +33,7 @@ from .windows.widgets import CTkSlider from .windows.widgets import CTkSwitch from .windows.widgets import CTkTabview from .windows.widgets import CTkTextbox +from .windows.widgets import CTkScrollableFrame # import windows from .windows import CTk diff --git a/customtkinter/windows/widgets/__init__.py b/customtkinter/windows/widgets/__init__.py index 2e21484..a75c63d 100644 --- a/customtkinter/windows/widgets/__init__.py +++ b/customtkinter/windows/widgets/__init__.py @@ -13,3 +13,4 @@ from .ctk_slider import CTkSlider from .ctk_switch import CTkSwitch from .ctk_tabview import CTkTabview from .ctk_textbox import CTkTextbox +from .ctk_scrollable_frame import CTkScrollableFrame 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 c401c3d..10518b6 100644 --- a/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py +++ b/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py @@ -179,8 +179,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas elif isinstance(image, CTkImage): return image else: - warnings.warn(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. " + - f"Image can not be scaled on HighDPI displays, use CTkImage instead.\n") + warnings.warn(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. Image can not be scaled on HighDPI displays, use CTkImage instead.\n") return image def _update_dimensions_event(self, event): diff --git a/customtkinter/windows/widgets/ctk_button.py b/customtkinter/windows/widgets/ctk_button.py index 45ed025..0648d92 100644 --- a/customtkinter/windows/widgets/ctk_button.py +++ b/customtkinter/windows/widgets/ctk_button.py @@ -40,7 +40,7 @@ class CTkButton(CTkBaseClass): text: str = "CTkButton", font: Optional[Union[tuple, CTkFont]] = None, textvariable: Union[tkinter.Variable, None] = None, - image: Union[CTkImage, None] = None, + image: Union[CTkImage, "ImageTk.PhotoImage", None] = None, state: str = "normal", hover: bool = True, command: Union[Callable[[], None], None] = None, @@ -169,8 +169,11 @@ class CTkButton(CTkBaseClass): def _update_image(self): if self._image_label is not None: - self._image_label.configure(image=self._image.create_scaled_photo_image(self._get_widget_scaling(), - self._get_appearance_mode())) + if isinstance(self._image, CTkImage): + self._image_label.configure(image=self._image.create_scaled_photo_image(self._get_widget_scaling(), + self._get_appearance_mode())) + elif self._image is not None: + self._image_label.configure(image=self._image) def destroy(self): if isinstance(self._font, CTkFont): diff --git a/customtkinter/windows/widgets/ctk_frame.py b/customtkinter/windows/widgets/ctk_frame.py index fe9e226..67bf161 100644 --- a/customtkinter/windows/widgets/ctk_frame.py +++ b/customtkinter/windows/widgets/ctk_frame.py @@ -24,8 +24,8 @@ class CTkFrame(CTkBaseClass): bg_color: Union[str, Tuple[str, str]] = "transparent", fg_color: Optional[Union[str, Tuple[str, str]]] = None, border_color: Optional[Union[str, Tuple[str, str]]] = None, - background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None, + background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None, overwrite_preferred_drawing_method: Union[str, None] = None, **kwargs): diff --git a/customtkinter/windows/widgets/ctk_scrollable_frame.py b/customtkinter/windows/widgets/ctk_scrollable_frame.py new file mode 100644 index 0000000..57b6929 --- /dev/null +++ b/customtkinter/windows/widgets/ctk_scrollable_frame.py @@ -0,0 +1,98 @@ +from typing import Union, Tuple, List, Optional +import tkinter + +from .ctk_frame import CTkFrame +from .ctk_scrollbar import CTkScrollbar + + +class CTkScrollableFrame(tkinter.Frame): + + _xscrollincrement = 4 # horizontal scrolling speed + _yscrollincrement = 8 # vertical scrolling speed + + def __init__(self, + master: any, + width: int = 200, + height: int = 200, + corner_radius: Optional[Union[int, str]] = None, + border_width: Optional[Union[int, str]] = None, + + bg_color: Union[str, Tuple[str, str]] = "transparent", + fg_color: Optional[Union[str, Tuple[str, str]]] = None, + border_color: Optional[Union[str, Tuple[str, str]]] = None, + + activate_x_scrollbars: bool = False, + activate_y_scrollbars: bool = True): + + self._activate_x_scrollbars = activate_x_scrollbars + self._activate_y_scrollbars = activate_y_scrollbars + + self.parent_frame = CTkFrame(master=master, width=width, height=height, corner_radius=corner_radius, border_width=border_width) + self.parent_canvas = tkinter.Canvas(master=self.parent_frame, yscrollincrement=self._yscrollincrement, xscrollincrement=self._xscrollincrement) + if self._activate_x_scrollbars: + self.x_scrollbar = CTkScrollbar(master=self.parent_frame, orientation="horizontal", command=self.parent_canvas.xview) + self.parent_canvas.configure(xscrollcommand=self.x_scrollbar.set) + if self._activate_y_scrollbars: + self.y_scrollbar = CTkScrollbar(master=self.parent_frame, orientation="vertical", command=self.parent_canvas.yview) + self.parent_canvas.configure(yscrollcommand=self.y_scrollbar.set) + self._create_grid() + + super().__init__(master=self.parent_canvas, width=0) + + self.bind("", lambda e: self.parent_canvas.configure(scrollregion=self.parent_canvas.bbox("all"))) + self.bind_all("", self._mouse_wheel_all) + self.bind_all("", self._keyboard_shift_press_all) + self.bind_all("", self._keyboard_shift_press_all) + self.bind_all("", self._keyboard_shift_release_all) + self.bind_all("", self._keyboard_shift_release_all) + self.parent_canvas.bind("", self._parent_canvas_configure) + self._create_window_id = self.parent_canvas.create_window(0, 0, window=self, anchor="nw") + + self._shift_pressed = False + self.mouse_over_widget = False + + def _create_grid(self): + self.parent_frame.grid_columnconfigure(0, weight=1) + self.parent_frame.grid_rowconfigure(0, weight=1) + self.parent_canvas.grid(row=0, column=0, sticky="nsew") + + if self._activate_x_scrollbars: + self.parent_frame.grid_rowconfigure(1, weight=0) + self.x_scrollbar.grid(row=1, column=0, sticky="nsew") + if self._activate_y_scrollbars: + self.parent_frame.grid_columnconfigure(1, weight=0) + self.y_scrollbar.grid(row=0, column=1, sticky="nsew") + + def _parent_canvas_configure(self, event): + #self.parent_canvas.itemconfigure(self._create_window_id, width=event.width, height=event.height) + pass + + def _mouse_wheel_all(self, event): + if self.check_if_master_is_canvas(event.widget): + if self._shift_pressed: + self.parent_canvas.xview("scroll", -event.delta, "units") + else: + self.parent_canvas.yview("scroll", -event.delta, "units") + + def _keyboard_shift_press_all(self, event): + self._shift_pressed = True + + def _keyboard_shift_release_all(self, event): + self._shift_pressed = False + + def check_if_master_is_canvas(self, widget): + if widget == self.parent_canvas: + return True + elif widget.master is not None: + return self.check_if_master_is_canvas(widget.master) + else: + return False + + def pack(self, **kwargs): + self.parent_frame.pack(**kwargs) + + def place(self, **kwargs): + self.parent_frame.place(**kwargs) + + def grid(self, **kwargs): + self.parent_frame.grid(**kwargs)