Transformatortool Xbase ++ to Express

Ergebnis der Transformationsarbeit von XPP2DC ist "Express-Code" in der einfach zu lesenden @ x,y, Notation, der funktionsgleich zu nativen Xbase++ arbeitet, aber wesentlich einfacher zu pflegen ist.

Beispiel: Zusammenarbeit mit dem XPP-Formdesigner

Folgende Beispielmaske wurde mit dem XPP-Formdesigner erstellt:

Aus dieser Maske wurde nun mit dem Transformatortool Xbase-Code für Express erstellt, der nach Compilierung und dem Hinweis, daß 3 SLEs der Ausgangsmaske nicht aufgelöst werden konnten und manuell nachzubearbeiten sind, folgende Maske zum Ergebnis hat.

Der Grund, daß die transformierte Maske etwas größer ist als die Ausgangsmaske ist folgender: Die Schriftart für die Says und "Get-Felder" wird in dem erzeugten Express-Code durch den Rückgabewert der Funktionen
    func DcSayFont() und
    func DcGetFont()
bestimmt. Die Funktion DcGetFont lieferte für dieses Beispiel den  Fixedspace-Font Courier 10 zurück, daher muß das Format der transformierten Maske natürlich etwas größer als in der XppFd-Ausgangsmaske sein, die für die SLE´s einen Proportional-Font vorsah.

Der Zeichenabstand für die Berechnung der Zeichenabstände kann in Express jedoch durch die die "GetOption" COLPIXELS festgelegt werden, so daß auch das originale Erscheinungsbild der Maske mit nahezu identischen Abmessungen hergestellt werden könnte, wenn gewünscht.

Der in der Maske oben eingefügte Platz ist dafür vorgesehen, beim Einsprung in ein GET-Feld dort einen entsprechenden Eingabehinweis anzeigen zu können. Alle transformierten GET-Felder sind mit einer entsprechenden Funktion vorbelegt.

Der erzeugte Code enthält automatisch erzeugten Var-Declarationen für den Zugriff auf die Checkboxen, Sle´s usw. Die  Kommentare wurden automatisch erzeugt. Hier ein Ausschnitt aus dem erzeugten Code:

PROC Xpp2DC_MIETER( oParent, ABS, nIndex, lEdit, lNeu ) // please customize parameters
LOCAL getOptions
LOCAL lContinueOk
LOCAL oOldSetAppWindow := SetAppwindow( oParent )
LOCAL cMember
LOCAL ii
LOCAL iMax
LOCAL aAllInputFieldNames := {}
LOCAL aAllInputFields := {}
LOCAL xVal
LOCAL aXppIvars
LOCAL oXpp2DC
LOCAL oReUseCode
LOCAL lNachmieter := .F. 		// ganz nach gusto
LOCAL lVor__u__Nachmieter := .F. 	// ganz nach gusto
LOCAL lZwischenablesung := .F. 		// ganz nach gusto
LOCAL lHauptablesung := .F. 		// ganz nach gusto
LOCAL lWasser_ohne_ZWAB := .F. 		// ganz nach gusto
LOCAL lHeizung_ohne_ZWAB := .F. 	// ganz nach gusto
LOCAL lkeine_Berechnung := .F. 		// ganz nach gusto
LOCAL lEigentuemer_einfuegen := .F. 	// ganz nach gusto
LOCAL lMieter_einfuegen := .F. 		// ganz nach gusto
LOCAL aVarCheckBox := {; 		// Workaround, because not able to resolve Varnames
			{'lNachmieter', 3 }, ;
			{'lVor__u__Nachmieter', 3 }, ;
			{'lZwischenablesung', 3 }, ;
			{'lHauptablesung', 3 }, ;
			{'lWasser_ohne_ZWAB', 3 }, ;
			{'lHeizung_ohne_ZWAB', 3 }, ;
			{'lkeine_Berechnung', 3 }, ;
			{'lEigentuemer_einfuegen', 3 }, ;
			{'lMieter_einfuegen', 3 };
		}
LOCAL oXbpStatic_1 						// for a Static
LOCAL oXbpStatic_Nutzerwechselgebuehr_berechnen 		// for a Static
LOCAL oXbpStatic_Ableseart_fuer_Nutzerwechsel_bestimmen 	// for a Static
LOCAL oXbpStatic_Neuen_Nutzer_oder_Eigentuemer_einfuegen 	// for a Static
LOCAL oXbpStatic_Ordnungsbegriffe_DTA 				// for a Static

