Arjen Markus (20 february 2008) A question from a colleague inspired me to write the program below and the associated example. The question was related to making an easy-to-use system for analysing problems that may require either a simple computation in, say, a spreadsheet, or a much more sophisticated approach using advanced numerical modelling.
The client wants an answer fast, so the system would ask a bunch of questions to decide what solution method is appropriate - a decision tree, so to speak. Simple cases can be dealt with via simple computations, for more complex cases the client is advised to follow the sophisticated path.
Anyhow, such a system in this day and age requires a graphical user-interface and quite likely a web-oriented user-interface. While I did not do the latter bit yet, the first bit was easy enough. For the statistics:
Note that the program uses some more or less subtle namespace manipulations to keep the program's variables away from the user-defined ones.
With the program and the example below you get the following screens:
The use is simple:
The program itself looks like this:
# decision.tcl -- # Program to define and show GUIs for decision trees # # Decide -- # Namespace for running the GUI and associated computations # namespace eval ::Decide { # Private namespace namespace eval v { variable previous_window variable window variable window_title variable wcount set previous_window {} set wcount 0 } } # window -- # Define a new window # # Arguments: # name Name of the window # title Title # contents Script defining the contents of the window # # Result: # None # # Side effects: # Entry in array window filled # proc ::Decide::window {name title contents} { set v::window($name) $contents set v::window_title($name) $title } # text -- # Define/show a text label # # Arguments: # string String to present # second Second string if any # # Result: # None # # Side effects: # Label widget created # proc ::Decide::text {string {second {}}} { if { $second eq "" } { label .frame1.label$v::wcount -text $string -font "Helvetica, 14" grid .frame1.label$v::wcount - -sticky news } else { set l1 $v::wcount incr v::wcount set l2 $v::wcount label .frame1.label$l1 -text $string -font "Helvetica, 14" label .frame1.label$l2 -text $second -font "Helvetica, 14" grid .frame1.label$l1 .frame1.label$l2 -sticky news } incr v::wcount } # entry -- # Define/show an entry widget # # Arguments: # string String to present # name Variable name # # Result: # None # # Side effects: # Label widget created # proc ::Decide::entry {string name} { set l1 $v::wcount incr v::wcount set e1 $v::wcount ::label .frame1.label$l1 -text $string -font "Helvetica, 14" ::entry .frame1.entry$e1 -textvariable ::Decide::$name -font "Helvetica, 14" ::grid .frame1.label$l1 .frame1.entry$e1 -sticky news incr v::wcount } # choice -- # Define/show a set of radio buttons # # Arguments: # name Name of the associated variable # choices List of values and descriptive texts # # Result: # None # # Side effects: # Set of radio buttons created # proc ::Decide::choice {name choices} { if { ![info exists ::Decide::$name] } { set ::Decide::$name [lindex $choices 0] } foreach {value text} $choices { radiobutton .frame1.radio$v::wcount -variable ::Decide::$name -text $text \ -value $value -font "Helvetica, 14" grid .frame1.radio$v::wcount -sticky nw incr v::wcount } } # button -- # Define/show a pushbutton # # Arguments: # type Type of button # command Command associated with it (optional) # # Result: # None # # Side effects: # Pushbutton created # proc ::Decide::button {type {command {}}} { switch -- $type { "previous" { set command "::Decide::previousWindow" set text "<<" } "next" { set text ">>" } "done" { set command "exit" set text "Done" } default { tk_messageBox -code error -message "Unknown button type: $type" exit } } ::button .frame2.button$v::wcount -text $text -font "Helvetica, 14" \ -command [list namespace eval ::Decide $command] append v::buttons ".frame2.button$v::wcount " incr v::wcount } # show -- # Show a particular window # # Arguments: # name Name of the window # # Result: # None # # Side effects: # Entry in array window filled # proc ::Decide::show {name} { if { [info exists v::window($name)] } { foreach child [winfo children .frame1] { destroy $child } foreach child [winfo children .frame2] { destroy $child } set v::wcount 0 set v::buttons {} namespace eval ::Decide $v::window($name) if { $v::buttons ne "" } { eval grid $v::buttons } } else { tk_messageBox -type ok -icon error -message "No window defined by the name '$name'" exit } wm title . $v::window_title($name) lappend v::previous_window $name } # previousWindow -- # Show the previous window # # Arguments: # None # # Result: # None # # Side effects: # Previous window shown and "previous_window" updated # proc ::Decide::previousWindow {} { set prev [lindex $v::previous_window end-1] set v::previous_window [lrange $v::previous_window 0 end-2] show $prev puts >>$v::previous_window<< } # mainWindow -- # Set up the main window # # Arguments: # None # # Result: # None # # Side effects: # Main window set up with two frames # proc ::Decide::mainWindow {} { wm geometry . 400x250 frame .frame1 frame .frame2 pack .frame1 -side top -fill x -padx 4 pack .frame2 -side bottom -fill x -padx 4 } # main -- # test the code so far # if { 0 } { ::Decide::mainWindow ::Decide::window x "Title" { text "Welcome to this simple" text "Decision tree" choice v { red "Red" blue "Blue" green "Green text" } button next { show y } } ::Decide::window y "Title" { text "The end" button previous button done } ::Decide::show x } ::Decide::mainWindow namespace eval ::Decide { source [lindex $argv 0] }
The simple example:
# example.app -- # Simple example of the application of "decision.tcl": # Compute the area and volume of three simple geometrical # shapes # window "type" "Select the type" { text "Type of geometrical body:" choice type { cube "Cube" block "Block" sphere "Sphere" } button next { show $type } } window "cube" "Size of the cube" { text "Please enter the side of the cube:" entry "Size: " size button previous button next { set area [expr {6.0*$size*$size}] set volume [expr {$size*$size*$size}] show "result" } } window "block" "Dimensions of the block" { text "Please enter the three sides of the block:" entry "Length: " length entry "Width: " width entry "Height: " height button previous button next { set area [expr {2.0*($length*$width+$length*$height+$width*$height)}] set volume [expr {$length*$width*$height}] show "result" } } window "sphere" "Size of the sphere" { text "Please enter the radius of the sphere:" entry "Radius: " radius button previous button next { set area [expr {4.0*acos(-1.0)*$radius*$radius}] set volume [expr {4.0*acos(-1.0)*$radius*$radius*$radius/3.0}] show "result" } } window "result" "Area and volume" { text "Area:" "$area" text "Volume:" "$volume" button previous button done } # # Start the application # show "type"
fred_stmk - 2010-03-24 14:59:33
<could you please add a sample file for an unskilled user?>
AM Sure, but how much simpler can I make it? There have to be some decisions and some calculations, otherwise it gets really boring. Can you describe an example that you would like to see turned into this form?