Arjen Markus (12 October 2002) Every so often, a graphical user-interface would be nice for a relatively simple program, just gather the information, put it into a file and run the program. Picking up the results and presenting them can then be done in another program.
Now, if there is a budget and time to make it a really nice user-interface, there are plenty of possibilities. But what if you need to something quickly and for low costs - the program is important enough to build such a GUI, but there is no time or money to do a proper job.
What you need then is a poor man's approach to building GUIs. And this page presents one such solution.
The philosophy is this:
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 2006
Recently, 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 2007
I have something (almost) ready :). It works under its own CGI/HTTP server (happily derived from a tiny one on this Wiki). Main issues left:
AM Another update, 27 august 2013
Picking up this HTML idea again, I could not find the code anymore, but with small steps I am able to reconstruct it. Targeting SCGI and NGINX. (MG It's archived here - AM I was unclear about it: I meant the HTML version)
AM I created a project on Chiselapp that will hold the code.
AM (6 october 2013) I have managed to adapt the code for the HTML version to use slave interpreters - one per connection, so that you get independent sessions. Still some issues left, especially with cleaning up, but it is looking promising.
Here is an overly simple little example:
Well, what can be simpler? This is the input for the PMG package/application:
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 form
The template file, blockv.tpl looks like:
[length ] Length of block [width ] Width of block [height ] Height of block
In 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...