Fenster zu, wenn’s zieht!

Im Xojo-Forum gibt es gerade einen kleinen Thread über ein Fenster, das sich einfach immer wieder öffnet. Da dieses Verhalten insbesondere Neuprogrammierer häufiger mal in Verblüffung stürzt: Guter Anlass für einen kleinen Einsteiger-Beitrag meinerseits!

Xojo macht es dem Anwender auch wirklich leicht: Erstellt man ein neues Projekt, wird einem der Bequemlichkeit halber ein Spielfeld eingerichtet, das der gewählten Plattform entspricht. Soll heißen: Ein Desktop-Projekt hat sein Window, eine Web-App ein Browserfenster und eine iOS-Anwendung ein View. iOS und Web bleiben in dieser Betrachtung mal außen vor, denn bei ersterem hat man es üblicherweise mit einem View zu tun (das man wechseln oder splitten kann, aber man hat nicht mehrere gleichzeitig auf dem Tablett), und man hat auch selten das Bedürfnis, den Browser des Benutzers mit zig Fenstern in Tabs zu belegen). Beim Desktop allerdings mag es häufig nötig sein, verschiedene Fenster gleichzeitig zu benutzen.

Tja, und dann gibt es eben Fälle wie solche: Der Benutzer schließt das Fenster, und das Programm prüft dann irgendwann, ob dieses noch da ist:

If Window1 <> nil then irgendwas

Und schwupp: Da ist es wieder sichtbar! Dabei wollte man das gar nicht.

Bevor Sie an dieser Stelle Xojo aufschrauben und nachschauen, ob was kaputt ist: Schauen Sie lieber, ob die Implicit Instance des Fensters eingeschaltet ist!

Implicitinstance.png

Standardmäßig ist das der Fall. Aber was bedeutet das?

Ein in Xojo bestücktes Window ist, wie schon an anderer Stelle mal erwähnt, eigentlich eine eigene Unterklasse des Typs Window. Wie das mit Klassen so ist, müsste also eigentlich zunächst ein neues Objekt dieses Typs angelegt (Fachsprache: instanziiert) werden, also etwa

Dim w as New Window1

bevor man etwas damit machen kann. Da es aber oft so ist, dass eine Fensterart nur einmal im Programm vorkommt (Ausnahmen sind etwa Dokumentfenster in einer Anwendung, die mehrere Dokumente gleichzeitig bearbeiten kann), bietet Xojo hier Convenience-Kost und erspart einem diese Arbeit. Sie können ein implizit instanziiertes Fenster direkt verwenden. Das aber nicht nur bis zum Schließen: Wird per Programmcode wieder auf es zugegriffen, wenn es eigentlich nicht mehr da ist (wie in der obersten Codezeile), erzeugt Xojo schnell ein neues Fenster dieses Namens.

Ob das gut oder schlecht ist, müssen Sie entscheiden. Ich bin da nicht so harsch wie der geschätzte Bob Keeney in seinem Blogbeitrag, von dem ich mich habe inspirieren lassen.

Wollen Sie jedenfalls Überraschungen dieser Art vermeiden , dann bewegen Sie den Schalter einfach auf Off. In Ihrer App können Sie dann das Fenster manuell erzeugen – siehe 2. Codezeile oben –, und wenn Sie wollen, können Sie es natürlich einer Property zuweisen – z.B. im App-Objekt oder einem Modul.

Grundsätzlich können Sie, egal ob Implicit Instance nun an oder aus ist, beliebig viele Fenster der Klasse Window1 erzeugen (bzw. natürlich jeder Window-Subklasse, egal wie Sie sie nennen). Sie müssen dazu auch keiner Property zugewiesen werden – Fenster sind selbstreferenzierend, soll heißen, so lange ein Fenster geöffnet ist, sorgt es selbst dafür, dass es am Leben erhalten wird. Bis es geschlossen wird. Probieren Sie’s mal aus:

  • Öffnen Sie in Xojo ein neues Desktop-Projekt und schalten Sie die Implicit Instance des automatisch angelegten Window1 aus.
  • Geben Sie dem Open-Event des App-Objekts folgenden Code:
    Sub Open() Handles Open
     for q as integer = 1 to 10
      dim w as new window1
     next
    End Sub
  • Lassen Sie das Projekt laufen: 11 gestaffelte Fenster.

Wieso 11?

Das ist eine zweite Inspector Property, die man gerne übersieht: Das App-Objekt besitzt ein DefaultWindow-Popup. Und das sieht standardmäßig so aus:

AppInspector.png

