Populate a ttk::treeview given a nested dict

Takes a nested dict, made here using makeNestedDict, and populates a ttk::treeview

The nested dict is of the form {node1 {node1Child1 {child1Leaf1 {} child1Leaf2 {}}} node2 {node2Child1 ...}}, where

  • Nodes with children are keys pointing to values which are themselves keys pointing to more values, and
  • Leaves are nodes whose children are empty {}

An example of a function which makes a nested dict follows.

# builds a flat list of files/directories one level under $path
proc ls {path} {
  # catch permissions errors
  if {[catch {glob -nocomplain -tails -directory $path *} result]} {
    set result {}
  }
  return $result
}

# recursively builds a nested dict of all files/directories under $path
proc ls-R {path} {
  set result {}
  foreach item [ls $path] {
    if {[file isdirectory [file join $path $item]]} {
      dict set result $item [ls-R [file join $path $item]]
    } else {
      dict set result $item {}
    }
  }
  return $result
}

# makes the filesystem tree
proc makeNestedDict {path} {
  return [ls-R $path]
}

Here is the code to populate a ttk::treeview given a nested dict

# returns bool: is the element a leaf?
proc isLeaf element {
  return [expr {$element == {}}]
}

# fills given ttk::treeview $tree with contents of nested dict $dic
proc populateTreeView {tree parent dic} {
  foreach {k v} $dic {
    set text [file rootname $k]
    $tree insert $parent end -id [list {*}$parent $k] -text $text
    if {![isLeaf $v]} {
      populateTreeView $tree [list {*}$parent $k] $v
    }
  }
}

proc makeTreeView { } {
  ttk::treeview .tree
  .tree configure -columns "items"
  .tree configure -height 20
  .tree column "items" -width 200 -anchor center
  set nestedDict [makeNestedDict /var]
  populateTreeView .tree {} $nestedDict
  pack .tree
}

makeTreeView

rgf 2011-07-18 : Interesting implementation - very small amount of code to show nested directory listings but ... the command 'makeNestedDict' needs a path argument in proc 'makeTreeView', and the procedures need to be declared before 'makeTreeView' can be evaluated.

daedra 2011-07-31 : Fixed.

aspect: Nice. I tidied up some little things in the above implementation but I like its brevity. Below is an alternative to populateTreeView that is maybe a little more Tclish and perhaps a bit harder to understand at first:

# for every node in a tree, call $cmd
#  first argument to $cmd is a list describing the path to this node
#  second argument is the name of this node's child, or {} for leaves
proc walkTree {path tree cmd} {
    foreach k [dict keys $tree] {
        {*}$cmd $path $k
        set v [dict get $tree $k]
        if {$v == {}} {
            {*}$cmd [list {*}$path $k] {}
        } else {
            walkTree [list {*}$path $k] $v $cmd
        }
    }
}

# the path is passed as a list
proc treeviewInsert {tree path name} {
    $tree insert $path end -id [list {*}$path $name] -text $name
}

Using the above you would replace the populateTreeView call with:

 walkTree {} $nestedDict {treeviewInsert .tree}

Another thing that would be nice is to differentiate files from empty directories in the result of ls-R, but I can't decide how to fit this into the nested dictionary data structure best.