Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A TCL-TK Form Generator

0.00/5 (No votes)
30 Sep 2019 1  
A Tcl -Tk Form Generator that can be used alone or used to provide a fast Form generation for language in which this activity can be complex

Introduction

This article is about a form generator[1] (TK FormGen) developed in Tcl - Tk for creating and managing Forms from simple message box to relatively complex forms with text fields, combos, radio buttons and so on. TK FormGen can be used by TCL applications or invoked by other languages, in particular, those that don't have a native GUI; here, rather than an exhaustive presentation of the product[2] it is illustrated how it can be used by other languages.

Using the Program

The form generator is composed by the scripts fGen.tcl that contain the procedure for generating a Form and fGenProc.tcl which contains some procedures; this last is automatically included by fGen.tcl.
The form generator can be called:

  • by a Tcl script:
    proc cBack {data} {
    	foreach id [dict keys $data] {puts "$id\t : [dict get $data $id]"}
    	exit
    }
    append src [file dirname [file normalize [info script ]]] "/fGen.tcl"
    source $src
    set ask "Title,Form generalities;Ground,silver;"
    append ask "CMB,Theme,,,|alt|clam|classic|default|vista|winnative|xpnative;"
    append ask "CMB,Ground,,,|cyan|gray|magenta|olive|silver|teal;"
    append ask "CMB,Form,Form type,,|T:Texts|B:Buttons|C:Combo boxes|F:Files 
           and folders|S:Scales and combos;"
    append ask "Default,Theme:clam,Ground:teal;"
    formGen[list $ask cBack]
  • directly from command line, e.g. tclsh fgen.tcl "T,name,User name;P,psw,Enter password" data.json
  • by another language

Using Form Generator With Some Languages

Image 1

The examples deal with a form (see image above) that can be used for test widgets and also for exploring different form backgrounds and themes.

When the form is closed, the data are sent in Json format to standard output to be transformed in internal variables of the host language; below the list, contained in the file params.txt, for creating the form:

CMB,Theme;# themes are set at the beginning with the available themes;
Labels,Right, :;
Comment,Hexadecimal color value must have the form #RRGGBB;
CMT,Ground,,,|cyan|gray|magenta|olive|silver|teal|#C0D0E0;
CMB,Form,
Form examples,,|Texts|Buttons|Combo boxes|Files and folders|Scales and Check box;
T,try,Try this,400;
Default,Theme:clam,Ground:teal;
Validate,Ground is ^#[0-9a-fA-F]{6}$,Not valid color;
Default,try:C#2CThis is a form for test some widgets\nCondor Informatique - Turin#3b
CMB#2CGround#2C#2C#2C|cyan|gray|magenta|olive|silver|teal|#C0D0E0;

In all the languages with which TK FormGen was tested, the form was generated by executing the TCL interpreter via external command and data was read from the standard output and transformed in internal variables.
The command sent is: tclsh fGen.tcl params.txt handler.tcl::tryForm handler.tcl::handle where tclsh is the Tcl interpreter, fGen.tcl is the Tcl script that it generates a Form if it is invoked with appropriate parameters. handler.tcl contains a procedure handle for manage events, in particular, the event Start and the event Select of combo Form. The procedure tryForm is invoked when the form is closed and it generates a new form taking the data from the field try.

