TCL Treeview issues

227 views
Skip to first unread message

GeorgeB

unread,
Sep 18, 2022, 3:28:10 PMSep 18
to
Hello,

Newbie here, and I'm having some difficulties with treeview in TCL.

I'm working off a demo example from Active State TCL.

1) I wondered if someone could help, I get the following error but don't know what to do to fix or how to fix.

Error is invalid command error ERROR: invalid command name "populateTree"

command bound to event: "populateTree .fc.tv.tree [.fc.tv.tree focus]"

Code is below

2) How do I get a selection from multiple items treeview to be saved in a variable.


# temp dir to mimic Network dir
set ::dir "C:/Dev"

proc populateRoots {tree} {
populateTree $tree [$tree insert {} end -text "Network File" \
-values [list $::dir directory]]
}

## Code to populate a node of the tree
proc populateTree {tree node} {
if {[$tree set $node type] ne "directory"} {
return
}
set path [$tree set $node fullpath]
$tree delete [$tree children $node]
foreach f [lsort -dictionary [glob -nocomplain -dir $path *]] {
set type [file type $f]
set id [$tree insert $node end -text [file tail $f] \
-values [list $f $type]]
if {$type eq "directory"} {
## Make it so that this node is openable
$tree insert $id 0 -text dummy ;# a dummy
$tree item $id -text [file tail $f]/
} elseif {$type eq "file"} {
set size [file size $f]
set ttime [file mtime $f]
## Format the file size nicely
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"
}
$tree set $id size $size
}
}

# Stop this code from rerunning on the current node
$tree set $node type processedDirectory
}

# ## Create the tree and set it up


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"
$tw.tree column size -stretch 0 -width 70
populateRoots $tw.tree
bind $tw.tree <<TreeviewOpen>> {populateTree %W [%W focus]}

# ## Arrange the tree and its scrollbars in the toplevel
lower [ttk::frame $tw.dummy]
pack $tw.dummy -fill both -expand 1
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


Thank you in advance

George

Ralf Fassel

unread,
Sep 19, 2022, 6:01:32 AMSep 19
to
* GeorgeB <george.b...@gmail.com>
| Hello,
>
| Newbie here, and I'm having some difficulties with treeview in TCL.
>
| I'm working off a demo example from Active State TCL.
>
| 1) I wondered if someone could help, I get the following error but don't know what to do to fix or how to fix.
>
| Error is invalid command error ERROR: invalid command name "populateTree"
>
| command bound to event: "populateTree .fc.tv.tree [.fc.tv.tree focus]"
>
| Code is below

The code you posted works for me (I don't get that error, and from the
code logic, I could not understand how populateTree could not be
defined), so there probably is something else wrong. Are you using
additional namespace code around that example?

| ttk::treeview $tw.tree -columns {fullpath type size date time} -displaycolumns {size date time} \
| -yscroll "$tw.vsb set" -xscroll "$tw.hsb set"

Note that it is always preferable to use [list] to build up callback
commands:

... -yscroll [list $tw.vsb set] -xscroll [list $tw.hsb set]

In your example it depends on what $tw is set to whether it makes a
difference, but if more complex arguments are passed in the callbacks,
the [list] approach is the better one.

| 2) How do I get a selection from multiple items treeview to be saved in a variable.

It seems that [pathname selection] returns the currently selected items,
so just store that in a variable. But since that seems obvious maybe
that is not your question...

HTH
R'

GeorgeB

unread,
Sep 19, 2022, 7:05:48 AMSep 19
to
Hi Ralf,

Thank you for you response.

Please pardon as I'm very new to TCL.

Yes there is an additional namespace around this code, and I have tried multiple examples all seem to give me an invalid name error.

Is there a way to define "populateTree" so that the error is not generated? or should the tree have it's own name space?

2) How do I get a selection from multiple items treeview to be saved in a variable.
> It seems that [pathname selection] returns the currently selected items,
> so just store that in a variable. But since that seems obvious maybe
> that is not your question...

I've tried [pathname selection], it doesn't print anything in console

Alan Grunwald

unread,
Sep 19, 2022, 7:45:29 AMSep 19
to
The error message says that the system doesn't know anything about the
command populateTree. This is a little surprising since you have defined
it in the code you have posted.

Probably what has happened is that when you executed "proc populateTree
{tree node} {...}" you were inside a namespace, so you ended up defining
<someNamespace>::populateTree; since the code associated with the event
operates in the global namespace, it is looking for ::populateTree and
can't find it.

You can ensure that you define populateTree in the global namespace by
changing your [proc] statment to "proc ::populateTree {tree node}...".
If you are interested to know where your current populateTree command is
ending up, you could add "puts stdout [namespace current]" immediately
before the [proc] statement, which willwrite the current namespace to
standard output when the script is first sourced.

Good luck sorting out your script, I hope this proves helpful,

Alan

GeorgeB

unread,
Sep 19, 2022, 8:28:35 AMSep 19
to
Alan, you genius....you have sorted the problem, thank you so much.
It also sorted one other issue I was having populating the nodes from a folder in network location...Thank you, I was going round in circles!.

Now I just need to figure out how to print selection (single or multiple) into console. It always comes up blank...

If you any tips or suggestion I'd greatly appreciate.

Ralf Fassel

unread,
Sep 19, 2022, 8:40:47 AMSep 19
to
* GeorgeB <george.b...@gmail.com>
| Hi Ralf,
>
| Thank you for you response.
>
| Please pardon as I'm very new to TCL.
>
| Yes there is an additional namespace around this code, and I have
| tried multiple examples all seem to give me an invalid name error.

