Richtige Übergabe eines ViewModels

  • WPF

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von Nofear23m.

    Richtige Übergabe eines ViewModels

    Hallo liebes Forum,

    heute habe ich wieder ein kleines Problem.

    Grob gesagt geht es darum. In meinem MainViewModel habe ich eine (Observable)Collection von vielen Objekten (die als ViewModels vorhanden sind).
    Nun möchte ich eines dieser ViewModels (das, was in der SelectedElements Eigenschaft steht) zur Bearbeitung in einem externen Fenster öffnen.

    Doch wie stelle ich das ohne CodeBehind an?
    Ach ja, jedes ViewModel hat noch die Eigenschaft ID an der man es eindeutig identifizieren kann.

    PS: Ich nutze noch eine DataService Klassek, um die Dateien mit XML zu serialisieren.

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Hallo

    Nutzt du ein MVVM Framework oder nicht?

    Falls nicht musst du nun erstmal ein Interface mit Methoden zum öffnen eines Fensters erstellen. Dann brauchst du einen als Singleton implementierten ServiceContainer der deine Instanzen (Klassen welche eben solche "Service-Interfaces" implementieren) verwaltet. Anschliessend kannst du vom ViewModel aus ein Fenster öffnen und schliessen sowie z.b. einen Datenkontext übergeben ohne die View ins ViewModel zu holen.

    Das ist nicht so einfach zu verstehen, falls du da erstmal ansich von der Struktur her verständnisschwierigkeiten hast müsste ich dir glaube ich ein Diagram erstellen, sonst versteht man das glaube ich nicht.
    Dialoge, Fenster, Messageboxen und der gleichen sind glaube ich der Knackpunkt bei MVVM, das ist genau der Punkt wo die meißten dann Anfangen die View herein zu holen.

    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. ##

    Hallo,

    Nofear23m schrieb:

    Nutzt du ein MVVM Framework oder nicht?

    Ne, nutze ich nicht, ich weiß auch nicht was das ist.

    Nofear23m schrieb:

    Falls nicht musst du nun erstmal ein Interface mit Methoden zum öffnen eines Fensters erstellen. Dann brauchst du einen als Singleton implementierten ServiceContainer der deine Instanzen (Klassen welche eben solche "Service-Interfaces" implementieren) verwaltet. Anschliessend kannst du vom ViewModel aus ein Fenster öffnen und schliessen sowie z.b. einen Datenkontext übergeben ohne die View ins ViewModel zu holen.

    Ok.....
    Klingt schon mal nicht so leicht. Aber ich bin ja gewillt, die korrekte WPF Umsetzung zu lernen.

    Nofear23m schrieb:

    falls du da erstmal ansich von der Struktur her verständnisschwierigkeiten hast müsste ich dir glaube ich ein Diagram erstellen

    Ich denke, das wäre sehr gut, wenn es dir nicht all zu viel Arbeit bereitet.

    Ach ja, ich könnte nartürlich auch ein MVVM Framework benutzen, wenn das die Sache vereinfacht, dann müsstest du mir aber kurz erklären, wie das geht :D

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor

    flori2212 schrieb:

    dann müsstest du mir aber kurz erklären, wie das geht


    Da liegt der Hase im Pfeffer. Kann ich nicht.
    Ich habe mich noch nie richtig mit solche einem Framework beschäftigt. Nur darin umgeguckt. Jedes dieser Frameworks hat seine stärken und schwächen und da ich kein Freund davon bin das ich alles aus der Hand gebe habe ich mein eigenes "Framework aufgebaut". Framework ist übertrieben. Es kann aber alles was man benötigt um mit MVVM korrekt zu arbeiten, nur das der Code verständlich ist, bei manchen Frameworks muss man sich da echt zum Teil verbiegen.

    Mal, sehen. Ich werde mir was einfallen lassen wie ich diese Thematik näherbringen kann. Wird aber erst morgen was.

    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:

    Mal, sehen. Ich werde mir was einfallen lassen wie ich diese Thematik näherbringen kann. Wird aber erst morgen was.

    Kein Problem, lass dir Zeit.

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Hallo

    Ich habe mal versucht so ein Diagram zu erstellen. Naja, nicht so einfach wenn mans verstehen auch soll.



    Ich habe in diesem Diagram ein Beispiel mit zwei Services gemacht. Man kann natürlich X Services reinpacken.
    Im ViewModel sind die Interfaces welche vorgeben was eine Messagebox machen kann/können soll. Muss einen Rückgabewert haben, muss zwei Strings sowie Icon und der gleichen entgegennehmen.

    Die Applikation selbst hat dann für jedes dieser Interfaces eine Klasse wo die Methoden, Eigenschaften und vieleicht Events dieser Schnittstellen implementiert und mit normaler Logik befüllt werden.
    Sprich wenn das IMessageBoxService eine Methode Show() implementiert dann wirst du dort drinnen dann ganz normal die Messagebox aufrufen. Denn die App (in der sind wir ja gerade) hat ja Zugriff auf View/UI und somit auf Fenster, Messageboxen usw.
    Beim start der Anwendung wird einem ServiceContainer in Form einer Singleton-Klasse (ja, das ist der richtige einsatz einer Signleton) eine Instanz solch einer Klasse des jeweiligen Typs hinzugefügt.

    In UnitTests will ich aber keine Messageboxen aufrufen. Wie auch. UnitTests sind automatisiert und können gar keine Fenster oder MEssageboxen aufrufen. Also kann ein UnitTest dann sogenannte Fakes implementieren. Wie? Indem in Tests wie in der App eine Klasse erstellt wird welche das jeweilige Interface implementiert. Allerding wird die Methode (in diesem Beispiel die Show() Methode) nicht wirklich mit Logik befüllt sondern gibt für die Frage ob man speichern will einfach True oder False zurück, je nachdem was man gerade Testen will. Denn bei einem Test (selbst wenn dieser ne MessageBox öffnen könnte) wäre ja keiner da der auf ja klickt.

    Im ViewModel kann man nun ganz einfach mit maximal 2 zeilen ein Fenster oder eine MessageBox anzeigen/öffnen.

    Übrigens. Ein sauberes Beispiel findest du im WPF Notes2 PRojekt unter: [WpfNote2] Mehrschichtanwendungen mit MVVM und Generischem Repository mittels EF Core

    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. ##

    Hallo Sascha,

    vielen Dank für das Diagramm und deine Mühe.

    Das Beispielprojekt, was du verlinkt hast, ist schon seht kompliziert. Ich hab aber noch mal ein wenig gesucht und das Projekt von dir gefunden:
    github.com/NoFear23m/Schiebepuzzle

    Vielleicht erinnerst du dich noch daran.

    Hier setzt du ja das gleiche Konzept um.

    Ich werde das jetzt mal in einem Testprojekt probíeren.

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Hallo ich bins nochmal,

    Ich habe es jetzt mal selber probiert, anhand deiner Klassen in dem Projekt "Schiebepuzzle"
    Mit der MessageBox hat es geklappt, das System hab ich im großen und ganzen verstanden habe aber im Beispielprojekt noch ein paar Fragen mit Kommentare an den Rand geschrieben.

    Allerding kommt die Unverständnis bei den Dialogen. Brauche ich da jetzt für jedes Fenster einen eigenen Serivce?
    Oder wie stelle ich das da an?

    Viele Grüße
    Florian
    Dateien
    ----

    WebApps mit C#: Blazor
    Hallo

    Ich gehe mal auf die Fragen ein:


    Im MessageBoxService:
    'In deiner Klasse hattest du hier einen WaitingCusor, warum?


    Ich hatte in diesem Projekt auf einen Service (wie den MessageboxService) welcher den Mousecursor steuert. (Sanduhr oder nicht)
    Nun kann es ja sein das ich irgendwo in meinem Code dieses Service benutze und mit der "Sanduhr" signalisiere das die App läd.
    An einer anderen Stelle rufe ich aber vieleicht Zeitgleich oder vieleicht als Zwischenschritt die Messagebox auf. Da will ich nicht daran denken müssen das ich im ViewModel extra wieder den Mauscursor zurücksetze, das soll automatisch gehen. Also stellt der Aufruf der Messagebox sicher das der Cursor wieder auf normal geht, sonst müsste der User mit einer Sanduhr auf die Buttons klicken, ist nicht schön.


    Im ServiceInjector:
    'Den Sinn dieser Klasse verstehe ich nicht: was spricht dagegen, diese Anweisungen direkt in die StartUp Methode zu packen?

    Nichts spricht dagegen, nur mein Hang zu Ordnung. Wenn ich das in einer Klasse separat ablege weis ich jederzeit wo ich den Code finde und habe an einer Stelle alle Services welche ich in diesen Container reinpacke und beim start der Anwendung lediglich eine einzige Zeile code. Schön, aufgeräumt und wartungsarm.


    Im IMessageboxService:
    'Wenn ich z.B. kein Bild bräuchte, würde ich es hier gar nicht definieren, richtig?

    Richtig, die letzten 3 Parameter sind Optional und geben einen Wert vor der greift wenn dieser nicht angegeben wird.
    Optional image As EnuMessageBoxImage = 0 würde in diesem Fall einfach kein Bild laden. 0 = None

    Was deine Fragen im ServiceContainer angeht würde ich dich bitte einfach ein wenig zu Googeln, ist ein bischen viel. Deine Annahmen welche du hinterlegt hast stimmen aber!

    flori2212 schrieb:

    Brauche ich da jetzt für jedes Fenster einen eigenen Serivce?

    Nein. Wäre ja ein riesen Aufwand. Da Hilft uns die WPF mit Ihren DataTemplates. Die Idee dahinter ist das du dir entweder 1x ein Fenster nach deinen Vorstellungen erstellst, also BorderStyle, Background usw. - oder du erstellst eines im Code in der ShowDialog Methode des DialogWindowService und setzt über Code die Eigenschaften, wie du willst - wichtig ist nur das der einzige Inhalt dieses Fensters ein ContentControl ist dessen DataContext auf den Context des Fensters gebunden ist.

    XML-Quellcode

    1. <ContentPresenter Content="{Binding}"/>


    Im DialogWindowService übergiebst du das ViewModel dem Window als DatenContext.
    In der Application.xaml hast du dann DataTemplates welche der WPF mitteilen - pass auf, wenn du als DatenContext das ViewModel xyzViewModel hast dann Rendere das doch bitte mit dem UserControl uclXyz.

    XML-Quellcode

    1. <DataTemplate DataType="{x:Type viewModel:xyzViewModel}">
    2. <local:uclXyz/>
    3. </DataTemplate>


    Klingt nach Magie, aber es funktioniert super. Und hier wirst du dann sehen was die Trennung zwischen View und ViewModel aufmacht und wie gut die WPF das meistert.

    Hoffe ich konnte es einigermaßen gut erklähren. Eine genauere erklärung kommt dann wenn wir bei dem Kapitel in der Tutorialreihe angelangt sind.

    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. ##

    Hallo,

    vielen vielen Dank Sascha! Ich habs verstanden.

    Nur noch mal kurz zum kompletten Verständnis:
    Ich hab also in meinem gesammten Projekt nur 1 Fenster mit dem Content:

    XML-Quellcode

    1. ​<ContentPresenter Content="{Binding}"/>

    Und den Rest regele ich über UserControls und DataTemplates.
    Dann stellt sich aber für mich die letzte Frage. Wo speichere ich dann zum Beispiel die Höhe und Breite des Fensters, weil das ist ja keine Eigenschaft im ViewModel?

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Hallo

    Reden wir da eh wirklich von speichern? Also so das die größe des Fensters irgendwo gespeichert wie so das z.b. der User die größe verändert und beim nächsten mal geht es wieder in der größe auf.
    Oder meinst du - ob du vom ViewModel aus bestimmen kannst in welcher größe das Fenster aufgeht?

    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. ##

    Hier will ich lediglich auf das zweite hinaus.

    Nofear23m schrieb:

    Oder meinst du - ob du vom ViewModel aus bestimmen kannst in welcher größe das Fenster aufgeht?



    Die Größe speichern spielt hier keine Rolle.

    -Edit: Merke gerade, dass ich mich im obrigen Post wirklich sehr ungünstig ausgedrückt habe. Sorry für die Verwirrung.

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    OK, also willst du einfach bestimmen können in welcher Größe das Fenster aufgeht.

    Da gibt es mehrere Möglichkeiten.

    Die erste (nicht so schöne) ist das du der Methode ShowDialog einfach noch Parameter spendierst. So kannst du im ViewModel die Größe mit angeben.
    Irgendwie blöd immer die Größe mit anzugeben. Auch schlecht wartbar weil ich mir wenn ich was am View ändere alle stellen raussuchen muss an denen ich dieses View öffne. Richtig.

    Die viel bessere und dynamischere (liegt auch auf der Hand) - ich stelle bei Fenster welches als Container gilt SizeToContent="WidthAndHeight" ein.
    So, nun bestimmt das UserControl (welches ja über das Binding des ContentPresenter hier rein kommt) die Größe. Das Fenster passt sich also automatisch an die Größe dessen an was du im DataTemplate drinnen hast.

    Feine Sache oder?

    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. ##

    Hallo,

    ich muss jetzt nochmal nachfragen.
    Ich komm hier noch nicht so klar mit den Fenstern.

    Erstmal, in dem Projekt WPFNotes steht in dem Codebehind von dem SPSWindow extrem viel, was ich nicht verstehe.
    Auch verstehe ich nicht ganz wie eine ganz simple WindowService Klasse auszusehen hat. Ohne Schnickschnack. Einfach mit öffnen und schließen.

    Wenn du mir da nochmal helfen könntest, wäre es super.

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Das stimmt. Das steht ein wenig Code drinnen. Das ist soweit nichts allzu wichtiges. Ich habe mir da einfach ein paar Helferlein reingepackt.
    Beispielsweise damit ich eben setzen kann ob man die Instanz eines Fensters per ESC schliessen kann usw.

    Weiters habe ich einen Konstruktor erstellt dem ich einfach ein paar Dinge übergeben kann.

    Die Interessanteste Methode ist die FindOwnerWindow(viewModel as Object). Um die zu erklären muss ich ein wenig ausholen.
    Wir haben ja keine View im ViewModel zur Verfügung. Öffnet man ein Fenster, egal als Dialog oder nicht ist es immer gut einen Owner anzugeben. Das hat auswirkungen auf die Taskleiste, Alt+Tab usw.
    Auf jeden Fall ist es gut einen Owner anzugeben. Der Owner ist vom Typ Window. Können wir also nicht übergeben.
    In der Methode OpenWindow des WindowService verwende ich die Methode um ein Fenster mit einem ViewModel zu finden. Ich übergebe als vim ViewModel aus: Ich möchte das Fenster welches das ViewModel "xyz" als DatenContext hat als Owner setzen.

    DIe Methode FindOwnerWindow geht dann alle geöffneten Window-Instanze durch um das Fenster mit dem angegebenen DatenKontext zu finden.

    Aber weiter (und noch interessanter:
    Dir ist ja sicher aufgefallen das ich im Interface zwei Methoden zum schliessen eines Fensters hinterlegt habe. CloseWindow und CloseWindow(vm as Object)
    Warum?

    Ich kann ja ein Fenster über das Service (der ServiceContainer kann nur eine Instanz eines WindowService halten) öffnen. Gut, das ist dann offen und mit CloseWindow aus dem ViewModel kann ich es wieder schliessen. Gut.
    Angenommen ich öffne nun ein Fenster und über das ViewModel welches auf dieses Fenster gebunden ist öffne ich wieder ein Fenster. Es kann vorkommen das ich das erste Fenster nun schliessen möchte.
    Cool, ich rufe also CloseWindow auf und ups! Das falsche Fenster schliesst sich. Nämlich das letzte. Deshalb die Methode CloseWindow(vm As Object), hier kann ich zum schliessen ein ViewModel übergeben.

    Der Code in dieser Methode findet ähnlich wie FindOwnerWindow das Fenster mit dem DataContext um gezielt dieses zu schlissen. Ein kleiner Trick. ;)

    Ich hoffe ich habs einigermaßen erklären können.

    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. ##