Konfiguration von Xbase-Parts

Aus Wiki des Deutschsprachige Xbaseentwickler e. V.
Zur Navigation springen Zur Suche springen

Was sind Xbase-Parts

Xbase-Parts sind überwiegend von Windows-Controls abgeleitete graphische Elemente, die dazu verwendet werden, graphische Benutzerschnittstellen zu erstellen.

Die unterstützten Fenstertypen sind ebenfalls als Xbase-Parts definiert.

Alle Xbase-Parts, die im Zusammenhang mit der XbpBrowse()-Klasse stehen, sind nicht direkt von "echten" Windows-Controls abgeleitet.


Der Lebenszyklus eines Xbase-Parts

Der Lebenszyklus eines Xbase-Parts ist grundsätzlich durch drei Methoden gekennzeichnet:

  • :new()
  • :create()
  • :destroy()

Mit der Methode :new() wird ein neues Objekt erzeugt, das aber nicht immer zwingend auch direkt verwendbar ist, bevor nicht mittels :create() erforderliche Systemresourcen angefordert werden. Mit :destroy() kann ein Objekt gezielt zerstört werden. Die "Entsorgung" geschieht durch den Garbage Collector, wenn keine Verweise auf das Objekt mehr bestehen.

Thematisch gehört auch die Methode :configure() in diesen Kreis: sie wird verwendet, wenn das Objekt während seines Lebenszyklus geändert wird. Manche Änderungen werden erst wirksam, wenn das Objekt durch :configure() neu konfiguriert wird.


:new()

Jedes Xbase-Part "reagiert" auf die Methode :new() mit der Rückgabe einer Referenz auf ein neues Xbase-Part der eigenen Klasse:

oDlg := XbpDialog():new(<oParent>, <oOwner>, <aPos>, <aSize>,;
   <aPresParam>, <lVisible>)

Im Anschluss kann das neue Xbase-Part konfiguriert werden, um den gewünschten Anforderungen zu genügen.

Einige der Konfigurationsparameter können über eigene Instanzvariablen oder Methoden eingestellt (oder geändert) werden, während andere nur über das Präsentations-Parameter Array festgelegt werden können. Spätere Änderungen sind dann über eine Änderung der Präsentations-Parameter und einen Aufruf der Methode :configure() möglich.

Parameter

<oParent>

Der Parent ist das Objekt, in dessen Kontext das neue Objekt erstellt werden soll. Wird kein Parameter angegeben, ist das Objekt, das durch SetAppWindow() zurückgegeben wird, der Parent für das neue Objekt.

Diese Beziehung kann durch die Methode :setParent() abgefragt und auch verändert werden.


<oOwner>

Der Owner ist das Objekt, in dessen inhaltlichen Kontext das neue Objekt gestellt wird. Ein Beispiel ist ein XbStatic() vom Typ GroupBox, in der mehrere XbpRadioButton() angesiedelt werden. Durch den gleichen Owner ist geregelt, dass von all diesen XbpRadioButton() immer nur einer aus der Gruppe aktiv sein kann, die dem gleichen Owner zugeordnet sind.

Wird der Parameter Owner nicht angegeben, wird standardmässig der Parent ebenfalls als Owner verwendet.


<aPos>

aPos ist ein zwei-dimensionales Array, das standardmässig den linken, unteren Punkt angibt, an dem das Objekt platziert wird. Der erste Wert gibt die Entfernung in Pixel vom linken Rand des Parent an, während der zweite Wert die Entfernung in Pixel um unteren Rand des Parent angibt. Wird kein Array übergeben, ist der Unterlassungswert {0, 0}.


<aSize>

aSize ist ein zwei-dimensionales Array, das die Grösse des zu erzeugenden Xbase-Parts vorgibt. Der erste Wert gibt die Breite in Pixel an, der zweite Wert gibt die Höhe in Pixel an. Wird kein Array übergeben, wird das Xbase-Part mit einer Grösse von {0, 0} erzeugt.

<aPresParam>
<lVisible>

Der Parameter lVisible gibt an, ob das Xbase-Part nach seiner Erstellung sichtbar sein soll oder nicht. Die Sichtbarkeit hängt aber auch von der Sichtbarkeit des Parents ab: ist der Parent nicht sichtbar, ist auch das dem Parent zugeordnete Xbase-Part nicht sichtbar.

:create()