Usually it is best to post a _complete_ example which actually shows the
error. The code you posted did not use an additional namespace, so it
does not show the error.

If you define a proc inside a namespace, you need to specify that
namespace in callbacks, since those callbacks usually are invoked
outside of that namespace when they run later.

Instead of
bind $tw.tree <<TreeviewOpen>> {populateTree %W [%W focus]}
try
bind $tw.tree <<TreeviewOpen>> [namespace code [list populateTree %W [%W focus]]

The [namespace code ...] captures the current namespace for the
asynchronous invocation of the command later.

| Is there a way to define "populateTree" so that the error is not
| generated? or should the tree have it's own name space?

It's not how "populateTree" is defined, but how it is to be called.
If you define it inside a namespace, you need to specify that namespace
when calling it.

Note that procs which are defined in the same namespace can call other
procs in that namespace without specifying that namespace explicitly,
but for callbacks like [bind] you need to specfiy the namespace via
[namespace code ...].

| 2) How do I get a selection from multiple items treeview to be saved in a variable.
| > It seems that [pathname selection] returns the currently selected items,
| > so just store that in a variable. But since that seems obvious maybe
| > that is not your question...
>
| I've tried [pathname selection], it doesn't print anything in console

You need to replace 'pathname' by $tw.tree, then select something in the
treeview, then issue that command:

set selected [$tw.tree selection]
puts $selected
=> I004 I005 I006

HTH
R'

Rich

unread,
Sep 19, 2022, 9:49:38 AMSep 19
to
GeorgeB <george.b...@gmail.com> wrote:
> On Monday, September 19, 2022 at 12:45:29 PM UTC+1, Alan Grunwald wrote:
>> On 18/09/2022 20:28, GeorgeB wrote:
>> > Error is invalid command error ERROR: invalid command name "populateTree"
>> >
>>
>> Probably what has happened is that when you executed "proc populateTree
>> {tree node} {...}" you were inside a namespace, so you ended up defining
>> <someNamespace>::populateTree; since the code associated with the event
>> operates in the global namespace, it is looking for ::populateTree and
>> can't find it.
>>
>> You can ensure that you define populateTree in the global namespace by
>> changing your [proc] statment to "proc ::populateTree {tree node}...".
>> If you are interested to know where your current populateTree command is
>> ending up, you could add "puts stdout [namespace current]" immediately
>> before the [proc] statement, which willwrite the current namespace to
>> standard output when the script is first sourced.
>
> Alan, you genius....you have sorted the problem, thank you so much.

> If you any tips or suggestion I'd greatly appreciate.

If you do wish to use namespaces to structure your code, and if you do
not wish to pollute the global namespace (implied yes given you appear
to be using namespaces), then you should use Ralf's suggestion of
utilizing 'namespace code' to generate the callback script bound to the
binding event.

That will do the work of setting up the binding to call the proc inside
the namespace where it is defined, without you having to add lots of
global procs or having to manually handle the insertion of the
namespace pathname for each binding.

Ralf Fassel

