Last active
May 12, 2024 12:35
-
-
Save thegamecracks/5595dad631ec50bdc021f945054e86fb to your computer and use it in GitHub Desktop.
A tkinter label with automatic text wrapping
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
from tkinter import Event, Tk | |
from tkinter.ttk import Frame, Label | |
if sys.platform == "win32": | |
from ctypes import windll | |
windll.shcore.SetProcessDpiAwareness(2) | |
LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce malesuada ipsum enim, feugiat venenatis sapien tempus et. Morbi auctor egestas metus vel faucibus. Nullam in nunc nisi. Maecenas eget sagittis ante. Sed ut turpis turpis. Morbi suscipit massa ac efficitur iaculis. Suspendisse risus nisi, tempor non pulvinar sed, vehicula a sapien. Pellentesque iaculis ligula sed sapien faucibus, eget mollis magna volutpat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi porttitor luctus tellus, nec tincidunt lacus. Nunc dui eros, laoreet sed pretium id, porttitor eget nunc." | |
class WrapLabel(Label): | |
"""A label that automatically wraps its text when resized. | |
When using this label, the geometry manager must be configured | |
to resize the widget in some way, otherwise the width will | |
default to the text's max length. | |
This can mean using ``.pack(fill="both", expand=True)``, or | |
``.place(relwidth=1, relheight=1)``, or by configuring grid | |
weights appropriately. | |
``minwidth=`` can be specified to prevent wrapping under | |
a certain width which can significantly improve performance | |
for longer lines of text. | |
As a limitation of the current implementation, this class can only | |
match its bounding box when used with the grid geometry manager. | |
When using other geometry managers like pack or place, WrapLabel will | |
always wrap to the window's entire width, regardless of the actual | |
bounding box allocated for them. As a result, the text may appear clipped. | |
If you use those geometry managers, it is recommended to grid / pack | |
each WrapLabel inside a frame so it can calculate the wrap length from | |
that frame's width. For example:: | |
# Instead of packing the label directly: | |
label = WrapLabel(root, text="Some long piece of text") | |
label.pack(fill="both", expand=True, padx=200, pady=200) | |
# Pack a container frame and have the label expand inside it: | |
frame = Frame(root) | |
frame.pack(fill="both", expand=True, padx=200, pady=200) | |
label = WrapLabel(frame, text="Some long piece of text") | |
label.pack(fill="both", expand=True) | |
In effect, the container frame serves as a way to compute the true | |
bounding box allocated by the geometry manager, helping the label | |
wrap to the correct width. | |
""" | |
def __init__(self, *args, minwidth: int = 1, **kwargs): | |
super().__init__(*args, **kwargs) | |
self.minwidth = minwidth | |
self.bind("<Configure>", self.__on_configure) | |
def __on_configure(self, event: Event): | |
width = max(self.minwidth, self.__get_width()) | |
if width != 1: # Prevent wrapping on initial configuration | |
self.configure(wraplength=width) | |
def __get_width(self) -> int: | |
if self.winfo_manager() == "grid": | |
# Wrap to the bounding box reserved for the label | |
# instead of the container's full width. | |
# Not doing this might lead to clipped text. | |
options = self.grid_info() | |
bbox = self.master.grid_bbox(options["column"], options["row"]) | |
if bbox is None: | |
return 1 | |
width = bbox[2] | |
if isinstance(options["padx"], int): | |
padx = options["padx"] * 2 | |
else: | |
padx = sum(options["padx"]) | |
return width - padx | |
else: | |
return self.master.winfo_width() | |
class MyFrame(Frame): | |
def __init__(self, parent): | |
super().__init__(parent) | |
# In this example, if both grid weights are removed, the label won't wrap. | |
self.grid_columnconfigure(1, weight=1) | |
for i in range(5): | |
self.grid_rowconfigure(i, weight=1) | |
col1 = Label(self, text=f"This is row {i+1}!") | |
col1.grid(row=i, column=0) | |
col2 = WrapLabel(self, text=LOREM_IPSUM, minwidth=600) | |
col2.grid(row=i, column=1) | |
app = Tk() | |
app.geometry("640x480") | |
app.grid_columnconfigure(0, weight=1) | |
app.grid_rowconfigure(1, weight=1) | |
label = WrapLabel(app, text=LOREM_IPSUM, minwidth=600) | |
label.grid(padx=10, pady=10, sticky="ew") | |
frame = MyFrame(app) | |
frame.grid(padx=10, pady=(0, 10), sticky="nesw") | |
app.mainloop() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment