edu-senex/edu/content/tutorials/08-window-hierarchy-deepdive.md

457 Zeilen
16 KiB
Markdown

# 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
```omnis
## 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:
```omnis
## 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` | Drucken |
| `$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
```omnis
## 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)
```omnis
## 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
```omnis
## 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
```omnis
## 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
1. **User klickt "Edit"** im Hauptfenster
2. `wMainWindow.$cEdit` wird aufgerufen
3. `ibControl2SubWindow` ist `kTrue` -> an `iiRef2SubWindow.$cEdit` delegieren
4. `wSubWindow.$cEdit` (Ebene 1) wird aufgerufen
5. `ibControl2SubWindow` auf Ebene 1 ist `kFalse` -> `ioCode.$cEdit()` aufrufen
6. 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):
```omnis
## wAdditionalCosts.$EnableTabPane:
# Verriegelt Reiter der TabPane gegeneinander
# und ruft im Elternfenster die Methode $EnablePane
Do iiRef2Parent.$EnableTabPane($cinst.inEDMode)
```
### Im wBaseWindow:
```omnis
## wBaseWindow.$EnableTabPane:
Do $cwind.$objs.TP.$enableTabPanes(pEDMode)
```
**Ablauf:**
1. SubWindow wechselt in Edit-Modus (`inEDMode = kEdit`)
2. SubWindow ruft `iiRef2Parent.$EnableTabPane(kEdit)`
3. Parent sperrt seine TabPane-Reiter (`TP.$enableTabPanes(kEdit)`)
4. User kann nicht mehr zwischen Tabs wechseln waehrend er editiert!
5. Bei Cancel/Save: `$EnableTabPane(kFalse)` -> Tabs wieder frei
---
## 7. Sonderfaelle und Varianten
### wWebPortal - Kontrolle ablehnen
```omnis
## 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
```omnis
## 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
```omnis
## wOrderItems.$CallParentDoCommand:
Do iiRef2Parent.$CallDoCommand
```
> Manchmal muss ein SubWindow den Parent bitten, einen Command auszufuehren.
### wOrder.$SubWindowSavesAttachments - SubWindow-spezifische Logik
```omnis
## 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 `wSubWindow` als 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 `ioCode` in deinem SubWindow
### Schritt 4: $construct ueberschreiben (optional)
```omnis
## wMeinSubWindow.$construct:
Do inherited ## IMMER inherited aufrufen! Setzt die Referenzen!
# ... eigene Initialisierung ...
```
### Schritt 5: Commands implementieren
Wenn dein SubWindow eigene Commands braucht:
```omnis
## wMeinSubWindow.$cEdit:
Do inherited ## Oder: eigene Logik wenn noetig
# ... zusaetzliche Aktionen ...
```
### Schritt 6: $EnableTabPane ueberschreiben (wenn noetig)
```omnis
## 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
1. **`ibControl2SubWindow` bestimmt ALLES** - Es entscheidet wohin Commands gehen
2. **$construct baut die Kette auf** - `inherited` IMMER aufrufen!
3. **$destruct baut die Kette ab** - Kontrolle zurueckgeben!
4. **Commands fliessen ABWAERTS** - MainWindow -> SubWindow -> SubSubWindow
5. **TabPane-Sperren fliessen AUFWAERTS** - SubWindow -> Parent -> MainWindow
6. **$closeWindow delegiert AUFWAERTS** - SubWindows schliessen sich nie selbst
7. **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:
```omnis
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 reicht `wSubWindow`. Bei Cross-Library-Vererbung muss `solution2.wSubWindow` angegeben werden!