Hier ein anderer Ausschnitt:

	@ 5.8, 61.3 DCSTATIC
	    XBPSTATIC_TYPE_GROUPBOX ;
	    SIZE 44.0, 3.6 ;
	    OBJECT oXbpStatic_Ableseart_fuer_Nutzerwechsel_bestimmen ;
	    CAPTION 'Ableseart für Nutzerwechsel bestimmen' ;
	    TOOLTIP { || '' }
	@ 6.6, 62.7 DCCHECKBOX lZwischenablesung ; // PleaseCheck llZwischenablesung := Logical
	    PROMPT 'Zwischenablesung' ;
	    OBJECT oReUseCode:lZwischenablesung ;
	    ACTION {|| NIL } ;
	    ;
	    GOTFOCUS { | uNIL1, uNIL2, oSle | DC_EHWg( oDlgEdit, oSle, "Eingabefeld: <Return>Weiter <Esc>Abbruch", , ,;
				.f., , , ) ;
	    } ;

Die Verfahrensweise bei der Transformation im Einzelstepmodus:

Der XPP-Formdesigner erstellt folgenden Code aus der XFF-Datei:

******************************************************************************

* This class is derived from the implementation-level class of the form.
* Instance variables are declared in the _NewForm class.

******************************************************************************

CLASS NewForm FROM _NewForm
EXPORTED:
	METHOD init
	METHOD create
ENDCLASS

******************************************************************************

* Initialize form

******************************************************************************

METHOD NewForm:init( oParent, oOwner, aPos, aSize, aPP, lVisible )

	* Execute method of the super class
	::_NewForm:init( oParent, oOwner, aPos, aSize, aPP, lVisible )

RETURN self


******************************************************************************

* Request system resources

******************************************************************************

METHOD NewForm:create( oParent, oOwner, aPos, aSize, aPP, lVisible )

	* Execute method of the super class
	::_NewForm:create( oParent, oOwner, aPos, aSize, aPP, lVisible )

	* Open databases and assign work area
	USE MIETER NEW EXCLUSIVE
	::MIETER := Select()

	* Transfer values to EditControls
	AEval ( ::EditControls, { | oXbp | oXbp:SetData() } )

	* Display the form
	::show()

RETURN self

* Include program code for the implementation-level class of the form
#include "_MIETER.PRG"

Ich modifiziere dann die Datei zum Test der Form MIETER.PRG auf folgende Art und produziere dann damit den Express-Code :

PROC MAIN()

	oDlg:= NewForm():new()
	oDlg:create()
	oDc :=;
		Xpp2DC():new(;
			{"NewForm" },; 	// 1 or more classes used for Dialogs, which are to transformed to DC-Code
			NIL,; 		// Name of 2-dim array I used to put my fields in
			NIL,; 		// PreFix for Defines when they are inherited from Fieldnames like this #define SYM_CONST_FOR_CLIENTNAME
			"oDlg" ) 	// Name for the Xbp-window I used in my callbackslots killInputfocus
				 	// and setInputfocus e.g.: { u1, u2, oSle|| MyFunc( oDlg, oSle ) }
	oDc:collect( oDlg ) 		// oDlg is the XbpDialog or XbpCrt you want to translate in DC-Style
	oDc:close() 			// Adds Header
	oDc:show() 			// writes the result in "Xpp2Dc_.PRG"
RETURN

Ich binde dann die erzeugte Datei "Xpp2Dc_.PRG"  in das Link-Script und ändere die Procedure Main wie folgt, dann ist es soweit :

PROC MAIN()
	Xpp2DC_MIETER( setAppWindow(), NIL, NIL, .T., .F. )
RETURN

Drei SLEs  konnten in der obigen Beispielmaske vom XPP2DC-Transformatortool nicht aufgelöst werden. Der Grund war jeweils, daß im XppFd für diese SLEs kein :datalink angegeben wurde. Entsprechende Express-GETs wurden trotzdem erzeugt (so hat man wenigstens schon mal die genaue Position der SLEs im Express-Code), mussten aber von mir manuell auskommentiert werden. Dann compilierte es fehlerfrei durch.

Dann noch linken und das Ergebnis ist oben zu sehen. Dauer der Transformation bis zum Ergebnis: Ca. 5 Minuten vom nach dem Erstellen der XppFd-Form an gerechnet.

Mit der Transformation dieser Maske hat XPP2DC zwar schon einiges getan, aber dieses Tool kann noch mehr:

Transformation von "handcodierten" Xbase-pur Masken

(Mit handcodierten Xbase-Pur-Masken meine ich Masken, die nicht mehr in dem Format vorliegen, wie es auf dem Formdesigner rauskam, sondern die schon angepasst worden. Beispielsweise kann das die Einbindung neuer Klassen sein, z.B. DataDlg oder auch die nachträgliche Bestückung von Slots wie validate, killinputfocus, usw. mit Codeblöcken. Auch können das Masken sein, die gleich ganz ohne den Formdesigner erstellt worden sind.)

