- We can divide the input into small forms, each taking a bunch of input parameters, but not that many.
- We collect all the input in individual variables, perhaps validating them with simple rules.
- We define the input forms and the output file via templates. The procedures below allow us to automatically set up a form, save the input and then write out the results.
- Basic constraint: the variables are fairly isolated, they do not require extensive checking against other data. The forms can be simple and straightforward.
The most recent code can be found on Chiselapp
A similar alternative for a simple GUI specification is TkVSform.See TEPAM.
AM Update, 6 november 2002:I reorganised the source code so that it is now possible to add your own widget types and parameter types (thanks to Rolf Ade for the idea). Support for tables is on its way. Proper documentation (alas) is not.Should you be interested (I dare not post the 1200 lines of code here - what is the limit?), just let me know.AM Update, 14 november 2002:I have now written quite a bit of documentation. It should help any one who wants to use it with setting up an application. (Status for tables: no advance yet)AM Update, 21 december 2006Recently, interest arose to turn a few of the applications I built with this package into web demos. I have been experimenting with generating HTML pages instead of Tk widgets and that works fine, but the real step forward would be to use Tclhttpd. I see quite a few possibilities there and the nice thing would be that by merely loading a different version you could have a standalone application or a web-based application.Now, all I need to have is some time to make this dream come true ...AM Update, 22 january 2007I have something (almost) ready :). It works under its own CGI/HTTP server (happily derived from a tiny one on this Wiki). Main issues left:
- The equivalent for "exit" in such an environment?
- Making it possible that there is more than one simulateneous user
Here is an overly simple little example:
- We want to calculate the volume of a rectangular block via a specialised program.
- This program uses an input file that contains the three dimensions of the block.
- The user-interface should read the three numbers and write them into such an input file.
package require PMG realParameter length realParameter width realParameter height defineForm form { Length = [length ] (m) Width = [width ] (m) Height = [height ] (m) <ok_b > <cancel_b> } formButton form ok_b "OK" { outputData "blockv.tpl" "blockv.inp" saveData "blockv.prv" exit } formButton form cancel_b "Cancel" { exit } # # Code to run the GUI # loadData "blockv.prv" showForm formThe template file, blockv.tpl looks like:
[length ] Length of block [width ] Width of block [height ] Height of blockIn both templates, the form template and the file template, use is made of [ and ] to delimit a entry field. Inside the entry field is the name of the variable that needs to be set.Other field definitions (only in the forms, not in the output) include:Field defining one radio button (repeat the same variable name, properties via choiceParameter):
[(o) radio_choice ]Field to define an option menu (define the properties via via choiceParameter):
[(?) option_menu ]Field to define a checkbox (define the properties via logicalParameter):
[(x) check_box ]Field to define a label whose contents may be vary: properties via logicalParameter):
{label_variable }Field to define a push button (define the properties via formButton)
<push_button >The layout is derived from the form and the fields in the form, the width of the fields in the window is the same as the width of the fields in the form (in characters). Pushbuttons must be defined separately, via the formButton command.The package makes elementary checks on consistency in the definitions and provides some facilities for validation.Note:The script below is much more limited than you might expect from the above description. It provides very little consistency checking, it does not load and save the variables yet, it is not possible yet to properly cancel an action.Also, choices via a set of radio buttons or an option button are not implemented yet.Nevertheless, it does provide a start.
14oct02 escargo - OK. Here's my persistent question: What kind of license do you release this code with? Public domain? BSD? GPL (and what version)?AM Just plain public domain. I thought of this a couple of days ago, independent of your discussion (I had not read the page yet) and had a go at a proof of concept - as below. I surely want to develop this somewhat further and then put it on CANTCL.
# package PMG -- # namespace eval ::PMG { variable params variable forms variable font "Courier 12" namespace export realParameter integerParameter logicalParameter \ choiceParameter defineForm showForm formButton } # realParameter -- # Define a real parameter (possibly with valid range) # # Arguments: # name Name of the real parameter # lower Lower bound (inclusive) for any values (optional) # upper Upper bound (inclusive) for any values (optional) # # Result: # None # # Side effect: # Create an entry in the array params, parameters are considered to # be global for all forms # proc ::PMG::realParameter {name {lower {}} {upper {}}} { variable params set params($name) 0.0 set params($name,type) "real" set params($name,lower) $lower set params($name,upper) $upper if { $lower != {} } { set params($name) $lower } } # integerParameter -- # Define an integer parameter (possibly with valid range) # # Arguments: # name Name of the integer parameter # lower Lower bound (inclusive) for any values (optional) # upper Upper bound (inclusive) for any values (optional) # # Result: # None # # Side effect: # Create an entry in the array params, parameters are considered to # be global for all forms # proc ::PMG::integerParameter {name {lower {}} {upper {}}} { variable params set params($name) 0 set params($name,type) "integer" set params($name,lower) $lower set params($name,upper) $upper if { $lower != {} } { set params($name) $lower } } # logicalParameter -- # Define a logical parameter (value: 0 or 1) # # Arguments: # name Name of the logical parameter # text Text to describe the parameter # # Result: # None # # Side effect: # Create an entry in the array params, parameters are considered to # be global for all forms # proc ::PMG::logicalParameter {name text} { variable params set params($name,type) "logical" set params($name,text) $text set params($name) 0 } # choiceParameter -- # Define a choice parameter (values and descriptive text) # # Arguments: # name Name of the choice parameter # values Pairs of values and descriptive text # # Result: # None # # Side effect: # Create an entry in the array params, parameters are considered to # be global for all forms # proc ::PMG::choiceParameter {name values} { variable params set params($name,type) "choice" set params($name,choices) $values set params($name) [lindex $values 0] } # defineForm -- # Define the contents of a form # # Arguments: # name Name of the form # layout Layout of the form # # Result: # None # # Side effect: # Create an entry in the array forms # # Note: # There is no check for the validity # proc ::PMG::defineForm {name layout} { variable forms set forms($name) $layout } # formButton -- # Define the properties of a button in a form # # Arguments: # form Name of the form to which the button belongs # button Formal name of the button # label Label on the button # script Script to execute when the button is pressed # # Result: # None # # Side effect: # Create an entry in the array forms # # Note: # There is no check for the validity # proc ::PMG::formButton {form button label script} { variable forms set forms($form,$button,label) $label set forms($form,$button,script) $script } # showForm -- # Show the form in a new toplevel window # # Arguments: # form Name of the form # # Result: # None # # Side effect: # Create a new toplevel window with the form in it # proc ::PMG::showForm {form} { variable forms variable font # # Check if the toplevel window exists (.f_$form) # Also check if there is such a form # set f .f_$form set t $f.text if { [winfo exists $f] } { wm raise $f return } if { [array get forms $form] == {} } { error "Form $form not defined" } # # Create the toplevel and the text widget holding the form # set width 0 set height 0 set layout [split $forms($form) "\n"] foreach line $layout { incr height set length [string length $line] if { $length > $width } { set width $length } } incr width 2 incr height 2 toplevel $f text $t -width $width -height $height -font $font -background gray pack $t foreach event {<KeyPress> <<PasteSelection>>} {bind $t $event break} foreach line $layout { set line [string trimright $line] # # Split the line into pieces, ordinary text and fields # set line2 \ [string map {[ "~[" ] "]~" \{ "~\{" "\}" "\}~" < "~<" > ">~"} $line] set line2 [split $line2 "~"] foreach segment $line2 { set first [string index $segment 0] if { [string first $first "\[]\{\}<>"] > -1 } { InsertField $t $form $segment } else { $t insert end $segment } } $t insert end "\n" } } # InsertField -- # Create a widget specific to the field's type and insert into the # text widget # # Arguments: # t Text widget # form Name of the form # field Definition of the field # # Result: # None # # Side effect: # Create a new embedded widget # proc ::PMG::InsertField {t form field} { variable font variable forms variable params set type0 [string index $field 0] set type1 [string index $field 1] set type2 [string index $field 2] set field_length [string length $field] switch -- $type0 { "\[" { if { $type1 != "(" } { set param [string trim [string range $field 1 end-1]] entry $t.$param -textvariable ::PMG::params($param) \ -font $::PMG::font -width $field_length $t window create end -window $t.$param bind $t.$param "<FocusOut>" \ +[list ::PMG::ValidateParameter $t.$param $param] } else { set param [string trim [string range $field 4 end-1]] switch -- $type2 { o { radiobutton $t.$param.$count -variable ::PMG::params($param) \ -text "???" -value "???" \ -font $::PMG::font -width $field_length $t window create end -window $t.$param.$count } x { checkbutton $t.$param -variable ::PMG::params($param) \ -text $::PMG::params($param,text) \ -font $::PMG::font -width $field_length \ -anchor nw $t window create end -window $t.$param } default { return } } } } "<" { set param [string trim [string range $field 1 end-1]] button $t.b_$param -text $::PMG::forms($form,$param,label) \ -command $::PMG::forms($form,$param,script) \ -font $::PMG::font -width $field_length $t window create end -window $t.b_$param } "\{" { set param [string trim [string range $field 1 end-1]] label $t.$param -textvariable ::PMG::params($param) \ -font $::PMG::font -width $field_length \ -anchor nw $t window create end -window $t.$param } default { return } } } # ValidateParameter -- # Validate the new value for a parameter # # Arguments: # entry Entry widget that holds the parameter # param Parameter name # # Result: # None # # Note: # This is invoked when the focus leaves the entry widget. # At least on Windows, there is no FocusOut event if you press a # button, rather than move to the next entry field. # P.M: # This requires extra attention # # Side effect: # Presents a message box if the new value is out of range # proc ::PMG::ValidateParameter {entry param} { variable params set lowup 0 set message 0 if { $params($param,lower) != {} } { incr lowup 1 if { $params($param) < $params($param,lower) } { set message 1 } } if { $params($param,upper) != {} } { incr lowup 2 if { $params($param) > $params($param,upper) } { set message 1 } } if { $message } { switch -- $lowup { 1 { set text "Parameter $param must be greater/equal $params($param,lower)" } 2 { set text "Parameter $param must be lower/equal $params($param,upper)" } 3 { set text "Parameter $param must be between $params($param,lower) and $params($param,upper)" } } tk_messageBox -message $text -type ok -title "Value out of range" } # PM: Set the focus back into the entry return } package provide PMG 0.1 # main -- # Set up the form, show it # # Note: # This is NOT identical to the example appearing in the text! # # package require PMG namespace import ::PMG::* realParameter length 0.0 realParameter width 0.0 realParameter height 0.0 realParameter volume logicalParameter save_data "Save data" defineForm form { Length = [length ] (m) Width = [width ] (m) Height = [height ] (m) { volume } - Volume [(x) save_data] <calc_b> <exit_b> } formButton form calc_b "Calculate" { #outputData "blockv.tpl" "blockv.inp" #saveData "blockv.prv" # # TODO: get rid of this clumsy way of working # set ::PMG::params(volume) \ [expr {$::PMG::params(length)*$::PMG::params(width)*$::PMG::params(height)}] #exit } formButton form exit_b "Exit" { exit } # # Code to run the GUI # #loadData "blockv.prv" showForm form wm withdraw .
MHo: This is funny! Many years ago I followed the very same ideas: 'drawing' a dialog with a simple text editor is enough, let the computer do the stupid rest! Ok, in between there are two programs that has to be written ;-) one for checking the syntax and 'compiling' a sort of bytecode, and one to interpret these intermediate code, which means: to display a nice GUI under MSDOS:
Arlie L. Codina: I think I can really used this. It would be nice if this could be documented and PMG is further developed. I'm a Tcl/Tk newbie.AM PMG is actually available via CANTCL. Has been for a couple of years actually, let me know if you need advice or have problems using it...