unread,
Sep 19, 2022, 11:27:58 AMSep 19
to
* Ralf Fassel <ral...@gmx.de>
| Instead of
| bind $tw.tree <<TreeviewOpen>> {populateTree %W [%W focus]}
| try
| bind $tw.tree <<TreeviewOpen>> [namespace code [list populateTree %W [%W focus]]

Nah, that's nonsense, since the [%W focus] needs to run later when the
binding is triggered, not when it is defined.

Easiest solution seems to move the "%W focus" into populateTree:

proc populateTree {tree node} {
...
=>
proc populateTree {tree {node ""}} {
if {$node eq ""} {
set node [$tree focus]
}
...

and then just do

bind $tw.tree <<TreeviewOpen>> [namespace code [list populateTree %W]]

HTH
R'

GeorgeB

unread,
Sep 19, 2022, 11:32:45 AMSep 19
to
Hi Rich and Ralf,

Thank you for the suggestion,

I've replaced the binding with
bind $tw.tv.tree <<TreeviewOpen>> {::FG::populateTree %W [%W focus]}

However if I use bind $tw.tree <<TreeviewOpen>> [namespace code [list populateTree %W [%W focus]]], where code = container name I get error
ERROR: invalid command name "%W"
in "%W focus"


> | I've tried [pathname selection], it doesn't print anything in console
> You need to replace 'pathname' by $tw.tree, then select something in the
> treeview, then issue that command:
>
> set selected [$tw.tree selection]
> puts $selected
> => I004 I005 I006

I'd done exactly as you had suggested...but realised I forgot to select items in the tree....doh!!!

I'm assuming I will have to map the id e.g. I004 etc to actual value in the tree?
i.e. if id = I004 and I004 relates to file config.txt in tree then I'll have to map it somehow to each other .

Is it possible to selected a node and get all values underneath it? On selection of the node is just gives me one single ID?
e.g.
Network file
|- Folder 1
|- file0.txt
|- file1.txt
|- file2.txt
|- file3.txt

I would like to get all items underneath Folder 1 by selecting Folder 1

GeorgeB

unread,
Sep 19, 2022, 11:39:48 AMSep 19
to
Ralf
I just saw this as I posted my last reply 5mins ago...this seems to work better.

Rich

unread,
Sep 19, 2022, 5:03:28 PMSep 19
to
GeorgeB <george.b...@gmail.com> wrote:
> However if I use bind $tw.tree <<TreeviewOpen>> [namespace code [list populateTree %W [%W focus]]]
> , where code = container name I get error
> ERROR: invalid command name "%W"
> in "%W focus"

That is because if you look at the above, you are asking tcl to execute
a command named %W (note the []') at the time you define the binding.

There is no command named %W defined (unless you defined a proc by that
name), so you get "invalid command name "%W".

You would be best to follow Ralf's suggestion and perform the 'focus'
inside the proc, and just pass the window name that Tk inserts for %W
when the binding fires into the proc, then use that window name to
perform the focus. Note, are you sure you want to do a focus here?

>> | I've tried [pathname selection], it doesn't print anything in
>> console You need to replace 'pathname' by $tw.tree, then select
>> something in the treeview, then issue that command:
>>
>> set selected [$tw.tree selection]
>> puts $selected
>> => I004 I005 I006
>
> I'd done exactly as you had suggested...but realised I forgot to
> select items in the tree....doh!!!
>
> I'm assuming I will have to map the id e.g. I004 etc to actual value
> in the tree? i.e. if id = I004 and I004 relates to file config.txt
> in tree then I'll have to map it somehow to each other .

You get the internal ID's of the selected items. That is all Tk knows.
If you need to relate those ID's to something else then you have to
build that relationship map yourself.

> Is it possible to selected a node and get all values underneath it?
> On selection of the node is just gives me one single ID?

You can get a list of the children of a given node with the 'children'
subcommand (see the treeview man page).

If you wish to recurse down the tree, then you have to write your own
proc to walk down the tree and get all the grand/greatgrand/etc.
children.

saitology9

unread,
Sep 19, 2022, 6:39:30 PMSep 19
to
On 9/19/22 5:03 PM, Rich wrote:
> GeorgeB <george.b...@gmail.com> wrote:
>> However if I use bind $tw.tree <<TreeviewOpen>> [namespace code [list populateTree %W [%W focus]]]
>> , where code = container name I get error
>> ERROR: invalid command name "%W"
>> in "%W focus"
>
> That is because if you look at the above, you are asking tcl to execute
> a command named %W (note the []') at the time you define the binding.
>
> There is no command named %W defined (unless you defined a proc by that
> name), so you get "invalid command name "%W".
>
> You would be best to follow Ralf's suggestion and perform the 'focus'

I agree Ralf's suggestion is the best approach here. Nevertheless, it
might be useful to OP to know that they could fix the error above by
properly constructing the callback like so:


bind $tw.tree <<TreeviewOpen>> \
[namespace code [list populateTree %W [list %W focus]]]


et4

unread,
Sep 19, 2022, 10:56:19 PMSep 19
to
On 9/19/2022 5:28 AM, GeorgeB wrote:

>

> Now I just need to figure out how to print selection (single or multiple) into console. It always comes up blank...
>
> If you any tips or suggestion I'd greatly appreciate.
>

Welcome new tcl programmer.

Might I suggest "The Tcl Programming Language" book, both paper and pdf.

https://www.magicsplat.com/ttpl/index.html

I recommend both. They are an exact match; searching in the pdf can take you to the exact same page in the book.

The index and TOC in the pdf are hyperlinked. You can download the TOC for free on the above page and you can get a zip from there with all the utility functions used in the book as well.

You mentioned you print to the console, so probably on windows. Snippets of code from the book can be copied/pasted from the pdf right into a console window where you can interactively try things out.

Your issue with callbacks vs. scripts is discussed in section 10.7.1. and 12.2.1 when combined with namespaces.

I also recommend the free and portable PDF Xchange Viewer program on windows.

et4

unread,
Sep 19, 2022, 11:13:05 PMSep 19
to
Almost forgot, a great on-line manual too:

https://www.magicsplat.com/tcl-docs/docindex.html

GeorgeB

unread,
Sep 20, 2022, 3:25:37 AMSep 20
to
On Tuesday, September 20, 2022 at 4:13:05 AM UTC+1, et4 wrote:
> Almost forgot, a great on-line manual too:
>
> https://www.magicsplat.com/tcl-docs/docindex.html

Morning Rich, saitology9 & et4,

I've got most of the errors sorted (Ralf/Rich, thank you for the help, most appreciated).

I think as I get use to TCL I'll have a better understanding, right now I've been thrown in the deep end and hence steep learning curve.

@Rich thank you for the clarification on the mapping, I suspected as such but thought I'd double check.

@et4, thank you for the resource, anything on tree view (for immediate help), plus I need to add more widgets to treeview once I've figured out mapping.

Lastly, if I have more questions relating to the code shown in my first thread (as I build on it), should I create a new thread or continue with current thread for continuity?

Ralf Fassel

unread,
Sep 20, 2022, 4:29:09 AMSep 20
to
* saitology9 <saito...@gmail.com>
| I agree Ralf's suggestion is the best approach here. Nevertheless, it
| might be useful to OP to know that they could fix the error above by
| properly constructing the callback like so:
>
>
| bind $tw.tree <<TreeviewOpen>> \
| [namespace code [list populateTree %W [list %W focus]]]

I don't think so, since that just builds a list, but does not *execute*
the "%W focus" when the binding is triggered. It just passes the string
"$tw.tree focus" as second parameter to [populateTree].

It is usually very tricky to create callbacks with execution syntax in
them, especially when there might be whitespace involved in the
parameters (let alone arbitrary user input). The better approach is to
keep the arguments minimal and put the workload *inside* the callback.

R'

Ralf Fassel

unread,
Sep 20, 2022, 4:37:21 AMSep 20
to
* GeorgeB <george.b...@gmail.com>
| > set selected [$tw.tree selection]
| > puts $selected
| > => I004 I005 I006
>
| I'd done exactly as you had suggested...but realised I forgot to select items in the tree....doh!!!

:-)

| I'm assuming I will have to map the id e.g. I004 etc to actual value in the tree?
| i.e. if id = I004 and I004 relates to file config.txt in tree then
| I'll have to map it somehow to each other .

That would be

$tw.tree item $item -text

or any other item option you are interested in:

foreach s [$tw.tree selection] {
puts "$s => [$tw.tree item $s -text]"
}
=>
I004 => _csp
I005 => _rpm
I006 => _s
I007 => _sk

| Is it possible to selected a node and get all values underneath it? On
| selection of the node is just gives me one single ID? e.g. Network
| file
| |- Folder 1
| |- file0.txt
| |- file1.txt
| |- file2.txt
| |- file3.txt
>
| I would like to get all items underneath Folder 1 by selecting Folder 1

Iterate over

$tw.tree children $item

HTH
R'

GeorgeB

unread,
Sep 20, 2022, 6:57:37 AMSep 20
to
:)

Thanks Ralf.

Rich

unread,
Sep 20, 2022, 9:50:06 AMSep 20
to
GeorgeB <george.b...@gmail.com> wrote:
> On Tuesday, September 20, 2022 at 9:37:21 AM UTC+1, Ralf Fassel wrote:
>> * GeorgeB <george.b...@gmail.com>
>> | > set selected [$tw.tree selection]
>> | > puts $selected
>> | > => I004 I005 I006
>> >
>> | I'd done exactly as you had suggested...but realised I forgot to
>> | select items in the tree....doh!!!
>> | :-)
>> | I'm assuming I will have to map the id e.g. I004 etc to actual value in the tree?
>> | i.e. if id = I004 and I004 relates to file config.txt in tree then
>> | I'll have to map it somehow to each other .
>> That would be
>>
>> $tw.tree item $item -text
>>
>> or any other item option you are interested in:
>>
>> foreach s [$tw.tree selection] {
>> puts "$s => [$tw.tree item $s -text]"
>> }
>
> :)
>
> Thanks Ralf.

Note also that you can add a list of tags to treeview items (read the
manpage for details). You can make use of tags for, among other uses,
tracking how a given row relates to the other data you are marshalling.

Note that the tags associated with an item are a Tcl list, so use
list operators to manipulate them.

GeorgeB

unread,
Sep 20, 2022, 11:30:47 AMSep 20
to
Thanks Rich,

One thing I'm trying to figure out is how to filter on file extension.

Now I'm passing two folder (Folder 1 & Folder 2) to simulate the structure (see below), the tree structure is created as one would expect. However I was hoping to filter on *.ini files.

In doing so I was trying to figure out were it determines the file names before it populates the tree.

For Folder with out sub folder e.g. Folder 2, applying filter to "foreach f [lsort -dictionary [glob -nocomplain -dir $path *.ini]]" works, but not for folder with sub folder i.e. Folder 1

|- Folder 1
|- Sub folder
|- file0.ini
|- file1.docx
|- Sub Folder 2
|- file1.txt
|- file1.ini
|- Sub Folder 3
|- file2.ini
|- file2.txt
|- file2.docx

|- Folder 2
|- file01.ini
|- file11.docx
|- file21.xlsx
|- file31.txt

Rich

unread,
Sep 20, 2022, 1:22:42 PMSep 20
to
GeorgeB <george.b...@gmail.com> wrote:
> On Tuesday, September 20, 2022 at 2:50:06 PM UTC+1, Rich wrote:
>> Note also that you can add a list of tags to treeview items (read
>> the manpage for details). You can make use of tags for, among other
>> uses, tracking how a given row relates to the other data you are
>> marshalling.
>>
>> Note that the tags associated with an item are a Tcl list, so use
>> list operators to manipulate them.
>
> Thanks Rich,
>
> One thing I'm trying to figure out is how to filter on file
> extension.

Read the 'file' manpage, specifically the "extension" subcommand.

> Now I'm passing two folder (Folder 1 & Folder 2) to simulate the
> structure (see below), the tree structure is created as one would
> expect. However I was hoping to filter on *.ini files.
>
> In doing so I was trying to figure out were it determines the file
> names before it populates the tree.

As we have no idea what "it" is, we are at a loss to offer much in the
way of suggestions.

saitology9

unread,
Sep 20, 2022, 1:23:17 PMSep 20
to
On 9/20/22 4:29 AM, Ralf Fassel wrote:
>>
> | bind $tw.tree <<TreeviewOpen>> \
> | [namespace code [list populateTree %W [list %W focus]]]
>
> I don't think so, since that just builds a list, but does not *execute*
> the "%W focus" when the binding is triggered. It just passes the string
> "$tw.tree focus" as second parameter to [populateTree].
>

True indeed - it does not execute any code. What it does is enable
proper substitution as the original author of that code must have
intended, and passes its result as the argument. The argument happens
to contain a valid command that can be eval'ed, e.g., in the callback proc.

I am still learning the language but my suspicion is that anyone using
"%W" would know that it is only there to be substituted with the actual
target window name later when the callback takes place. Given the fact
that the original code the OP posted was from a demo file from
ActiveState, I assumed that they would know what they were doing with
that type of callback argument construction.


I completely agree with the rest of your post.




saitology9

unread,
Sep 20, 2022, 1:30:31 PMSep 20
to
On 9/20/22 11:30 AM, GeorgeB wrote:
>
> For Folder with out sub folder e.g. Folder 2, applying filter to "foreach f [lsort -dictionary [glob -nocomplain -dir $path *.ini]]" works, but not for folder with sub folder i.e. Folder 1
>
> |- Folder 1
> |- Sub folder
> |- file0.ini
> |- file1.docx

Hello,

I doubt that, as Rich pointed out, "it" knows that you mean to separate
files from folders. Here you are dealing with two kinds of nodes in the
tree widget, and I believe it is up to you to determine when to add a
"folder" node and when to add a ".ini" file, or something else, and
where to add them in the tree hierarchy.






GeorgeB

unread,
Sep 20, 2022, 2:34:39 PMSep 20
to
Yes I've read that bit of the documentation and can filter within directories with extensions.

What I meant was in the code I posted in my first thread, I can't seem to figure out where to correctly apply the filter.

If I apply it where I think I should, foreach f [lsort -dictionary [glob -nocomplain -dir $path *.ini]], doesn't yield the desired result. This only seems to work with folders where files are at top level e.g. Folder 2 in my previous thread.

Hence I was trying to ascertain where within the code (as posted in the first thread), does it create a node for Folder 1 > Sub Folder > file

Since I couldn't figure it out I came asking help.

Maybe this is the downside of using demo.

Anyhoo, I'll try with fresh eyes approach tomorrow.

GeorgeB

unread,
Sep 20, 2022, 2:35:51 PMSep 20
to
Thanks saitology9, I'll have a think tomorrow.

saitology9

unread,
Sep 20, 2022, 3:11:34 PMSep 20
to
On 9/20/22 2:34 PM, GeorgeB wrote:
>
> Hence I was trying to ascertain where within the code (as posted in the first thread), does it create a node for Folder 1 > Sub Folder > file
>
> Since I couldn't figure it out I came asking help.
>

I took a look at the initial code you posted.

The first node, i.e., the root, is created when the widget is created by
populateRoots. This proc creates a folder node associated with "C:/Dev".

Then according to the binding, whenever you click on a node,
populateTree is called. This one does a glob to find the files and
folders, and creates a node *at that level*, i.e., inside the node that
was clicked.

I am not fully sure what you are trying to achieve with ".ini" files but
the proper place to trim it might be in an if-statement inside the
foeach loop right there. Put an extra check for file extension inside
the block:

..
} elseif {$type eq "file"} {
..

and handle it as you wish.




GeorgeB

unread,
Sep 20, 2022, 3:32:37 PMSep 20
to
Hi saitology9,

Thank you for your response, I "kind of "worked that out in last half hour and did something very similar to what you described.

I'll give it a bit more thought tomorrow.

The goal is to populate the tree with specific file type , in this case any files with .ini extension and ignore the rest.

Lastly can't thank you folks (Ralf, Rich and yourself) enough, every exchange helps me...trust me.

clt.to...@dfgh.net

unread,
Sep 21, 2022, 10:18:13 AMSep 21
to

>The goal is to populate the tree with specific file type , in this case any files with .ini extension and ignore the rest.

You might look at the fileutil package from tcllib, in particular find or findByPattern
Something like:

package require fileutil

set iniFiles [fileutil::findByPattern C:/Dev -glob {*.ini}]

would give you a list of complete paths to all .ini files in and under the C:/Dev directory. Directory tree branches with no ini files woud not be in the list. I'm not sure if that is an advantage in your case or not.


Dave B




GeorgeB

unread,
Sep 21, 2022, 4:29:42 PMSep 21
to
Hi Dave, thanks I'll have a look at the fileutil function.

GeorgeB

unread,
Sep 21, 2022, 4:35:48 PMSep 21
to
On Tuesday, September 20, 2022 at 8:11:34 PM UTC+1, saitology9 wrote:
I was trying to re-write the code so that I could filter on a specific file type. I got 90% sorted :) but seem to have messed something else....