namespace eval handler {
	variable examplesMap [dict create \
			"Texts" "Text,Text;P,psw,Password,,Almost 6 characters;T,
             Mail,E mail,18;N,Number;IN,Integer,Integer Number;\
		     DN,DecNumber,Decimal Number;A,textArea,Text area,5;;
             Default,Mail:ross@lib.it,Integer:7,Number:-11,DecNumber:3.1415;" \
			"Scales and Check box" "CMB,greeck,Greek letters,,
             A:Alfa|Beta|G:Gamma|Delta;CMT,spinBox,Combo and text,,
             A:Alfa|Delta|G:Gamma|Omega|T:Tau;\
				Scale,Scale2,Scale 2,222,1 -1;QS,Urgency,,,
                White|Green|Yellow|Red;Default,Scale2:-0.35,Urgency:Yellow,ckBox:1; \
				Check,ckBox,Check box,,Accept news;Check,ckBox2,Check box2,,Agree;" \
			"Buttons" "P,psw,Password,18;RDB,Gender,,,M:Male|F:Female|
             U:Unspecified;B,infoButton,\u24d8,,infoPsw;After,infoButton,psw;\
				B,Start,\u270E;B,fg_Cancel,\u2718;B,fg_Reset,\u21B6;
                Default,Gender:M;Required,psw;Validate,psw is .{6#2c},Password too short;\
				RDB,Langue,,,us:img\\uss.png|fr:img\\frs.png;"\
			"Combo boxes" "CMB,greeck,Greek letters,,A:Alfa|Beta|G:Gamma|Delta;CMT,
             spinBox,Combo and text,,A:Alfa|Delta|G:Gamma|Omega|T:Tau;"\
			"Files and folders" "File,File,,40,Images:*.gif *.jpg *.png,All:*;
             Default,File:C:\\www\\condorinformatique\\nodejs\\tcl;D,
             Folder,,30;WF,wFile,Write File;"]
}
proc handle {e} {
	lassign $e event path name
	if {$event == "Start"} {
		# populate the combo Theme with themes supported 
		fg_setCombo Theme "[join [ttk::style theme names] "|"]"
	}
	if {$name == "Form" && $event == "Select"} {
		# Insert in text area a sample of form
		if {[valueOf $name] != ""} {setValue try 
           "[dict get $::handler::examplesMap [valueOf $name]]"}
	}
}
proc tryForm {data} {
	append prefix "Title,Form example of " [dict get $data "Form"] ";Ground," 
           [dict get $data "Ground"] "," [dict get $data "Theme"] ";Labels,Right, :;"
	set systemTime [clock seconds]
	append prefix "H,hField,[clock format $systemTime];" [dict get $data "try"]
	formGen [list "$prefix"]
}

Below is an example of output (Ruby).

>ruby tcltk.rb
{
  "fg_Button" => "Ok",
  "Text" => "Text",
  "psw" => "Lafayette77",
  "Mail" => "ross@lib.it",
  "Number" => -11,
  "Integer" => 17,
  "DecNumber" => 3.1415,
  "textArea" => "big\ttext\n\n"
}
Sum: 9.141500
>Exit code: 0

Using With Languages

C++

The use of FGen in C ++ 11 was the most laborious; C++ was chosen instead of C for its support of regular expressions and map structure.

I didn't use the Json libraries to avoid including third-party libraries, on the other hand, I only needed to read a simple key-value structure so I preferred to use a regular expression to extract the key-value pairs and insert them in a map (function json2Map).

// C++ 11
#include <bits stdc="">
using namespace std;
// function to find all the matches
map<string, string=""> <code>json2Map</code>(string jsonData) {
    map<string, string=""> dataMap;
    string regExspr("\\\"(\\w+)\\\":\\s*(\\\"([^\"]*)\\\"|(([+-]?\\d*)(\\.\\d+)?))");
    regex re(regExspr);
    for (sregex_iterator it = sregex_iterator
        (jsonData.begin(), jsonData.end(), re);it != sregex_iterator(); it++) {
            smatch match;
            match = *it;
            int indexValue = 3;
            if (match.str(3) == "") indexValue = 2;
            cout << match.str(1) << "\t" << match.str(indexValue) << endl;
            dataMap[match.str(1)] =  match.str(indexValue);
    }
    return dataMap;
}
int main( int numberArgs, char* argsList[] ) {
    vector<string> params{ "fGen.tcl","T,t1;N,n1", "", "" };
    int commandLength = 40; // roughly default + fixed (i.e. tclsh ... >j.json)
    for (int i=1; i<numberargs; i=""> j.json", params[0].c_str(), 
         params[1].c_str(),params[2].c_str(),params[3].c_str());
    int status = system(tcl);
    ifstream jsonFile("j.json");
    string jsonData {istreambuf_iterator<char>(jsonFile), istreambuf_iterator<char>()};
    cout << jsonData << endl;
    map<string, string=""> dataMap = json2Map(jsonData);
    if (dataMap.count("Integer") > 0) {
        cout << "Sum: " << (atof(dataMap["Integer"].c_str()) + 
        atof(dataMap["Number"].c_str()) + atof(dataMap["DecNumber"].c_str())) << endl;
    }
	return 0;
}
Node
const args = process.argv.slice(2)
const {exec} = require('child_process');
var command = 'tclsh ' + args[0] + ' "' + args[1] + '" ' + args[2] + ' "' + args[3]
const tcl = exec(command, function (error, stdout, stderr) {
	if (error) {    
		console.error(`exec error: ${error}`);
		return;
	}
	console.log('Child Process STDOUT: '+stdout); 
});
let jsonData = '';
tcl.stdout.on('data', (chunk) => {
  jsonData += chunk.toString();
});
tcl.on('exit', () => {
	console.log(jsonData);
	var data = JSON.parse(jsonData)	// the data becomes a JavaScript object 
	if ("Integer" in data) {
			console.log("Sum " + (data.Integer + data.Number + data.DecNumber))
	}
});
Python
import os
import subprocess
import json
def main():
    pass
os.chdir("C:\\Sviluppo\\fGenTk\\")
p = subprocess.Popen("tclsh fGen.tcl params.txt handler.tcl::tryForm handler.tcl::handle", 
                      shell=True, stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
data = json.loads(stdout.decode('utf-8'))
print(data)
if 'Integer' in data:
   print ("Sum: {}".format(data['Integer'] + data['Number'] + data['DecNumber']))
if __name__ == '__main__':
    main()
R
library("rjson")
setwd("C:\\Sviluppo\\fGenTk")
parameters <- c("fGen.tcl", "params.txt", "handler.tcl::tryForm", "handler.tcl::handle")
json <- system2("tclsh",parameters,stdout=TRUE)
tmp <- tempfile()
cat(json,file = tmp)
data <- fromJSON(readLines(tmp))
data$fg_Button
if ("Integer" %in% names(data)) 
	cat("Sum:",data$Integer + data$Number + data$DecNumber,"\n")
Ruby

Ruby has perhaps the easiest use of TK Fgen, with only two instructions, we get form and data:

require 'json'
require 'FileUtils'
FileUtils.cd('c:/Sviluppo/fGenTk')
output = `tclsh fGen.tcl params.txt handler.tcl::tryForm handler.tcl::handle`
data = JSON.parse(output) if output != ""
puts JSON.pretty_generate(data).gsub(":", " =>")
if data.has_key?("Integer") then
	puts sprintf("Sum: %f",data['Integer'] + data['Number'] + data['DecNumber'])
end

Note

  1. ^ This is one of some form generators (for Autoit, Powershell, B4A, B4J and JavaScript) which can be found on my site.
    Also, there are some articles on Codeproject:
    1. A JavaScript Form Generator
    2. A Form Generator for Android
    3. A PowerShell Form Generator [Tip/Trick]
  2. ^ The package and documentation can be download here.

History

  • 30th September, 2019: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here