mirror of
https://github.com/TomSchimansky/CustomTkinter.git
synced 2023-08-10 21:13:13 +03:00
progress on scrollable frame
This commit is contained in:
parent
110e9bbcbf
commit
2359a6ce39
@ -64,6 +64,9 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
|
||||
self._iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop()
|
||||
self._block_update_dimensions_event = False
|
||||
|
||||
# save focus before calling withdraw
|
||||
self.focused_widget_before_widthdraw = None
|
||||
|
||||
# set CustomTkinter titlebar icon (Windows only)
|
||||
if sys.platform.startswith("win"):
|
||||
self.after(200, self._windows_set_titlebar_icon)
|
||||
@ -137,24 +140,26 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
|
||||
|
||||
def update(self):
|
||||
if self._window_exists is False:
|
||||
self._window_exists = True
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists:
|
||||
# print("window dont exists -> deiconify in update")
|
||||
self.deiconify()
|
||||
|
||||
self._window_exists = True
|
||||
|
||||
super().update()
|
||||
|
||||
def mainloop(self, *args, **kwargs):
|
||||
if not self._window_exists:
|
||||
self._window_exists = True
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
self._windows_set_titlebar_color(self._get_appearance_mode())
|
||||
|
||||
if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists:
|
||||
# print("window dont exists -> deiconify in mainloop")
|
||||
self.deiconify()
|
||||
|
||||
self._window_exists = True
|
||||
|
||||
super().mainloop(*args, **kwargs)
|
||||
|
||||
def resizable(self, width: bool = None, height: bool = None):
|
||||
@ -267,9 +272,11 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
|
||||
# print("window_exists -> state_before_windows_set_titlebar_color: ", self.state_before_windows_set_titlebar_color)
|
||||
|
||||
if self._state_before_windows_set_titlebar_color != "iconic" or self._state_before_windows_set_titlebar_color != "withdrawn":
|
||||
self.focused_widget_before_widthdraw = self.focus_get()
|
||||
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
|
||||
else:
|
||||
# print("window dont exists -> withdraw and update")
|
||||
self.focused_widget_before_widthdraw = self.focus_get()
|
||||
super().withdraw()
|
||||
super().update()
|
||||
|
||||
@ -298,7 +305,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
|
||||
except Exception as err:
|
||||
print(err)
|
||||
|
||||
if self._window_exists:
|
||||
if self._window_exists or True:
|
||||
# print("window_exists -> return to original state: ", self.state_before_windows_set_titlebar_color)
|
||||
if self._state_before_windows_set_titlebar_color == "normal":
|
||||
self.deiconify()
|
||||
@ -311,6 +318,10 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
|
||||
else:
|
||||
pass # wait for update or mainloop to be called
|
||||
|
||||
if self.focused_widget_before_widthdraw is not None:
|
||||
self.after(1, self.focused_widget_before_widthdraw.focus)
|
||||
self.focused_widget_before_widthdraw = None
|
||||
|
||||
def _set_appearance_mode(self, mode_string: str):
|
||||
super()._set_appearance_mode(mode_string)
|
||||
|
||||
|
@ -70,6 +70,9 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
|
||||
self._iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color
|
||||
self._block_update_dimensions_event = False
|
||||
|
||||
# save focus before calling withdraw
|
||||
self.focused_widget_before_widthdraw = None
|
||||
|
||||
# set CustomTkinter titlebar icon (Windows only)
|
||||
if sys.platform.startswith("win"):
|
||||
self.after(200, self._windows_set_titlebar_icon)
|
||||
@ -238,6 +241,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
|
||||
if sys.platform.startswith("win") and not self._deactivate_windows_window_header_manipulation:
|
||||
|
||||
self._state_before_windows_set_titlebar_color = self.state()
|
||||
self.focused_widget_before_widthdraw = self.focus_get()
|
||||
super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
|
||||
super().update()
|
||||
|
||||
@ -268,6 +272,10 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl
|
||||
self._windows_set_titlebar_color_called = True
|
||||
self.after(5, self._revert_withdraw_after_windows_set_titlebar_color)
|
||||
|
||||
if self.focused_widget_before_widthdraw is not None:
|
||||
self.after(10, self.focused_widget_before_widthdraw.focus)
|
||||
self.focused_widget_before_widthdraw = None
|
||||
|
||||
def _revert_withdraw_after_windows_set_titlebar_color(self):
|
||||
""" if in a short time (5ms) after """
|
||||
if self._windows_set_titlebar_color_called:
|
||||
|
@ -9,7 +9,7 @@ class CTkAppearanceModeBaseClass:
|
||||
|
||||
- destroy() must be called when sub-class is destroyed
|
||||
- _set_appearance_mode() abstractmethod, gets called when appearance mode changes, must be overridden
|
||||
- _apply_appearance_mode()
|
||||
- _apply_appearance_mode() to convert tuple color
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
|
@ -9,9 +9,6 @@ try:
|
||||
except ImportError:
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
# removed due to circular import
|
||||
# from ...ctk_tk import CTk
|
||||
# from ...ctk_toplevel import CTkToplevel
|
||||
from .... import windows # import windows for isinstance checks
|
||||
|
||||
from ..theme import ThemeManager
|
||||
@ -74,7 +71,7 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas
|
||||
super().bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget as well
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame, tkinter.LabelFrame, ttk.Frame, ttk.LabelFrame, ttk.Notebook)) and not isinstance(self.master, CTkBaseClass):
|
||||
if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame, tkinter.LabelFrame, ttk.Frame, ttk.LabelFrame, ttk.Notebook)) and not isinstance(self.master, (CTkBaseClass, CTkAppearanceModeBaseClass)):
|
||||
master_old_configure = self.master.config
|
||||
|
||||
def new_configure(*args, **kwargs):
|
||||
|
@ -133,7 +133,7 @@ class CTkEntry(CTkBaseClass):
|
||||
|
||||
self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
self._draw()
|
||||
self._draw(no_color_updates=True)
|
||||
|
||||
def _update_font(self):
|
||||
""" pass font to tkinter widgets with applied font scaling and update grid with workaround """
|
||||
@ -153,14 +153,14 @@ class CTkEntry(CTkBaseClass):
|
||||
def _draw(self, no_color_updates=False):
|
||||
super()._draw(no_color_updates)
|
||||
|
||||
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
|
||||
|
||||
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 requires_recoloring or no_color_updates is False:
|
||||
self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
|
||||
|
||||
if self._apply_appearance_mode(self._fg_color) == "transparent":
|
||||
self._canvas.itemconfig("inner_parts",
|
||||
fill=self._apply_appearance_mode(self._bg_color),
|
||||
@ -342,13 +342,13 @@ class CTkEntry(CTkBaseClass):
|
||||
return self._entry.get()
|
||||
|
||||
def focus(self):
|
||||
return self._entry.focus()
|
||||
self._entry.focus()
|
||||
|
||||
def focus_set(self):
|
||||
return self._entry.focus_set()
|
||||
self._entry.focus_set()
|
||||
|
||||
def focus_force(self):
|
||||
return self._entry.focus_force()
|
||||
self._entry.focus_force()
|
||||
|
||||
def index(self, index):
|
||||
return self._entry.index(index)
|
||||
|
@ -1,15 +1,18 @@
|
||||
from typing import Union, Tuple, List, Optional
|
||||
from typing import Union, Tuple, Optional
|
||||
try:
|
||||
from typing import Literal
|
||||
except ImportError:
|
||||
from typing_extensions import Literal
|
||||
import tkinter
|
||||
import sys
|
||||
|
||||
from .ctk_frame import CTkFrame
|
||||
from .ctk_scrollbar import CTkScrollbar
|
||||
from .appearance_mode import CTkAppearanceModeBaseClass
|
||||
from .core_widget_classes import CTkBaseClass
|
||||
|
||||
|
||||
class CTkScrollableFrame(tkinter.Frame):
|
||||
|
||||
_xscrollincrement = 4 # horizontal scrolling speed
|
||||
_yscrollincrement = 8 # vertical scrolling speed
|
||||
|
||||
class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass):
|
||||
def __init__(self,
|
||||
master: any,
|
||||
width: int = 200,
|
||||
@ -21,58 +24,120 @@ class CTkScrollableFrame(tkinter.Frame):
|
||||
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):
|
||||
orientation: Literal["vertical", "horizontal"] = "vertical"):
|
||||
|
||||
self._activate_x_scrollbars = activate_x_scrollbars
|
||||
self._activate_y_scrollbars = activate_y_scrollbars
|
||||
self._orientation = orientation
|
||||
|
||||
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.parent_frame = CTkFrame(master=master, width=width, height=height, corner_radius=corner_radius,
|
||||
border_width=border_width, bg_color=bg_color, fg_color=fg_color, border_color=border_color)
|
||||
self.parent_frame.grid_propagate(0)
|
||||
self.parent_canvas = tkinter.Canvas(master=self.parent_frame, highlightthickness=0, width=0, height=0)
|
||||
self._set_scroll_increments()
|
||||
|
||||
if self._orientation == "horizontal":
|
||||
self.scrollbar = CTkScrollbar(master=self.parent_frame, orientation="horizontal", command=self.parent_canvas.xview)
|
||||
self.parent_canvas.configure(xscrollcommand=self.scrollbar.set)
|
||||
elif self._orientation == "vertical":
|
||||
self.scrollbar = CTkScrollbar(master=self.parent_frame, orientation="vertical", command=self.parent_canvas.yview)
|
||||
self.parent_canvas.configure(yscrollcommand=self.scrollbar.set)
|
||||
self._create_grid()
|
||||
|
||||
super().__init__(master=self.parent_canvas, width=0)
|
||||
tkinter.Frame.__init__(self, master=self.parent_canvas, highlightthickness=0)
|
||||
CTkAppearanceModeBaseClass.__init__(self)
|
||||
|
||||
self.bind("<Configure>", lambda e: self.parent_canvas.configure(scrollregion=self.parent_canvas.bbox("all")))
|
||||
self.parent_canvas.bind("<Configure>", self._fit_frame_dimensions_to_canvas)
|
||||
self.bind_all("<MouseWheel>", self._mouse_wheel_all)
|
||||
self.bind_all("<KeyPress-Shift_L>", self._keyboard_shift_press_all)
|
||||
self.bind_all("<KeyPress-Shift_R>", self._keyboard_shift_press_all)
|
||||
self.bind_all("<KeyRelease-Shift_L>", self._keyboard_shift_release_all)
|
||||
self.bind_all("<KeyRelease-Shift_R>", self._keyboard_shift_release_all)
|
||||
self.parent_canvas.bind("<Configure>", self._parent_canvas_configure)
|
||||
self._create_window_id = self.parent_canvas.create_window(0, 0, window=self, anchor="nw")
|
||||
|
||||
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self.parent_frame.cget("fg_color")))
|
||||
|
||||
self._shift_pressed = False
|
||||
self.mouse_over_widget = False
|
||||
|
||||
def destroy(self):
|
||||
tkinter.Frame.destroy(self)
|
||||
CTkAppearanceModeBaseClass.destroy(self)
|
||||
|
||||
def _set_appearance_mode(self, mode_string):
|
||||
super()._set_appearance_mode(mode_string)
|
||||
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self.parent_frame.cget("fg_color")))
|
||||
|
||||
def configure(self, **kwargs):
|
||||
if "fg_color" in kwargs:
|
||||
self.parent_frame.configure(fg_color=kwargs.pop("fg_color"))
|
||||
tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self.parent_frame.cget("fg_color")))
|
||||
|
||||
for child in self.winfo_children():
|
||||
if isinstance(child, CTkBaseClass):
|
||||
child.configure(bg_color=self.parent_frame.cget("fg_color"))
|
||||
|
||||
if "corner_radius" in kwargs:
|
||||
self.parent_frame.configure(corner_radius=kwargs.pop("corner_radius"))
|
||||
self._create_grid()
|
||||
|
||||
if "border_width" in kwargs:
|
||||
self.parent_frame.configure(border_width=kwargs.pop("border_width"))
|
||||
self._create_grid()
|
||||
|
||||
self.parent_frame.configure(**kwargs)
|
||||
|
||||
def _fit_frame_dimensions_to_canvas(self, event):
|
||||
if self._orientation == "horizontal":
|
||||
self.parent_canvas.itemconfigure(self._create_window_id, height=self.parent_canvas.winfo_height())
|
||||
elif self._orientation == "vertical":
|
||||
self.parent_canvas.itemconfigure(self._create_window_id, width=self.parent_canvas.winfo_width())
|
||||
|
||||
def _set_scroll_increments(self):
|
||||
if sys.platform.startswith("win"):
|
||||
self.parent_canvas.configure(xscrollincrement=1, yscrollincrement=1)
|
||||
elif sys.platform == "darwin":
|
||||
self.parent_canvas.configure(xscrollincrement=4, yscrollincrement=8)
|
||||
|
||||
def _create_grid(self):
|
||||
border_spacing = self.parent_frame.cget("corner_radius") + self.parent_frame.cget("border_width")
|
||||
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:
|
||||
if self._orientation == "horizontal":
|
||||
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_canvas.grid(row=0, column=0, sticky="nsew",
|
||||
padx=border_spacing, pady=(border_spacing, 0))
|
||||
self.scrollbar.grid(row=1, column=0, sticky="nsew",
|
||||
padx=border_spacing)
|
||||
elif self._orientation == "vertical":
|
||||
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
|
||||
self.parent_canvas.grid(row=0, column=0, sticky="nsew",
|
||||
pady=border_spacing, padx=(border_spacing, 0))
|
||||
self.scrollbar.grid(row=0, column=1, sticky="nsew",
|
||||
pady=border_spacing)
|
||||
|
||||
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")
|
||||
if sys.platform.startswith("win"):
|
||||
if self._shift_pressed:
|
||||
if self.parent_canvas.xview() != (0.0, 1.0):
|
||||
self.parent_canvas.xview("scroll", -int(event.delta/6), "units")
|
||||
else:
|
||||
if self.parent_canvas.yview() != (0.0, 1.0):
|
||||
self.parent_canvas.yview("scroll", -int(event.delta/6), "units")
|
||||
elif sys.platform == "darwin":
|
||||
if self._shift_pressed:
|
||||
if self.parent_canvas.xview() != (0.0, 1.0):
|
||||
self.parent_canvas.xview("scroll", -event.delta, "units")
|
||||
else:
|
||||
if self.parent_canvas.yview() != (0.0, 1.0):
|
||||
self.parent_canvas.yview("scroll", -event.delta, "units")
|
||||
else:
|
||||
self.parent_canvas.yview("scroll", -event.delta, "units")
|
||||
if self._shift_pressed:
|
||||
if self.parent_canvas.xview() != (0.0, 1.0):
|
||||
self.parent_canvas.xview("scroll", -event.delta, "units")
|
||||
else:
|
||||
if self.parent_canvas.yview() != (0.0, 1.0):
|
||||
self.parent_canvas.yview("scroll", -event.delta, "units")
|
||||
|
||||
def _keyboard_shift_press_all(self, event):
|
||||
self._shift_pressed = True
|
||||
@ -96,3 +161,27 @@ class CTkScrollableFrame(tkinter.Frame):
|
||||
|
||||
def grid(self, **kwargs):
|
||||
self.parent_frame.grid(**kwargs)
|
||||
|
||||
def pack_forget(self):
|
||||
self.parent_frame.pack_forget()
|
||||
|
||||
def place_forget(self, **kwargs):
|
||||
self.parent_frame.place_forget()
|
||||
|
||||
def grid_forget(self, **kwargs):
|
||||
self.parent_frame.grid_forget()
|
||||
|
||||
def grid_remove(self, **kwargs):
|
||||
self.parent_frame.grid_remove()
|
||||
|
||||
def grid_propagate(self, **kwargs):
|
||||
self.parent_frame.grid_propagate()
|
||||
|
||||
def grid_info(self, **kwargs):
|
||||
self.parent_frame.grid_info()
|
||||
|
||||
def lift(self, aboveThis=None):
|
||||
self.parent_frame.lift(aboveThis)
|
||||
|
||||
def lower(self, belowThis=None):
|
||||
self.parent_frame.lower(belowThis)
|
||||
|
Loading…
Reference in New Issue
Block a user