0

I'm making a little front end for sqlite database, here is where a new record gets inserted

proc new_record {account_db key} {
    upvar db_path db_path
    set thisdb $db_path$account_db.db
... <some stuff> ...
    grid [button .n.bottom.proceed  -background grey65 -text "Add Record" -pady 3 -width 17 -command {
        ad $thisdb $desc $log $pass
        destroy .n
        return
    }]
... <more stuff> ...
}

Error says "Error: can't read "thisdb": no such variable

I try to debub using Tk_messageBox:

proc new_record {account_db key} {
    upvar db_path db_path
    set thisdb $db_path$account_db.db
... <some stuff> ...
    tk_messageBox -message $thisdb;return
    grid [button .n.bottom.proceed  -background grey65 -text "Add Record" -pady 3 -width 17 -command {
        ad $thisdb $desc $log $pass
        destroy .n
        return
    }]
... <more stuff> ...
}

message box shows the varible $thisdb, which is the full path to the database file. I put the message box within the button command, I get an error, no such variable.

Obviously I'm missing something in plain sight, but I'm just not seeing it, any ideas?

The traceback:

can't read "thisdb": no such variable
can't read "thisdb": no such variable
    while executing
"ad $thisdb $desc $log $pass"
    invoked from within
".n.bottom.proceed invoke"
    ("uplevel" body line 1)
    invoked from within
"uplevel #0 [list $w invoke]"
    (procedure "tk::ButtonUp" line 24)
    invoked from within
"tk::ButtonUp .n.bottom.proceed"
    (command bound to event)
Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
dana
  • 61
  • 6

1 Answers1

2

The -command callbacks of Tk widgets execute in the global scope. Amazingly, I can't find mention of this critical fact in the official documentation; it must have only ever been described in Ousterout's book. However it is definitely the case that it is so, and this is because at the point where the callback is invoked, the stack frame that the variable is in may well not exist any more (no automatic scope capture is done).

In any case, because of this it is strongly recommended that you write your code with a helper procedure (and a list-generated actual callback of it) like this:

proc AddRecordButtonCallback {w thisdb desc log pass} {
    ad $thisdb $desc $log $pass
    destroy $w
}
proc new_record {account_db key} {
    upvar db_path db_path
    set thisdb $db_path$account_db.db
... <some stuff> ...
    # Split over two lines for clarity only
    grid [button .n.bottom.proceed  -background grey65 -text "Add Record" -pady 3 -width 17 \
            -command [list AddRecordButtonCallback .n $thisdb $desc $log $pass]]
... <more stuff> ...
}

It's almost a mechanical transformation.

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
  • Thanks for that Donal, as a simple hobbyist, I was unaware of the -command / global thing – dana Oct 24 '22 at 12:15