Flame ttk::treeview widget lacks one important function for me - inplace editing of the contents. I've decided to try to implement it, though tktreectrl suits more for this task. The main problem is to track focus changes since there is no mechanism in ttk::treeview for this.
JE See also: <URL: http://www.flightlab.com/~joe/repos/themebag/inplace.tcl >.
DDG - 2020-04-14: See dgw::tvmixins for a snit::widget adaptor of the code below which simplifies addition to ttk::treeview widgets.
# BSD license
package require Tk 8.5
package provide xtreeview 1.2
namespace eval xtreeview {
# intercept all the events changing focus
bind XTreeview <<TreeviewSelect>> "+::xtreeview::checkFocus %W"
bind XTreeview <ButtonRelease-1> "+::xtreeview::checkFocus %W %x %y"
bind XTreeview <KeyRelease> "+::xtreeview::checkFocus %W"
bind XTreeview <ButtonPress-4> "+after idle ::xtreeview::updateWnds %W"
bind XTreeview <ButtonPress-5> "+after idle ::xtreeview::updateWnds %W"
bind XTreeview <MouseWheel> "+after idle ::xtreeview::updateWnds %W"
bind XTreeview <B1-Motion> {+if {$ttk::treeview::State(pressMode)=="resize"} { ::xtreeview::updateWnds %W }}
bind XTreeview <Configure> "+after idle ::xtreeview::updateWnds %W"
bind XTreeview <Home> {%W focus [lindex [%W children {}] 0]}
bind XTreeview <End> {%W focus [lindex [%W children {}] end]}
# images indicating sort order
image create bitmap ::xtreeview::arrow(0) -data {
#define arrowUp_width 7
#define arrowUp_height 4
static char arrowUp_bits[] = {
0x08, 0x1c, 0x3e, 0x7f
};
}
image create bitmap ::xtreeview::arrow(1) -data {
#define arrowDown_width 7
#define arrowDown_height 4
static char arrowDown_bits[] = {
0x7f, 0x3e, 0x1c, 0x08
};
}
image create bitmap ::xtreeview::arrowBlank -data {
#define arrowBlank_width 7
#define arrowBlank_height 4
static char arrowBlank_bits[] = {
0x00, 0x00, 0x00, 0x00
};
}
variable curfocus
# check, if focus has changed
proc checkFocus {w {X {}} {Y {}} } {
variable curfocus
if {![info exists curfocus($w)]} {
set changed 1
} elseif {$curfocus($w)!=[$w focus]} {
_clear $w $curfocus($w)
set changed 1
} else {
set changed 0
}
set newfocus [$w focus]
if {$changed} {
if {$newfocus!=""} {
_focus $w $newfocus
if {$X!=""} {
set col [$w identify column $X $Y]
if {$col!=""} {
if {$col!="#0"} {
set col [$w column $col -id]
}
}
catch {focus $w.$col}
}
}
set curfocus($w) $newfocus
updateWnds $w
}
}
# update inplace edit widgets positions
proc updateWnds {w} {
variable curfocus
if {![info exists curfocus($w)]} { return }
set item $curfocus($w)
if {$item==""} { return }
foreach col [concat [$w cget -columns] #0] {
set wnd $w.$col
if {[winfo exists $wnd]} {
set bbox [$w bbox $item $col]
if {$bbox==""} {
place forget $wnd
} else {
place $wnd -x [lindex $bbox 0] -y [lindex $bbox 1] -width [lindex $bbox 2] -height [lindex $bbox 3]
}
}
}
}
# remove all inplace edit widgets
proc _clear {w item} {
foreach col [concat [$w cget -columns] #0] {
set wnd $w.$col
if {[winfo exists $wnd]} {
destroy $wnd
}
}
}
# called when focus item has changed
proc _focus {w item} {
set cols [$w cget -displaycolumns]
if {$cols=="#all"} {
set cols [concat #0 [$w cget -columns]]
}
foreach col $cols {
event generate $w <<TreeviewInplaceEdit>> -data [list $col $item]
if {[winfo exists $w.$col]} {
bind $w.$col <Key-Tab> {focus [tk_focusNext %W]}
bind $w.$col <Shift-Key-Tab> {focus [tk_focusPrev %W]}
}
}
}
# hierarchical sorting procedure
proc _sorttree {tree col direction {isroot 1} {root {}} } {
if {$isroot} {
if {$col!="#0"} {
set col [$tree column $col -id]
}
set selection [$tree selection]
$tree selection remove $selection
set focus [$tree focus]
$tree focus {}
checkFocus $tree
}
# Build something we can sort
set data {}
if {$col=="#0"} {
foreach row [$tree children $root] {
lappend data [list [$tree item $row -text] $row]
}
} else {
foreach row [$tree children $root] {
lappend data [list [$tree set $row $col] $row]
}
}
if {$data!=""} {
set dir [expr {$direction ? "-decreasing" : "-increasing"}]
set r -1
# Now reshuffle the rows into the sorted order
foreach info [lsort -dictionary -index 0 $dir $data] {
$tree move [lindex $info 1] $root [incr r]
if {[$tree item [lindex $info 1] -open]} {
_sorttree $tree $col $direction 0 [lindex $info 1]
}
}
}
if {$isroot} {
# Switch the heading so that it will sort in the opposite direction
variable curfocus
catch {
eval [lindex [after info $curfocus($tree,sorticon)] 0]
after cancel $curfocus($tree,sorticon)
}
set curfocus($tree,sorticon) [after 3000 [list catch [list $tree heading $col -image ::xtreeview::arrowEmpty]]]
$tree heading $col -command [namespace code [list _sorttree $tree $col [expr {1-$direction}]]] -image ::xtreeview::arrow($direction)
$tree selection set $selection
$tree focus $focus
checkFocus $tree
}
}
# installs in-place edit bindings, adjusts tree header columns width, assigns column names, installs sorting handlers
proc _treeheaders {path {sort true} {treecolumnname {}} } {
set tags [bindtags $path]
if {[lsearch -exact $tags XTreeview]<0} {
bindtags $path [linsert $tags [lsearch -exact $tags Treeview]+1 XTreeview]
}
set font [::ttk::style lookup [$path cget -style] -font]
if {$font==""} {
set font TkTextFont
}
foreach col [$path cget -columns] {
if {$col!=""} {
if {$sort} {
$path heading $col -text $col -command [namespace code [list _sorttree $path $col 0]]
} else {
$path heading $col -text $col
}
$path column $col -width [font measure $font @@@@$col]
}
}
if {$treecolumnname!=""} {
$path heading #0 -text $treecolumnname
$path column #0 -width [font measure $font @@@@$treecolumnname]
if {$sort} {
$path heading #0 -command [namespace code [list _sorttree $path #0 0]]
}
}
}
# helper functions for inplace edit
proc _get_value {w column item} {
if {$column=="#0"} {
return [$w item $item -text]
} else {
return [$w set $item $column]
}
}
proc _set_value {w column item value} {
if {$column=="#0"} {
$w item $item -text $value
} else {
$w set $item $column $value
}
}
proc _update_value {w column item} {
variable curfocus
set value [_get_value $w $column $item]
set newvalue $curfocus($w,$column)
if {$value!=$newvalue} {
_set_value $w $column $item $newvalue
}
}
# these functions create widgets for in-place edit, use them in your in-place edit handler
proc _inplaceEntry {w column item} {
variable curfocus
set wnd $w.$column
ttk::entry $wnd -textvariable [namespace current]::curfocus($w,$column) -width 3
set curfocus($w,$column) [_get_value $w $column $item]
bind $wnd <Destroy> [namespace code [list _update_value $w $column $item]]
}
proc _inplaceEntryButton {w column item script} {
variable curfocus
set wnd $w.$column
ttk::frame $wnd
pack [ttk::entry $wnd.e -width 3 -textvariable [namespace current]::curfocus($w,$column)] -side left -fill x -expand true
pack [ttk::button $wnd.b -style Toolbutton -text "..." -command [string map [list %v [namespace current]::curfocus($w,$column)] $script]] -side left -fill x
set curfocus($w,$column) [_get_value $w $column $item]
bind $wnd <Destroy> [namespace code [list _update_value $w $column $item]]
}
proc _inplaceCheckbutton {w column item {onvalue 1} {offvalue 0} } {
variable curfocus
set wnd $w.$column
ttk::checkbutton $wnd -variable [namespace current]::curfocus($w,$column) -onvalue $onvalue -offvalue $offvalue
set curfocus($w,$column) [_get_value $w $column $item]
bind $wnd <Destroy> [namespace code [list _update_value $w $column $item]]
}
proc _inplaceList {w column item values} {
variable curfocus
set wnd $w.$column
ttk::combobox $wnd -textvariable [namespace current]::curfocus($w,$column) -values $values -state readonly
set curfocus($w,$column) [_get_value $w $column $item]
bind $wnd <Destroy> [namespace code [list _update_value $w $column $item]]
}
proc _inplaceSpinbox {w column item min max step} {
variable curfocus
set wnd $w.$column
spinbox $wnd -textvariable [namespace current]::curfocus($w,$column) -from $min -to $max -increment $step
set curfocus($w,$column) [_get_value $w $column $item]
bind $wnd <Destroy> [namespace code [list _update_value $w $column $item]]
}
}
if { $argv0 eq [info script] } {
catch {console show}
pack [ttk::treeview .tv -columns {bool int list} -show {tree headings} -selectmode extended -yscrollcommand {.sb set}] -fill both -expand true -side left
pack [ttk::scrollbar .sb -orient v -command {after idle ::xtreeview::updateWnds .tv;.tv yview}] -fill y -side left
xtreeview::_treeheaders .tv true text
.tv insert {} end -text {Sample text} -values {true 15 {Letter B}}
set i0 [.tv insert {} end -text {Sample text} -values {false 25 {Letter C}}]
.tv insert {} end -text {Sample text} -values {true 35 {Letter D}}
.tv insert $i0 end -text {Sample subitem} -values {true 45 {Letter A}}
for {set i 0} {$i<50} {incr i} {
.tv insert $i0 end -text "Subitem $i" -values [list true $i {Letter B}]
}
bind .tv <<TreeviewInplaceEdit>> {
if {[%W children [lindex %d 1]]==""} {
switch [lindex %d 0] {
{#0} { xtreeview::_inplaceEntry %W {*}%d }
{bool} { xtreeview::_inplaceCheckbutton %W {*}%d true false}
{int} { xtreeview::_inplaceSpinbox %W {*}%d 0 100 1 }
{list} { xtreeview::_inplaceList %W {*}%d {"Letter A" "Letter B" "Letter C" "Letter D"} }
}
} elseif {[lindex %d 0]=="list"} {
xtreeview::_inplaceEntryButton %W {*}%d {
set %%v "tree: %W, column,item=%d"
}
}
}
}