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

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

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

  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):

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

  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

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

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

  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:

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!