Wenn die Konfiguration abgeschlossen ist, wird das Xbase-Part "funktionsfähig" gemacht, indem Systemresourcen angefordert werden und (unter normalen Umständen) das Xbase-Part sichtbar gemacht wird.

Ab diesem Zeitpunkt können Änderungen an den Präsentations-Parametern nur über

oXbp:setPresParam(aPP)
oXbp:configure()

geändert werden, d.h. das Xbase-Part muss explizit neu konfiguriert werden, damit die Änderungen auch wirksam werden.


:destroy()

Diese Methode wird wohl selten explizit ausgeführt. Sie wird jedoch rekursiv durchgeführt, wenn z.B. ein XbpDialog() mittels :destroy() zerstört wird. In diesem Fall wird bei allen abhängigen Xbase-Parts die Methode :destroy() ausgeführt, sofern sie nicht noch in einem anderen Kontext vorhanden sind (z.B. abweichender Owner).


Auswahl von Konfigurations-Variablen:

:caption

Falls verfügbar, zeigt diese Variable einen beschreibenden/ergänzenden Text am Xbase-Part an.


:tabStop

Diese Instanz-Variable legt fest, ob das Xbase-Part unter Verwendung der Tab-Taste angesprungen werden kann (Standard-Verhalten unter Windows). Entscheidend für die Reihenfolge, in der die Xbase-Parts angesprungen werden, ist die zeitliche Reihenfolge, in der :tabStop gesetzt wurde.


:cargo

Diese Instanz-Variable wird von Xbase++ nicht verwendet, sondern räumt dem Programmierer die Möglichkeit ein, bestimmte Information in einem Xbase-Part zu hinterlegen, ohne eine eigenen Klasse abzuleiten und darin weitere Felder zu definieren. Verwendung von :cargo auf eigene Gefahr.


Beispiele

Beispiel 1 - Auswahl aus einer Liste über einen modalen Dialog

Dialoge unter Windows sollten so gestaltet werden, dass sie nicht modal sind. Unter einem modalen Dialog versteht man eine Abfrage, die ein anderweitiges Arbeit mit dem Programm so lange blockiert, bis die Abfrage beantwortet wurde.

Das Beispiel fragt zu Programm-Start ab, welche SQL-Datenbank für den Programm-Lauf verwendet werden soll. Unterstellt wird die Verwendung der AppSys() aus dem AppSys.prg Artikel

#INCLUDE "Xbp.CH"
Function Main()
  Local aPos, aSize, aList
  Local cTitle, cDataBase
  Local nI, nResult
  Local oDlg, oDlgWin, oXbp, oList
  // Schritt 1 - Definition der Auswahlliste
  aList := {"Produktion", "Test 1", "Test 2", "Remote Test"}
  // Schritt 2 - Erstellen des Dialogs
  aPos := {100, 100}
  aSize := {300, 300}
  cTitle := "Select Database For Operation"
  oDlgWin := XbpDialog():new(AppDesktop(),;
     RootWindow():drawingarea, aPos, aSize, NIL, .F.)
  oDlgWin:title := cTitle
  oDlgWin:create()
  // Schritt 3 - Bereitstellen einer Variablen für die :drawingArea
  oDlg := oDlgWin:drawingArea
  // Schritt 4 - Erzeugen einer XBpListbox()
  aPos := {10, 50}
  aSize := {260, 200}
  oList := XbpListBox():new(oDlg, oDlg, aPos, aSize)
  oList:tabStop := .T.
  oList:create()
  // Schritt 5 - Füllen der XbpListBox() mit den Daten der Auswahlliste
  FOR nI := 1 TO Len(aList)
     oList:addItem(aList[nI])
  NEXT
  // Schritt 6 - Erstellen eines OK-Buttons
  aPos := {10, 10}
  aSize := {120, 30}
  oXbp := XbpPushButton():new(oDlg, oDlg, aPos, aSize)
  oXbp:tabStop := .T.
  oXbp:caption := "OK"
  oXbp:default := .T.
  oXbp:create()
  // Schritt 7 - Erstellen eines Cancel-Buttons
  aPos := {160, 10}
  oXbp := XbpPushButton():new(oDlg, oDlg, aPos, aSize)
  oXbp:tabStop := .T.
  oXbp:caption := "Cancel"
  oXbp:cancel  := .T.
  oXbp:create()
  // Schritt 8 - Vorbereiten der Anzeige des XbpDialog()
  CenterControl(oDlgWin)
  // Schritt 9 - modale Anzeige des Dialogs
  nResult := oDlgWin:showModal()
  // Schritt 10 - verstecken und zerstören des Dialogs
  oDlgWin:hide()
  oDlgWin:destroy()
  // Schritt 11 - Abfrage der Auswahl
  IF nResult = XBP_MRESULT_OK
     cDatabase := oList:getItem(oList:getData()[1])
  ELSE
     cDatabase := ""
  ENDIF
  ConfirmBox(, "Ausgewählt wurde Datenbank " + cDatabase, "Ergebnis",;
     XBPMB_OK, XBPMB_INFORMATION)
