简体   繁体   中英

Tcl increasing a value in a procedure

I just started to learn Tcl. I wanted to write a simple procedure. When the procedure starts, it opens a browse window to browse for files. There you can select a file you want to open.

Then a pop-up windows comes up and asks if you want to selected another file. Every file that you select has to go into an array.

I have to following code:

########## Defining the sub procedures ############
proc open_file {} {
    set n 0
    set title "Select a file"
    set types {
      {{GDS files} {.gds} }
      {{All Files} * }
    }

    set filename [tk_getOpenFile -filetypes $types -title $title]

    set opendFiles($n) $filename
    set n [expr $n + 1]

    set answer [tk_messageBox -message "Load another GDS file?" -type yesno -icon question]
    if {$answer == yes } {
       open_file
    } else {
       show_files ($opendFiles)
    }
}

proc show_files {} {
    foreach key [array names opendFiles] {
        puts $opendFiles($key)
    }
}

########## Main Program ###########
open_file

I having the following problems. Because I always recall the proc ' open_file ' the variable $n keeps setting to 0 . But I don't know how to recall the opening of the window without recalling the whole subroutine....

The second problem is sending the array to the next proc. When I send to the to the proc ' show_files ', I always get the next error : can't read "opendFiles": variable is array .

I can't seem to find both answers..

You need global variables for that. This works for me:

########## Defining the sub procedures ############
set n 0
array set openedFiles {}

proc open_file {} {
    set title "Select a file"
    set types {
        {{GDS files} {.gds} }
        {{All Files} * }
    }

    set filename [tk_getOpenFile -filetypes $types -title $title]

    set ::openedFiles($::n) $filename
    incr ::n

    set answer [tk_messageBox -message "Load another GDS file?" -type yesno -icon question]
    if {$answer == yes } {
        open_file
    } else {
        show_files
    }
}

proc show_files {} {
    foreach key [array names ::openedFiles] {
        puts $::openedFiles($key)
    }
}

########## Main Program ###########
open_file

Array Problem

In Tcl you can't send arrays to procs. You need to convert them to a list with array get send this list to the proc and than convert it back to an array again with array set .

Global variables are very useful at times, but I believe they are best avoided where possible. In this case I'd rather process the loop and the array in the main program rather than the proc .

Also, where you'd use an array in other programming languages, it's often better to use a list in Tcl, so something like:

proc open_file {} {
    set title "Select a file"
    set types {
        {{GDS files} {.gds} }
        {{All Files} * }
    }

    set filename [tk_getOpenFile -filetypes $types -title $title]
    return $filename
}

proc show_files {files} {
    foreach file $files {
        puts $file
    }
}

set openedFiles [list]
set answer yes

while {$answer == yes}
    lappend openedFiles [open_file]
    set answer [tk_messageBox -message "Load another GDS file?" -type yesno -icon question]
}
show_files $openedFiles

If you're into brevity, show_files could be written

proc show_files {files} {
    puts [join $files \n]
}

and, now that it's so short, you could just put it in line, rather than have another proc.

Finally, have you considered what you want to do if the user presses cancel in tk_getOpenFile ? In this case filename will be set to an empty (zero-length) string. You could either

  • ignore these; or
  • get rid of the tk_messageBox call and have the user press cancel when they have entered as many files as they want.

If you want to just ignore those times when the user pressed cancel, you could do

set filename [open_file]
if {[string length $filename] > 0} {
    # The user entered a new filesname - add it to the list
    lappend openedFiles $filesname
} else {
    # The user pressed cancel - just ignore the filename
}

If you wanted to use cancel to break out of the loop, then the main program becomes something like:

set openedFiles [list]
set filename dummy
while {[string length $filename] > 0} {
    set filename [open_file]
    if {[string length $filename] > 0} {
        lappend openedFiles $filename
    }
}
show_files $openedFiles

in this case, you might want to put up a message box right at the start of the main program telling the user what's going on.

For the state of a variable to persist between calls to a procedure, you need to make that variable live outside the procedure. The easiest way is to use a global variable:

# Initialize it...
set n 0
proc open_file {} {
    # Import it...
    global n
    ...
    # Use it...
    set openedFiles($n) $filename
    incr n
    ...
}

Arrays are not values, and as such can't be passed directly to another procedure. You can handle this by passing in the name and using upvar 1 to link a local alias to the variable in the calling stack frame:

proc show_files {varName} {
    upvar 1 $varName ary
    foreach key [array names ary] {
        puts $ary($key)
    }
}

Which is called using the name of the array, so no $ :

show_files openedFiles

(You could also pass a serialization of the array in with array get openedFiles to serialize and array set ary $serialization to deserialize, but that carries some overhead.)

You probably ought to add that openedFiles variable to the global line, so that it is persistent across all invokations of open_file .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM