Author: Brian O'Hagan <[email protected]>
State: Final
Type: Project
Tcl-Version: 9.1
Vote: Done
Vote-Summary: Accepted 5/0/0
Votes-For: AN,HO,KW,MC,SL
Votes-Against: None
Votes-Present: None
Created: 30-Nov-2025
Keywords: Tk TTk Treeview
Tk-Branch: enhanced_treeview
Forum: 00337adac1
Abstract
This tip proposes numerous functionality and performance improvements to the treeview widget. These address limitations of the treeview widget and improve its performance and usability. As part of these updates, numerous Ttk theme improvements have been made too.
Rationale
Although the treeview widget has sufficient functionality and performance to meet most user needs, there are a few things missing and a few areas where we can do better. Indeed it is much faster than non-C extensions such as tablelist, but several design decisions limit its performance and the missing functionality requires the users to implement work-arounds. This adds a burden to the users and limits its usefulness for non-Tcl users. This TIP attempts to address many of these performance improvements and missing functionality needs.
Specification
Demos
A new demo treeview.tcl has been added to the demos to show off many of the changes mentioned below.
Performance Enhancements
The base widget does not cache all useful info such that conversions/lookups are needed when getting the id for an item, last item in a list of children, etc. This leads to lower performance to convert these or find the values on the fly. To address these limitations, the item id is now stored in the Item struct as a Tcl object to avoid the repeated conversion to/from a TCL object since many methods operate on or return the item id. A pointer to the last child in the list of children was also added to the Item struct to make adding items to the end of the children an O(1) operation.
Auto Row Heights
One of the most commonly asked questions is how to set the row height for treeview widgets. Currently, the only way to set this is via the "ttk::style configure Treeview -rowheight" styling option. Another issue is there currently is no way to set or get the heading height. The only work-around is to use the identify method to query the widget to find where the items begin. Since the heading height is dynamically calculated based on the font and contents (ie newlines), this can be an issue for the bindings to determine the first visible item. This proposal fixes these issues and makes the "::ttk::setTreeviewRowHeight" function obsolete. It is replaced by an empty procedure for backwards compatibility.
The ttk::style configure Treeview command has been changed to support 2 options: -rowheight and -headingheight. If these options are set to 0 (the default), the height values will be auto calculated. If set to any value > 0, then auto sizing will be overridden and the heights set to the specified value. This also supports backwards compatibility for -rowheight.
The Treeview widget has been updated to add 2 new configuration options: -rowheight and -headingheight. These can be both set and queried. If the associated ttk:style option is set to 0, then these values are auto calculated using the theme and font. This is the default. The user can override these values by setting these options, but the value will be reset at the next theme or font change. If the associated ttk::style value is set to a value > 0, then that value will be used for these options. In this case, the values can't be overridden.
Bindings
The base treeview widget has a few useful bindings that allow for basic keyboard navigation and selection of items. However it is missing bindings to extend selections and navigate by page, row start/end, or to the edge of the view. It also does not use the virtual bindings introduced in Virtual Bindings and listed in library/tk.tcl to abstract the key sequence from the implementation. This TIP addresses these by using the virtual events and adding bindings to navigate between items and cells, extend the selection to the adjacent items or cells, move to first/last cell in row or item in column, extend the selection to the same, move to first/last visible row or column in view, extend selection to same, etc. Several of the handlers also needed to be updated to take advantage of the new capabilities in the following sections and increase performance. See the documentation or the library/ttk/treeview.tcl file for the full list of bindings.
Support for the <<Copy>> virtual event was also added to copy the contents of selected items and cells to the clipboard without the use of -exportselection and the PRIMARY selection.
Also added are cell <Enter> and <Leave> virtual events support for the tag bindings. This can be used for tool tips or other usages similar to how the <Motion> event is currently used.
Cell Editing
This TIP does not fully add in-place cell editing, but there is an example of how to do it in the new treeview demo. See the Out-of-Scope section for the rationale. What is included is some support to make it easier to implement. See the Focus section for the new <<TreeviewFocus>> virtual event. The bindings have been updated such that if an item or cell is double-clicked, when an item or cell has the focus and the Return key is pressed, or the <<Invoke>> virtual event is triggered, and if the user provided ::ttk::treeview::EditItem procedure exists, it will be called with 3 arguments, the widget path, item id, and column id. This can be used to trigger an in-line cell edit, to invoke the default method for an item or cell (i.e. open a file), etc. See the demo for an example of how this works.
Potentially reference implementations for cell editing using entry, combobox, spinbox, etc. with hooks for data validation could be included in an external library for people to use. See the treeview demo for an example implementation.
Child Items
An area for improvement is the ability to determine if an item has any children without the need to query each item with the children method and its associated performance impact. This TIP adds the haschildren method to return whether an item has children. It also adds a new size method to return the number of children and optionally the descendants of an item with the option to include hidden items. A new identifier method (and shortcut id) is added to return the id of an item at an index in the children of an item. Lastly an optional index argument has been added to the index method to use the item at that index. Together these avoid the need to use the children method to get the list of child items and find the one we want.
pathName haschildren item
Returns 1 if item has children, 0 if not.
pathName identifier item index
Returns the identifier of the item at index within item's list of children.
pathName index item ?index?
(Existing) Returns the numeric index of item within its parent's list of children or if index is specified, the index of the item at index in item's list of children.
pathName size ?-hidden? ?-recurse? item
Returns the count of children belonging to item. Use -hidden to include hidden items and -nohidden (default) to exclude them. Use -recurse to include descendants of item and -norecurse (default) to exclude them. If both -hidden and -recurse are used, descendants of closed items will be included.
Column Moves and Resize
The current bindings only supported column resizing and sorting if the -command option is specified. A missing need is for the users to be able to move columns (aka drag and drop them). This could be implemented by user custom code, but doing so could interfere with the column resize or sort bindings. This TIP changes the bindings to support all 3 functions (resize, sort, and move). Drag and drop of columns is accomplished by pressing Button 1 on the column heading, then dragging it over another column or separator, and dropping or releasing it. The heading states are now updated to reflect both the column to be moved and which column it will be moved before. Column #0 can't be moved.
Columns can also be auto sized to their contents via a <Double-Button-1> on the column separator. This works best when the column is set to -stretch 0. Use <Alt-Double-Button-1> (Windows and Unix) or <Option-Double-Button-1> (Mac) to auto size all columns to their contents.
Convenience Functions
My usage of the Treeview widget has led to the need for several convenience functions that make life easier. This includes a depth method to return the depth of any item in the widget and a visible method to return if an item is currently visible or not.
pathName depth item
Returns the number of levels between item and the root item.
pathName visible item
Returns whether item is visible or not. An item is visible if it is not detached, hidden, and all ancestors are open and not hidden.
Detached Items
When items are detached, only the item itself is marked as detached and its descendants are not. Thus they won't return the right response when queried with the detached method. This TIP adds an -all option to get all explicitly detached items and their descendants. It also changes the detached item check to check if any ancestors are detached and if so, return that.
pathName detached ?-all|item?
(Existing) Without any arguments, returns a list of all detached items, but not their descendants, in an arbitrary order. If item is provided, returns a Boolean value indicating whether item or an ancestor is detached or not. If -all is specified, returns a list of all the detached items and their descendants in an arbitrary order. The root item is never detached.
Events
Currently, the <<TreeviewOpen>>, <<TreeviewClose>>, and <<TreeviewFocus>> virtual events don't indicate to which item they apply. The user would need to use the focus method to figure it out. This TIP changes this so the detail or user data field now contains the item id or {} if none.
Focus and Select Modes
Treeview is patterned after the listbox widget and uses a few of the same -selectmode options. However, it is missing the single selection mode to allow the user to select a single item and the multiple selection mode to allow the user to select multiple items without changing the selection state of other items and without the need to use a key modifier such as Shift or Control. The multiple mode is very useful for checklists and other use cases when multiple selections are allowed. This TIP adds these modes and updates the binding handlers to support them.
The treeview widget supports the concept of a focus item. However, there is a bug in the current implementation which will cause a core dump when the focus is set to {}, since it is treated as the root node rather than clearing the focus. This has been fixed. Another issue is when cell selections were added in 'TIP 552', a similar cell focus was not also added. This TIP adds a cellfocus method too.
Also, the Treeview widget does not indicate which cell is the currently active cell. Tk widgets provided this capability via an "index active" query. Since the index method doesn't support this type of capability, the current method was added to return the currently active item and column.
pathName cellfocus ?cell?
Sets the focus to cell. Use {} to clear the cell focus. Without cell, returns the current focus cell id or {} if none.
pathName current
Returns the current item id and column id. The current item is the item/cell under the mouse pointer or {} if none.
pathName focus ?item?
(Existing) Sets the focus item to item. Use {} to clear focus item. Without item, returns the current focus item id or {} if none.
However, the bigger issue is the focus acts more as an anchor for selections rather than a true focus since there is no visual indicator to denote which item has the focus. For the single and browse select modes, this is less of an issue since only one item can be selected. It is an issue for extended selections since there is no way for the user to know which item has the focus when multiple items are selected. It is a big issue for none and multiple select modes since the selection and focus are separate concepts whereby the focus can change without changing the selection and vice versa. This limitation makes it difficult for the user to use the key bindings to navigate between items/cells or use the spacebar binding to select items/cells since they don't know which item has the focus until they select it. This TIP corrects this issue by adding a focus ring element with options to control it. Since it is a focus element, it is fully theme-able so the Aqua theme can have padding between the focus ring and selection box and to only show a focus ring when a cell has the focus so as not to take up space. The ttk::style configurable options are:
-focuscolor color
Specifies the color of the focus ring for tree entries. The default is #000000. Some themes may change the default focus color.
-focussolid boolean
Specifies whether to draw the focus ring as a dotbox (false) or solid ring (true). The default is true.
-focusthickness pixels
Specifies the focus ring thickness using any of the forms acceptable to Tk_GetPixels.
The current defaults are set for a file or item browser style focus ring:
ttk::style configure Row -focuscolor #000000 -focussolid 1 -focusthickness 0
ttk::style map Row -focusthickness [list focus 1]
To set the focus ring options for a spreadsheet-style focus ring use:
ttk::style configure Row -focuscolor #008000 -focussolid 1 -focusthickness 0
ttk::style map Row -focusthickness [list focus 2]
Also, the -selectmode bindings for the browse option have been updated to support B1-Motion where the selection follows the mouse like the Listbox widget.
The last change is related to detecting when the item or cell focus changes. This TIP adds a new virtual event <<TreeviewFocus>>, that is generated whenever the item or cell focus changes including when it is unset. This can be used to track when the current focus changes, for in-place cell edits to determine when the user has moved to another cell, etc.
Item Indices
The treeview widget is inconsistent in handling item indices. This TIP consolidates the index lookup process into a single function like how column ids are handled and uses the standard Tk API to support all common index forms. The index can use any of the following forms:
number
Specifies the numerical index of a child within its parent, where 0 corresponds to the first item.
end
Indicates the end of the list of children. For most commands this refers to the last child, but for a few methods such as index, insert, and move it refers to the item just after the last one.
first
Indicates the first child in the list of children.
last
Indicates the last child in the list of children.
end-n
Indicates the item at position n relative to the end.
m+n or m-n
Indicates the item at position n relative to index m.
Item Insertion and Movement
An interesting design choice of the Treeview widget is to insert and move items based on an index in the parent's children. If the user only needs to insert at the beginning or end of the children, this works ok. In this TIP with the previously mentioned addition of a pointer to the last child in the Item struct, this makes inserts and moves to the end an O(1) operation. However, if the user wants to insert/move before or after an existing item they need to lookup the index for the item to insert at, then call the insert/move function which needs to convert it back to an item. Why not skip this and allow for the user to provide the item? This TIP addresses this by adding before and after methods. The only limitation is items won't be able to use before or after as item ids.
pathName insert parent index ?-id id? options...
(Existing) Creates a new item, inserts it in parent's children at position index, and returns its identifier. See ITEM OPTIONS for the list of available options. Use the empty string {} for parent to create a new top-level item. See INDICES for valid forms of index. If index is negative or zero, the new item is inserted at the beginning; if index is greater than or equal to the current number of children, it is inserted at the end. If -id is specified, then id is used as the identifier of the item; id must not already exist in the tree. Otherwise, a new unique identifier is generated.
pathName insert before|after item ?-id id? options...
Creates a new item, inserts it into item's parent before or after item, and returns its identifier. See ITEM OPTIONS for the list of available options. If -id is specified, then id is used as the identifier of the item; id must not already exist in the tree. Otherwise, a new unique identifier is generated.
pathName move item parent index
(Existing) Moves item to position index in parent's list of children. If index is negative or zero, item is moved to the beginning; if greater than or equal to the number of children, it is moved to the end. See INDICES for valid forms of index. It is illegal to move an item under one of its descendants.
pathName move item before|after otherItem
Moves item to before or after otherItem in otherItem's parent's children. It is illegal to move an item under one of its descendants.
Item Navigation
The existing next and previous methods only return siblings of the specified item. This limits their usefulness when the user wants to move to the next or previous visible item which can be a sibling, child, parent, etc. Also, the next and previous methods do not honor the hidden attribute which confuses the user when a hidden item is the next/previous item so the focus/selection disappears. This TIP addresses these and other limitations with the new after and before methods. These methods are used to find the next or previous visible item, but also have options to control whether hidden or child/ancestor items can be chosen. The reason new methods were chosen is for backwards compatibility since after and before have different hidden and recurse defaults from next and previous. The rationale is the defaults should best reflect the common usage case, so the user only needs to override it when a different usage is needed.
All of the following commands use the -hidden, -nohidden, -recurse, and -norecurse options. Only the non default options are shown in the summary below (and other commands that use these options). The defaults were chosen based on the most common use case so as to lessen the need for the users to override them. The "-option/-nooption" style was chosen to shorten the user input but otherwise there is no preference over the "-option boolean" style.
pathName after ?-hidden? ?-norecurse? item
Returns the identifier of the item after item in the current view. It can be the first child, next sibling, next sibling of an ancestor, or {} for none. Use -hidden to include hidden items and -nohidden (default) to exclude them. Use -recurse to include descendants/ancestors of item (default) and -norecurse to exclude them. If both -hidden and -recurse are used, descendants of closed items will be included. The next method is equivalent to
pathName after -hidden -norecurse item.pathName before ?-hidden? ?-norecurse? item
Returns the identifier of the item before item in the current view. It can be the previous sibling, parent of item, or {} for none. Use -hidden to include hidden items and -nohidden (default) to exclude them. Use -recurse to include descendants/ancestors of item (default) and -norecurse to exclude them. If both -hidden and -recurse are used, descendants of closed items will be included. The prev method is equivalent to
pathName before -hidden -norecurse item.
In a similar manner the current widget lacks the ability to select a range of visible items at differing depths as needed by the extend bindings. This TIP adds a range method to return all of the visible items between the first and last (inclusive) items.
pathName range ?-hidden? ?-norecurse? first last
Returns a list of items from first through last, inclusive. Use -hidden to include hidden items and -nohidden (default) to exclude them. Use -recurse to include descendants/ancestors of item (default) and -norecurse to exclude them. If both -hidden and -recurse are used, descendants of closed items will be included.
Multi Item Operations
The treeview widget uses item attributes to track hidden and collapsed/open states. Thus to change multiple items, the user must call the item method for every item to be changed. This can be a performance impact when changing a large number of items. This TIP addresses this limitation by adding new collapse and expand methods to do bulk changes to the open/close state of items. It also adds hide and unhide methods to do the same for the hidden state.
pathName collapse ?-recurse? itemList
Sets all of the items in itemList to closed state. Same as "pathName item item -open 0" for each item. With the -recurse option, will also close all of their descendants. Use {} for root item.
pathName expand ?-recurse? itemList
Set all of the items in itemList to open state. Same as "pathName item item -open 1" for each item. With the -recurse option, will also open all of their descendants. Use {} for root item.
pathName hide ?-recurse? itemList
Hide all of the items in itemList and all child items. Same as "pathName item item -hidden 1" for each item. With the -recurse option, will also hide all of their descendants. Use {} for root item.
pathName unhide ?-recurse? itemList
Unhide all of the items in itemList. Same as "pathName item item -hidden 0" for each item. With the -recurse option, will also unhide all of their descendants. Use {} for root item.
See Items
Treeview supports the see method to scroll the widget in the vertical direction so an item is visible. This TIP adds a optional column argument to also scroll in the horizontal direction to see a column. This can also be used to see a cell when used with the expand operator.
pathName see item ?column?
(Existing) Ensure that item is visible by setting all of item's ancestors to -open true, and scroll the widget in the vertical direction, if necessary, so that item is within the visible portion of the tree. If column is specified, then the widget is scrolled in the horizontal direction, if necessary, so that column is within the visible portion of the tree. Only non-title columns and rows will be scrolled.
pathName see {*}cell
Same as previous, but uses expand operator to convert cell into item and column components.
Search
A key missing function is a built-in search function. Besides the obvious performance improvement and avoiding the need for everyone to reinvent the wheel to write Tcl code to manually search by getting each item and calling lsearch, this makes it much easier for the users to interface with the widget. This TIP implements the search method and uses similar options to the lsearch command and the text widget search function but tailored to the treeview widget needs. This implementation uses the public TCL APIs for string matching.
pathName search parent ?-option ...? pattern
Searches within parent's children for pattern and returns the identifier of the first match. Valid options are:
Comparisons
-ascii
The values are to be compared to pattern as Unicode strings (the name is for backward-compatibility reasons.) This is the default.
-dictionary
The values are to be compared to pattern using dictionary-style comparisons. For search, this is the same as -ascii but also sets -nocase. NOTE: This means I18n and I018N are not the same.
-integer
The values are to be compared to pattern as integers. Empty values will be ignored, but non-numeric values will generate an error.
-nocase
Comparisons are to be performed in a case-insensitive manner. Only used with -ascii and -dictionary options for the -exact, -glob, and -regexp matching.
-real
The values are to be compared to pattern as floating-point values. Empty values will be ignored, but non-numeric values will generate an error.
-unicode
Alias for -ascii.
Direction
-backwards
The search is to be performed backwards starting from -start item or cell.
-forwards
The search is to be performed forwards starting from -start item or cell. This is the default.
Pattern Matching
-exact
Pattern is a literal string that is compared for exact equality against each cell value. Used with -ascii and -dictionary options. This is the default.
-glob
Pattern is a glob-style pattern which is matched against each cell value using the same rules as the string match command. Used with -ascii and -dictionary options.
-regexp
Pattern is an advanced regular expression that is matched against each cell value using the rules described in the re_syntax reference page. Used with -ascii and -dictionary options.
Search Modifiers
-columns columns
Limit search to only the columns specified in columns list. The default is to search in all displayed columns. See COLUMN IDENTIFIERS for valid forms of column.
-hidden
Search within hidden and closed items too. The default is to exclude hidden and descendants of closed items from the search.
-recurse
Search within all descendants of parent. The default is to only search within the children of parent.
-start item|cell
Start the search at item or if the -cell option is also specified, then start the search at cell. The default start item and cell when used with the -forwards option is the first child of parent and the first display column. For the -backwards option, the default is the last child of parent (or descendant when used with -recurse) and the last display column. The item or cell must be a descendant of parent. When resuming a search, use the before or after methods to get the previous or next item to start the search at. All other options should be the same as the initial search including parent.
-stop item|cell
Stop the search at item or if the -cell option is also specified, then stop the search at cell. The default stop item and cell when used with the -forwards option is the last child of parent (or descendant when used with -recurse) and the last display column. For the -backwards option, the default is the first child of parent and the first display column. When used with the -wraparound option, the default is just before the start item or cell (in other words, the start item or cell won't be searched again). This means, to wrap-around and stop at the -start item, only use the -start and -wraparound options. If the -start and -stop options both use the same item, then only that item will be searched. The item or cell must be a descendant of parent.
-wraparound
When used with the -start option, enables the search to wrap-round to the first/last item in parent to continue to the -stop item or cell.
Result Modifiers
-all
Return the identifiers of all of the items that match pattern rather than only the first one.
-cell
Return cell ids instead of item ids. The default is to return item ids. Also switches the -start and -stop options to use cell values.
-not
Negates the match, so only the items that do not match pattern, will be returned.
Another option considered, but not included is the -operator option. This would allow the user to specify a math operator to be used with searching integer and real values. For example pathName search -all -column Value -integer -operator "<" 42, would return the ids for all items with a value of less than 42 in column Value. Also, the in or ni operators could be used to check for elements in a list. For example pathName search -all -column Color -operator "in" [list red green blue], would return the ids for all items with red, green, or blue values in the Color column. This effectively could be used as a filter, which is a common function for spreadsheets. This was left out since only one filter could be applied and with the limitations of the expression operators.
Selection
The selection and cellselection methods are key functions of the widget since they allow the user to select items or cells to interact with. However, the current implementation is limited in that the only way to know if items/cells are selected is to get the selected items/cells list. This has obvious performance impacts when a large number of items/cells are selected. To avoid this, this TIP adds a present method to return a boolean with whether there are selected items/cells or not, a size method to return a count of the number of selected items/cells, and an includes method (with alias has) to return whether a list of items/cells are selected or not.
pathName cellselection has|includes cellList
Returns 1 if cell selection includes all of the cells in cellList or 0 if any are not selected.
pathName cellselection present
Returns 1 if there are selected cells or 0 if none.
pathName cellselection size
Returns the count of selected cells.
pathName selection has|includes itemList
Returns 1 if selection includes all items in itemList or 0 if any are not selected.
pathName selection present
Returns 1 if there are selected items or 0 if none.
pathName selection size
Returns the count of selected items.
Another limitation is the need to have the complete list of items before calling the selection add, remove, set, or toggle functions. Often times when a user is selecting items they may want to select a range by defining an anchor at the focus item/cell and then the end item/cell. This is how the extended select mode bindings work. The current binding handlers would then need to enumerate the list of items/cells and pass it to the selection or cellselection methods. This TIP adds the ability to select a range of items to the add, remove, set, and toggle methods similar to what cellselection already supports from TIP 552. It also adds options to control whether no hidden and no descendants are also included. In addition, these functions were rewritten for better performance. The key bindings are set to skip hidden items, but will include visible descendants. This TIP also adds an anchor method for the binding handlers to use to mark the selection anchor item or cell.
pathName cellselection add|remove|set|toggle ?-nohidden? ?-norecurse? cellFirst cellLast
(Existing) Add|remove|set|toggle the cells within the rectangle defined by cellFirst and cellLast to/from the cell selection. With -nohidden, hidden cells will be excluded and with -norecurse, cell in descendant items will be excluded.
pathName cellselection anchor ?cell?
Sets the cell selection anchor to cell. The anchor is used as the cellFirst for the selection extend bindings. Without cell, the current cell selection anchor is returned. Use empty string {} to unset the anchor.
pathName selection add|remove|set|toggle ?-nohidden? ?-norecurse? first last
(Existing) Add|remove|set|toggle the items from first to last (inclusive) to/from the selection. Use -hidden to include hidden items (default) and -nohidden to exclude them. Use -recurse to include descendants and ancestors (default) and -norecurse to exclude them. If -hidden and -recurse are used, all descendants of closed items will be included.
pathName selection anchor ?item?
Sets the selection anchor to item. The anchor is used as the first item for the selection extend bindings. Without item, the current selection anchor is returned. Use empty string {} to unset the anchor.
These changes also enable a new interactive extending of the item and cell selection by pressing B1 on and item or cell, then dragging the selection to expand it. This only works for multiple and extended select modes.
Set method
The set method is very handy for setting and getting the value of an item for a column. However it is limited in that it won't work for the #0 column or allow the user to set multiple values at one time. This TIP addresses this by adding #0 to the columns that can be set or get and allowing multiple column values to be set.
pathName set item ?column? ?value? ?column value ...?
(Existing) Set or get item column values. With no column or value arguments, returns a dictionary of all column/value pairs. With only the column argument, returns the current value for column. With one or more column and value pair arguments, sets the value of each column to the associated value. Use column #0 for the tree column. See COLUMN IDENTIFIERS for valid forms of column.
Sort
Another key missing function is a built-in sort function. In addition to the obvious performance improvement by skipping the need to get the widget contents, put into a list, call lsort, extract the item id from the sorted list, and then update the widget children to reflect the new order, this makes it much easier for the users to interface with the widget. This TIP implements the sort method and uses similar options to lsort but tailored to the treeview widget needs. See below for the performance comparisons. This implementation borrows much of the lsort code since there are no public TCL APIs for sorting.
pathName sort parent ?-option ...?
Sort the children of parent. Valid options are:
-ascii
The values are to be sorted in Unicode character code order (the name is for backward-compatibility reasons.) This is the default.
-dictionary
The values are to be sorted using dictionary-style comparisons. This is the same as -ascii except (a) case is ignored except as a tie-breaker and (b) if two strings contain embedded numbers, the numbers compare as integers, not characters. For example, in -dictionary mode, "bigBoy" sorts between "bigbang" and "bigboy", and "x10y" sorts between "x9y" and "x11y". Overrides the -nocase option.
-integer
The values are to be sorted as integers. Non-integer values will generate an error.
-nocase
Comparisons are to be performed in a case-insensitive manner. Only used with -ascii option.
-real
The values are to be sorted as floating-point values. Non-numeric values will generate an error.
-unicode
Alias for -ascii.
-command command
Use command as a comparison command. To compare two elements, evaluate a Tcl script consisting of command with the two elements appended as additional arguments. The script should return an integer less than, equal to, or greater than zero if the first element is to be considered less than, equal to, or greater than the second, respectively.
-increasing
Sort the list in increasing order (“smallest” items first). This is the default.
-decreasing
Sort the list in decreasing order (“largest” items first).
-column column
Sort based on values in column. See also COLUMN IDENTIFIERS. The default is the first display column (#0 if tree in -show or #1 if not).
-ignoreempty
Used with -integer or -real to ignore empty entries. Normally these would trigger an error and abort the sort. Empty entries will be sorted first for -increasing and last for -decreasing.
-recurse
Sort all descendants of parent. The default is to only sort the children of parent.
Another function considered but not included is support for locale-based collate sorting. The C lib does provide the strcoll command, but it isn't kept current for all locales. A better option is to use the International Components for Unicode (ICU) library which TCL/Tk kinda supports (several more TIPs in work for 9.1). In any case this should likely be added to the lsort command first, then here. These are the proposed sort options if it were to be implemented here:
-collate
The values are to be sorted in the -locale option specified order.
-locale name
Use name as the locale for collate sorts. If not specified or set to {}, will default to the current locale for the LC_COLLATE category. Will also set the sort mode to collate.
Sort Performance Comparisons
The following are the performance comparisons for treeview sorting using lsort vs the new built-in sort option. All tests used 10 iterations to generate the average microseconds value. The test cases use random values, so only values on the same line can be directly compared as they used the same values. The table below shows a built-in sort performance improvement of 10% to 500% over the lsort-based approach.
TCL/Tk 8.6.14
Sort using lsort command (microseconds)
| # Items | ASCII | Dictionary | Integer | Real |
|---|---|---|---|---|
| 1 | 4.8 | 9.5 | 8.8 | 8.5 |
| 10 | 12.3 | 14.6 | 16.0 | 17.9 |
| 100 | 58.1 | 81.2 | 58.0 | 57.9 |
| 1,000 | 493.6 | 854.3 | 513.5 | 447.2 |
| 10,000 | 6,174.7 | 12,471.6 | 7,068.5 | 7,086.5 |
| 100,000 | 116,739.1 | 216,388.1 | 131,833.2 | 131,983.0 |
| 1,000,000 | 1,913,023.6 | 3,379,958.6 | 2,130,087.6 | 2,121,458.4 |
| 10,000,000 | 31,142,539.8 | 52,089,703.7 | 37,374,689.5 | 37,737,530.6 |
TCL/Tk 9.0.2
Sort using lsort command (microseconds)
| # Items | ASCII | Dictionary | Integer | Real |
|---|---|---|---|---|
| 1 | 7.3 | 7.4 | 9.4 | 9.7 |
| 10 | 13.2 | 15.9 | 16.3 | 15.3 |
| 100 | 69.3 | 79.6 | 73.0 | 66.9 |
| 1,000 | 574.2 | 850.7 | 542.6 | 592.0 |
| 10,000 | 8,114.0 | 13,123.9 | 7,334.9 | 8,068.7 |
| 100,000 | 134,758.6 | 205,805.2 | 129,762.2 | 132,376.2 |
| 1,000,000 | 2,081,523.7 | 3,280,798.4 | 2,111,405.7 | 2,115,432.9 |
| 10,000,000 | 32,273,038.9 | 49,774,938.8 | 36,779,597.7 | 37,018,572.1 |
Sort using built-in treeview sort (microseconds)
| # Items | ASCII | Dictionary | Integer | Real |
|---|---|---|---|---|
| 1 | 5.9 | 4.3 | 4.2 | 4.7 |
| 10 | 7.1 | 7.6 | 6.4 | 5.3 |
| 100 | 18.4 | 37.1 | 14.9 | 15.2 |
| 1,000 | 183.6 | 513.9 | 101.5 | 117.5 |
| 10,000 | 2,278.3 | 8,363.5 | 1,592.4 | 1,593.6 |
| 100,000 | 37,645.5 | 137,373.5 | 31,237.9 | 32,378.0 |
| 1,000,000 | 536,106.2 | 2,007,398.3 | 466,426.5 | 482,019.3 |
| 10,000,000 | 7,934,092.6 | 29,230,320.3 | 7,030,695.2 | 7,282,455.3 |
States
The treeview widget currently supports states for headings and the widget as a whole. This TIP adds support for item states thus making items individually theme-able like in Windows Explorer or the Mac Finder. Also, when the widget loses focus the !focus state will be converted into the background state so theming can set to correct background colors. This was done to avoid interference with the item and cell use of the focus state. The supported dynamic states are active, background, disabled, focus, and selected.
For Headings, the Mac Aqua theme supports showing sort indicators when the user1 state is combined with the selected state (ascending sort) or the alternate state to (descending sort). This TIP expands this functionality to the Windows and generic themes too. This is in addition to the -image option, not in place of it like in the Aqua theme. This TIP also adds use of the pressed state to denote which column is to be moved and the active state to indicate the drop column for column moves.
For items, a new state option has been added to the item subcommand options to set and query an item's state. It works the same as the headings state function. Most states are supported except the focus, selected, and open states which can only be set or cleared via the corresponding subcommands. Notably this can be used to disable an item (user specified) or set the active state for when the cursor is over an item (built-in).
Cells also support the active state, and the binding handlers have been updated to honor the disabled state and not allow focus or selection operations. The readonly state is supported too, but there are no default bindings to edit the Treeview data, so it is the same as the normal state.
Styles
This TIP also includes a new CheckTreeview style that uses check buttons to denote which items are selected. Checklist or Checked Treeview widgets are common in other GUI toolkits. Alternatively, this could be renamed CheckboxTreeview. Both names seem to be used by other toolkits, so there isn't a clear preference. This style works well with the new multiple select mode option. Also, this style should only be used with -selecttype item. Many thanks to Csaba who helped with these improvements.
Theme Improvements
Numerous improvements to the Ttk themes have also been incorporated to make Treeview look and act more like Windows Explorer and Mac's Finder. Most of the open issues for the Windows and Mac Aqua themes have been fixed, but a few minor issues remain to be worked.
The Treeview changes include the open/close and sort indicators are now chevron style arrows in the default and clam themes. The alternate and classic themes retain the triangle arrow indicators. The Mac Aqua theme has been cleaned up, while the Windows XP and Vista themes now use native theming indicators for items, headings, etc.
The new chevron style arrows have also been ported to the combobox, spinbox, and menubutton widgets in the default and clam themes. This required numerous changes in the default arrow sizes too.
Numerous other Windows and Mac Aqua theme improvements have been made to support Windows states, proper scaling when the tk scaling command is used, etc. The scaling functions also needed to be updated to fix all of the scaling issues. See the treeview demo to try this out. Many thanks to Csaba who helped with these improvements.
Bug fixes, Test Suite, etc.
This TIP fixes numerous bugs in the current implementation and also adds an extensive test suite to thoroughly test all of the existing and new functionality. This includes checking for the root item when it is not valid, error conditions, etc. All functionality in the previous test suite has been included in the new test file, but the test cases and numbering have been changed to better organize the tests. The old test file has been renamed treeview_old.test and the test cases were updated to be compatible with the changes. It can be deleted when it is no longer needed.
Out-of-Scope Items
The above is not a complete list of everything that people would want treeview to do. Probably the biggest function not implemented is in-place editing of cell values. See the Cell Editing section for what is implemented. A full implementation depends on how the user wants to use it. The included changes should be adequate for users to implement basic single-line cell editing using the entry, spinbox, combobox, etc. widgets when using treeview as a directory/file browser, email client, database viewer, or other entry-based needs where only limited cell navigation is needed and multi-line cells are not needed. Database record editors are usually better served with a multi-entry dialog to view large records and to edit them with callbacks to validate the data. For spreadsheet or cell-based usage needs, TkTable 3.0 is usually a better choice since it offers in-place cell editing with seamless navigation between cells, selecting text in cells, multi-line cells, easy row resizing, etc. This is not possible in the Treeview widget without extensive rework. I'm sure there are others who may disagree with this assessment and want for a built-in cell edit capability. That is left for a future TIP and implementor.
Implementation
See repo: Enhanced Treeview for the Tk 9.0 compatible version and in the Tk repo branch enhanced_treeview for the Tk 9.1 compatible version.
Discussion
The TIP 740 forum thread is set up for discussion of the TIP. Please submit comments there.
Copyright
This document has been placed in the public domain.
