ttk.Treeview - how to get multi-line columns

105 Views Asked by At

I'm trying to implement a multi-line column within a tkinter treeview. In the below example, I'd like the text in the Description column to wrap across multiple lines. If the user drags the column borders to adjust its size, the text should wrap accordingly. Is this possible? Thanks!

Example

The code for the above example is shown below -

from tkinter import ttk

if __name__ == "__main__":
    root = tk.Tk()
    root.title("TreeView Example")

    tree = ttk.Treeview(root, columns=('Category', 'Product ID', 'Description', 'Quantity'))
    tree.heading('#0', text='')
    tree.heading('Category', anchor=tk.W, text='Category')
    tree.heading('Product ID', anchor=tk.W, text='Product ID')
    tree.heading('Description', anchor=tk.W, text='Description')

    tree.heading('Quantity', text='Quantity')
    tree.column('Quantity', anchor=tk.CENTER)

    tree.insert('', 'end', "Level 1", text="Level 1")
    tree.insert("Level 1", 'end', "Level 2", text="Level 2")
    tree.insert("Level 2", 'end', "Level 3", text="Level 3")

    tree.insert("Level 3", 'end', "line-1", text="")
    tree.set("line-1", 'Category', "CatA")
    tree.set("line-1", 'Product ID', "ProdA")
    tree.set("line-1", 'Description', "The quick brown fox jumped over the lazy dog. The quick brown fox " +
                  "jumped over the lazy dog. The quick brown fox jumped over the lazy dog. The quick brown fox " +
                  "jumped over the lazy dog. The quick brown fox jumped over the lazy dog. The quick brown fox " +
                  "jumped over the lazy dog. The end")
    tree.set("line-1", 'Quantity', "1")

    tree.pack(expand=True, fill='both')

    root.mainloop()
2

There are 2 best solutions below

0
Dendy Rosyadi On

Apply wraplength Style: style = ttk.Style() style.configure("Treeview", wraplength=300) # Adjust wraplength as needed

Insert \n Characters: long_description = "This is a long description\n

0
Variegatus Bradypus On

I did some search in wish of there being easy way to implement the feature you want, but it looks there is no simple way or feature supported from ttk side[1].

To make the feature work with ttk.TreeView, you might need to be able to properly force-wordwrap your text responsively upon any column-resizing event. You should find out how many newline character (\n) needs to be inserted between your description text based on current width of the column. For this you need to be able to implement, upon each column-resizing event:

  1. Determine how long (in pixels) your description text is[2].
  2. Determine current width (in pixels) of description column
  3. Use 1) and 2) to determine in which positions of your text \n needs to be inserted.
  4. Set newline character-inserted new text onto the cell.
  5. Row height needs to be changed also to show the whole wordwrapped text in the cell.

Below I added some crude version of code which I was able to implement. Basically, a function was defined to add newline characters at proper positions based on current description column width and length of the description text, and it was bound to <ButtonRelease-1> event as column-resizing always accompanies with this event (you need to click-drag and release the left mouse button).

It would be great if this helps in implementing the feature you want.

import tkinter as tk
from tkinter import ttk

from tkinter import font
import textwrap

def get_wrapped_text(text, length=50):
    return '\n'.join(textwrap.wrap(text, length))

def measure_text_length(text):
    font_config = font.Font(family='TkDefaultFont')
    return font_config.measure(text)

def measure_column_width(tv):
    return tv.column('Description', 'width')

def get_responsively_wrapped_text(text, tv):
    text_length_in_pxl = measure_text_length(text)
    col_width_in_pxl = measure_column_width(tv)
    num_text_chunks = int(text_length_in_pxl/col_width_in_pxl)
    num_text_len_per_line = int(len(text)/num_text_chunks)
    return get_wrapped_text(text, length=num_text_len_per_line)

line_1 = {"Category": "CatA",
          "Product ID": "ProdA",
          "Description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.",
          "Quantity": 1}

if __name__ == "__main__":
    root = tk.Tk()
    root.title("TreeView Example")

    style = ttk.Style()
    tree = ttk.Treeview(root, columns=('Category', 'Product ID', 'Description', 'Quantity'))
    unit_height = font.Font(family='TkDefaultFont').metrics('linespace')

    tree.heading('#0', text='')
    tree.heading('Category', anchor=tk.W, text='Category')
    tree.heading('Product ID', anchor=tk.W, text='Product ID')
    tree.heading('Description', anchor=tk.W, text='Description')

    tree.heading('Quantity', text='Quantity')
    tree.column('Quantity', anchor=tk.CENTER)

    tree.insert('', 'end', "Level 1", text="Level 1")
    tree.insert("Level 1", 'end', "Level 2", text="Level 2")
    tree.insert("Level 2", 'end', "Level 3", text="Level 3")

    tree.insert("Level 3", 'end', "line-1", text="")
    tree.set("line-1", 'Category', line_1['Category'])
    tree.set("line-1", 'Product ID', line_1['Product ID'])
    tree.set("line-1", 'Description', get_responsively_wrapped_text(line_1['Description'], tree))
    tree.set("line-1", 'Quantity', line_1['Quantity'])

    tree.bind("<ButtonRelease-1>",
              lambda event: responsive_wordwrap(line_1['Description'], tree))

    def responsive_wordwrap(text, tv):
        len = int(measure_text_length(text)/measure_column_width(tv))
        tree.set("line-1", 'Description', get_responsively_wrapped_text(line_1['Description'], tv))
        style.configure("Treeview", rowheight=unit_height*(len+2))

    tree.pack(expand=True, fill='both')

    root.mainloop()