RETURN(.T.)


Schritt 1 - Definition der Auswahlliste

Der Variablen aList wird für unser Beispiel eine Liste von zur Verfügung stehenden Datenbanken zugewiesen.


Schritt 2 - Erstellen des Dialogs

Hierzu werden Position und Grösse des Dialogs festgelegt, und der Variablen cTitle die Überschrift für den Dialog zugewiesen.

Wichtig ist, dass als Parent der Desktop verwendet wird. Standardmässig wird der Parent disabled, und damit auch der modale Dialog, wenn er als Child definiert ist. Dies trifft nicht zu, wenn der Desktop als Parent vorgegeben wird.

Anschliessend wird der Instanzvariablen :title der String zugewiesen, der in cTitle hinterlegt ist. Man kann alternativ die Zuweisung auch ohne den Umweg über cTitle machen.

Danach wird der Dialog erzeugt, indem Systemresourcen angefordert werden. Der Dialog selbst ist aber noch "unsichtbar".


Schritt 3 - Bereitstellen einer Variablen für die :drawingArea

Ein Fenster ist erst einmal ein rechtwinkliger Bereich. Ein XbpDialog() umfasst standardmässig (!) sowie die sogenannte Titlebar mit Systemmenü, Überschrift, sowie den Buttons zum Minimieren, Maximieren und Schliessen. Ebenfalls zur Grundausstattung gehört der Rahmen um einen solchen Dialog, und eventuell auch eine Menüleiste.

Der XbpDialog() besitzt einen als :drawingArea bezeichneten Bereich, der innerhalb (!) der gerade beschriebenen Elemente liegt und als "Zeichnungsfläche" für Xbase-Parts verwendet werden kann (soll).

Nach dieser Zuweisung sind oDlgWin:drawingArea und oDlg identisch, sie zeigen beide auf den gleichen Bereich. Die Verwendung der Variablen oDlg erlaubt es, mit weniger Schreibarbeit auszukommen.


Schritt 4 - Erzeugen einer XBpListbox()

Die XbpListBox() soll im oberen Bereich des Fensters angezeigt werden, entsprechend werden die Variablen aPos und aSize belegt. Da alle Elemente in diesem Dialog mit der Tab-Taste anspringbar sein sollen, muss bei jedem Xbase-Part die Instanzvariable :tabStop mit TRUE belegt werden.


Schritt 5 - Füllen der XbpListBox() mit den Daten der Auswahlliste

Erst nach dem Ausführen von :create() können nun Werte in die XbpListBox() eingetragen werden. Dies geschieht durch die Methode :addItem(). Die hinzugefügten Einträge werden in der Reihenfolge angezeigt, in der sie mittels :addItem() eingetragen wurden!


Schritt 6 - Erstellen eines OK-Buttons

Da wir mit einem modalen Dialog arbeiten wollen, gibt es bestimmte Bedingungen, die erfüllt werden müssen. Dazu gehört, dass es ein XbpPushButton()-Element geben muss, dessen Instanzvariable :default auf TRUE gesetzt sein muss (dies ist die Standard-Auswahl). Wie vorher bemerkt, soll der Anwender zwischen den Elementen mit der Tab-Taste springen können, daher wird auch hier :tabStop auf TRUE gesetzt. Als Caption wird "OK" vorgegeben.


Schritt 7 - Erstellen eines Cancel-Buttons

Um eine Auswahl zu bieten, muss auch ein Cancel-Button vorhanden sein. Analog zum OK- oder Default-Button gibt es auch hier eine Instanz-Variable, :cancel, die mit TRUE belegt sein muss, um dem XbpDialog() zu signalisieren, dass dieser XbpPushButton() zum Beenden des modalen Dialogs verwendet werden kann.


Schritt 8 - Vorbereiten der Anzeige des XbpDialog()

Mittels CenterControl(oDlgWin) wird der Dialog auf dem Bildschirm zentriert.


