# 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!