...dem Anwender die Möglichkeit geben, sein Formular selbst zu gestalten. Er soll sich seine Spezialwünsche selbst realisieren können, und mich nicht damit aufhalten, einige Textboxen von da nach dort zu schieben oder irgendwelche Farben nach seinem Geschmack zu Gestalten. Vielleicht will er auch einige Felder hinzufügen und diese auf seinem Formular dann natürlich auch anzeigen und bearbeiten.
Wer mit Outlook etwas häufiger arbeitet, wird diese Funktion dort kennen, wo ein Anwender sich seine eigenen Formulare anlegen und bearbeiten kann:
Einge Menge Arbeit das mit VFP zu realisieren? Bestimmt schon einiges. Das entscheidende ist aber ein richtiges Konzept und die richtige Klassenstruktur. Hier will ich eine Möglichkeit vorstellen:
Wann immer ein Objekt die Anfrage macht: „ich möchte diese Eigenschaft von mir abspeichern“, muss dies zuerst dazu führen, dass in einer dafür vorgesehenen Objekt-Tabelle (im Folgenden kitobj genannt) der Datensatz für dieses Objekt gefunden oder aber neu angelegt wird. Ist die Tabelle noch nicht vorhanden, müsste diese erzeugt werden, z.B. so:
CREATE TABLE (tcFileName) FREE (objScx c(50),objref c(120),objprop m,objIndex i)
lcOldCollate = SET("COLLATE")
SET COLLATE TO "MACHINE" && Andernfalls wird Indexlänger überschritten
INDEX ON lower(objScx+objref) FOR !DELETED() TAG refname
INDEX ON lower(objscx+PADR(LEFT(objref,RAT('.',objref)-1),100))+;
STR(objIndex) FOR !DELETED() TAG refindex
SET COLLATE TO lcOldCollate
Man sieht an der obigen Struktur, welche Felder bzw. Indices notwendig wären, um auf diese Weise ein Objekt eindeutig zu identifizieren und zu suchen.
Beide zusammengenommen ergeben dann eine eindeutige Bezeichnung des Objekts.
Bevor nun die Eigenschaften abgespeichert oder gelesen werden können, muss der dafür vorhandene Datensatz gelesen werden. Wenn es sich um ein neues Objekt handelt, darf er neu angelegt werden, sonst nicht.
lcRef = SYS(1272,tuObject)
lcScx = this._GetFilename(SYS(1271,tuObject))
lcSeek = LOWER(PADR(lcScx,50)+PADR(lcRef,100))
tuObject = .NULL.
SET ORDER TO TAG refName IN kitobj
SEEK lcSeek
IF !FOUND()
INSERT INTO kitobj (objref,objscx,objindex) ;
VALUES (lcRef,lcscx,tnIndex)
ENDIF
Nun bleibt noch die Frage, wohin die Eigenschaftswerte gespeichert werden. Ich schreibe sie in meinem Beispiel – ähnlich wie das VFP selbst in den VCX und SCX-Dateien tut – in ein Memofeld untereinander. Dabei ist natürlich zu berücksichtigen, dass ich in das Memofeld nur Strings speichern kann. Es empfiehlt sich, für einen solchen Vorgang eine eigene Grid-Methode anzulegen, die dann jeweils für jede Eigenschaft aufgerufen werden kann und einen String zusammenbaut, der dann schließlich in das Memofeld abgelegt wird.
*-- String für Wert bauen
lcType = VARTYPE(tuValue)
DO CASE
CASE lcType='L'
lcValue=IIF(tuValue,'.T.','.F.')
CASE lcType='N'
lcValue=ALLTRIM(STR(tuValue))
CASE lcType='C'
lcValue = '"'+tuValue+'"'
ENDCASE
*-- Positionierung im MemoFeld bzw. tcPropString einbauen
lnLine = ATLINE(upper(tcprop)+'=',upper(kitobj.objprop))
IF TYPE('tcPropString')='L'
lcOldString = kitobj.objprop
ELSE
lcOldString = tcPropString
ENDIF
IF lnLine # 0
*-- 1. Fall: Eigenschafts-Eintrag existiert schon.
lcOldLine = MLINE(lcOldString,lnLine)
IF !tlNonOverwrite
*-- Mit diesem Parameter wird gesteuert ob ggf. von bestehenden
*-- Eigenschaften, der Wert überschrieben werden soll.
lcNewLine = STUFF(lcOldLine,AT('=',lcOldLine)+1,254,lcValue)
lcNewString = STUFF(lcOldString,AT(lcOldLine,;
lcOldString),LEN(lcOldLine),lcNewLine)
ELSE
lcNewLine = lcOldLine
lcNewString = lcOldString
ENDIF
ELSE
*-- 2. Fall: Eigenschaftseintrag existiert noch nicht.
lcNewLine = IIF(EMPTY(lcOldString),'',CHR(13))+;
tcProp + '=' + lcValue
lcNewString = lcOldString + lcNewLine
ENDIF
IF TYPE('tcPropString')='L'
*ein Parameter, der Steuert, ob diese Eigenschaft nur an
*einen String gehängt werden soll. Erst bei der letzten Eigenschaft
*wird abgespeichert in die Tabelle.
REPLACE objprop WITH lcNewString IN kitobj
ELSE
tcPropString = lcNewString
ENDIF
Diese Methode sollte erst bei der letzten Eigenschaft in die Tabelle gespeichert werden (Aufruf ohne den
Parameter tcPropString). Dies muss bei der aufrufenden Stelle erledigt werden.
1. tcProp --> Der Eigenschaftsname
2. tuObject --> Die Objektreferenz
3. tnIndex --> Index des Objektes in tuObject (optional)
4. tnNewMember --> Was ist das Objekt (optional)
0 = Objekt existiert bereits
1 = Objekt existiert noch nicht
2 = For Later use
LPARAMETERS tcProp,toObject,tnIndex,tnNewMember
LOCAL lcLine
*-- Wenn das Objekt mit übergeben wurde, signalisiert dies,
*-- daß der richtige Datensatz in kitobj noch nicht ausgewählt wurde,
*-- und gesucht werden muß.
*-- Wenn tnIndex mit übergeben wird, existierte das Objekt noch nicht
*-- und es konnte nur dessen Parent als Objekt übergeben werden
lcLine = MLINE(kitobj.objprop,;
ATLINE(lower(tcprop),lower(kitobj.objprop)))
IF EMPTY(lcLine)
*-- Die Eigenschaft tcprop ist nicht vorhanden
RETURN .null.
ELSE
lcLine = ALLTRIM(SUBSTR(lcLine,AT('=',lcLine)+1))
RETURN EVAL(lcLine)
ENDIF
Im Folgenden findet sich die Methode, wie sie nötig wäre, um sämtliche Eigenschaften eines Objekts zu lesen und einzustellen, die in der Objekt-Tabelle abgelegt sind.
LPARAMETERS toObject,tnIndex
LOCAL lcLine,lcProp,lcCommand,lcParent,lcValue,lcObject
*-- Auf den richtigen Datensatz in KITOBJ positionieren
IF !this.objectGetRecord(toObject,tnIndex)
RETURN .F.
ENDIF
IF TYPE('thisform.parent') # 'O'
*-- Formular ist in keinem Formset enthalten
lcParent = 'thisform'
ELSE
*-- Formular ist in Formset enthalten
lcParent = 'thisformset'
ENDIF
*-- Für alle Zeilen des Memofeldes
FOR licount = 1 to MEMLINES(kitobj.objprop)
lcLine=MLINE(kitobj.objprop,licount) && Zeile
lcProp=LOWER(LEFT(lcLine,AT('=',lcLine)-1)) && Eigenschaft
lcValue=EVAL(SUBSTR(lcLine,AT('=',lcLine)+1))&& Wert der Eigenschaft
lcObject=SYS(1272,toObject) && Objekthierarchie
*-- Für die Bestimmung des ersten Eintrags von lcObject
*-- muß dieser nun durch thisform oder thisformset ersetzt werden.
lcObject=STUFF(lcObject,1,AT('.',lcObject)-1,lcParent)
*-- Auszuführendes Kommando zusammenbauen
lcCommand=lcObject + '.' + lcLine
*-- Wenn die Eigenschaft nicht schreibgeschützt
*-- und nicht protected ist:
IF !PEMSTATUS(toObject,lcProp,1)
IF !PEMSTATUS(toObject,lcProp,2)
&lcCommand &&Kommando-Ausführung
ENDIF
ENDIF
ENDFOR
Eine Möglichkeit bestimmte Eigenschaften nun den Anwender verändern zu lassen bestünde in Menüpunkten in einem Kontext-Menü (auf der rechten Maustaste). Im Folgenden Beispiel haben wir Menü erzeugt mit dem Namen _popScm:
DEACTIVATE POPUP _popScm
DEFINE POPUP _popScm ;
FROM MROW(),MCOL() ;
MARGIN ;
RELATIVE ;
SHORTCUT
**** Menü-Definition
DEFINE BAR 1 OF _PopScm PROMPT 'Beenden'
DEFINE BAR 2 OF _PopScm PROMPT 'Größe speichern'
ON SELECTION BAR 1 OF _PopScm _screen.activeform.QueryUnload()
ON SELECTION BAR 2 OF _PopScm _screen.activeform.oPsc.Save('Size',;
_screen.activeform)
**** Menü-Auswahl
ON SELECTION POPUP _popScm DEACTIVATE POPUP _popScm
ACTIVATE POPUP _popScm
RELEASE POPUP _popScm
IF BAR()=0
RETURN .F.
ENDIF
In diesem einfachen Kontextmenü gibt es einen Menüpunkt zum Beenden des Formulars und einen zum Abspeichern der Größe des Formulars. Es wird jeweils ein Objekt oPsc angesprochen. Unter diesem Namen ist das Objekt zum Verwalten von Eigenschaften in das Formular hineingelegt. Die angesprochene Methode Save wäre hier die Methode zum Abspeichern der Eigenschaft. Dabei nimmt sie als ersten Parameter die gewünschte Eigenschaft oder aber die Reihe von Eigenschaften entgegen (hier „Size“) und als zweiten Parameter das Objekt, dessen Eigenschaften gespeichert werden sollen.
Achtung:
Sie können sich bei der ON SELECTION-Anweisung nicht auf thisform. beziehen, wenn Sie eine Formularmethode ansprechen wollen, da der dieser Befehl gewissermaßen von „außen“ (also außerhalb von „thisform“ aus) ausgeführt wird. Daher wird auf das Formular und dessen Methoden mit _screen.activeform referenziert.
Wenn Sie das Bewegen von Objekten im Formular freigeben wollen (und anschließend das Abspeichern der Formularposition), sollten Sie u.U. die Möglichkeit geben, dass die Objekte in einem bestimmten Raster bleiben.
Dazu justieren Sie die neu eingestellte Objektposition nach, indem Sie beispielsweise über die INT()-Funktion den ganzzahligen Wert einer Division durch den Rasterwert verwenden als justierter Objektpositionswert.
Dann geben Sie dem Objekt aktuell die nachjustierten Eigenschaften (von Top, Left, evtl. auch Width, Height) und speichern diese erst danach ab.
Beim Wiederherstellen verwenden Sie die Objects()-Eigenschaft. Hier sind Objektreferenzen auf sämtliche Unterobjekte des Formulars vorhanden. Prüfen Sie dabei bei jedem Objekt, ob es wieder Unterobjekte hat etc.
Bei jedem Objekt müsste nun in der Tabelle gelesen werden, ob ein Datensatz dafür existiert. Ist dies der Fall müssen alle Eigenschaften wieder eingestellt werden unter Verwendung der oben beschriebenen Methoden.