Now I can't seem to get the "Size Date Time" columns to populate correctly, basically Time populates under Size and Date and Time are empty.

I've re-created code here:

##########################
package require Tk

set tw .tree
catch {destroy $tw}
toplevel $tw
wm title $tw "Directory Browser"
wm iconname $tw "tree"
positionWindow $tw


set file_list {A B C D E}
ttk::treeview $tw.tree -columns {fullpath type size date time} -displaycolumns {size date time} -yscroll [list $tw.vsb set] -xscroll [list $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"
$tw.tree column #0 -anchor e -minwidth 150 -width 250 -stretch 1
$tw.tree heading size -text "Size"
$tw.tree column size -stretch 0 -width 70
$tw.tree heading time -text "Time"
$tw.tree column time -stretch 0 -width 70
$tw.tree heading date -text "Date"
$tw.tree column date -stretch 0 -width 70


$tw.tree insert {} end -id Folder -text "Folder 1"
foreach key [lsort $file_list] {
$tw.tree insert Folder end -text $key -values [list 80KB 18:00 21/09/2022]
}

lower [ttk::frame $tw.dummy]
pack $tw.dummy -fill both -expand 1
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
grid columnconfigure $tw 2 -weight 1
grid rowconfigure $tw 2 -weight 1

GeorgeB

unread,
Sep 21, 2022, 4:42:58 PMSep 21
to
The line below is created by reading some files from Folder_A but here represented by a variable
> set file_list {A B C D E}

List below is processed by reading files from Folder_A but here shown as a list of expected values.

saitology9

unread,
Sep 21, 2022, 5:48:22 PMSep 21
to
On 9/21/22 4:35 PM, GeorgeB wrote:
>
> I was trying to re-write the code so that I could filter on a specific file type. I got 90% sorted :) but seem to have messed something else....
>
> Now I can't seem to get the "Size Date Time" columns to populate correctly, basically Time populates under Size and Date and Time are empty.
>