Schritt 9 - modale Anzeige des Dialogs

Die Methode :showModal() ist eine Neuerung, die einen Dialog ohne Event-Loop anzeigt. Der Event-Loop wird intern realisiert, und die Methode gibt zurück, ob OK oder Cancel ausgewählt wurde. Da wir hier gleichzeitig aber auch eine weitere Auswahl vornehmen wollen, müssen wir noch auf den Status von oList zugreifen:


Schritt 10 - verstecken und zerstören des Dialogs

Als erstes werden wir - in diesem Beispiel - den Dialog verstecken und zerstören. Wir haben ja noch eine Referenz auf die XbpListBox() in der Variablen oList.


Schritt 11 - Abfrage der Auswahl

Wenn der Dialog mit OK beendet wurde, lesen wir den ausgewählten Eintrag der XbpListBox() aus. Dies geschieht über die Methode :getData().


Eine Bemerkung zur Verwendung der Variablen

Es fällt vielleicht auf, dass die zwei XbpPushButton() unter Verwendung der gleichen Variablen, oXbp, erstellt werden.

Unter Clipper war es so, dass mit der zweiten Zuweisung die erste zerstört worden wäre, aber wir sind nicht mehr unter DOS, sondern unter Windows.

Bei der Erzeugung der XbpPushButton() wird dieser im Kontext des <oParent> erstellt. Man kann sich das - ganz stark vereinfacht - als eine Art AAdd(oParent, oXbp) vorstellen. Wenn die Variable oXbp verändert wird, bleibt der Eintrag in dem Array oParent unverändert.

Vielleicht kommt jetzt der Einwand: "Wie greife ich dann auf das oXbp zu, das auf den XbpPushButton() verweist?" Die Antwort ist einfach: "Gar nicht."

Ein direkter Zugriff auf die XbpPushButton() ist (eigentlich) nicht mehr erforderlich. Wenn etwas mit der XbpListBox() passiert, auf das unser Programm reagieren muss, dann kommt der Event-Loop ins Spiel: hier erhalten wir dann ein entsprechendes Event (mit oder ohne belegte Message Parameter) und eine Referenz auf die XbpListBox().

In diesem Beispiel wird die XbpListBox() mit einer eigenen Variablen erzeugt, oList - der Grund ist einfach: nach dem :showModal() wollen wir auf die XbpListBox() zugreifen und Daten auslesen. Darum müssen wir die Referenz auf dieses Xbase-Part "festhalten".


Ergebnis

Abbruch.

Der Zugriff auf oList funktioniert nicht so, wie man es erwarten würde. Obwohl ein Element ausgewählt und dann OK geklickt wurde, liefert

oList:getData()

ein leeres Array als Ergebnis zurück. Im Debugger bringt auch ein

oList:getItem(1)

als Ergebnis einen leeren String.

Mit der Anweisung oDlgWin:destroy() wurden auch alle abhängigen Xbase-Parts "zerstört". Dabei handelt es sich um

  • XbpListBox
  • XbpPushButton (OK)
  • XbpPushButton (Cancel)

Da das XbpListBox-Element noch durch oList referenziert wird, wird die gesamte Kette eben nicht entsorgt, sondern oDlgWin, oDlg und oList bleiben "vorhanden", aber in einem Zustand, der "nicht gebrauchsfähig" ist.


Beispiel 1a - Auswahl aus einer Liste über einen modalen Dialog

Für dieses Beispiel ändern wir die Reihenfolge der letzten Anweisungen:

  // Schritt 9 - modale Anzeige des Dialogs
  nResult := oDlgWin:showModal()
  // Schritt 10 - Abfrage der Auswahl
  IF nResult = XBP_MRESULT_OK
     cDatabase := oList:getItem(oList:getData()[1])
  ELSE
     cDatabase := ""
  ENDIF
  ConfirmBox(, "Ausgewählt wurde Datenbank " + cDatabase, "Ergebnis",;
     XBPMB_OK, XBPMB_INFORMATION)
  // Schritt 11 - verstecken und zerstören des Dialogs
  oDlgWin:hide()
  oDlgWin:destroy()

Wenn das :destroy() nach der Abfrage ausgeführt wird, ist die Auswirkung auf unser Problem egal, da wir bereits die erforderliche Information abgefragt haben.


Beispiel 2 - Auswahl aus einer Liste über einen modalen Dialog mit mehr Komfort

