Daten zw. Klassen austauschen

  • VB.NET

Es gibt 24 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Daten zw. Klassen austauschen

    Hallo zusammen.
    Wie greift man auf die Daten einer anderen Klasse zu? Bisher habe ich nur das Senden kennen gelernt. Man legt z.B. eine neue Instanz eines Fensters an und übergibt dieser Instanz z.B. Daten zur Anzeige.
    Mein Projekt soll mehrere Tabs enthalten und jeder soll seine Aufgaben in einer eigenen Klasse abarbeiten. Jeder Tab enthält eine Observable Collection. Nun soll der Zugriff aus der Klasse01 auf die Collection in Klasse02 möglich sein. Aber hier kann man ja nicht einfach eine neue Instanz anlegen, weil man ja dann nicht auf die Daten der originalen Instanz, sondern auf die Leere Collection der neuen Instanz zugreift. Wie macht man das also?
    Es handelt sich übrigens um ein WPF-Projekt, aber das Problem mit den Klassen sollte ja allgemien sein, oder?

    Gruß

    eddi
    @eichseinet Parent-Klassen haben eine Instanz von Child-Klassen, da können sie direkt rein greifen.
    Wenn Child-Klassen was von ihren Parents haben wollen, senden sie ein Event.
    Mit entsprechenden Event-Argumenten können auch Daten "mitgenommen" werden.
    Sollen sich Childs untereinander verständigen, sollte das Parent via Event bemühen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    Danke schon mal für die Antwort. Hatte schon fast befürchtet, dass man die Daten in eine Richtung tatsächlich über Events fordern muss. Da muss ich mir jetzt erst mal einige Gedanken über die Struktur machen. Vor heute Abend wird da aber nichts mehr draus.
    Eine Frage wäre da noch, auch wenn es nicht direkt hier hin gehört; Wäre es da besser gleich auf MVVM umzustellen? Davon hab ich noch keine Ahnung, aber vielleicht lohnt sich der Aufwand ja. (also die Daten sollen dort ja auch nochmal getrennt vom Programmteil liegen, wenn ich das richtig verstanden habe)
    Diesbezüglich hätte ich auch eine Frage, da ich ebenfalls gerade vor der Aufgabe stehe, Daten zwischen Klassen auszutauschen. Meine Situation ist wie folgt: Klasse A liefert die Daten, Klasse B braucht sie, um per DataBindings die Controls der Hauptform zu füttern ('Klasse C'). Letztendlich sind Klasse A&B beides Parents, die keine Instanz voneinander haben können, nur Klasse C verfügt über jeweils eine Instanz von A&B. Meine Frage: wie tausche ich Daten zwischen den Klassen A&B aus - wäre ein Interface ein weg (habe bislang noch keine Erfahrung mit Interfaces)?
    @Peterle Ohne jetzt die Struktur genau zu kennen:
    A sendet ein Event an C, C handelt die Daten mit B und gibt danach A eine entsprechende Antwort.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    Klasse A: generiert Daten
    Klasse B: braucht Daten aus Klasse A
    Klasse B: In Abhängigkeit der Daten werden Controls auf Hauptform gesteuert

    Also A sendet per Event an B, B handelt Daten und setzt Controls auf Hauptform (DataBinding). Wie sendet A an B, ohne gegenseitige Vererbung? Per Übergabe einer Instanz von A nach B?

    Peterle schrieb:

    Letztendlich sind Klasse A&B beides Parents
    Damit wir zukünftig nicht aneinander vorbeireden. Ich kenne den Begriff Parent so, dass wenn zwei Klassen existieren und eine davon (A) die andere (B) als Objektvariable hat, dann ist A der Parent von B:

    VB.NET-Quellcode

    1. Public Class A
    2. Private InstanceOfB As New B
    3. End Class
    4. Public Class B
    5. End Class

    Demnach wäre in Deiner Beschreibung C Parent von A und B, während A und B zueinander ... (Adoptiv-?)Geschwister und Kinder von C sind.

    ##########

    A und B reden gar nicht direkt miteinander, weil sie sich nicht kennen. Sie reden über das HauptForm miteinander, wie RfG schrieb: A feuert ein Daten-Sende-Event mit allen Daten als EventArguments. C schnappt das Event auf und leitet es direkt (also ohne Event, sondern durch Aufrufe von Subs) an B weiter. B verarbeitet die Daten und schickt per Event die Antwort ans Hauptform zurück. Und C leitet diese Antwort an A weiter. So »unterhalten« sich A und B miteinander.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Häufig von mir verwendete Abkürzungen: CEs = control elements (Labels, Buttons, DGVs, ...) und tDS (typisiertes DataSet)
    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht in den Spekulatiusmodus gehen.
    Die Parent-/Child-Beziehung, hatte ich auch so interpretiert, wie du sie dargestellt hast. Bei mir haben A und B keine Instanz voneinander, weswegen ich beide als Parent bezeichnet habe (die noch keine Kinder haben ;-). Jetzt bin ich mit den Begrifflichkeiten vertraut und den Datenaustausch werde ich auch genauso umsetzen, indem A und B über C kommunizieren. Danke für die Klärung!
    @Peterle Die Informationskette könnte so ablaufen (dies ist kein getesteter Quelltext!):
    Spoiler anzeigen

    Quellcode

    1. Class A
    2. Public Event MyEvent(sender As Object, e As MyEventArgs)
    3. ' ...
    4. Dim e = New MyEventArgs
    5. RaiseEvent Me, e
    6. MessageBox.Show(e.TestVariable.ToString())
    7. End Class
    8. Class MyEventArgs
    9. Public TestVariable As Boolean
    10. End Class
    11. Class C
    12. Dim WithEvents aa As New A
    13. AddHandler aa.MyEvent, AdressOf(MyEventProcedure)
    14. ' ...
    15. Sub MyEventProcedure(sender As Object, e As MyEventArgs)
    16. ' Hier werden die Daten von C für A bereitgestellt:
    17. e.TestVariable = True
    18. End Sub
    19. End Class
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    es gibt da noch einen weiteren Ansatz: event-based Components.
    Dabei sendet Klasse A ein Event mit Daten, und Klasse B hat einen dazu passenden EventHandler, kann aber von sich aus nichts von Klasse A abonnieren, weils die garnet kennt.
    Das Abonnieren erledigt die gemeinsame Parent-Klasse C, welche ja beide kennt.
    Dort wird einmalig der Eventhandler von B ans Event von A angeschlossen, und im weiteren hat C mit der Kommunikation zuischen den beiden nix mehr zu tun.

    Praktischerweise nimmt man statt Events auch oft generische Delegaten (Func(Of TData)), auch weil Events im klassischen Sinne ist dieses Messaging ja eiglich nicht.

    Sehr schnuckliges Konzept - Bei Interesse fragen.
    @RodFromGermany
    Genauso setze ich es jetzt um.

    @ErfinderDesRades
    Ein Beispiel würde mich definitiv interessieren, um den Aufwand dieser Methodik abschätzen/vergleichen zu können. Sind die generischen Delegaten jetzt Teil des von dir erwähnten Ansatzes oder ist das nur eine Zusatzinformation, dass man den Datenaustausch per Events auch durch Delegaten umsetzen könnte?
    @Peterle Ein Event ist auch nur ein spezieller Delegate.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    Ich habe jetzt das Problem, dass mir die Klasse A die Daten per Timer, also per Nebenthread holt.

    Klasse A holt die Daten per Timer und sendet sie per Event an die Hauptform:


    VB.NET-Quellcode

    1. Public Class Klasse_A
    2. Private Sub TimerReadData_Elapsed(sender As Object, e As EventArgs)
    3. ' Zuweisung von Buffer ausgelassen...
    4. Dim args As CNewPlcDataReadyEventArgs = New CNewPlcDataReadyEventArgs With {.BufferDB120 = _bufferDB120}
    5. RaiseEvent NewPlcDataReady(Me, args)
    6. End Sub
    7. End Class


    Hauptform erhält die Daten per Event von Klasse A und sendet sie per Subs an Klasse B

    VB.NET-Quellcode

    1. Public Class Hauptform
    2. Public Sub New()
    3. InitializeComponent()
    4. lblDB120_B104.DataBindings.Add("Text", Klasse_B, "DB120B104_AbschaltungAufEndTemperatur")
    5. AddHandler Klasse_A.NewPlcDataReady, AddressOf PlcDataProcessing_NewPlcDataReady
    6. End Sub
    7. Private Sub PlcDataProcessing_NewPlcDataReady(sender As Object, e As CNewPlcDataReadyEventArgs)
    8. Klasse_B.UpdateBufferDB120(CType(e.BufferDB120, Byte()))
    9. End Sub
    10. End Class


    Klasse B wertet Daten aus und soll per Databinding Controls auf Hauptform ansteuern:

    VB.NET-Quellcode

    1. Public Class Klasse_B : Implements INotifyPropertyChanged
    2. Private _DB120B104_AbschaltungAufEndTemperatur As Integer
    3. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    4. Public Property DB120B104_AbschaltungAufEndTemperatur As Integer
    5. Get
    6. Return _DB120B104_AbschaltungAufEndTemperatur
    7. End Get
    8. Set(value As Integer)
    9. OnPropertyChanged(_DB120B104_AbschaltungAufEndTemperatur, value)
    10. End Set
    11. End Property
    12. Public Overridable Sub OnPropertyChanged(Of T)(ByRef field As T, value As T, <CallerMemberName> Optional propertyName As String = Nothing)
    13. If Not EqualityComparer(Of T).Default.Equals(field, value) Then
    14. field = value
    15. Dim e = New PropertyChangedEventArgs(propertyName)
    16. RaiseEvent PropertyChanged(Me, e)
    17. End If
    18. End Sub
    19. End Class


    Ich erhalte die Fehlermeldung:
    Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.


    Es hängt sicher mit dem Timer aus Klasse A zusammen, aber ich weiss gerade keine Lösung, wie ich das Problem grundsätzlich angehe. Funktioniert das DataBinding in diesem Fall überhaupt oder muss ich die Label direkt in der Hauptform invoken?

    PS: Habe jetzt in Kürze versucht, das Wichtigste an Code zusammenzufassen... hoffe es ist ansonsten alles klar, was passiert.

    Peterle schrieb:

    muss ich die Label direkt in der Hauptform invoken?
    Ich sehe nicht, wo die Exception auftritt.
    Aber genau diese Zeile(n) must Du in ein Invoke() packen und feddich.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    Ich konnte den Fehler erst ausfindig machen, als ich den Setter hier aus Klasse_B in nen Try/Catch-Block gepackt habe:

    VB.NET-Quellcode

    1. Public Property DB120B104_AbschaltungAufEndTemperatur As Integer
    2. Get
    3. Return _DB120B104_AbschaltungAufEndTemperatur
    4. End Get
    5. Set(value As Integer)
    6. OnPropertyChanged(_DB120B104_AbschaltungAufEndTemperatur, value)
    7. End Set
    8. End Property


    Beim OnPropertyChanged geht er also in die Exception. In meiner Hauptform mache ich dann ja nichts weiter als:

    VB.NET-Quellcode

    1. lblDB120_B104.DataBindings.Add("Text", Klasse_B, "DB120B104_AbschaltungAufEndTemperatur")


    Daher ist mir nicht klar, an welcher Stelle ich mit meinem Konstrukt invoken soll/kann.

    Peterle schrieb:

    nichts weiter
    Du versuchst, aus einem Nebenthread ein GUI-Element zu manipulieren.
    Pack genau diese Zeile in ein Invoke und feddich.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    Das wirft bei mir jetzt eine Unmenge an Fragen auf, wie man ein Projekt nun am Besten angeht.
    Mein Projekt besteht aus mehreren Tabs. Nun dachte ich mir für jeden Tab eine Page zu nehmen und dort dann die Oberfläche einzubauen.

    1. Page 1 fordert die komplette Collection aus Page 2 an und legt die Daten in eine Kopie der original Collection. (so soll das doch laufen, oder?) Die Kopie wird bearbeitet und damit die original Collection beim Rücksenden überschrieben? Hab ich das Grundprinzip korrekt verstanden?

    2. Ändert der Einsatz von MVVM an diesem Prinzip etwas? Wäre der Zugriff bei MVVM vielleicht einfacher?

    eichseinet schrieb:

    MVVM
    Willst Du von WinForm auf WPF umsteigen?
    Da muss ich Dich leider allein lassen, davon hab ich keine Ahnung.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    VB-Fragen über PN / Konversation werden ignoriert!
    Ich bin bereits umgestiegen. Die Kommunikation zw. Klassen sollte ohne MVVM aber gleich sein (soweit mir bekannt!). Daher hab ich's hier gepostet. Eigentlich wollte ich zwar WPF aber noch kein volles MVVM einsetzen. Nur wenn's damit einfacher wäre lohnt sich der Aufwand eventuell.
    Aktuell steht der gesamte Code nämlich im Code Behind vom MainWindow und das ist unübersichlicher Schrott.
    Habe gerade ein 1h Video zu MVVM gesehen und werde das morgen mal versuchen umzusetzen.

    Danke für die interessanten Beiträge.

    Peterle schrieb:

    Ein Beispiel würde mich definitiv interessieren

    Als Beispiel habich als "Sender" (alias "Producer") ein Dingens gemacht, was alle 1500 ms einen Double verschickt:

    VB.NET-Quellcode

    1. Imports System.Timers
    2. Public Class UpCounter
    3. Public Ticked As Action(Of Double) ' Public OutPin, uninitialisiert - hieran kann der entsprechende InPin einer beliebigen ConsumerKlasse zugewiesen werden
    4. Private WithEvents _Timer As Timers.Timer
    5. Private _Rnd As New Random
    6. Public Sub New(syncObj As System.ComponentModel.ISynchronizeInvoke)
    7. _Timer = New Timers.Timer(1500) With {.SynchronizingObject = syncObj, .Enabled = True}
    8. End Sub
    9. Private Sub _Timer_Elapsed(sender As Object, e As ElapsedEventArgs) Handles _Timer.Elapsed
    10. Ticked?.Invoke(_Rnd.NextDouble) ' beachte den '?.'-Operator: ist am OutPin nix angeschlossen, so wird auch nix gesendet
    11. End Sub
    12. End Class


    Consumer ist eine Klasse 'Viewmodel', die die Daten in eine BindingList packt

    VB.NET-Quellcode

    1. Public Class Viewmodel
    2. ' Public ReadOnly InPin, initialisiert - zur Zuweisung an den entsprechenden OutPin einer beliebigen Producer-Klasse
    3. Public ReadOnly Ticked As Action(Of Double) = Sub(dbl) Data.Add(Tuple.Create(dbl))
    4. Public Data As New System.ComponentModel.BindingList(Of Tuple(Of Double))
    5. End Class



    Die BindingList ist DataSource eines DGVs.
    Und in Zeile #9 das entscheidende: Producer und Consumer werden miteinander verknüppert

    VB.NET-Quellcode

    1. Public Class frmEbcSimple
    2. Private _UpCounter As New UpCounter(Me)
    3. Private _VM As New Viewmodel
    4. Public Sub New()
    5. InitializeComponent()
    6. BindingSource1.DataSource = _VM.Data ' Databinding: das Gui bindet sich ans Viewmodel
    7. _UpCounter.Ticked = _VM.Ticked ' EBC: zwei Objekte werden miteinander verknüpft
    8. End Sub
    9. End Class


    Also Merksätze:
    • InPin des Consumers und OutPin des Producers sind public, und vom gleichen Datentyp, nämlich eine Action.
      Sollen Werte transportiert werden, nimmt man eine Action(Of T). Es können ebensogut auch mehrere Werte transportiert werden - nimmt man eben eine Action(Of T1, T2)...
    • Der InPin ist ReadOnly, und ist von vornherein initialisiert mit Verarbeitungslogik
      Einfache Logik kann gleich als anonyme Sub reingecoded sein, ausführliche Logik würde man normal implementieren, und von der anonymen Sub aus aufrufen.
    • Eine übergeordnete Klasse, die beide Klassen kennt, verknüpft dann Out- und In-Pin durch simple Zuweisung des InPins des Empfängers an den OutPin des Senders.
    feddich.

    Im Vergleich zu RFGs klassischem Ansatz ist der Aufwand sogar etwas geringer.
    Die architektonischen Abhängigkeiten sind dieselben: Producer und Consumer kennen sich nicht, aber es muss eine ParentKlasse geben, die beide kennt.
    Nur bei EBC beschränkt sich des Parents Aktivität auf nur eine Zeile Initialisierungs-Code, und dann kommen die beide alleine klar.


    (die Benamung 'UpCounter' ist etwas verunglückt, besser wäre: 'Producer')
    Dateien
    • EbcSimple00.zip

      (14,65 kB, 12 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „ErfinderDesRades“ ()