Glad to hear you got it sorted.
I don't recall ever using the ttk::treeview widget but it looks like the
shift in columns you see is because you are creating the tree widget
with 5 columns but when you insert nodes into it, you provide data for
only three columns.


> ttk::treeview $tw.tree -columns {fullpath type size date time} ...

> $tw.tree insert Folder end -text $key -values [list 80KB 18:00 21/09/2022]


I don't know what your file list looks like (i.e., plain file names,
full paths, a dictionary, etc.). But you can at least pass the file
name and its type (may be ".ini"?) as the first two elements of the list:

% $tw.tree insert Folder end -text $key -values [list $key ".ini" 80KB
18:00 21/09/2022]




GeorgeB

unread,
Sep 22, 2022, 3:28:26 AMSep 22
to
Yup (and Doh!), you are absolutely correct, I was passing only three elements.... for 5 columns. Apologies for overlooking the that bit...I shouldn't be working so late...

Thank you saitology9

GeorgeB

unread,
Sep 22, 2022, 2:35:03 PMSep 22
to
So I did as suggested by saitology9,

if I pass 3 variables to line $tw.tree insert Folder end -text $key -values [$key $key $s $t $d]

where s, t and d are variables for size time and date created by reading files from a folder and processing to get the desired attributes.

If I pass these to the $tw.tree insert Folder end -text $key -values [A B $s $t $d] which is within foreach loop, I only get the last value in the list for variables s, d and t

