ttk.Treeview - Can't change row height

16,288

Solution 1

Perhaps like you, I expected lines to expand as necessary. But I confirmed the problem with the code below, with the solution (the two style lines) omitted. When I could not find the solution here and the corresponding Style page, I googled and found this. Scroll down to Emiliano's answer, and some of the following (there is also an indent option).

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.geometry('500x200')
style = ttk.Style(root)
style.configure('Treeview', rowheight=40)  #SOLUTION
tree = ttk.Treeview(root)
tree.insert('', 0, text='Line 1 of many XXX', tags='T')
tree.insert('', 1, text='Line 2 of many XXX', tags='T')
tree.insert('', 2, text='Line 3 of many XXX', tags='T')
tree.column('#0', stretch=True)
tree.tag_configure('T', font='Arial 20')
tree.pack(fill='x')

The above, with the answer omitted, is an example of minimal code that exhibits the problem. This is the sort of thing to post!

EDIT 1:

To make the Calendar widget properly importable and usable in another application, it should use a custom style, so its style does not affect any other treeviews in the app.

style.configure('Calendar.Treeview', rowheight=40)
tree = ttk.Treeview(root, style='Calendar.Treeview')

EDIT 2:

I am just learning about ttk styles myself. To answer your relief question, I went to this style doc and tried the following in Idle's Shell after running the above, with the two modifications in Edit 1.

>>> style.layout('Calendar.Treeview')
[('Treeview.field', {'sticky': 'nswe', 'children': [('Treeview.padding',
{'sticky': 'nswe', 'children': [('Treeview.treearea', {'sticky': 'nswe'})]})], 'border': '1'})]
>>> style.element_options('Calendar.Treeview.border')
('-relief',)
>>> style.lookup('Calendar.Treeview.border', 'relief')
''
>>> style.configure('Calendar.Treeview.border', relief='raised')
{}

I do not see any border nor did not see any effect of the setting. Perhaps relief applies to borders between columns. I don't know. (Note that changing rowheight is immediately available, so configuration is 'live'.)

Solution 2

I found that the Tkinter Font object has a metrics() method, that gives its height as "linespace". That allows the row height to be scaled dynamically:

try:
    from tkinter.font import Font
    from tkinter.ttk import Style, Treeview
    from tkinter import *         
except:
    from tkFont import Font
    from ttk import Style, Treeview
    from Tkinter import *

font=Font(family='Arial', size=20)
font.metrics()
#output: {'ascent': 31, 'descent': 7, 'linespace': 38, 'fixed': 0}

With that, you can get the font height with:

font.metrics()['linespace']
#output: 38

Then use it to set the rowheight in your Treeview widget:

fontheight=font.metrics()['linespace']

style=Style()
style.configure('Calendar.Treeview', font=font, rowheight=fontheight)   

tree=Treeview(style='Calendar.Treeview')

Changing the font object parameters comfortably updates the Treeview widget, but the rowheight doesn't get updated, and needs to be redone. So for example, scaling the font size with a keyboard shortcut may look like this:

def scaleup(event):
    font['size']+=1
    style.configure('Calendar.Treeview', rowheight=font.metrics()['linespace'])

def scaledown(event):
    font['size']-=1
    style.configure('Calendar.Treeview', rowheight=font.metrics()['linespace'])

tree.bind('<Control-equal>', scaleup)
tree.bind('<Control-minus>', scaledown)

I actually wanted to do the same with Control-MouseWheel, but didn't figure out the behavior yet (Would be glad to hear how that works).

Hope this comes handy.

Solution 3

How can I change the height of the rows in the treeview as in prevoius attempts font size can be increased but the row height does not increase with font size. - STILL AWAITING HELP

In case you are still awaiting help on this one, there is a way to change the row height, though that google groups thread says that it isn't officially supported by Tk:

#apply any configuration options
ttk.Style().configure('Treeview',rowheight=30)
Share:
16,288
MistUnleashed
Author by

MistUnleashed

Learning to code! Languages i have some knowledge of: XHTML HTML5 CSS &amp; CSS3 PHP jQuery Javascript Java Python Visual Basic

Updated on September 15, 2022