Der besondere Witz des XPP2DC-Tools  liegt in einer speziell auf die spezifische Maske abgestimmten statischen "Express<->Xbase-Pur"-Kompatibilitätsklasse

	oReUseCode:= oReUseCode():new()
	oReUseCode:create()

die für jede auch noch so komplex aufgebaute Xbase-Maske vom Tool erzeugt wird und die Weiterverwendung des mit der alten Xbase-Pur-Codes und der alten XppForm zusammenhängenden Codes (sofern solcher schon geschrieben wurde) ermöglicht! Und das (oft) ohne* Eingriffe am Code und dies natürlich unter Verwendung der Expressmaske und auch im Zusammenhang mit der neuen Expressmaske.

* Alle in Callback-Slots wie :killInputFocus(), :setInputFocus(), Methoden wie :setdata() :getdata() und IVars wie validate der zu transformierenden Maske implementierten Codeblöcke und Funktionsaufrufe werden nicht nur in den Express-Code übernommen, sondern funktionieren im allgemeinen auch weiter so wie bisher. Dies ist nicht selbstverständlich, denn die aufgerufenen Funktionen greifen ja nach wie vor auf  die IVars, Methoden und Slots der alten "Xbase-Pur"-Objekte zu, und nicht auf die neuen von Express-Objekte. Eine kleine Einschränkung gibt es allerdings: Aus Codeblocks heraus aufgerufenen Funktionen, die die Inhalte der Maske ändern, müssen an die die neue Express-GET-Objekte angepasst werden. Der Aufwand ist jedoch sehr überschaubar. In den aufgerufenen Funktionen müssen (soweit ich erkennen kann) nur noch die Aufrufe von XbpSLE:setdata() angepasst werden-> o:setdata( "Neuer Wert") wird zu o:XbpSle:Setdata( "Neuer Wert"). Dies jedoch kann wohl kaum jemals durch ein Tool automatisiert werden, insofern leistet das doch Tool schon so ziemlich alles, was zur automatischen Transformation bei maximalem Erhalt und Weiterverwendung des Ausgangscodes überhaupt möglich ist.

Ein weiteres kleines Detail

Manche Programmierer bevorzugen es, nicht direkt auf der Datenbank zu editieren, sondern lesen die zu editierenden Daten erst einmal in ein Array, editieren das Array und schreiben dann alles komplett zurück. Etwa so:

#define SYM_NAME 1
#define SYM_VORNAME 2
#define SYM_ADRESSE 3

   aEdit := { {fieldget(SYM_NAME), fieldget(SYM_VORNAME ), fieldget(SYM_ADRESSE )}}

// Ich nehme hier mal gets mit read statt XbpSles und Eventloop, das ist kürzer
// und am Prinzip ändert sich ja dadurch nichts
   @ x++, y get aEdit[ nIndex,  SYM_NAME  ]
   @ x++, y get aEdit[ nIndex,  SYM_VORNAME ]
   @ x, y   get aEdit[ nIndex,  SYM_ADRESSE ]
   read
   fieldput( SYM_NAME ,    aEdit[ nIndex, SYM_NAME  ])
   fieldput( SYM_VORNAME , aEdit[ nIndex, SYM_VORNAME ])
   fieldput( SYM_ADRESSE , aEdit[ nIndex, SYM_ADRESSE ]) 

So wird vermieden, daß der Datensatz im Netz lange gelockt werden muß. Auf diese Arrays wird dann zwecks Lesbarkeit meist mit symbolischen Konstanten zugegriffen (wie oben, zumeist aber per .ch-files eingebunden). Diese symbolischen Konstanten sind natürlich zur Laufzeit des Programms (wenn das Tool diese Maske analysiert und daraus Express-Code erzeugt) aufgelöst.

XPP2DC erlaubt die "Rückwärtsauflösung" von Arrays, die zum Einlesen benutzt werden, so daß im erzeugten Code wieder gut lesbarer Code (mit symbolischen Konstanten) steht.

Ein nützlicher Nebeneffekt von XPP2DC:
FALLS BISHER KEINE SYBOLISCHEN KONSTANTEN IM CODE ZUM EINLESEN VON ARRAYS BENUTZT WURDEN, KANN MAN DAS TOOLS AUCH DAZU VERWENDEN, KONSTANTEN DURCH SYMBOLISCHE KONSTANTEN ZU ERSETZEN, INDEM MAN EINFACH PER CH-FILE DIE ERSETZUNGEN VORGIBT.