3

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

Daniel Huckson
  • 1,157
  • 1
  • 13
  • 35
  • 1
    The indentation can apparently be controlled in the underlying Tcl/Tk environment via `ttk::style configure Treeview -indent ` - I'm not sure exactly how to translate that to Python, but maybe it will be a starting point. I didn't see any corresponding option for the arrow size. – jasonharper Apr 17 '20 at 21:29
  • Thank you that info help me, in Tkinter Python it's...style.configure('Treeview', indent=100). Now if I could figure out how to change the image I would be all set. :) – Daniel Huckson Apr 17 '20 at 21:36
  • I don't think there is an option to resize the indicator, but if you don't need to put images in your treeview, you could use them to replace the indicator, using tags to keep track of the opened/closed items. – j_4321 Apr 20 '20 at 08:04
  • @j_4321 yes you can change the indicator size, but I don't now how to change the indicator image. When you use the ttk alt theme it has a square box with a plus sign for the indicator while the clam theme uses a triangle. Here is the what you do to change the indicator size. font = tkfont.nametofont('TkTextFont') style.configure(".", indicatorsize=font.metrics('linespace')) – Daniel Huckson Apr 20 '20 at 12:44
  • @DanielHuckson thanks, good to know. If you want to change the indicator image, I guess you need to create your own theme. There are a few custom themes here: https://github.com/TkinterEP/ttkthemes/tree/master/ttkthemes/themes, but the code for the themes is mostly in tcl. – j_4321 Apr 20 '20 at 13:02
  • Great question. I'm surprised I'm the first person to upvote. – WinEunuuchs2Unix Nov 05 '20 at 00:38

2 Answers2

8

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

j_4321
  • 15,431
  • 3
  • 34
  • 61
  • 1
    Once again you have come up with the correct answer I needed thanks a lot. – Daniel Huckson Apr 20 '20 at 14:59
  • is it possible to add an image in tree view child items? – Keyur Vala Jul 18 '20 at 19:36
  • This is really cool. I must confess though down arrows and right arrows are counter intuitive to me. A big red circle with black plus sign or back negative sign makes more sense. Do you have links on where to read up on drawing these objects with Tk.PhotoImage()? – WinEunuuchs2Unix Jul 24 '20 at 22:28
  • Disregard last part of my comment. I've adopted to the "triangle culture" in my heading: `"Click on ▼ to collapse and ▶ to expand an Artist or Album"` – WinEunuuchs2Unix Jul 25 '20 at 17:46
  • I'm using these enlarged triangles and they are awesome. You can save time though by changing: `style = ttk.Style(root)` to `style = ttk.Style()` and removing the three references sequential parameters of `, master=root`. – WinEunuuchs2Unix Nov 05 '20 at 00:38
  • 1
    @WinEunuuchs2Unix The reason why I pass explicitly the master everywhere is because I often use the Jupyter QtConsole to test code with `%gui tk` to see immediately the changes without running mainloop. However this creates a hidden `Tk` instance which is used as the default root window instead of the one I want. – j_4321 Nov 05 '20 at 08:41
  • It took half a year to fit it into development cycle but I finally have triangles working perfectly for my setup. I've posted an answer below your accepted answer. An added benefit of `ttk.Style()` is all five treeviews inherit style elements from `toplevel`. Thank you for your gracious assistance! – WinEunuuchs2Unix Nov 08 '20 at 17:39
1

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

WinEunuuchs2Unix
  • 1,801
  • 1
  • 17
  • 34