Benutzer-Werkzeuge

Webseiten-Werkzeuge


k12:k12.2:k12.2.9:start

12.2.9 Datenaustausch zwischen Anwendungsfenstern

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:

  • Sind dem Fenster F1 über den Konstruktor Start-Werte mitzugeben?
  • Erfolgt der Daten-Austausch zwischen FMain und F1 bidirektional oder unidirektional?
  • Erfolgt der Daten-Austausch zwischen FMain und F1 einmalig oder mehrmalig, getaktet oder durch die Auswertung von Ereignissen?

In den folgenden Abschnitten werden Ihnen jeweils Kapitel genannt, in denen der Datentransfer für die o.a. Überlegungen schon beschrieben wurde.

12.2.9.1 Start-Werte F1

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.

12.2.9.2 Unidirektionaler oder bidirektionaler Daten-Austausch

  • Selbst entwickelte Dialog-Fenster → Kapitel 12.2.4 Dialog-Fenster
  • Persistente Fenster → Kapitel 12.2.6 Form – Persistenz (Projekt)

Sie können auch die Verschiebung von Daten aus Steuer-Elementen auf FMain und F1 über die Methoden Drag&Drop als Daten-Transfer auffassen.

12.2.9.3 Daten-Austausch – Projekt 1

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:

  • Die Anzeige der Temperatur ändert sich nur dann, wenn die Temperatur-Differenz größer ist als der Wert der Variablen fTITrigger.
  • Die Klasse FSensor enthält wegen der engen Bindung an die Klasse FMain keinen wieder-verwendbaren Quelltext, wie es besonders die letzten Zeilen in FSensor.class deutlich zeigen; der Quelltext wird schwer lesbar.
  • Wenn sich zum Beispiel der Name der Klasse FMain ändert, dann sind diverse Änderungen in beiden Klassen notwendig.

12.2.9.4 Daten-Austausch – Projekt 2

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:

  • Wenn Sie die Interaktion von FMain und FSensor betrachten können Sie feststellen, dass FSensor Daten produziert und durch das Ereignis 'Change' FMain nur benachrichtigt, wenn neue Daten verfügbar sind. FMain liest über eine öffentliche Schnittstelle mit bekannter Signatur den Temperaturwert als ReadOnly-Eigenschaft von FSensor aus und bereitet ihn auf, um ihn im Steuer-Element lblTemperature1 anzuzeigen. Das ist vorbildliche Daten-Kapselung: Kein Objekt weiß vom anderen mehr, als es unbedingt sein muss und jedes kümmert sich um seine eigenen Aufgaben!
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
  • In gleicher Weise wird FMain über das Ereignis 'Sensor1_Close()' informiert, dass das Anwendungsfenster FSensor geschlossen wurde. Wie die Klasse FMain auf dieses Ereignis reagiert wird allein im Quell-Text der Klasse FMain festgelegt!
Public Sub Sensor1_Close()
  piboxOn.Picture = Picture["LED/led_yellow.svg"]
  btnStartSensor1.Enabled = True
  lblTemperature1.Text = "--- °C"  
  ValueBox1.Value = fTITrigger
End
  • Die Eigenschaft FSensor.TriggerValue kann nur über eine öffentliche Schnittstelle gelesen und geschrieben werden.
  • Die Klasse FSensor verfügt über einen Konstruktor mit zwei Parametern, die im vorliegenden Projekt nur den Temperatur-Bereich festlegen, in dem die Temperatur-Werte zufällig erzeugt werden:

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:

  • Sie können die Steuerelemente auf FMain problemlos umbenennen oder allgemeiner gesagt die Implementierung der Temperaturdarstellung vollständig ändern, ohne im Hinterkopf haben zu müssen, welche Klassen sich auf die aktuellen Implementierungsdetails verlassen und diese dann entsprechend zu ändern.
  • Der interne Code der Klasse FSensor ist leichter verständlich, weil er in sich geschlossen ist.
  • Der Zustand eines Objekts der Klasse ist konsistenter, wenn der Zugriff von außen nur durch die öffentlichen Schnittstellen geregelt ist. Anders gesagt: Wenn weniger nach außen sichtbar ist, können auch die Schnittstellen weniger falsch benutzt werden.

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.

12.2.9.5 Daten-Austausch – Projekt 3

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:

  • Die Zeile 8 bietet den Vorteil, dass beim Schließen von FMain alle von FMain geöffneten Fenster ebenfalls geschlossen werden.
  • Die Anzahl der Temperaturbereiche in der Zeile 21 bestimmt die Anzahl der Instanzen der Klasse FSensor.
  • In der Zeile 24 wird eine neue Instanz der Klasse FSensor erzeugt, der 2 Parameter übergeben werden.
  • Beachten Sie, dass alle Observer den gleichen Event-Namen 'Sensor“ besitzen, unter dem Ereignisse ausgelöst werden!
  • Das Speichern der Nummer des Sensors und des Observers in der Tag-Eigenschaft (Zeile 26 und Zeile 31) bietet den Vorteil, auf die beiden Ereignisse Sensor.Change() und Sensor.Close() in den Zeilen 36 bis 45 und in den Zeilen 47 bis 56 kompakt zu reagieren. Dazu müssen Sie nur die Referenz auf das Objekt ermitteln, das das letzte Ereignis ausgelöst hat. Das gelingt mit der Kombination aus Last.Tag und Last.Temperature, denn Last gibt eine Referenz auf das Objekt zurück, das das letzte Ereignis ausgelöst hat.

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

Download

Die Website verwendet zwei temporäre Session-Cookies. Diese technisch notwendigen Cookies werden gelöscht, wenn der Web-Browser geschlossen wird! Informationen zu Cookies erhalten Sie in der Datenschutzerklärung.
k12/k12.2/k12.2.9/start.txt · Zuletzt geändert: 02.08.2016 (Externe Bearbeitung)

Seiten-Werkzeuge