for e.g. all entries for the files are populated with 80KB 18:00 21/09/2022

Any advice.

Rich

unread,
Sep 22, 2022, 9:42:28 PMSep 22
to
GeorgeB <george.b...@gmail.com> wrote:
> So I did as suggested by saitology9,
>
> if I pass 3 variables to line $tw.tree insert Folder end -text $key
> -values [$key $key $s $t $d]
>
> where s, t and d are variables for size time and date created by
> reading files from a folder and processing to get the desired
> attributes.
>
> If I pass these to the $tw.tree insert Folder end -text $key -values
> [A B $s $t $d] which is within foreach loop, I only get the last
> value in the list for variables s, d and t
>
> for e.g. all entries for the files are populated with 80KB 18:00
> 21/09/2022
>
> Any advice.

Make sure your code is as above, and not as you posted a few articles
ago:

$tw.tree insert Folder end -text $key -values [list 80KB 18:00 21/09/2022]

Your result implies you still have the constants in your source.

Note also that the code you posted above is broken, it will not run.
You'll get better answers if you check that your code you are about to
post actually runs before you post it.

GeorgeB

unread,
Sep 23, 2022, 4:03:41 AMSep 23
to
Hi Rich,

Apologies, please see code.

I must admit I don't fully understand procedures (need some basics), and I'm still working off examples.

However in the process, I'm recreating code based on these examples (using them as guide/ reference).

##########################
package require Tk

unset -nocomplain ::fldr_lib

set tw .tree
catch {destroy $tw}
toplevel $tw
wm title $tw "Directory Browser"
wm iconname $tw "tree"
positionWindow $tw

catch {console show}
set rmDir "C:/"

proc rglob { dirpath patterns {exclude_pats {}} } {
set rlist {}
set f_exclude [glob -nocomplain -types f -directory ${dirpath} {*}${exclude_pats}]
set d_exclude [glob -nocomplain -types d -directory ${dirpath} {*}${exclude_pats}]
foreach fpath [glob -nocomplain -types f -directory ${dirpath} {*}] {
if { ${fpath} ni ${f_exclude} } {
lappend rlist ${fpath}
}
}
foreach dir [glob -nocomplain -types d -directory ${dirpath} *] {
if { ${dir} ni ${d_exclude} } {
lappend rlist {*}[rglob ${dir} ${patterns} ${exclude_pats}]
}
}
return ${rlist}

}
# ##################################


set ifname [rglob $rmDir *.ini {*.docx *.pptx *.txt *.xlsx}]

foreach ::dir [lsort $ifname] {
set split [file split $::dir]
set ::nodeid [lindex $split end-1]
set ::fname [lindex $split end-0]



set ::size [file size $::dir]
set ::ttime [file mtime $::dir]
set ::date [clock format $::ttime -format "%d/%m/%Y"]
set ::time [clock format $::ttime -format "%H:%M:%S"]

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"
append ::date " "
append ::time " "
}

if {$::nodeid == "Folder"} {
lappend ::fldr_lib $::fname
#puts "Check within if statement $::fldr_lib"
set ::f1NodeId $::nodeid
set ::s $::size
set ::t $::time
set ::d $::date
}
}

#puts "Check if all files are written in variable $::fldr_lib"

