16 KiB
Deep-Dive: Fensterprogrammierung in Solution2
Warum dieses Tutorial?
Die Fensterprogrammierung in Solution2 ist einer der komplexesten Bereiche des Systems. Das liegt am rekursiven SubWindow-Muster: Fenster enthalten SubWindows, die wiederum SubWindows enthalten koennen - und alle muessen koordiniert kommunizieren.
Dieses Tutorial erklaert die Architektur von Grund auf, mit echtem Code aus dem Solution2-Repository.
1. Die 3-Stufen-Klassenhierarchie
Solution2 hat drei zentrale Fenster-Superklassen, alle in der solution2-Library unter SuperClasses:
wBaseWindow <-- Wurzel (keine Superklasse)
|
+-- wMainWindow <-- Hauptfenster (Auftraege, Teile, Rechnungen...)
|
+-- wSubWindow <-- Eingebettete Panels (Tab-Inhalte, Detail-Ansichten)
Visuelle Darstellung der Architektur
+============================================================+
| wMainWindow (z.B. Sales/wOrders) |
| extends wBaseWindow |
| |
| Instanzvariablen: |
| ioCode -> oOrders (Business-Logik) |
| iiRef2SubWindow -> Referenz auf aktives SubWindow |
| ibControl2SubWindow -> true/false (Wer hat Kontrolle?) |
| ilList, irRow -> Haupt-Datenliste/Zeile |
| inEDMode -> Aktueller Bearbeitungsmodus |
| |
| +------------------------------------------------------+ |
| | TabPane "TP" | |
| | | |
| | +-----------------------------------------------+ | |
| | | wSubWindow (z.B. Sales/wOrder) | | |
| | | extends wBaseWindow | | |
| | | | | |
| | | Instanzvariablen: | | |
| | | iiRef2Parent -> zurueck zum MainWindow | | |
| | | iiRef2SubWindow -> eigenes SubWindow | | |
| | | ibControl2SubWindow -> true/false | | |
| | | ioCode -> eigene Business-Logik | | |
| | | icFormWindow -> Name fuer Rechtepruefung | | |
| | | | | |
| | | +------------------------------------------+ | | |
| | | | wSubWindow (Ebene 3, z.B. wOrderItems) | | | |
| | | | extends wBaseWindow | | | |
| | | | | | | |
| | | | iiRef2Parent -> zurueck zu wOrder | | | |
| | | | ioCode -> oOrderItems | | | |
| | | +------------------------------------------+ | | |
| | +-----------------------------------------------+ | |
| +------------------------------------------------------+ |
+============================================================+
2. wBaseWindow - Die Wurzel
wBaseWindow ist die Wurzel-Superklasse ALLER Solution2-Fenster. Sie definiert die gemeinsame Infrastruktur:
Wichtige Instanzvariablen (geerbt von ALLEN Fenstern)
| Variable | Typ | Bedeutung |
|---|---|---|
ioCode |
Object-Ref | Business-Logik-Objekt (z.B. oOrders) |
iiRef2SubWindow |
Item-Ref | Referenz auf das aktive SubWindow |
ibControl2SubWindow |
Boolean | kTrue = SubWindow hat Kontrolle |
ilList |
List | Haupt-Datenliste des Fensters |
irRow |
Row | Aktuelle Daten-Zeile |
irRowSearch |
Row | Such-Kriterien |
inEDMode |
Integer | Aktueller Modus (Anzeige/Edit/Insert/...) |
icCurrTabPane |
Char | Name des aktuell aktiven Tab-Reiters |
Wichtige Methoden auf wBaseWindow
| Methode | Zweck |
|---|---|
$EnableTabPane(pEDMode) |
Sperrt/Entsperrt Tab-Reiter basierend auf Modus |
$setRowName(pName) |
Setzt den Namen der Row-Variable |
$setListName(pName) |
Setzt den Namen der List-Variable |
$translateObjects($cinst) |
Uebersetzt UI-Beschriftungen |
$EnableTabPane - Code
## wBaseWindow.$EnableTabPane:
# Freigabe und Sperren von TabPane in Abhaengigkeit von angewaehltem Bearbeitungsmodus
Do $cwind.$objs.TP.$enableTabPanes(pEDMode)
Merke: Das TabPane-Objekt heisst immer
TP- das ist Konvention in Solution2!
3. wMainWindow - Das Hauptfenster
wMainWindow erbt von wBaseWindow und ist die Superklasse fuer alle eigenstaendigen Dateneingabe-Fenster (Auftraege, Teile, Rechnungen, etc.).
Zusaetzliche Instanzvariablen
| Variable | Typ | Bedeutung |
|---|---|---|
irRowCount |
Row | Zaehler fuer Datensaetze |
irRowMax |
Row | Maximum-Zaehler |
irStatusBarPane |
Row | Statusleisten-Daten |
Das Command-Routing-Pattern
DAS ist das zentrale Muster! Jeder Befehl (Edit, Save, Cancel, Delete, Print...) wird nach dem gleichen Schema delegiert:
## wMainWindow.$cCancel:
Do $cinst.$closeRelatedWindow() ## 1. Zugehoerige Fenster schliessen
If $cinst.ibControl2SubWindow ## 2. Wer hat die Kontrolle?
Do iiRef2SubWindow.$cCancel() ## -> SubWindow!
Else
Do $cinst.ioCode.$cCancel() ## -> Business-Logik!
End If
Alle Commands folgen diesem Muster
| Methode | Aktion |
|---|---|
$cEdit |
Bearbeitung starten |
$cInsert |
Neuen Datensatz anlegen |
$cSave |
Speichern |
$cCancel |
Abbrechen |
$cDelete |
Loeschen |
$cPrint |
|
$cAudit |
Aenderungshistorie anzeigen |
$cNextVersion |
Naechste Version |
$cFirst / $cLast |
Navigation |
Essentielle Erkenntnis: wMainWindow ruft IMMER $closeRelatedWindow() VOR der Command-Delegation auf. Das schliesst alle zugehoerigen Popup-Fenster.
4. wSubWindow - Das eingebettete Fenster
wSubWindow erbt ebenfalls von wBaseWindow und ist die Superklasse fuer alle eingebetteten Panels/Tabs. Es ist der Schluessel zur rekursiven Architektur.
Zusaetzliche Instanzvariablen
| Variable | Typ | Bedeutung |
|---|---|---|
iiRef2Parent |
Item-Ref | Referenz ZURUECK zum Elternfenster |
icFormWindow |
Char | Name des Formulars (fuer Rechte-Check) |
icSubWindow |
Char | Name des SubWindow-Felds |
iiRef2Window |
Item-Ref | Referenz zum Hauptfenster |
Der Lebenszyklus eines SubWindows
Phase 1: $construct - Aufbau
## wSubWindow.$construct:
Do inherited ## 1. Geerbte Initialisierung
Set reference $cinst.iiRef2SubWindow to $cinst.$objs.wSubWindow.$ref ## 2. Eigenes SubWindow finden
Set reference $cinst.iiRef2Parent to pRef2Parent ## 3. Rueck-Referenz zum Eltern setzen
Do $cinst.$translateObjects($cinst) ## 4. UI uebersetzen
Calculate icFormWindow as pFormWiindow ## 5. Formular-Name merken
Do iiRef2Parent.$setControl2SubWindow(kTrue) ## 6. KONTROLLE UEBERNEHMEN!
Do $cinst.$setRowName('irRow') ## 7. Standard Row-Name
Do $cinst.$setListName('ilList') ## 8. Standard List-Name
Schritt 6 ist ENTSCHEIDEND! Hier meldet sich das SubWindow beim Elternfenster an: "Ab jetzt gehen alle Commands an MICH!"
Phase 2: Command-Delegation (identisch zum MainWindow)
## wSubWindow.$cEdit:
If $cinst.ibControl2SubWindow ## Hat DIESES SubWindow ein eigenes SubWindow?
Do iiRef2SubWindow.$cEdit ## -> Ja: Weiterleiten!
Else
Do $cinst.ioCode.$cEdit() ## -> Nein: Eigene Business-Logik!
End If
Phase 3: $destruct - Aufraemen
## wSubWindow.$destruct:
If iiRef2Parent
Do iiRef2Parent.$setControl2SubWindow(kFalse) ## 1. Kontrolle ZURUECKGEBEN!
End If
Do ilList.$define() ## 2. Liste leeren
Do irRow.$define() ## 3. Row leeren
Do irRowSearch.$define() ## 4. Such-Row leeren
WICHTIG: Ohne diesen $destruct wuerde das Elternfenster denken, das SubWindow existiert noch!
Phase 4: $closeWindow - Fenster schliessen
## wSubWindow.$closeWindow:
Do iiRef2Parent.$closeWindow() ## Delegiert das Schliessen an das Elternfenster!
SubWindows schliessen sich NICHT selbst - sie bitten den Parent, das ganze Fenster zu schliessen!
5. Die bidirektionale Referenz-Kette
Das Herzstueck der Architektur ist die bidirektionale Verkettung:
wMainWindow wSubWindow (Ebene 1) wSubWindow (Ebene 2)
+-----------------+ +--------------------+ +------------------+
| iiRef2SubWindow | --------> | $cinst | | |
| | | iiRef2Parent |----+--------> | (existiert nicht |
| ibControl2Sub- | | iiRef2SubWindow|----|--------> | auf dieser Ebene)|
| Window = kTrue | | | | |
+-----------------+ | ibControl2Sub- | +------------------+
| Window = kFalse |
+--------------------+
Aufwaerts: iiRef2Parent (Kind -> Elter)
Abwaerts: iiRef2SubWindow (Elter -> Kind)
Wie die Kontrolle fliesst
- User klickt "Edit" im Hauptfenster
wMainWindow.$cEditwird aufgerufenibControl2SubWindowistkTrue-> aniiRef2SubWindow.$cEditdelegierenwSubWindow.$cEdit(Ebene 1) wird aufgerufenibControl2SubWindowauf Ebene 1 istkFalse->ioCode.$cEdit()aufrufen- Die Business-Logik (
oOrders) fuehrt die eigentliche Aktion aus
Konkretes Beispiel: Sales/wOrders
wOrders (wMainWindow)
|
+-- iiRef2SubWindow -> wOrder (wSubWindow)
|
+-- iiRef2Parent -> wOrders (zurueck)
+-- iiRef2SubWindow -> wOrderItems (wSubWindow)
|
+-- iiRef2Parent -> wOrder (zurueck)
+-- ioCode -> oOrderItems (Business-Logik)
Command-Fluss bei "Edit":
wOrders.$cEdit
-> ibControl2SubWindow = kTrue
-> wOrder.$cEdit
-> ibControl2SubWindow = kTrue (falls OrderItems aktiv)
-> wOrderItems.$cEdit
-> ibControl2SubWindow = kFalse
-> oOrderItems.$cEdit() <-- HIER wird die Aktion ausgefuehrt!
6. Das $EnableTabPane-Pattern
Wenn ein SubWindow in den Edit-Modus wechselt, muessen die Tab-Reiter gesperrt werden - und zwar aufwaerts in der Hierarchie!
Im SubWindow (z.B. wAdditionalCosts):
## wAdditionalCosts.$EnableTabPane:
# Verriegelt Reiter der TabPane gegeneinander
# und ruft im Elternfenster die Methode $EnablePane
Do iiRef2Parent.$EnableTabPane($cinst.inEDMode)
Im wBaseWindow:
## wBaseWindow.$EnableTabPane:
Do $cwind.$objs.TP.$enableTabPanes(pEDMode)
Ablauf:
- SubWindow wechselt in Edit-Modus (
inEDMode = kEdit) - SubWindow ruft
iiRef2Parent.$EnableTabPane(kEdit) - Parent sperrt seine TabPane-Reiter (
TP.$enableTabPanes(kEdit)) - User kann nicht mehr zwischen Tabs wechseln waehrend er editiert!
- Bei Cancel/Save:
$EnableTabPane(kFalse)-> Tabs wieder frei
7. Sonderfaelle und Varianten
wWebPortal - Kontrolle ablehnen
## wWebPortal.$construct:
Do inherited
Do pRef2Parent.$setControl2SubWindow(kFalse) ## Kontrolle NICHT uebernehmen!
Do $cinst.$translateObjects($cinst)
Manche SubWindows wollen die Kontrolle NICHT haben! Sie sind nur Anzeige-Panels.
wOrder.$ActiveWindow.$assign - Upstream-Delegation
## wOrder (Sales).$ActiveWindow.$assign:
Do iiRef2Parent.$ActiveWindow.$assign(pActive) ## CGrid im Elternfenster ausschalten
SubWindows koennen auch beliebige Custom-Methoden an den Parent delegieren!
wOrderItems.$CallParentDoCommand - Command nach oben
## wOrderItems.$CallParentDoCommand:
Do iiRef2Parent.$CallDoCommand
Manchmal muss ein SubWindow den Parent bitten, einen Command auszufuehren.
wOrder.$SubWindowSavesAttachments - SubWindow-spezifische Logik
## wOrder.$SubWindowSavesAttachments:
Switch iiRef2SubWindow.$classname
Case 'solution2.wText'
Do iiRef2SubWindow.$SaveData()
Quit method kTrue
Default
Quit method kFalse
End Switch
Das SubWindow kann abfragen, WELCHES SubWindow gerade aktiv ist und unterschiedlich reagieren!
8. Praxis-Anleitung: Eigenes SubWindow erstellen
Schritt 1: Klasse anlegen
- Erstelle eine neue Window-Klasse (z.B.
wMeinSubWindow) - Setze
wSubWindowals Superklasse (solution2.wSubWindow)
Schritt 2: SubWindow-Feld einbetten
- Fuege in das Elternfenster ein Subwindow-Feld ein
- Name:
wSubWindow(Konvention!) - Setze die
$classname-Property auf deine neue Klasse
Schritt 3: Business-Logik-Objekt
- Erstelle ein Object (z.B.
oMeinCode) mit den Commands - Instanziiere es als
ioCodein deinem SubWindow
Schritt 4: $construct ueberschreiben (optional)
## wMeinSubWindow.$construct:
Do inherited ## IMMER inherited aufrufen! Setzt die Referenzen!
# ... eigene Initialisierung ...
Schritt 5: Commands implementieren
Wenn dein SubWindow eigene Commands braucht:
## wMeinSubWindow.$cEdit:
Do inherited ## Oder: eigene Logik wenn noetig
# ... zusaetzliche Aktionen ...
Schritt 6: $EnableTabPane ueberschreiben (wenn noetig)
## wMeinSubWindow.$EnableTabPane:
Do iiRef2Parent.$EnableTabPane($cinst.inEDMode)
9. Haeufige Fehler und Debugging
Fehler 1: "Method not found" bei Command-Aufruf
Ursache: Das SubWindow hat ibControl2SubWindow = kTrue, aber das referenzierte SubWindow existiert nicht mehr.
Loesung: Pruefe ob $destruct korrekt $setControl2SubWindow(kFalse) aufruft.
Fehler 2: Tabs lassen sich nicht sperren
Ursache: $EnableTabPane ruft den Parent nicht auf.
Loesung: Do iiRef2Parent.$EnableTabPane($cinst.inEDMode) im SubWindow.
Fehler 3: Aenderungen gehen verloren beim Tab-Wechsel
Ursache: Der Tab-Wechsel zerstoert das SubWindow, aber die Daten wurden nicht gespeichert.
Loesung: Im $destruct oder vor dem Tab-Wechsel $cSave aufrufen.
Fehler 4: Commands landen im falschen Fenster
Ursache: ibControl2SubWindow ist nicht korrekt gesetzt.
Debug: Pruefe zur Laufzeit: Calculate lDebug as $cinst.ibControl2SubWindow
Fehler 5: SubWindow erhaelt keine Daten
Ursache: Die bidirektionale Referenz wurde nicht aufgebaut.
Loesung: Sicherstellen dass Do inherited in $construct aufgerufen wird!
Fehler 6: "Inherited does nothing"
Ursache: Die Superklasse ist falsch gesetzt (z.B. wBaseWindow statt wSubWindow).
Loesung: Superklasse pruefen: Muss solution2.wSubWindow sein (mit Library-Prefix bei cross-library!).
10. Zusammenfassung: Die 7 goldenen Regeln
ibControl2SubWindowbestimmt ALLES - Es entscheidet wohin Commands gehen- $construct baut die Kette auf -
inheritedIMMER aufrufen! - $destruct baut die Kette ab - Kontrolle zurueckgeben!
- Commands fliessen ABWAERTS - MainWindow -> SubWindow -> SubSubWindow
- TabPane-Sperren fliessen AUFWAERTS - SubWindow -> Parent -> MainWindow
- $closeWindow delegiert AUFWAERTS - SubWindows schliessen sich nie selbst
- ioCode ist der letzte Stopp - Wenn kein SubWindow aktiv ist, macht ioCode die Arbeit
11. Referenz: Alle delegierten Commands
Auf wMainWindow (mit $closeRelatedWindow vorab):
$cEdit, $cCancel, $cAudit, $cNextVersion
Auf wSubWindow (ohne $closeRelatedWindow):
$cEdit, $cCancel, $cInsert, $cDelete, $cSave, $cPrint, $cAudit, $cNextVersion, $cFirst, $cLast
Gemeinsames Pattern:
If $cinst.ibControl2SubWindow
Do iiRef2SubWindow.$cXxx() ## Weiterleiten
Else
Do $cinst.ioCode.$cXxx() ## Selbst ausfuehren
End If
12. Cross-Library Verwendung
SubWindows werden oft library-uebergreifend genutzt:
| Library | Klasse | Superklasse |
|---|---|---|
Sales |
wOrder |
solution2.wSubWindow |
Sales |
wOrderItems |
solution2.wSubWindow |
Manufacturing |
wResourceBasicData |
solution2.wSubWindow |
Manufacturing |
wVariantsExclLock |
solution2.wSubWindow |
solution2 |
wReminder |
wSubWindow |
solution2 |
wAdditionalCosts |
wSubWindow |
solution2 |
wRTF |
wSubWindow |
solution2 |
wNotice |
wSubWindow |
solution2 |
wNoticeboard |
wSubWindow |
Beachte: Innerhalb der
solution2-Library reichtwSubWindow. Bei Cross-Library-Vererbung musssolution2.wSubWindowangegeben werden!