Das hier eingetragene Fenster wird automatisch bei Programmstart geöffnet – bzw., falls dieses nicht implizit instanziiert werden soll, wird eben doch eine Instanz erzeugt und angezeigt. Deshalb gehen auch nicht automatisch sämtliche Fenster einer App auf, bei denen Implicit Instance eingeschaltet ist, sondern nur eines.

Sie können, wenn Sie mögen, hier auch „none“ anwählen – dann stimmt die Rechnung.

Ganz hübsch – aber wie sprechen Sie die Fenster jetzt an, wenn Sie aus dem Programm auf sie zugreifen wollen?

Dafür gibt es die Window-Methode zusammen mit WindowCount. Letztere sagt Ihnen, wie viele Fenster Ihr Programm geöffnet hat, und mit Window(index As Integer) können Sie ein beliebiges ansprechen:

  • Beenden Sie das Programm, platzieren Sie einen Pushbutton auf Window1 und geben Sie ihm den folgenden Action-Eventhandler:
Sub Action() Handles Action
 For q as integer = 0 to WindowCount -1
  dim w as window = window(q)
  w.close
 Next
End Sub
  • Starten Sie das Programm, bewundern Sie noch einmal die 10 (oder 11) Fenster (jetzt mit Button) und drücken Sie auf einen davon.

Hups? Gehen nur fünf zu? Nach dem nächsten Klick sind noch zwei da, dann eines, und erst dann sind alle weg?

Das ist kein Fehler! Wenn Sie das Programm gedanklich durchspielen: Zunächst wird Window(0) geschlossen. Damit rückt das bisherige Window(1) auf Platz 0 vor. Der nächste Schleifendurchlauf schließt also das frühere Window(2), woraufhin Ex-Window(3) auf seinen Platz rutscht usw.

Hier gibt es zwei Lösungsmöglichkeiten:

Entweder Sie drehen die Schleifenreihenfolge um:

For q as integer = WindowCount -1 downto 0

oder aber (was man bei größeren oder häufig durchlaufenen Schleifen eh machen sollte, damit Berechnungen nicht immer wieder ausgeführt werden):

Dim Fenster As Integer = WindowCount - 1
For q as integer = 0 to Fenster
 dim w as window = window(0)
 w.close
Next

Beide Methoden führen zum gewünschten Ziel, und die zweite sollte sogar ein minimalstel schneller gehen – eben wegen der nur einmal durchgeführten Abfrage von WindowCount (nicht, dass das in diesem Zusammenhang eine Rolle spielen würde).

Letztes Kapitel: Meistens hat man ja auch irgendwelche Steuerelemente auf seinen Fenstern, nicht nur einen Knopf zum Schließen. Die Klasse Window hat natürlich keine, ist ja Sache des Programmierers. Machen Sie mal folgendes:

  • Platzieren Sie ein Textfield auf dem Fenster, also etwa so:

Fenster mit TextField.png

  • Und jetzt gehen Sie in den Action-Event des Buttons, der ja bisher für die Massenschließung zuständig war, und ändern Sie die Zeile w.close in
w.TextField1.Text = "Text in allen Fenstern"
  • Klicken Sie auf Run und – Fehler!

Wie gesagt: Window besitzt ja kein TextField1, nur Window1. Sie müssen Ihrer App noch beibringen, das Fenster auch als Instanz der Klasse Window1 zu betrachten. Dazu bedient man sich des TypeCasting – man hüllt den Mantel einer Subklasse über die Superklasse. Der Code sieht dann so aus:

Sub Action() Handles Action
 dim Fenster as Integer = WindowCount - 1
 For q as integer = 0 to fenster
  dim w as Window1 = Window1(window(q))
  w.textfield1.text = "Text in allen Fenstern"
 Next
End Sub

Probieren Sie’s mal aus – jetzt hat’s geklappt, oder?

In einem größeren Projekt kann man sich aber nicht sicher sein, dass ein Fenster auch von einer entsprechenden Klasse ist. Um sicherzugehen, das richtige erwischt zu haben (bzw. um die Exception zu vermeiden, die sonst aufträte), kann man die Art des Fensters vorher abfragen. Nämlich mit dem aus dem Englischen sehr gut zu merkenden IsA – ist ein …

Sub Action() Handles Action
 dim Fenster as Integer = WindowCount - 1
 For q as integer = 0 to fenster
  dim w as Window = window(q)
  if w isa window1 then Window1(w).textfield1.text = "Text in allen Fenstern"
 Next
End Sub

Auf dass es nicht mehr zugig wird.

Sind Fragen offengeblieben? Schreiben Sie mir!

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