ttk::treeview $tw.tree -columns {fullpath type size date time} -displaycolumns {size date time} -yscroll [list $tw.vsb set] -xscroll [list $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"
$tw.tree column #0 -anchor e -minwidth 150 -width 250 -stretch 1
$tw.tree heading size -text "Size"
$tw.tree column size -stretch 0 -width 70
$tw.tree heading time -text "Time"
$tw.tree column time -stretch 0 -width 70
$tw.tree heading date -text "Date"
$tw.tree column date -stretch 0 -width 70


$tw.tree insert {} end -id Folder -text "Folder"
foreach key [lsort $::fldr_lib] {
puts "Size $::s | Date $::d | Time $::t"
$tw.tree insert Folder end -text $key -values [list $key $key $s $d $t]

Rich

unread,
Sep 23, 2022, 8:37:07 AMSep 23
to
GeorgeB <george.b...@gmail.com> wrote:
> On Friday, September 23, 2022 at 2:42:28 AM UTC+1, Rich wrote:
>> GeorgeB <george.b...@gmail.com> wrote:
>> > So I did as suggested by saitology9,
>> >
>> > if I pass 3 variables to line $tw.tree insert Folder end -text $key
>> > -values [$key $key $s $t $d]
>> >
>> > where s, t and d are variables for size time and date created by
>> > reading files from a folder and processing to get the desired
>> > attributes.
>> >
>> > If I pass these to the $tw.tree insert Folder end -text $key -values
>> > [A B $s $t $d] which is within foreach loop, I only get the last
>> > value in the list for variables s, d and t
>> >
>> > for e.g. all entries for the files are populated with 80KB 18:00
>> > 21/09/2022
>> >
>> > Any advice.
>> Make sure your code is as above, and not as you posted a few articles
>> ago:
>> $tw.tree insert Folder end -text $key -values [list 80KB 18:00 21/09/2022]
>> Your result implies you still have the constants in your source.
>>
>> Note also that the code you posted above is broken, it will not run.
>> You'll get better answers if you check that your code you are about to
>> post actually runs before you post it.
>
> Hi Rich,
>
> Apologies, please see code.

Did you actually try running what you posted, *before* you posted it?

I.e., did you put only what you posted here into a file, all alone, and
try to launch it?

Because the code you posted again *does not run*:

Error in startup script: invalid command name "positionWindow"
while executing
"positionWindow $tw"

GeorgeB

unread,
Sep 23, 2022, 10:08:51 AMSep 23
to
Yes I did, and I don't get the error, if I could I would provide a screenshot.

GeorgeB

unread,
Sep 23, 2022, 10:28:41 AMSep 23
to
I'm coping the code again.

Rich

unread,
Sep 23, 2022, 1:52:34 PMSep 23
to
GeorgeB <george.b...@gmail.com> wrote:
> I'm coping the code again.

Are you testing against a 'clean' Tcl interpreter?

Do this experiment:

1) copy out the code from Usenet that you posted, into a brand new
empty text file

2) from a terminal, launch the text file this way:

wish copied.txt

And you'll get this error as well:

Error in startup script: invalid command name "positionWindow"
while executing
"positionWindow $tw"
(file "ott" line 11)

Or, if you don't know how to open a windows terminal and do the
launch, then open a brand new, clean, never before used, wish with
console, and in that, clean, interpreter, do:

source copied.txt

Above I'm assuming you named the new text file "copied.txt" --
substitute with whatever name you actually used.

If you launch in an interpreter that you've previously used, then you
have prior definitions of procs and/or variables that do not exist for
us. You need to try launching the code the same way we have to, in a
brand new, clean, interpreter.

saitology9

unread,
Sep 23, 2022, 1:55:41 PMSep 23
to
On 9/23/22 10:28 AM, GeorgeB wrote:
> I'm coping the code again.

I am afraid it still doesn't run. You need to initialize "tw". It is
easy fix it by setting it to empty string so it corresponds to the main
toplevel window.


> set ifname [rglob $rmDir *.ini {*.docx *.pptx *.txt *.xlsx}]

A side point, but you seem to have complicated your situation
needlessly, at least at this point where you don't have a working
version. If you are searching for a specific file type, all others will
get eliminated automatically, right?

>
> foreach ::dir [lsort $ifname] {
> set split [file split $::dir]
> set ::nodeid [lindex $split end-1]
> set ::fname [lindex $split end-0]
>
..
>
> if {$::nodeid == "Folder"} {
..

What you get in "nodeid" is not what you think it is.

Also, you can't change or capitalize constants that are required by the
commands and packages. I can guess you meant to compare the file type
and see if it is a "folder". You can't capitalize it.


My main recommendation would be 1) to review the basic language syntax,
and then 2) to review the documentation of the treeview widget. Knowing
the language does not translate to the packages. 3) You may be better
off fixing the first version you posted instead of this latest one.
Because to be honest, I do not see much influence in your code of the
recommendations you have received so far. They should have helped you
quite a bit.

Alternatively, search the wiki for file browsers. There are several
Tcl-only ones, and they will accept the option to limit the listings to
certain files.




saitology9

unread,
Sep 23, 2022, 1:59:38 PMSep 23
to
On 9/23/22 1:55 PM, saitology9 wrote:
> On 9/23/22 10:28 AM, GeorgeB wrote:
>> I'm coping the code again.
>
>
> Alternatively, search the wiki for file browsers.  There are several
> Tcl-only ones, and they will accept the option to limit the listings to
> certain files.
>
>

You haven't mentioned any context around this but you may be perfectly h
appy with a plain listbox. I would look into it as well.


GeorgeB

unread,
Sep 24, 2022, 4:30:38 AMSep 24
to
@Rich, ok I now understand..what I have been doing all this while is using the tree.tcl available in my activeState installation (C:\TCL\demos\Tk8.6)

And have been modifying that file itself (backed up the original). So if I understand correctly that file references widget.tcl (also present in Demo Directory), maybe that initialises additional variables.

@saitology9, I agree, I think I've messed up and should go back to my originally posted code, I've implemented all the recommendations in the original code.

