mirror of
https://github.com/ColugoMum/Smart_container.git
synced 2025-06-03 21:54:04 +08:00
363 lines
11 KiB
Python
363 lines
11 KiB
Python
"""
|
|
This module contains various custom scrolling widgets, including
|
|
`ScrolledText` and `ScrolledFrame`.
|
|
"""
|
|
import ttkbootstrap as ttk
|
|
from ttkbootstrap.constants import *
|
|
from tkinter import Pack, Place, Grid
|
|
|
|
|
|
class ScrolledText(ttk.Frame):
|
|
"""A text widget with optional vertical and horizontal scrollbars.
|
|
Setting `autohide=True` will cause the scrollbars to hide when the
|
|
mouse is not over the widget. The vertical scrollbar is on by
|
|
default, but can be turned off. The horizontal scrollbar can be
|
|
enabled by setting `vbar=True`.
|
|
|
|
This widget is identical in configuration to the `Text` widget other
|
|
than the scrolling frame. https://tcl.tk/man/tcl8.6/TkCmd/text.htm
|
|
|
|

|
|
|
|
Examples:
|
|
|
|
```python
|
|
import ttkbootstrap as ttk
|
|
from ttkbootstrap.constants import *
|
|
from ttkbootstrap.scrolled import ScrolledText
|
|
|
|
app = ttk.Window()
|
|
|
|
# scrolled text with autohide vertical scrollbar
|
|
st = ScrolledText(app, padding=5, height=10, autohide=True)
|
|
st.pack(fill=BOTH, expand=YES)
|
|
|
|
# add text
|
|
st.insert(END, 'Insert your text here.')
|
|
|
|
app.mainloop()
|
|
```
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
master=None,
|
|
padding=2,
|
|
bootstyle=DEFAULT,
|
|
autohide=False,
|
|
vbar=True,
|
|
hbar=False,
|
|
**kwargs,
|
|
):
|
|
"""
|
|
Parameters:
|
|
|
|
master (Widget):
|
|
The parent widget.
|
|
|
|
padding (int):
|
|
The amount of empty space to create on the outside of
|
|
the widget.
|
|
|
|
bootstyle (str):
|
|
A style keyword used to set the color and style of the
|
|
vertical scrollbar. Available options include -> primary,
|
|
secondary, success, info, warning, danger, dark, light.
|
|
|
|
vbar (bool):
|
|
A vertical scrollbar is shown when **True** (default).
|
|
|
|
hbar (bool):
|
|
A horizontal scrollbar is shown when **True**. Turning
|
|
on this scrollbar will also set `wrap="none"`. This
|
|
scrollbar is _off_ by default.
|
|
|
|
autohide (bool):
|
|
When **True**, the scrollbars will hide when the mouse
|
|
is not within the frame bbox.
|
|
|
|
**kwargs (Dict[str, Any]):
|
|
Other keyword arguments passed to the `Text` widget.
|
|
"""
|
|
super().__init__(master, padding=padding)
|
|
|
|
# setup text widget
|
|
self._text = ttk.Text(self, padx=50, **kwargs)
|
|
self._hbar = None
|
|
self._vbar = None
|
|
|
|
# delegate text methods to frame
|
|
for method in vars(ttk.Text).keys():
|
|
if any(["pack" in method, "grid" in method, "place" in method]):
|
|
pass
|
|
else:
|
|
setattr(self, method, getattr(self._text, method))
|
|
|
|
# setup scrollbars
|
|
if vbar:
|
|
self._vbar = ttk.Scrollbar(
|
|
master=self,
|
|
bootstyle=bootstyle,
|
|
command=self._text.yview,
|
|
orient=VERTICAL,
|
|
)
|
|
self._vbar.place(relx=1.0, relheight=1.0, anchor=NE)
|
|
self._text.configure(yscrollcommand=self._vbar.set)
|
|
|
|
if hbar:
|
|
self._hbar = ttk.Scrollbar(
|
|
master=self,
|
|
bootstyle=bootstyle,
|
|
command=self._text.xview,
|
|
orient=HORIZONTAL,
|
|
)
|
|
self._hbar.place(rely=1.0, relwidth=1.0, anchor=SW)
|
|
self._text.configure(xscrollcommand=self._hbar.set, wrap="none")
|
|
|
|
self._text.pack(side=LEFT, fill=BOTH, expand=YES)
|
|
|
|
# position scrollbars
|
|
if self._hbar:
|
|
self.update_idletasks()
|
|
self._text_width = self.winfo_reqwidth()
|
|
self._scroll_width = self.winfo_reqwidth()
|
|
|
|
self.bind("<Configure>", self._on_configure)
|
|
|
|
if autohide:
|
|
self.autohide_scrollbar()
|
|
self.hide_scrollbars()
|
|
|
|
def _on_configure(self, *_):
|
|
"""Callback for when the configure method is used"""
|
|
if self._hbar:
|
|
self.update_idletasks()
|
|
text_width = self.winfo_width()
|
|
vbar_width = self._vbar.winfo_width()
|
|
relx = (text_width - vbar_width) / text_width
|
|
self._hbar.place(rely=1.0, relwidth=relx)
|
|
|
|
def hide_scrollbars(self, *_):
|
|
"""Hide the scrollbars."""
|
|
try:
|
|
self._vbar.lower(self._text)
|
|
except:
|
|
pass
|
|
try:
|
|
self._hbar.lower(self._text)
|
|
except:
|
|
pass
|
|
|
|
def show_scrollbars(self, *_):
|
|
"""Show the scrollbars."""
|
|
try:
|
|
self._vbar.lift(self._text)
|
|
except:
|
|
pass
|
|
try:
|
|
self._hbar.lift(self._text)
|
|
except:
|
|
pass
|
|
|
|
def autohide_scrollbar(self, *_):
|
|
"""Show the scrollbars when the mouse enters the widget frame,
|
|
and hide when it leaves the frame."""
|
|
self.bind("<Enter>", self.show_scrollbars)
|
|
self.bind("<Leave>", self.hide_scrollbars)
|
|
|
|
|
|
class ScrolledFrame(ttk.Frame):
|
|
"""A widget container with a vertical scrollbar.
|
|
|
|
The scrollbar has an autohide feature that can be turned on by
|
|
setting `autohide=True`.
|
|
|
|
There is unfortunately not a clean way to implement this megawidget
|
|
in tkinter. A common implementation is to reference an internal
|
|
frame as the master for objects to be packed, placed, etc... I've
|
|
chosen to expose the internal container foremost, so that you can
|
|
use this `ScrolledFrame` just as you would a normal frame. This
|
|
is more natural. However, there are cases when you need to have
|
|
the actual parent container, and for that reason, you can access
|
|
this parent container object via `ScrolledFrame.container`.
|
|
Specifically, you will need this object when adding a
|
|
`ScrolledFrame` to a `Notebook` or `Panedwindow`. For example,
|
|
`mynotebook.add(myscrolledframe.container)`.
|
|
|
|
Examples:
|
|
|
|
```python
|
|
import ttkbootstrap as ttk
|
|
from ttkbootstrap.constants import *
|
|
from ttkbootstrap.scrolled import ScrolledFrame
|
|
|
|
app = ttk.Window()
|
|
|
|
sf = ScrolledFrame(app, autohide=True)
|
|
sf.pack(fill=BOTH, expand=YES, padx=10, pady=10)
|
|
|
|
# add a large number of checkbuttons into the scrolled frame
|
|
for x in range(20):
|
|
ttk.Checkbutton(sf, text=f"Checkbutton {x}").pack(anchor=W)
|
|
|
|
app.mainloop()
|
|
```
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
master=None,
|
|
padding=2,
|
|
bootstyle=DEFAULT,
|
|
autohide=False,
|
|
height=None,
|
|
width=None,
|
|
**kwargs,
|
|
):
|
|
"""
|
|
Parameters:
|
|
|
|
master (Widget):
|
|
The parent widget.
|
|
|
|
padding (int):
|
|
The amount of empty space to create on the outside of
|
|
the widget.
|
|
|
|
bootstyle (str):
|
|
A style keyword used to set the color and style of the
|
|
vertical scrollbar. Available options include -> primary,
|
|
secondary, success, info, warning, danger, dark, light.
|
|
|
|
autohide (bool):
|
|
When **True**, the scrollbars will hide when the mouse
|
|
is not within the frame bbox.
|
|
|
|
height (int):
|
|
The height of the frame in screen units.
|
|
|
|
width (int):
|
|
The widget of the frame in screen units.
|
|
|
|
**kwargs (Dict[str, Any]):
|
|
Other keyword arguments.
|
|
"""
|
|
self.container = ttk.Frame(
|
|
master=master,
|
|
relief=FLAT,
|
|
borderwidth=0
|
|
)
|
|
self._canvas = ttk.Canvas(
|
|
self.container,
|
|
relief=FLAT,
|
|
borderwidth=0,
|
|
highlightthickness=0,
|
|
height=height,
|
|
width=width,
|
|
)
|
|
self._canvas.pack(fill=BOTH, expand=YES)
|
|
self._vbar = ttk.Scrollbar(
|
|
master=self.container,
|
|
bootstyle=bootstyle,
|
|
command=self._canvas.yview,
|
|
orient=VERTICAL,
|
|
)
|
|
self._vbar.place(relx=1.0, relheight=1.0, anchor=NE)
|
|
self._canvas.configure(yscrollcommand=self._vbar.set)
|
|
|
|
super().__init__(
|
|
master=self._canvas,
|
|
padding=padding,
|
|
bootstyle=bootstyle,
|
|
**kwargs
|
|
)
|
|
self._winsys = self.tk.call('tk', 'windowingsystem')
|
|
self._wid = self._canvas.create_window((0, 0), anchor=NW, window=self)
|
|
|
|
# delegate text methods to frame
|
|
_methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys()
|
|
for method in _methods:
|
|
if any(["pack" in method, "grid" in method, "place" in method]):
|
|
setattr(self, method, getattr(self.container, method))
|
|
|
|
self.bind("<Configure>", self._resize_canvas)
|
|
self._canvas.bind("<Enter>", self._enable_scrolling, "+")
|
|
self._canvas.bind("<Leave>", self._disable_scrolling, "+")
|
|
|
|
if autohide:
|
|
self.autohide_scrollbar()
|
|
self.hide_scrollbars()
|
|
|
|
def _resize_canvas(self, *_):
|
|
"""Resize the canvas when frame is resized"""
|
|
self.update_idletasks()
|
|
self._canvas.config(scrollregion=self._canvas.bbox(ALL))
|
|
self._canvas.itemconfig(
|
|
self._wid,
|
|
width=self._canvas.winfo_width(),
|
|
height=self._canvas.winfo_height()
|
|
)
|
|
|
|
def hide_scrollbars(self, *_):
|
|
"""Hide the scrollbars."""
|
|
try:
|
|
self._vbar.lower(self._canvas)
|
|
except:
|
|
pass
|
|
try:
|
|
self._hbar.lower(self._canvas)
|
|
except:
|
|
pass
|
|
|
|
def show_scrollbars(self, *_):
|
|
"""Show the scrollbars."""
|
|
try:
|
|
self._vbar.lift(self._canvas)
|
|
except:
|
|
pass
|
|
try:
|
|
self._hbar.lift(self._canvas)
|
|
except:
|
|
pass
|
|
|
|
def autohide_scrollbar(self, *_):
|
|
"""Show the scrollbars when the mouse enters the widget frame,
|
|
and hide when it leaves the frame."""
|
|
self.container.bind("<Enter>", self.show_scrollbars)
|
|
self.container.bind("<Leave>", self.hide_scrollbars)
|
|
|
|
def _on_mousewheel(self, event):
|
|
if self._winsys.lower() == 'win32':
|
|
delta = -int(event.delta / 120)
|
|
elif self._winsys.lower() == 'aqua':
|
|
delta = -event.delta
|
|
elif event.num == 4:
|
|
delta = -1
|
|
elif event.num == 5:
|
|
delta = 1
|
|
self._canvas.yview_scroll(delta, UNITS)
|
|
|
|
def _enable_scrolling(self, *_):
|
|
"""Enable mousewheel scrolling on the frame and all of its
|
|
children."""
|
|
children = self.winfo_children()
|
|
for widget in [self, *children]:
|
|
if self._winsys.lower() == 'x11':
|
|
widget.bind("<Button-4>", self._on_mousewheel, "+")
|
|
widget.bind("<Button-5>", self._on_mousewheel, "+")
|
|
else:
|
|
widget.bind("<MouseWheel>", self._on_mousewheel, "+")
|
|
|
|
def _disable_scrolling(self, *_):
|
|
"""Disabled mousewheel scrolling on the frame and all of its
|
|
children."""
|
|
children = self.winfo_children()
|
|
for widget in [self, *children]:
|
|
if self._winsys.lower() == 'x11':
|
|
widget.unbind("<Button-4>")
|
|
widget.unbind("<Button-5>")
|
|
else:
|
|
widget.unbind("<MouseWheel>")
|
|
|
|
|