Spätestens dann, wenn Sie aus einem (Haupt-)Fenster FMain heraus ein weiteres Fenster F1 starten, könnte die Frage zu beantworten sein, nach welchem Konzept der Daten-Transfer zwischen den zwei Anwendungsfenstern erfolgen soll. In das Konzept werden sicher die folgenden Überlegungen eingehen:
In den folgenden Abschnitten werden Ihnen jeweils Kapitel genannt, in denen der Datentransfer für die o.a. Überlegungen schon beschrieben wurde.
Start-Werte können Sie dem Fenster F1 mit einem entsprechend gestalteten Konstruktor über obligatorische und optionale Parameter in der _new-Methode mitgeben → Kapitel 12.2.7.1 Projekt 1. Eine andere Möglichkeit bieten selbst entwickelte Dialoge → Kapitel 24.3.4 Account.Manager.
Sie können auch die Verschiebung von Daten aus Steuer-Elementen auf FMain und F1 über die Methoden Drag&Drop als Daten-Transfer auffassen.
In einem realen, erprobten Projekt wird die Temperatur über einen Temperatursensor auf einer Platine über eine RS232-Schnittstelle in einem Anwendungsfenster FSensor erfasst. Von einem Haupt-Fenster FMain aus wird auf den Temperatur-Messwert auf FSensor zugegriffen und die Temperatur in FMain angezeigt:
Abbildung 12.2.9.3.1: Temperaturanzeige auf FMain – daneben FSensor
Damit Sie das Projekt nachvollziehen können, werden die Temperatur-Werte über einen Zufallsgenerator in einem bestimmten Temperatur-Bereich erzeugt. Damit entfallen der Einsatz einer realen Platine und der Klasse SerialPort (gb.net).
Wenn Sie wechselseitig auf Eigenschaften und Methoden von Steuer-Elementen auf FMain und FSensor zugeifen wollen, dann müssen Sie neben der Deklaration projekt-weit geltender Variablen auch die Eigenschaft Public der betreffenden Steuer-Elemente bereits zur Entwicklungszeit auf True setzen. Zur Laufzeit ist das nicht möglich, weil die Eigenschaft Public virtuell ist. Wie bereits im → Kapitel 12.2.0 'Form – Klasse Window und Klasse Form' angemerkt gilt: So lange es keinen zwingenden Grund gibt, die Public-Eigenschaft von False auf True zu ändern, sollten Sie der vollständigen Kapselung des Zustandes und des Verhaltens von Objekten den Vorzug geben.
Andererseits gestaltet sich der bidirektionale Daten-Austausch zwischen FMain und FSensor über die öffentlichen Steuer-Elemente und Variablen sehr einfach. Der Quelltext für die Klassen FMain und FSensor in den Projekten 1 und 2 ist hinreichend kurz und wird deshalb vollständig angegeben. FMain.class
' Gambas class file Public Sub Form_Open() Application.MainWindow = FMain FMain.Center() FMain.Resizable = False piboxOn.Picture = Picture["LED/led_yellow.svg"] ValueBox1.Value = 0.2 btnStartSensor1.Text = "Temperaturmessung Sensor 1 starten" End Public Sub btnStartSensor1_Click() piboxOn.Picture = Picture["LED/led_green.svg"] FSensor.fTIBegin = 22.9 FSensor.fTIEnd = 24 FSensor.fTITrigger = ValueBox1.Value FSensor.Show() FSensor.X = FMain.X + FMain.W + 20 FSensor.Y = FMain.Y + FMain.H - FSensor.H btnStartSensor1.Enabled = False End Public Sub ValueBox1_Change() If ValueBox1.Value >= 0 And If ValueBox1.Value <= 0.9 Then FSensor.fTITrigger = ValueBox1.Value End Public Sub Form_Close() FMain.Close() End </close> FSensor.class <code gambas> ' Gambas class file Public fCurTemperature As Float Public fTIBegin As Float Public fTIEnd As Float Public fTITrigger As Float Private fSavedTemperature As Float Public Sub Form_Open() FSensor.Center() FSensor.Resizable = False TimerSimulation.Trigger() End Public Sub TimerSimulation_Timer() Dim bChanged As Boolean Randomize fCurTemperature = Rnd(fTIBegin, fTIEnd) bChanged = Abs(fCurTemperature - fSavedTemperature) > fTITrigger fSavedTemperature = fCurTemperature If bChanged Then FMain.lblTemperaturAnzeige.Text = Format(FSensor.fCurTemperature, "##.0 °C") Endif End Public Sub Form_Close() FMain.piboxOn.Picture = Picture["LED/led_yellow.svg"] ' piboxOn.Public = True FMain.btnStartSensor1.Enabled = True ' btnStartSensor1.Public = True FMain.lblTemperaturAnzeige.Text = "--- °C" ' lblTemperaturAnzeige.Public = True FMain.ValueBox1.Value = 0.2 ' ValueBox1.Public = True End
Kommentar:
Im zweiten Projekt erfolgt der Daten-Austausch zwischen den Anwendungsfenstern FMain und FSensor nur über öffentliche Schnittstellen, deren Signaturen bekannt sind. Jede Klasse kapselt somit ihre Daten vollständig gegenüber der anderen Klasse. Das wird auch im Quelltext für die beiden Klassen FMain und FSensor deutlich: Es gibt keine öffentlichen Steuer-Elemente und auch keine öffentlichen Variablen!
FMain.class ' Gambas class file Private fTITrigger As Float = 0.2 Public hSensor As FSensor Public Sub Form_Open() Application.MainWindow = FMain FMain.Center() FMain.Resizable = False piboxOn.Picture = Picture["LED/led_yellow.svg"] ValueBox1.Value = fTITrigger End Public Sub btnStartSensor1_Click() CreateSensor1() piboxOn.Picture = Picture["LED/led_green.svg"] btnStartSensor1.Enabled = False End Public Sub Sensor1_Close() piboxOn.Picture = Picture["LED/led_yellow.svg"] btnStartSensor1.Enabled = True lblTemperature1.Text = "--- °C" ValueBox1.Value = fTITrigger End Public Sub Sensor1_Change() lblTemperature1.Text = Format(hSensor.Temperature, "##.0 °C") End Public Sub ValueBox1_Change() If hSensor Then If ValueBox1.Value >= 0 And If ValueBox1.Value <= 0.9 Then hSensor.TriggerValue = ValueBox1.Value Endif Endif End Public Sub CreateSensor1() Dim hObserver As Observer Dim aRanges As Float[] = [19.9, 21] hSensor = New FSensor(aRanges[0], aRanges[1]) hSensor.Caption = "TEMPERATUR-SENSOR 1" hSensor.TriggerValue = fTITrigger hSensor.Show() hSensor.X = FMain.X + FMain.W + 20 hSensor.Y = FMain.Y + FMain.H - hSensor.H hObserver = New Observer(hSensor) As "Sensor1" End Public Sub Form_Close() FMain.Close() End
FSensor.class
' Gambas class file Private fSavedTemperature As Float = 0.0 Private $fIBegin As Float Private $fIEnd As Float '-------------------------------------------------------------------- '' Eigenschaft: Temperature, Zugriffsrecht: public, Modus: read-only Property Read Temperature As Float '' Eigenschaft: TriggerValue, Zugriffsrecht: public Property TriggerValue As Float '--------------------------------------------------------------------- Private $fTemperature As Float ' Lokale Variable ➘ Schnitt-Stelle Private $fTriggerValue As Float ' Lokale Variable ➘ Schnitt-Stelle '--------------------------------------------------------------------- '' Ereignis: Change Event Change '--------------------------------------------------------------------- Public Sub _new(IBegin As Float, IEnd As Float) If Not TypeOf(IBegin) = gb.Float Then Error.Raise("Typ-Fehler: Startwert von Temperatur-Intervall.") If Not TypeOf(IEnd) = gb.Float Then Error.Raise("Typ-Fehler: Endwert von Temperatur-Intervall.") If IBegin >= IEnd Then Error.Raise("Der Startwert ist größer oder gleich dem Endwert vom Intervall.") $fIBegin = IBegin $fIEnd = IEnd End Public Sub Form_Open() Me.Center() Me.Resizable = False TimerSimulation.Delay = 1000 TimerSimulation.Start() TimerSimulation.Trigger() End ' Form_Open() Public Sub TimerSimulation_Timer() Dim bChanged As Boolean Randomize $fTemperature = Rnd($fIBegin, $fIEnd) bChanged = Abs($fTemperature - fSavedTemperature) > $fTriggerValue fSavedTemperature = $fTemperature If bChanged Then Raise Change End ' Zusätzlicher Dienst oder Schnittstelle (öffentliche Methode): Temperatur_lesen Private Function Temperature_Read() As Float Return $fTemperature End ' Zusätzlicher Dienst oder Schnittstelle (öffentliche Methode): Trigger_lesen Private Function TriggerValue_Read() As Float Return $fTriggerValue End ' Zusätzlicher Dienst oder Schnittstelle (öffentliche Methode): Trigger_schreiben Private Sub TriggerValue_Write(Value As Float) If Value < 0 Or Value > 1 Then Error.Raise("Der Trigger-Wert liegt nicht im Intervall [0|1]") Endif $fTriggerValue = Value End
Kommentar:
Public Sub TimerSimulation_Timer() Dim bChanged As Boolean Randomize $fTemperature = Rnd($fIBegin, $fIEnd) bChanged = Abs($fTemperature - fSavedTemperature) > $fTriggerValue fSavedTemperature = $fTemperature If bChanged Then Raise Change End Public Sub Sensor1_Change() lblTemperature1.Text = Format(hSensor.Temperature, "##.0 °C") End
Public Sub Sensor1_Close() piboxOn.Picture = Picture["LED/led_yellow.svg"] btnStartSensor1.Enabled = True lblTemperature1.Text = "--- °C" ValueBox1.Value = fTITrigger End
FSensor.class:
Public Sub _new(IBegin As Float, IEnd As Float) If Not TypeOf(IBegin) = gb.Float Then Error.Raise("Typ-Fehler: Startwert.") If Not TypeOf(IEnd) = gb.Float Then Error.Raise("Typ-Fehler: Endwert von Temperatur-Intervall.") If IBegin >= IEnd Then Error.Raise("Der Startwert ist größer oder gleich dem Endwert).") $fIBegin = IBegin $fIEnd = IEnd End
FMain.class:
Public Sub CreateSensor1() Dim hObserver As Observer Dim aRanges As Float[] = [19.9, 21] hSensor = New FSensor(aRanges[0], aRanges[1]) hSensor.Caption = "TEMPERATUR-SENSOR 1" hSensor.TriggerValue = fTITrigger hSensor.Show() hSensor.X = FMain.X + FMain.W + 20 hSensor.Y = FMain.Y + FMain.H - hSensor.H hObserver = New Observer(hSensor) As "Sensor1" End
Der Observer muss erzeugt werden, da die Klasse FSensor zwar von der Klasse Form erbt, aber nicht die Erweiterungen (2 Eigenschaften und ein Ereignis) der Klasse 'im Blick' hat.
Das Projekt bietet m.E. mehrere Vorteile gegenüber Projekt 1:
Die Vorteile klingen überzeugend. Nur werden Sie recht bald feststellen, dass diese Vorteile bei größeren Projekten schnell dahin schmelzen, denn eine Klasse konsequent und ordentlich zu kapseln kann im Design sehr schwierig und langwierig sein. Gelegentlich wird der Code sogar zu umständlich. Nur das sind die Fälle für die sorgfältig abgewogene Verwendung öffentlicher Steuerelemente! So ein Fall liegt für das Projekt 1 nicht vor, so dass dem Projekt 2 der Vorzug gegeben werden sollte.
Das 3. Projekt arbeitet mit 3 Sensor-Anwendungsfenstern. Alle drei Anwendungsfenster sind Instanzen der Klasse FSensor, die aus dem Projekt 2 übernommen wird. Es wird deshalb nur der Quelltext der Klasse FMain vorgestellt und kommentiert:
[1] ' Gambas class file [2] [3] Private $iSensors As Integer = 1 [4] Private aSensors As New FSensor[] [5] Private aTrigger As Float[] = [0.1, 0.3, 0.5] [6] [7] Public Sub Form_Open() [8] Application.MainWindow = FMain [9] FMain.Center [10] FMain.Resizable = False [11] piboxOn.Picture = Picture["LED/led_green.svg"] [12] CreateSensors() [13] ValueBox1.Value = aTrigger[0] [14] ValueBox2.Value = aTrigger[1] [15] ValueBox3.Value = aTrigger[2] [16] End [17] [18] Private Sub CreateSensors() [19] Dim hSensor As FSensor, hObserver As Observer [20] Dim i As Integer [21] Dim aRanges As Float[][] = [[20, 21], [40.0, 42], [33, 35.8]] [22] [23] For i = 1 To aRanges.Count [24] hSensor = New FSensor(aRanges[i - 1][0], aRanges[i - 1][1]) [25] hSensor.Caption = "TEMPERATUR-SENSOR " & Str($iSensors) [26] hSensor.Tag = $iSensors ' Die Tag-Eigenschaft speichert die Nummer des Sensors [27] hSensor.TriggerValue = aTrigger[i - 1] [28] hSensor.Show() [29] aSensors.Add(hSensor) [30] hObserver = New Observer(aSensors[i - 1]) As "Sensor" [31] hObserver.Tag = $iSensors ' Die Tag-Eigenschaft speichert die Nummer des Observers [32] Inc $iSensors [33] Next [34] End [35] [36] Public Sub Sensor_Change() [37] Select Case Last.Tag [38] Case 1 [39] lblTemperature1.Text = Format(Last.Temperature, "##.0 °C") [40] Case 2 [41] lblTemperature2.Text = Format(Last.Temperature, "##.0 °C") [42] Case 3 [43] lblTemperature3.Text = Format(Last.Temperature, "##.0 °C") [44] End Select [45] End [46] [47] Public Sub Sensor_Close() [48] Select Case Last.Tag [49] Case 1 [50] lblTemperature1.Text = "--- °C" [51] Case 2 [52] lblTemperature2.Text = "--- °C" [53] Case 3 [54] lblTemperature3.Text = "--- °C" [55] End Select [56] End [57] [58] Public Sub ValueBox1_Change() [59] aSensors[0].TriggerValue = ValueBox1.Value [60] End [61] [62] Public Sub ValueBox2_Change() [63] aSensors[1].TriggerValue = ValueBox2.Value [64] End [65] [66] Public Sub ValueBox3_Change() [67] aSensors[2].TriggerValue = ValueBox3.Value [68] End
Kommentar:
Hinweis: Eine Alternative bietet sich, wenn Sie die Zeile 30 durch diesen Quelltext ersetzen:
hObserver = New Observer(aSensors[i - 1]) As "Sensor" & Str($iSensors)
Nun erhalten Sie drei Observer mit jeweils eigenem Event-Namen und können aus diesem Grund das Change()-Ereignis und das Close()-Ereignis separat auswerten, wenn Sie die Zeilen 36 bis 56 durch die folgenden beiden Quelltext-Abschnitte ersetzen:
Public Sub Sensor1_Change() lblTemperature1.Text = Format(aSensors[0].Temperature, "##.0 °C") End Public Sub Sensor2_Change() lblTemperature2.Text = Format(aSensors[1].Temperature, "##.0 °C") End Public Sub Sensor3_Change() lblTemperature3.Text = Format(aSensors[2].Temperature, "##.0 °C") End
Public Sub Sensor1_Close() lblTemperature1.Text = "--- °C" ValueBox1.Enabled = False End Public Sub Sensor2_Close() lblTemperature2.Text = "--- °C" ValueBox2.Enabled = False End Public Sub Sensor3_Close() lblTemperature3.Text = "--- °C" ValueBox3.Enabled = False End