Diplomatisch delegieren

Ein Anwendungsfall aus der alltäglichen Praxis: Man hat ein Auswahl-Element, sagen wir mal ein PopupMenu, und ein Ausgabeelement – vielleicht einen Canvas –, das aufgrund der Auswahl im ersteren ein passendes Resultat zeigen soll.

DelegateWinDelegate.png

Häufig geht man dann so vor, dass der Change-Event im PopupMenu den Canvas invalidiert, und in dessen Paint-Event wird dann geschaut, was im PopupMenu steht. Eine mitunter ellenlange Select Case- oder If … ElseIf – End If-Anweisung macht den Code dann zum Roman.

Auch hier wieder: Das kann man durchaus so machen. Nachteile sind der mit wachsender Auswahlmöglichkeit immer unübersichtlichere Code und im Falle einer 64 Bit-App steigende Kompilierungszeiten je nach angewählter LLVM-Optimierung. Der LLVM-Compiler ist schneller, wenn er kleine Codehäppchen optimieren soll, und steigert seine Übersetzungszeiten erheblich, wenn er Textwüsten vorgesetzt bekommt.

Letzteres lässt sich beheben, indem die einzelnen Ausgabevarianten in eigenständige Methoden ausgelagert werden. Die ganze Select Case- oder If-Schleife bleibt als Rahmen im Paint-Event, und je nach Länge kann das immer noch eine ziemlich mühselig zu ändernde Angelegenheit bleiben. Geht das nicht eleganter?

Natürlich geht das, und zwar mit einem

Delegate

Delegates gehören zu den seltener benutzten Sprachkonstruktionen von Xojo. Was schade ist, denn sie sparen viel Arbeit und verhelfen zu besser wartbarem Code. Aber was ist ein Delegate eigentlich?

Man kann sich Delegates als Platzhalter für Methoden vorstellen. Man legt in einem Delegate also fest, welche Ein- und Ausgabeparameter für diese Art von Funktion verfügbar sein sollen, und kann die eigentliche Methode dann dynamisch ersetzen. Ein Praxisbeispiel:

  • Legen Sie ein neues Desktop-Projekt an und platzieren Sie ein PopupMenu und einen Canvas auf dem Fenster – etwa so:DelegateWindowStart
  • Rechtsklicken Sie auf Window1 im Navigator und wählen Sie „Add … Delegate“:Delegate.png
  • Dem Delegate, der wie eine normale Methode im Navigator erscheint (nur unter Delegates, nicht unter Methods) geben Sie den Namen PaintDelegate und den Eingabeparameter g As Graphics:PaintDelegate

    Nebenbei: Den Unterschied zur normalen Methode merken Sie auch daran, dass Sie keinen Code in den Code-Editor eingeben können. Ein Delegate (in der IDE) legt die Form einer Methode fest, nicht deren (Code-)Inhalt! Strenggenommen müsste ein so definiertes Objekt eher „DelegateScheme“ heißen o.ä., denn der eigentliche Delegate – die Methode als Objekt – wird ja später erst zugewiesen.

  • Spendieren Sie dem Fenster jetzt noch eine Property PaintDel As PaintDelegate:Win1NavPaintDel.png

Sie haben jetzt also einen Delegate PaintDelegate deklariert und dessen äußere Struktur festgelegt. Außerdem gibt es eine Property PaintDel, die dankbar passende Methoden aufnehmen kann. Lassen Sie uns mal zwei, drei davon erstellen:

Private Sub MacheBlau(g as Graphics)
 g.ForeColor = color.Blue
 g.FillRect (0, 0, g.Width, g.Height)
End Sub

Also das, was in einem Paint-Event stehen würde: Der Canvas wird blau gefärbt. Noch zwei weitere, damit wir etwas Auswahl haben:

Private Sub MacheGrün(g as Graphics)
 g.ForeColor = color.Green
 g.FillRoundRect (0, 0, g.Width, g.Height, 10, 10)
End Sub
Private Sub ZeigeText(g as Graphics)
 g.ForeColor = color.red
 g.DrawString "Hallo Delegate!", 20, g.Height/2
End Sub

Also ein kleines bisschen Abwechslung, damit es nicht nur um pure Farbwechsel geht.

