On 10/4/22 3:44 PM, GeorgeB wrote:
>
> Basically what I would like to do is, based on the above example, upon selection in treeview get all items under Node0 i.e. A,B,C, as well as the single selection of "E" and dump it into a list.
>
> Node0 **<-- User Selection
> +--- A
> +--- B
> +--- C
> +--- D
> Node1
> Node2
> +--- E **<-- User Selection
> +--- F
> +--- G
> +--- H
>
> Thanks in advance.
The following code does this: if you click on a directory node, it will
automatically add or delete all of its sub-file nodes. If any of its
file nodes were already selected, it will de-select, and otherwise
select, all sub-files. It does not go deeper than that level for
sub-folders because it just seemed weird. It works as before when you
click on file nodes.
Right mouse click will clean the current selection.
package req Tk
proc reset_sel {tw} {
$tw item {} -values {}
$tw selection set {}
}
proc manage_sel {tw} {
set sel [$tw item {} -values]
set new [$tw focus]
if {$new eq ""} {
# no selection
return
} elseif {[$tw set $new type] eq "processedDirectory"} {
set add [list]
set rem [list]
foreach item [$tw children $new] {
if {[$tw set $item type] eq "processedDirectory"} {
# sub-folders are excluded from auto-selection
} elseif {$item in $sel} {
# a file is selected already
# de-select everything
set rem [$tw children $new]
set add [list]
break
} else {
lappend add $item
}
}
if {[llength $rem] == 0} {
# folder has no files selected
# add all the sub-files
lappend sel {*}$add
} else {
# folder or one of its files was already selected
set sel [lremove $sel $rem]
}
} elseif {[$tw set $new type] eq "file"} {
if {$new in $sel} {
set sel [lremove $sel $new]
} else {
lappend sel $new
}
} else {
}
$tw item {} -values $sel
$tw selection set {}
$tw selection set $sel
}
proc populateRoots {tree ROOT SHOW_TYPES} {
populateTree $tree $SHOW_TYPES \
[$tree insert {} end -text "Network File" \
-values [list $ROOT directory] \
-open 1]
}
## Code to populate a node of the tree
proc populateTree {tree SHOW_TYPES {node {}}} {
if {$node eq ""} { set node [$tree focus] }
if {[$tree set $node type] ne "directory"} {
return
}
set path [$tree set $node fullpath]
$tree delete [$tree children $node]
### display so that folders comes before files at the same level
### files and folders are displayed in alphabetical order
set folders [lsort -dictionary [glob -nocomplain -type d -dir \
$path *]]
set files [lsort -dictionary [glob -nocomplain -type f -dir \
$path *]]
foreach f [list {*}$folders {*}$files] {
set type [file type $f]
if {$type eq "directory"} {
### this is a folder
### Make it so that this node is openable
set id [$tree insert $node end -text [file tail $f] \
-values [list $f $type] \
-open 0]
$tree insert $id 0 -text dummy ;# a dummy
$tree item $id -text [file tail $f]/
### this folder has sub-folders
### process them automatically
### so the user does not have to click on each one
### to find where actual files are
populateTree $tree $SHOW_TYPES $id
} elseif {($type eq "file") && \
([file extension $f] in $SHOW_TYPES)} {
### get basic info on the file to display
### Format the file size nicely
set size [file size $f]
if {$size >= 1024*1024*1024} {
set size [format %.1f\ GB [expr {$size/1024/1024/1024.}]]
} elseif {$size >= 1024*1024} {
set size [format %.1f\ MB [expr {$size/1024/1024.}]]
} elseif {$size >= 1024} {
set size [format %.1f\ kB [expr {$size/1024.}]]
} else {
append size " bytes"
}
### format the date and time nicely
set ttime [file mtime $f]
set fdate [clock format $ttime -format "%Y-%m-%d"]
set ftime [clock format $ttime -format "%H-%M-%S"]
### display the file in the tree
set id [$tree insert $node end \
-tags SHOW_FILE \
-text [file tail $f] \
-values [list $f $type $size $fdate $ftime]]
### only open folders that are not empty
### THIS FOLDER HAS A DISPLAYABLE FILE IN IT
### AUTOMATICALLY OPEN IT
$tree set $id size $size
$tree item [$tree parent $id] -open 1 -tags FOLDER_HAS_ITEM
} else {
# a file type that will not be shown
}
}
# Stop this code from rerunning on the current node
$tree set $node type processedDirectory
}
# ## Create the tree and set it up
proc display_tree {tw ROOT SHOW_TYPES} {
catch { destroy $tw.tree }
catch { destroy $tw.vsb }
catch { destroy $tw.hsb }
catch { destroy $tw.dummy }
ttk::treeview $tw.tree -columns {fullpath type size date time} \
-displaycolumns {size date time} \
-yscroll "$tw.vsb set" -xscroll "$tw.hsb set"
ttk::scrollbar $tw.vsb -orient vertical -command "$tw.tree yview"
ttk::scrollbar $tw.hsb -orient horizontal -command "$tw.tree xview"
$tw.tree heading \#0 -text "Directory Structure"
$tw.tree heading size -text "File Size" -anchor w
$tw.tree heading date -text "Last Mod Date" -anchor w
$tw.tree heading time -text "Last Mod Time" -anchor w
$tw.tree column size -stretch 1 -width 20
$tw.tree column date -stretch 1 -width 20
$tw.tree column time -stretch 1 -width 20
### you can make files and non-empty folders more visible
### by playing around with colors and fonts as below
### change or remove as needed
$tw.tree tag config SHOW_FILE -foreground blue -font bold
$tw.tree tag config FOLDER_HAS_ITEM -foreground red -font bold
populateRoots $tw.tree $ROOT $SHOW_TYPES
bind $tw.tree <<TreeviewOpen>> [list populateTree %W $SHOW_TYPES]
# ## Arrange the tree and its scrollbars in the toplevel
lower [ttk::frame $tw.dummy]
pack $tw.dummy -fill both -expand 1
bind $tw.tree <Button-1> {}
bind $tw.tree <Button-3> {reset_sel %W}
bind $tw.tree <ButtonRelease-1> {manage_sel %W}
grid $tw.tree $tw.vsb -sticky nsew -in $tw.dummy
grid $tw.hsb -sticky nsew -in $tw.dummy
grid columnconfigure $tw.dummy 0 -weight 1
grid rowconfigure $tw.dummy 0 -weight 1
}
proc run_test {} {
wm iconify .
### first window
set ROOT_1 "C:/Dev"
set SHOW_TYPES [list .ini .txt .docx]
catch {destroy .t1 }
toplevel .t1
wm title .t1 $ROOT_1
wm geom .t1 500x400
display_tree .t1 $ROOT_1 $SHOW_TYPES
### second window with different files to show
set ROOT_2 "C:/Windows/INF"
set SHOW_TYPES [list .ini .csv]
catch {destroy .t2 }
toplevel .t2
wm title .t2 $ROOT_2
wm geom .t2 800x600
display_tree .t2 $ROOT_2 $SHOW_TYPES
}
### start here
run_test