Zugriff auf Variablen klassenübergreifend

  • WPF

Es gibt 25 Antworten in diesem Thema. Der letzte Beitrag () ist von kafffee.

    Zugriff auf Variablen klassenübergreifend

    Einen schönen Nachmittag euch allen,

    ich will nach dem MVVM Pattern programmieren und möchte aus meinem SucheViewModel auf eine ObservableCollection (Playlist) in meinem PlattendecksViewModel zugreifen.

    Jetzt kann man natürlich das machen:

    VB.NET-Quellcode

    1. Dim MeineInstanz As ViewModel.PlattendecksViewModel = New ViewModel.PlattendecksViewModel
    2. MeineInstanz.Playlist.Add(item)


    Aber ist das sinnvoll? Wohl eher nicht, da wird dann jedes Mal wenn der Code durchlaufen wird, eine neue Instanz erstellt oder?

    Aber was mach ich dann? Im MainViewModel in der Sub New() einmal eine Instanz erstellen und diese dann benutzen? Oder gar einfach gar keine verschiedenen ViewModels haben und alles in die MainViewModel packen?
    Wenn man hier deinen Kontext nicht kennt, kennt sich mit diesem Thread leider niemand aus!
    Ja, ich verfolge deine Threads und kenn mich aus was du hast, jemand anderes mal sicher nicht.

    OK, einmal mach ich noch deine Arbeit:

    Du hast ein MainViewModel, in diesem hast du eine Eigenschaft welcher du immer ein anderes ViewModel zuweist je nachdem was du im View anzeigen willst. eine davon ist das "SucheViewModel". Wieder ein anderes davon ist das PlattendecksViewModel.
    Die Instanz davon erstellst du innerhalb des MainViewModels.

    Soweit so gut.

    Willst du in deinem SucheViewModel nun auf eine Eigenschaft aus einem anderen ViewModel-Objekt zugreifen musst du dir die Instanz reinreichen.
    Am besten über den Konstruktor. Mach dir also einen Konstruktor in deinem PlattendeckViewModel welcher als Parameter ein "SucheViewModel" entgegennimmt und halte dir die Referenz in einer Variablen. Fertig.

    Ich mach jetzt absichtlich kein Codebeispiel, da es im Grunde zu simple ist und du ein wenig mitdenken musst. So lernst du besser. Teile dann am besten deinen Erfolg hier.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Nofear23m schrieb:

    Willst du in deinem SucheViewModel nun auf eine Eigenschaft aus einem anderen ViewModel-Objekt zugreifen musst du dir die Instanz reinreichen.

    Naja - ich mach meine MainViewmodels immer als "Pseudo-Singleton".
    Also es gibt nur eine einzige Public Shared Instanz davon.
    (Zur Not) kann auf diese Weise von jedem Viewmodel jedes andere erreicht werden
    - ohne dass ich anfangen muss mit gross herumreichen und reinreichen von sich selbst.

    ErfinderDesRades schrieb:

    ich mach meine MainViewmodels immer als "Pseudo-Singleton"

    Was natürlich auch eine Möglichkeit ist, aber später auch unangenehme Nebeneffekte haben kann. Gerade in Verbindung mit UnitTests. Ich weis, das ist beim TE noch kein Thema, aber deshalb empfehle ich es nicht pauschal.
    Aber da der TE eben auch noch nicht ganz so weit ist, ist Singleton glaube ich auch noch nicht so gut Begreiflich für ihn.

    Korrekt ist es einfach die Instanz zu übergeben.
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Ich nutze auch mehrere ViewModels und das ist auch überhaupt kein Problem. Der Vorteil ist, man kann alles semantisch zusammenpacken in die entsprechenden ViewModels.

    Ich glaube, du musst dir vor allem über eines klar werden: Deine Hierarchie. Wenn du in deinem Programm-Code Elemente hast, die du bei anderen Elemente häufig/immer brauchst, dann solltest du überlegen, den übergeordneten Elementen die unteren bekannt zu machen. Ich vergleiche das immer gerne mit Mitarbeitern und Abteilungen.

    Jede Abteilung bekommt Mitarbeiter zugewiesen. Klar.
    Nun ist es ja in der normalen Welt so, dass ich als Mitarbeiter automatisch weiß, welcher Abteilung ich angehöre. Im Programmcode ist das nicht immer so. Denn wenn meiner Abteilung meine Person zugewiesen wird, muss ich nun auch dafür sorgen, dass mir zugewiesen, welcher Abteilung ich angehöre.

    In diesem Fall hast du eine Hierarchische-Abhängigkeit. Es ergibt also keinen Sinn, einen Mitarbeiter zu erstellen, der keiner Abteilung angehört. Insofern muss du dafür sorgen, dass in deinem Mitarbeiter-Konstruktor zwingend immer eine Abteilung angegeben wird, und die kannst du intern im Mitarbeiter dann auch nutzen.

    Auf dein Beispiel übertragen: Wenn eine ViewModel eine direkte Abhängigkeit einer anderen hat, dann mache sie bekannt - so wie Sascha (aka NoFear) schon geschrieben hat.
    Also ich gehe mal eurer aller Hinweise nach, kann nicht schaden:

    @Nofear23m

    Nofear23m schrieb:

    Am besten über den Konstruktor. Mach dir also einen Konstruktor in deinem PlattendeckViewModel welcher als Parameter ein "SucheViewModel" entgegennimmt und halte dir die Referenz in einer Variablen. Fertig.

    Also das hier in der SucheViewModel:

    Private MeinPlattendecksViewModel As ViewModel.PlattendecksViewModel = New ViewModel.PlattendecksViewModel(ViewModelSuche)

    und dann das hier in der PlattendecksViewModel:

    Private MeinSucheViewModel As ViewModel.Instrastructure.ViewModelBase

    VB.NET-Quellcode

    1. Public Sub New(MeinViewModel As ViewModel.Instrastructure.ViewModelBase)
    2. MeinSucheViewModel = MeinViewModel
    3. End Sub


    Hast du dir das so in etwa gedacht? Ich glaub der Code selbst ist das kleinere Problem, eher die Idee die dahinter steckt hab ich noch net so ganz verstanden?

    @ErfinderDesRades
    Ich hab ja sowas hier sowieso schon für den Wechsel zwischen meinen Views:

    VB.NET-Quellcode

    1. Private ViewModelSuche As ViewModel.Instrastructure.ViewModelBase = New ViewModel.SucheViewModel
    2. Private ViewModelPlattendecks As ViewModel.Instrastructure.ViewModelBase = New ViewModel.PlattendecksViewModel
    3. Private ViewModelEqualizer As ViewModel.Instrastructure.ViewModelBase = New ViewModel.EqualizerViewModel
    4. Private ViewModelSettings As ViewModel.Instrastructure.ViewModelBase = New ViewModel.SettingsViewModel


    Also mach ich das daraus:

    VB.NET-Quellcode

    1. Public Shared ViewModelSuche As ViewModel.SucheViewModel = New SucheViewModel
    2. Public Shared ViewModelPlattendecks As ViewModel.PlattendecksViewModel = New PlattendecksViewModel
    3. Public Shared ViewModelEqualizer As ViewModel.EqualizerViewModel = New EqualizerViewModel
    4. Public Shared ViewModelSettings As ViewModel.SettingsViewModel = New SettingsViewModel


    Ist das so deine Idee die dahintersteckt und hab ich dich richtig verstanden?

    ErfinderDesRades schrieb:

    Naja - ich mach meine MainViewmodels immer als "Pseudo-Singleton".
    Also es gibt nur eine einzige Public Shared Instanz davon.


    Und genau so dann eine Instanz von meinem MainViewModel erstellen? Und wenn ja, von wo aus?

    @PadreSperanza

    Also meine Hierarchie wäre

    oben: MainViewModel
    unten: die anderen 4 ViewModels

    Man soll aber von jedem ViewModel auf jedes andere Zugriff haben, und das an tausend Stellen (naja nicht ganz) im Code... So wie ErfinderDesRades das gesagt hat...

    kafffee schrieb:

    Also das hier in der SucheViewModel

    Korrekt, haste ja eh schon

    kafffee schrieb:

    Private MeinSucheViewModel As ViewModel.Instrastructure.ViewModelBase

    Ne, da du ja explizit ein SucheViewModel übergeben willst bleibt bei dem Typ.
    Also:

    VB.NET-Quellcode

    1. Public Sub New(suchVm As ViewModel.SucheViewModel)
    2. MeinSucheViewModel = suchVm
    3. End Sub


    kafffee schrieb:

    @ErfinderDesRades
    Ich hab ja sowas hier sowieso schon für den Wechsel zwischen meinen Views:

    Ne, eine Signleton Klasse ist anders aufgebaut. Vergiss das wieder. Damit bekommst du später Probleme, erstrecht wenn du nicht mal weist was eine Singleton Klasse ist. Ist nur gut gemeint.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    kafffee schrieb:

    oben: MainViewModel
    unten: die anderen 4 ViewModels

    Man soll aber von jedem ViewModel auf jedes andere Zugriff haben [...]


    Ja, verstehe ich. Aber genau das wäre dann ein Grund, alle Operationen in einer übergeordneten Klasse zu bündeln, meinst du nicht? Du könntest das MainViewModel nehmen, darein packst du als Bekanntheit eine neue Klasse, z.B. LayerViewModel oder dergleichen und in dieser machst du die vier ViewModel bekannt. Und wenn du nun von einer Zugriff auf die andere brauchst, gehst du über das LayerViewModel, das Methoden besitzt, um an die anderen Model zu kommen:

    Das LayerModelView

    VB.NET-Quellcode

    1. Public Class LayerViewModel
    2. Private Property Suche As ViewModelSuche
    3. Private Property Plattendecks As ViewModelPlattendecks
    4. Sub New()
    5. Suche = New ViewModelSuche(Me)
    6. Plattendecks = New ViewModelPlattendecks(Me)
    7. End Sub
    8. Public ReadOnly Property SucheViewModel() As ViewModelSuche
    9. Get
    10. SucheViewModel = Suche
    11. Exit Property
    12. End Get
    13. End Property
    14. Public ReadOnly Property PlattendecksViewModel() As ViewModelPlattendecks
    15. Get
    16. PlattendecksViewModel = Plattendecks
    17. Exit Property
    18. End Get
    19. End Property
    20. End Class


    beinhaltet also nur die Instanzen und kapselt diese.

    die beiden anderen Classen sähen zB wie folgt aus:

    VB.NET-Quellcode

    1. Public Class ViewModelSuche
    2. Private Property LayerViewModel As LayerViewModel
    3. Sub New(Parent As LayerViewModel)
    4. LayerViewModel = Parent
    5. End Sub
    6. 'Mache etwas mit dem anderen ViewModel
    7. Function HolePlattendecksAuswahl(ByVal zusatz As Object) As Object
    8. '... weiterer Code und dann Zugriff auf das andere ViewModel
    9. LayerViewModel.PlattendecksViewModel.MacheIrgendwas()
    10. '... weiterer Code
    11. HolePlattendecksAuswahl = Nothing
    12. Exit Function
    13. End Function
    14. End Class
    15. Public Class ViewModelPlattendecks
    16. Private Property LayerViewModel As LayerViewModel
    17. Sub New(Parent As LayerViewModel)
    18. LayerViewModel = Parent
    19. End Sub
    20. Public Sub MacheIrgendwas()
    21. End Sub
    22. End Class


    Ich sage nicht, dass es nicht noch bessere Lösungen oder Ansätze gibt, aber diese hier hätte zumindest eine Kapselung und du hast Zugriff auf der Ebene, bis zu welcher du sie brauchst

    übrigens: Du musst nicht zwangsläufig im LayerViewModel neue Instanzen der anderen ViewModel erstellen, du kannst ja auch die bereits bestehenden in das LayerViewModel mit hineingeben

    kafffee schrieb:

    Man soll aber von jedem ViewModel auf jedes andere Zugriff haben

    Sorry, das hatte ich komplett überlesen.

    Bist du sicher das dies notwendig ist. Ich würde sagen du machst dir über die Struktur nochmals gedanken.
    Ansonsten (falls dies wirklich notwendig wäre) würde ich das MainViewModel als Service anmelden, dadurch hast du überall Zugriff und kannst dir jederzeit die Instanz holen.
    Diese Variante wäre auch für UnitTests Save und du hast die Service-Grundlage in meiner MVVM Vorlage ja integriert. Einfach auf die selbe art ud weise anmelden wie das WindowService.

    Vorausgesetzt du verstehst das Prinzip und weist was Interfaces sind und wie man diese dann nutzt.
    Du merkst also, Grundwissen in der OOP sind bei MVVM von nöten.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    kafffee schrieb:

    Ist das so deine Idee die dahintersteckt und hab ich dich richtig verstanden?
    Nein - ich sprach nicht von jedem Viewmodel, sondern vom MainViewmodel - nicht von allen anderen VMs, dies möglciherweise auch noch geben mag.

    (Üblicherweise) können alle VMs vom MainViewmodel aus erreicht werden - weil sind da iwie referenziert - sei es als Listen-Element oder als Property.
    Wenn also das MainViewmodel als Singleton global verfügbar ist, so kann man - mit etwas hässlicher Property-Cascade von überall aus auch jedes Sub-VM addressieren

    VB.NET-Quellcode

    1. dim dings = MainViewmodel.Instance.SubViewmodel.SelectedItem.Mp3File.Interpret.Name
    Wie gesagt: nicht schön, aber problemlos machbar.
    Die Unschönheit findich hier sogar gut, weil so ein Crossover-Gegrabsche ist nunmal nicht schön.

    ErfinderDesRades schrieb:

    Wenn also das MainViewmodel als Singleton global verfügbar ist

    Bitte, bitte, bitte, bitte, bitte empfehle das nicht jemanden der nicht genau weis was er da tut. Ich weis das du es gut meinst und jemand mit deinem Erfahrungsschatz weis in diesem Fall auch was er da tut, es ist aber dennoch keine gute Idee irgendein ViewModel als Singleton zu implementieren. Sowas tritt dir später (ja, nicht gleich) in den Hintern, und zwar echt kräftig. Ich habs hinter mir.

    Packt man es als "Service" in den Container (welcher Singleton ist) hat man genau die selbe Funktionalität als wäre das ViewModel selbst Singleton, nur mit dem Vorteil das es kontrollierbar ist, was wiederrum z.b. bei UnitTests um einiges flexibler ist und somit auch wieder dem Pattern entspricht, das kannst du überall auch nachlesen. Ich weis dich das Pattern nicht interessiert, aber das steht hier ja nicht zur Debatte.

    Fakt ist das man bei Singleton echt wissen muss was man da macht und wie sich Singleton-Klassen im ernstfall Verhalten können. Und das wissen viele hier eben nicht, somit würde ich das einem "neuling" nie empfehlen.

    Ich hoffe du fasst diesen Beitrag richtig auf.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Nofear23m schrieb:

    Ich hoffe du fasst diesen Beitrag richtig auf.
    Nein, fasse ich nicht. Oder eigentlich glaube ich doch - ich nehme an, mit "richtig" meinst du, dass ich jetzt nicht gekränkt bin oderso - nee, bin ich nicht.

    Andererseits teile ich nicht deine Ansicht der "Gefahren".
    Ich find Singleton nicht so rasend schwer zu verstehen.
    Die Alternativen: Modelse rumreichen, oder DI-Container verwenden - beides vor allem eine Verschleierung und Ver-umständlichung der simplen Notwendigkeit, dasses nur eine MainViewmodel-Instanz geben darf.

    Das ist doch leicht zu verstehen, dasses in einer Anwendung nur ein MainViewmodel geben darf, oder?
    Da sag ich nochmal das böse Wort: Singleton :P

    Aber binnich schon tiefer in die Diskussion eingestiegen als ich wollte, weil keine Lust auf Glaubenskrieg.

    Sowieso war mein Beitrag eine Antwort auf kafffees konkrete Nachfrage, ob er mich recht verstund, und solch findich sollte ich nachwievor beantworten.
    Ich sehe schon ich hab eine Diskussion ausgelöst und hoffe meinerseits, auch niemanden zu kränken. Zu der Frage von @Nofear23m: Ja ich hab mir über die Struktur nochmals Gedanken gemacht und bin zu dem Schluss gekommen dass es wohl das Beste ist, das Ganze zentral zu regeln wie z.b. in dem Code von @PadreSperanza. Also hab ich mal in die Tasten gehauen und den Code in mein Projekt integriert. Die Designtime-Fehler hab ich rausbekommen, nur leider ist da ein Laufzeit-Bug drin, der meine Debugging-Skills überfordert. Es ist ein wohlbekanntes "Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt." und lässt sich durch Klick auf eines der süssen kleinen Plus-Symbole links neben dem Interpretname in der Listbox "Interpreten" auf dem Startbildschirm auslösen. Es soll das gewählte Listbox Item aus der SucheViewModel bzw. SucheView (die Listbox heisst lstInterpreten) in die ObservableCollection Playlist in der PlattendecksView übertragen werden. Ich hoffe ich hab alles... Alles andere dürfte klar sein: Ein MainViewModel, ein LayerViewModel, je ein Viewmodel für Suche, Plattendecks, Equalizer und Strings... Also wenn du, PadreSperanza, Zeit und Lust hast, gerne mal in den Upload reinschauen (und natürlich auch die anderen), ich bin für jeden Tipp dankbar der mich dem Debugging näher bringt... Was ich konkret nicht verstehe ist welcher(!) Objektverweis da jetzt gemeint ist...
    @ErfinderDesRades
    Ich hab das Thema Singletons hier im Forum mal überflogen und bin zu dem Schluss gekommen, dass es, wie du sagst, halb so wild ist...
    @Nofear23m
    Ich weiss dir ist es wichtig dass ich was lerne, daher wahrscheinlich auch dein Vorschlag mit den Interfaces. Ich hab das Thema auch mal überflogen und fand es auf den ersten Blick eher abschreckend, aber so war das auch als ich mit MVVM angefangen hab...

    Also lange Rede kurzer Sinn: Wenn ich mit dem Vorschlag von PadreSperanza scheitere oder bei gegebenem Anlass versuche ich es gerne mit den anderen zwei Ansätzen....
    Dateien
    Ich habe mir den Code nicht angesehen aber der Fehler sagt das du etwas nicht instanziiert hast. Da es laut deiner Aussage passiert wenn man auf einen Button klickt setze einen Haltepunkt in das Command und schau dir alle Daten im Lokal Fenster an. Steppe in einzelschritten durch. Irgendwas wird da null sein. Das PlattendecksViewModel oder die ObservableColllection oder oder.
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.
    Hast du dich überhaupt schon mal intensiver mit dem Debuggen beschäftigt?
    Ich habe mir mal dein Projekt runtergeladen und so wie es ist gestartet und auf das kleine Plus (wie du sagtest) geklickt.
    Bei mir wird direkt die Zeile Markiert wo der Fehler auftritt. Nämlich bei

    VB.NET-Quellcode

    1. ​LayerViewModel.PlattendecksViewModel.Playlist.Add(item)

    Da ist ​Playlist Nothing.
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.
    @Akanel

    OK das dachte ich mir schon. Habs noch net ausprobiert weil wir hatten heut den Ganzen Tag nen Handwerker da...

    Ob ich mich schon intensiver mit Debuggen beschäftigt hab? Also ich weiss dass man z. B. mit der Maus über Objekte hovern kann und dann ihren Wert angezeigt bekommt, oder dass man Haltepunkte setzen kann oder z. B. sich die Ausgabe durchlesen kann. Was ich alles drei auch gemacht hab natürlich... Wie bist du auf dein Ergebnis gekommen? Mit der Maus über die Zeile in der der Fehler auftritt oder? Was hat er bei dir da angezeigt?

    Edit: ach ja und natürlich die Aufrufliste anschauen kann man sich auch...

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „kafffee“ ()

    Ich habe wie oben beschrieben das Programm gestartet und geklickt. Dann kam folgendes Bild. Fehler durchgelesen und über entsprechende Variable mit der Maus gegangen. Siehe da.... das ding ist Nothing.


    Edit: Jau, wird hier ja auch nirgend instanziiert:

    VB.NET-Quellcode

    1. Public Class PlattendecksViewModel
    2. Inherits Instrastructure.ViewModelBase
    3. Private _Playlist As ObservableCollection(Of Model.MP3FileInfo)
    4. Public Property Playlist As ObservableCollection(Of Model.MP3FileInfo)
    5. Get
    6. Return _Playlist
    7. End Get
    8. Set(value As ObservableCollection(Of Model.MP3FileInfo))
    9. _Playlist = value
    10. RaisePropertyChanged()
    11. End Set
    12. End Property
    13. Private Property LayerViewModel As ViewModel.LayerViewModel
    14. Public Sub New(Parent As ViewModel.LayerViewModel)
    15. LayerViewModel = Parent
    16. End Sub
    17. End Class
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Akanel“ ()

    @PadreSperanza

    Hey Padre! :)

    Nochmal vielen Dank für den Tipp mit dem LayerViewModel. Hat bis jetzt spitze funktioniert. Nur stoss ich gerade an meine Grenzen damit:

    Ich habe ein DialogFenster bzw. dessen ViewModel, das nicht nur ein mal instanziiert werden soll. Eine Anwendung wäre z.B.:

    VB.NET-Quellcode

    1. ​Dim StatusVM = New StatusFensterViewModel(LayerViewModel)
    2. StatusVM.MusikbibliothekAktualisieren()
    3. dialogService.ShowModalDialog("", StatusVM, Me, True, True)


    Wenn ich nun innerhalb des StatusVM in der MusikbibliothekAktualisieren() das hier mache:

    LayerViewModel.SucheViewModel.InhaltGesamt.Add(New MP3FileInfo([...]))

    Kommt SucheViewModel = Nothing raus...

    Kannst du mir vielleicht sagen woran das liegen kann bzw. was ich dagegen tun könnte?