Daniel Huckson
Daniel Huckson

Reputation: 1247

Tkinter, how to adjust treeview indentation size and indicator arrow image

I have a treeview widget that you can zoom in and out the font size. The problem is the indentation is not proportional when zoomed in as to when it's normal. Is there a way to adjust the indentation width? Below are two images one is normal the other is zoomed. I also wonder if there is a way to change the indicator image?

UPDATE: I have solved the indentation problem thanks to jasonharper comments below. I still need to change the indicator arrow image can't seem to find any info on the web.

image1

image2

Upvotes: 3

Views: 2147

Answers (2)

WinEunuuchs2Unix
WinEunuuchs2Unix

Reputation: 1947

I took the accepted answer and tweaked the code. It is a bit shorter and more robust by calculating polygon coordinates automatically using the treeview row height as a parameter.

    ''' Create images for open, close and empty '''
    width = row_height-7

    im_open, im_close, im_empty = triangle_raw_images(width, 
                                                'black', 'LightGrey')
    img_open = ImageTk.PhotoImage(im_open)
    img_close = ImageTk.PhotoImage(im_close)
    img_empty = ImageTk.PhotoImage(im_empty)

    # custom indicator
    style.element_create('Treeitem.myindicator', 'image', img_close,
                    ('user1', '!user2', img_open), ('user2', img_empty), 
                    sticky='w', width=width)

    # replace Treeitem.indicator by custom one
    style.layout('Treeview.Item',
    [('Treeitem.padding',
      {'sticky': 'nswe',
       'children': [
            ('Treeitem.myindicator', {'side': 'left', 'sticky': ''}),
            ('Treeitem.image', {'side': 'left', 'sticky': ''}),
            ('Treeitem.focus', {'side': 'left', 'sticky': '','children':
                                    [('Treeitem.text', 
                                      {'side': 'left','sticky': ''})]
                               })
                  ]
       })])

Changes above from Accepted Answer:

  • row_height is same variable passed to treeview which is needed for HiDPI screens when larger font size is used.
  • variable names are used instead of pointers to variables making some lines shorter. For example ImageTk.PhotoImage(im_open) is used instead of ImageTk.PhotoImage(im_open, name='img_open', master=root)
  • Line wrapping to adhere to PEP standard 79 character line length
  • Indent closing brackets to hopefully make code more readable.

The heart of the change is a new function:

def triangle_raw_images(hgt, outc, fillc):

    from PIL import Image, ImageTk, ImageDraw       # Pillow image processing

    # For comments in code assume passed hgt = 21
    wid = hgt                                       # square image
    hgt_off = 4                                     # top & bottom whitespace
    wxy = ( 0, hgt_off, )                           # west point x=0, y=4
    exy = ( wid-1, hgt_off, )                       # east point x=20, y=4
    sxy = ( int((hgt-1)/2), hgt-hgt_off, )          # south point x=10, y=17
    retn_images = []                                # list of three images

    # custom indicator images
    im_open = Image.new('RGBA', (wid, hgt), (0, 0, 0, 0))
    im_empty = Image.new('RGBA', (wid, hgt), (0, 0, 0, 0))
    draw = ImageDraw.Draw(im_open)
    draw.polygon([ wxy, exy, sxy ], outline=outc, fill=fillc)
    im_close= im_open.rotate(90)

    return im_open, im_close, im_empty

Here is the end result:

mserve open close triangles.png

Upvotes: 1

j_4321
j_4321

Reputation: 16179

Indentation

As said in the comments by jasonharper and Daniel Huckson, the indentation can be changed with

style.configure('Treeview', indent=100)

Indicator image

The indicator image can be changed by creating a custom theme element and using it in replacement of the standard indicator in the Treeview.Item layout.

The key point here is to know the names of the states of an opened item ('user1') and of an item without children ('user2'), closed being the default state. Therefore the open indicator needs to be mapped with items in the state ('user1', '!user2') and the empty image to the items in state ('user2', ).

I used PIL to create dummy images for the indicator, but one can directly load custom images instead.

from PIL import Image, ImageTk, ImageDraw
import tkinter as tk
from tkinter import ttk

root = tk.Tk()

style = ttk.Style(root)

# custom indicator images
im_open = Image.new('RGBA', (15, 15), '#00000000')
im_empty = Image.new('RGBA', (15, 15), '#00000000')
draw = ImageDraw.Draw(im_open)
draw.polygon([(0, 4), (14, 4), (7, 11)], fill='yellow', outline='black')
im_close= im_open.rotate(90)

img_open = ImageTk.PhotoImage(im_open, name='img_open', master=root)
img_close = ImageTk.PhotoImage(im_close, name='img_close', master=root)
img_empty = ImageTk.PhotoImage(im_empty, name='img_empty', master=root)

# custom indicator
style.element_create('Treeitem.myindicator',
                     'image', 'img_close', ('user1', '!user2', 'img_open'), ('user2', 'img_empty'),
                     sticky='w', width=15)
# replace Treeitem.indicator by custom one
style.layout('Treeview.Item',
[('Treeitem.padding',
  {'sticky': 'nswe',
   'children': [('Treeitem.myindicator', {'side': 'left', 'sticky': ''}),
    ('Treeitem.image', {'side': 'left', 'sticky': ''}),
    ('Treeitem.focus',
     {'side': 'left',
      'sticky': '',
      'children': [('Treeitem.text', {'side': 'left', 'sticky': ''})]})]})]
)


tree = ttk.Treeview(root)
tree.pack()
tree.insert('', 'end', text='item 1', open=True)
tree.insert('', 'end', text='item 2')
tree.insert('I001', 'end', text='item 11', open=False)
tree.insert('I001', 'end', text='item 12', open=False)
tree.insert('I004', 'end', text='item 121', open=False)

root.mainloop()

screenshot

Upvotes: 9

Related Questions