Normalerweise erwartet ein Anwender, dass man (eventuell) durch einen Doppelklick auf den Eintrag in der ListBox diesen auswählen kann. Unser Dialog erlaubt das aber nicht, daher verwenden wir den Callback-Slot :itemSelected des XbpListBox() Objektes, um bei Auswahl (= Doppelklick) eine bestimmte Aktion ausführen zu lassen.

Function Main()
  Local aPos, aSize, aList
  Local cTitle, cDataBase
  Local nI, nResult
  Local oDlg, oDlgWin, oXbp, oList
  // Schritt 1 - Definition der Auswahlliste
  aList := {"Produktion", "Test 1", "Test 2", "Remote Test"}
  // Schritt 2 - Erstellen des Dialogs
  aPos := {100, 100}
  aSize := {300, 300}
  cTitle := "Select Database For Operation"
  oDlgWin := XbpDialog():new(RootWindow():drawingArea,;
     RootWindow():drawingarea, aPos, aSize, NIL, .F.)
  oDlgWin:title := cTitle
  oDlgWin:create()
  // Schritt 3 - Bereitstellen einer Variablen für die :drawingArea
  oDlg := oDlgWin:drawingArea
  // Schritt 4 - Erzeugen einer XBpListbox()
  aPos := {10, 50}
  aSize := {260, 200}
  oList := XbpListBox():new(oDlg, oDlg, aPos, aSize)
  oList:tabStop := .T.
  oList:create()
  // Schritt 5 - Füllen der XbpListBox() mit den Daten der Auswahlliste
  FOR nI := 1 TO Len(aList)
     oList:addItem(aList[nI])
  NEXT
  // Schritt 6 - Erstellen eines OK-Buttons
  aPos := {10, 10}
  aSize := {120, 30}
  oXbp := XbpPushButton():new(oDlg, oDlg, aPos, aSize)
  oXbp:tabStop := .T.
  oXbp:caption := "OK"
  oXbp:default := .T.
  oXbp:create()
  // Schritt 6a - Verwendung von :itemSelected
  oList:itemSelected := {|| oXbp:activate(NIL, NIL, oXbp)}
  // Schritt 7 - Erstellen eines Cancel-Buttons
  aPos := {160, 10}
  oXbp := XbpPushButton():new(oDlg, oDlg, aPos, aSize)
  oXbp:tabStop := .T.
  oXbp:caption := "Cancel"
  oXbp:cancel  := .T.
  oXbp:create()
  // Schritt 8 - Vorbereiten der Anzeige des XbpDialog()
  CenterControl(oDlgWin)
  // Schritt 9 - modale Anzeige des Dialogs
  nResult := oDlgWin:showModal()
  // Schritt 10 - Abfrage der Auswahl
  IF nResult = XBP_MRESULT_OK
     cDatabase := oList:getItem(oList:getData()[1])
  ELSE
     cDatabase := ""
  ENDIF
  // Schritt 11 - verstecken und zerstören des Dialogs
  oDlgWin:hide()
  oDlgWin:destroy()
  ConfirmBox(, "Ausgewählt wurde Datenbank " + cDatabase, "Ergebnis",;
     XBPMB_OK, XBPMB_INFORMATION)
RETURN(.T.)


Schritt 6a - Verwendung von :itemSelected

Wir legen fest, dass bei der Auswahl eines ListBox-Elements die :activate-Methode von oXbp ausgeführt werden soll.


Schritt 10 - Abfrage der Auswahl

Erstaunlicherweise erhalten wir jetzt immer XBP_MRESULT_CANCEL?

In dem Codeblock, den wir in Schritt 6a definiert haben, wird auf oXbp verwiesen. Zum Zeitpunkt der Codeblock-Definition (!) deutet oXbp auf den OK-Button. Danach wird oXbp verwendet, um den Cancel-Button zu erstellen. Darum verweist oXbp (auch im Codeblock) auf den Cancel-Button.

Wir benötigen also eine weitere Variable, um den Cancel-Button erstellen zu können:

LOCAL oXbpC
...
aPos := {160, 10}
oXbpC := XbpPushButton():new(oDlg, oDlg, aPos, aSize)
oXbpC:tabStop := .T.
oXbpC:caption := "Cancel"
oXbpC:cancel  := .T.
oXbpC:create()

Und auf einmal funktioniert es, wie gewünscht:

Ein Doppelklick auf den gewünschten Eintrag in der ListBox löst automatisch auch einen virtuellen Klick auf den OK-Button aus.