My rationale was (I'm still newbie), to may be re-write some bits in the hope of learning and then being able to also limit the files in the tree to only those with extension of *.ini, clearly lot to learn.

Context around the code : generate a tree structure (or something similar) by looking a two folders, filter on *.ini files and dump user selection to a file.

Using tree.tcl I can achieve all the steps expect filter on *.ini files. I should be able to achieve all the objectives but I'm struggling to understand proc{} , how they operate.

hence attempted to re-write.

The need to filter on *.ini is because there is no way to add checkbox or is tricky to add with treeview (or so I read), I did find some examples but looks quite daunting.

Hope this explains.

I'll have a look at listbox.

Thanks in advance.

saitology9

unread,
Sep 24, 2022, 11:31:21 PMSep 24
to
On 9/24/22 4:30 AM, GeorgeB wrote:
>
> Context around the code : generate a tree structure (or something similar) by looking a two folders, filter on *.ini files and dump user selection to a file.
>
> Using tree.tcl I can achieve all the steps expect filter on *.ini files. I should be able to achieve all the objectives but I'm struggling to understand proc{} , how they operate.
>
> hence attempted to re-write.
>
> The need to filter on *.ini is because there is no way to add checkbox or is tricky to add with treeview (or so I read), I did find some examples but looks quite daunting.
>
> Hope this explains.
>
> I'll have a look at listbox.
>
> Thanks in advance.


Here is a working prototype based on your initial code. You can create
multiple trees displaying different folders, each with a different
filter. It has some extra features that may be helpful such as
auto-expansion for non-empty folders, color-coding, etc.

You can see how proc's help you organize your code and make it reusable.
I think *every* programming language has a similar construct.

----------------------------------------------------------------

package req Tk


proc populateRoots {tree ROOT SHOW_TYPES} {
populateTree $tree \
[$tree insert {} end -text "Network File" \
-values [list $ROOT directory] \
-open 1] \
$SHOW_TYPES
}

## Code to populate a node of the tree
proc populateTree {tree node SHOW_TYPES} {

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 $id $SHOW_TYPES

} 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>> {populateTree %W [%W focus]
$SHOW_TYPES}


# ## Arrange the tree and its scrollbars in the toplevel
lower [ttk::frame $tw.dummy]


pack $tw.dummy -fill both -expand 1


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





GeorgeB

unread,
Sep 25, 2022, 4:22:46 PMSep 25
to
@saitology9 Thank you so much, this worked wonderfully. Very much appreciated...

GeorgeB

unread,
Sep 26, 2022, 2:27:47 PMSep 26
to
Hello all, Thanks to saitology9 I've got a bit of head start.

I was wondering if adding a checkbox to the tree would be a good idea or would it over complicate things.

Looking at threads on the same topic, I realise it is not so easy unless I use treectrl etc.

But I read that one can use image of checkbox, below is saitology9's corrections and updates. I've added a checkbox to each item.

Question:
If I want to user to make a selection with mouse either selection top level or individual items (at same time toggle the image). Would I need to bind "<ButtonPress-1>" to selection?



package req Tk


image create photo ::img::cb(0) -data {
R0lGODlhDwAPANUAANnZ2Y6Pj/T09K6zua+0urS5vbu+wcvP1dDT2NXY3Nvd38HDxc3R1tLV2tjb
3t3f4eLj5MbHyM3R19DU2dTX2+Hi4+Xm5ujo6MzNzbK3vNrc3+Dh4+zs7O3t7dTV1ri7v+Tl5erq
6u/v7/Ly8tzd3ry/wuPk5enp6fX19eHi4sLExvDw8Pb29ubm5srLzNTU1dvb3ODh4ebn5+rr6+vs
7Ovr7Onp6v///////////////////////////////////yH5BAEAAAAALAAAAAAPAA8AAAZvQIBw
SCwCAsikMikMCJ7QqCDQFAyuWELBMK0ODuADIqFYdI9WMKPheEAiZ+dAMqEoKpYLJi7IJDQbFxwd
HR58Hw8gISIjjSR8JSYnHSMCKAIpfCqTKwIsny18Li8wMTIzNDU2fFJSVEdLsa9GtABBADs=}
image create photo ::img::cb(1) -data {
R0lGODlhDwAPAOYAANnZ2Y6Pj/T09Pj4+Pn5+fb29q6zucnM0J2nwHeGq9zf5PX19cvP1czQ1u3u
8VdqnURakrm/0M3R1uDi5q+4z0VakmV3pefn6NXZ3d/i5dXY3PLz9F5xoUdclLzD1tvc3MXJzcnP
3aOuyO3u7rrB1UlelmR2pdXV1t7g4W5/qs/U4md4p0tgl7e/1dzd3s3P0d7h6UhdlUlflmFzpPj5
+uHi4sbIyurs8IuZu0pfl0xhmLC50ebm5srLzNra29/i6YyZupCdvfLz9uzt7evr7Onp6v//////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////yH5
BAEAAAAALAAAAAAPAA8AAAeFgACCg4SFAAGIiYqJggECj5ACAwQFAgGNAgaamgcICQoLl4eZDKUN
Dg8QEQWijgalEhMUFRYXlpgGGBkaGxwdHh+3oyAhIiMkJSYDJ8KOKCkdKissLQUuzQIvMDEyLDM0
AjXYNjc4OTo7lDzYPT4/QEFCQ0RF2I8LBAMLka2L/qKGAgIIBAA7}
-open 0 \
-image ::img::cb(0)]
-values [list $f $type $size $fdate $ftime] \
-image ::img::cb(0)]
set ROOT_2 "C:/Network_Files"