Jetzt muss der Canvas auch entsprechend reagieren. Legen Sie seinen Paint-Eventhandler an mit dem Code

Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint
 If PaintDel <> Nil Then PaintDel.Invoke(g)
 #Pragma unused areas
End Sub

Invoke lässt sich mit Heraufbeschwören, zu Hilfe rufen oder in diesem Fall wohl am besten mit Aufrufen übersetzen. Wenn die Property PaintDel nicht Nil ist, also einen Wert zugewiesen bekommen hat, dann soll die dahinter stehende Methode mit dem Parameter g aus dem Paint-Event aufgerufen werden. Damit es keine Compiler-Warnung wegen unbenutzter Parameter gibt, wird Areas noch als ungenutzt deklariert. Funktionell hat das keine Unterschiede, nur ein beruhigend sauberes Bild beim Programm-Check (Analyze).

Wir müssen also nur noch dafür sorgen, dass PaintDel auch einen Wert erhält.

Die Berufungsurkunde – (Weak)AddressOf

Wir haben mit PaintDel eine Property für einen Delegate der Art PaintDelegate und verschiedene Methoden, die dafür qualifizieren. Aus diesen Methoden müssen also Delegates werden – und das, so verrät es auch die Sprachreferenz, erfolgt über den Befehl AddressOf (Methode) bzw. WeakAddressOf (Methode).

Der Unterschied zwischen beiden besteht darin, dass AddressOf eine starke Referenz erzeugt: Der ARC-Zähler der Methode wird erhöht und damit bleibt diese Methode auch noch „lebendig“, wenn das dahinterstehende Objekt eigentlich schon nicht mehr existieren sollte. Soll heißen: Erzeugen Sie mit AddressOf (MacheBlau) einen starken Delegate und schließen dann Window1, kann das zu Zirkelbezügen führen. MacheBlau ist eine Methode von Window1, und damit können Memory Leaks entstehen, weil Window1 nie ganz aus dem Speicher verschwindet, oder das Programm wird absturzgefährdet, weil die Methode, irgendwann invoziert, auf das Fenster zugreifen will, das aber gar nicht mehr da ist.

Mit WeakAddressOf erzeugte Delegates leben nur so lange wie ihr eigentliches Element. Schließt man im Beispiel also Window1, wird auch der aus einer seiner Methoden mit WeakAddressOf erzeugte Delegate wieder Nil. Keine Zirkelbezüge, keine Absturzgefahr! Und auch wenn Weak irgendwelche Anfälligkeiten signalisieren könnte: Solange Window1 existiert, wird auch ein aus ihm erzeugter schwacher Delegate leben.

Ab in die Praxis:

  • Geben Sie dem PopupMenu diesen Open-Eventhandler:
Sub Open() Handles Open
 Me.AddRow "Blau"
 Me.RowTag(Me.ListCount -1)= WeakAddressOf MacheBlau
 Me.AddRow "Grün"
 Me.RowTag(Me.ListCount -1)= WeakAddressOf MacheGrün
 Me.AddRow "Text"
 Me.RowTag(Me.ListCount -1)= WeakAddressOf ZeigeText
End Sub

PopupMenu1 bekommt also drei Einträge, und deren RowTags halten den schwachen Delegate der jeweiligen Methode. Nun noch den Change-Event des PopupMenus belegen:

Sub Change() Handles Change
 If Me.ListIndex > -1 Then
 PaintDel = Me.RowTag(Me.ListIndex)
 Canvas1.Invalidate
 End If
End Sub

Und das ist alles! Kein Select Case, kein If … End If! Der Rowtag, sofern ein Eintrag ausgewählt wurde, wird der Property PaintDel zugewiesen, auf die der Paint-Event von Canvas1 dann zugreift. Vergleichen Sie den Code mal mit dem üblicherweise mit den beiden Konditionalabfragen aufgebauten.

Spielen Sie gerne mal mit dem Demoprojekt, und experimentieren Sie selbst mit Delegates, die auch einen Rückgabeparameter besitzen. Hat man sich ein bisschen hineingefunden, tut sich schnell die Frage auf, warum man es sich bislang so schwer gemacht hat. Oder?

 

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s