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
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
).
#include <bits stdc="">
using namespace std;
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
- ^ 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:
- A JavaScript Form Generator
- A Form Generator for Android
- A PowerShell Form Generator [Tip/Trick]
- ^ The package and documentation can be download here.
History
- 30th September, 2019: Initial version