Comments

  • MistUnleashed
    MistUnleashed over 1 year

    I'm using ttkcalendar.py which can be found in this link.

    I've adapted it for use in Python 3.3

    Basically what i'm trying to do is enter this calendar widget into my Tkinter application, which works fine and there's no problems there.

    The problems I wish to overcome are:

    1. How do I change the font size of the calendar (Month,Days & Dates) - Completed
    2. How do I change the selected date so it goes bold. - Completed
    3. How can I change the height of the rows in the treeview as in prevoius attempts font size can be increased but the row height does not increase with font size. - STILL AWAITING HELP

    Thanks in advance.

    EDIT 1:

    Find below the code for the whole program:

    import calendar
    import tkinter as Tkinter
    import tkinter.font as tkFont
    from tkinter import ttk                             #Imports ttk Module
    
    def get_calendar(locale, fwday):
        #Instantiate Proper Calendar Class
        if locale is None:
            return calendar.TextCalendar(fwday)
        else:
            return calendar.LocaleTextCalendar(fwday, locale)
    
    class Calendar(ttk.Frame):
        datetime = calendar.datetime.datetime
        timedelta = calendar.datetime.timedelta
    
        def __init__(self, master=None, **kw):
            """
            WIDGET-SPECIFIC OPTIONS
    
                locale, firstweekday, year, month, selectbackground,
                selectforeground
            """
            #Remove Custom Options From kw BEFORE Initializating ttk.Frame
            fwday = kw.pop('firstweekday', calendar.MONDAY)
            year = kw.pop('year', self.datetime.now().year)
            month = kw.pop('month', self.datetime.now().month)
            locale = kw.pop('locale', None)
            sel_bg = kw.pop('selectbackground', '#EEEEEE')
            sel_fg = kw.pop('selectforeground', '#B6333B')
    
            self._date = self.datetime(year, month, 1)
            self._selection = None                          #No Date Selected
    
            ttk.Frame.__init__(self, master, **kw)
    
            self._cal = get_calendar(locale, fwday)
    
            self.__setup_styles()                           #Creates Custom Styles
            self.__place_widgets()                          #Pack/Grid Used Widgets
            self.__config_calendar()                        #Adjust Calendar Columns & Setup Tags
            #Configure a Canvas & Proper Bindings for Selecting Dates
            self.__setup_selection(sel_bg, sel_fg)
    
            #Store Item ids - Used for Insertion Later On
            self._items = [self._calendar.insert('', 'end', values='')
                                for _ in range(6)]
            #Insert Dates in the Currently Empty Calendar
            self._build_calendar()
    
            #Set Minimal Size for Widget
            self._calendar.bind('<Map>', self.__minsize)
    
        def __setitem__(self, item, value):
            if item in ('year', 'month'):
                raise AttributeError("attribute '%s' is not writeable" % item)
            elif item == 'selectbackground':
                self._canvas['background'] = value
            elif item == 'selectforeground':
                self._canvas.itemconfigure(self._canvas.text, item=value)
            else:
                ttk.Frame.__setitem__(self, item, value)
    
        def __getitem__(self, item):
            if item in ('year', 'month'):
                return getattr(self._date, item)
            elif item == 'selectbackground':
                return self._canvas['background']
            elif item == 'selectforeground':
                return self._canvas.itemcget(self._canvas.text, 'fill')
            else:
                r = ttk.tclobjs_to_py({item: ttk.Frame.__getitem__(self, item)})
                return r[item]
    
        def __setup_styles(self):
            #CUSTOM ttk Styles
            style = ttk.Style(self.master)
            arrow_layout = lambda dir: (
                [('Button.focus', {'children': [('Button.%sarrow' % dir, None)]})]
            )
            style.layout('L.TButton', arrow_layout('left'))
            style.layout('R.TButton', arrow_layout('right'))
    
        def __place_widgets(self):
            #Header Frame & Widgets
            hframe = ttk.Frame(self)
            lbtn = ttk.Button(hframe, style='L.TButton', command=self._prev_month)
            rbtn = ttk.Button(hframe, style='R.TButton', command=self._next_month)
            self._header = ttk.Label(hframe, width=15, anchor='center', font='Arial 20')
            #Main Calendar
            self._calendar = ttk.Treeview(show='', selectmode='none', height='6')
            #Pack The Widgets
            hframe.pack(in_=self, side='top', pady=4, anchor='center')
            lbtn.grid(in_=hframe)
            self._header.grid(in_=hframe, column=1, row=0, padx=12)
            rbtn.grid(in_=hframe, column=2, row=0)
            self._calendar.pack(in_=self, expand=1, fill='both', side='bottom')
    
        def __config_calendar(self):
            cols = self._cal.formatweekheader(3).split()
            self._calendar['columns'] = cols
            self._calendar.tag_configure('header', background='grey90', font='Arial 20')
            self._calendar.insert('', 'end', values=cols, tag=('header', 'dayFont'))
            #Change Font of dayFont TAG
            self._calendar.tag_configure('dayFont', font='Arial 20')
            #Adjust Column Widths
            font = tkFont.Font(size=20)
            maxwidth = max(font.measure(col) for col in cols)
            for col in cols:
                self._calendar.column(col, width=maxwidth, minwidth=maxwidth, anchor='c')
    
        def __setup_selection(self, sel_bg, sel_fg):
            self._font = tkFont.Font()
            canvas = Tkinter.Canvas(self._calendar, background=sel_bg, borderwidth=0, highlightthickness=0)
            self._canvas = canvas
            canvas.text = canvas.create_text(0, 0, fill=sel_fg, anchor='c')
    
            canvas.bind('<ButtonPress-1>', lambda evt: canvas.place_forget())
            self._calendar.bind('<Configure>', lambda evt: canvas.place_forget())
            self._calendar.bind('<ButtonPress-1>', self._pressed)
    
        def __minsize(self, evt):
            width, height = self._calendar.master.geometry().split('x')
            height = height[:height.index('+')]
            self._calendar.master.minsize(width, height)
    
        def _build_calendar(self):
            year, month = self._date.year, self._date.month
    
            #Update Header Text (Month, YEAR)
            header = self._cal.formatmonthname(year, month, 0)
            self._header['text'] = header.title()
    
            #Update Calendar Showing Dates
            cal = self._cal.monthdayscalendar(year, month)
    
            for indx, item in enumerate(self._items):
                week = cal[indx] if indx < len(cal) else []
                fmt_week = [('%02d' % day) if day else '' for day in week]
                self._calendar.item(item, values=fmt_week, tag='bodyFont')
                self._calendar.tag_configure('bodyFont', font='Arial 10')        
    
    
        def _show_selection(self, text, bbox): #SELECTION FONT
            """Configure canvas for a new selection."""
            x, y, width, height = bbox
    
            textw = self._font.measure(text)
    
            canvas = self._canvas
            canvas.configure(width=width, height=height)
            canvas.coords(canvas.text, width - textw, height / 2 - 1)
            canvas.itemconfigure(canvas.text, text=text, font='Arial 15 bold')
            canvas.place(in_=self._calendar, x=x, y=y)
    
        #Callbacks
    
        def _pressed(self, evt):
            """Clicked somewhere in the calendar."""
            x, y, widget = evt.x, evt.y, evt.widget
            item = widget.identify_row(y)
            column = widget.identify_column(x)
    
            if not column or not item in self._items:       #Clicked in the Weekdays Row or Just Outside The Columns
                return
    
            item_values = widget.item(item)['values']
            if not len(item_values):                        #Row is Empty For This Month
                return
    
            text = item_values[int(column[1]) - 1]
            if not text:                                    #Date is Empty
                return
    
            bbox = widget.bbox(item, column)
            if not bbox:                                    #Calendar is not Visible Yet
                return
    
            #Update & Then Show Selection
            text = '%02d' % text
            self._selection = (text, item, column)
            self._show_selection(text, bbox)
    
        def _prev_month(self):
            """Updated calendar to show the previous month."""
            self._canvas.place_forget()
    
            self._date = self._date - self.timedelta(days=1)
            self._date = self.datetime(self._date.year, self._date.month, 1)
            #Reconstruct Calendar
            self._build_calendar()
    
        def _next_month(self):
            """Update calendar to show the next month."""
            self._canvas.place_forget()
    
            year, month = self._date.year, self._date.month
            self._date = self._date + self.timedelta(
                days=calendar.monthrange(year, month)[1] + 1)
            self._date = self.datetime(self._date.year, self._date.month, 1)
    
            self._build_calendar() 
    
        #Properties
        #-----------------------------------------------------
    
        @property
        def selection(self):
            """Return a datetime representing the current selected date."""
            if not self._selection:
                return None
    
            year, month = self._date.year, self._date.month
            return self.datetime(year, month, int(self._selection[0]))
    
    #----------------------------------
    

    EDIT 2:

    How can I change the relief of the Treeview?