WPF Verständnisfragen

  • WPF

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

    WPF Verständnisfragen

    Hallo,

    ich habe mich entschlossen, mich in die Welt von WPF einzuarbeiten. Dazu habe ich einige Tutorials von A bis Z gelesen, für Orientierung und erste Schritte war es gut. Nun habe ich angefangen, ein erstes Projekt im WPF fertigzustellen. Beim Codeschreiben sind aber einige Fragen aufgekommen.

    1. Model und INotifyPropertyChanged

    Für mein Projekt benutze ich MSSQL localdb und als ORM EF Core. Ich habe die ersten Modell(Entity)klassen angelegt, Migration durchgeführt, alles Ok. Nun habe ich gelernt, dass damit das Databinding im WPF funktioniert, müssen die Modellklassen INotifyPropertyChanged implementieren. Dazu habe ich den klassichen Folder Model(s) angelegt und meine Entitymodelle quasi wiederholt, ergänzt um die Implementierung der INotifyPropertyChanged Interface (dazu verwende ich eine Basisklasse im Sinne DRY).

    Meine Fragen dazu: kann man seine eigene Entityklassen zur Implementierung der INotifyPropertyChanged verwenden? Muss man das Modell unbedingt wiederholen? Oder ist es besser, die zwei Klassen auseindanderzuhalten, da z. B. die Entityklasse auch Navigation Properties und evtl. Attribute für die Datenbanktabellenkonfiguration enthalten kann?

    2. Model und ViewModel

    Es gibt Tutorials, wo es eine eigene Modellklasse mit INotifyPropertyChanged erstellt wird und dann diese Klasse im ViewModel verwendet wird. Es gibt aber andere, wo die Modellproperties samt INotifyPropertyChanged im ViewModel deklariert werden. War es in diesen Fällen nur der Einfachheit halber so? Es ist doch sauberer und strukturierter, separate Modellklassen zu haben, richtig?

    3. Commands und EventHandlers

    Es wird geraten, im WPF mit Commands zu arbeiten, welche im ViewModel deklariert und im View durch Binding benutzt werden. Somit beinhaltet das Codebehind für XAML nur InitializeComponent und evtl. die Zuordnung des DataContexts zu einem ViewModel (falls es nicht im View selbst deklariert wurde). Wenn es so üblich ist, dann wozu braucht man EventHandlers? Man kann doch alles mit Commands lösen, oder? Ich bin noch nicht so tief ins Thema eingestiegen, vielleicht kommt das Nutzen des EventHandlers erst später zutage?

    4. Dependency Injection

    Unter WPF Core ist es empfohlen - ähnlich wie im ASP.NET Core - ein globales ServiceHost beim Applicationsstart aufzubauen. Das habe ich auch gemacht und den EF DataContext hinzugefügt. Man kann dies u. a. auch für das Repository Pattern gut benutzen, wenn man es möchte (Stichwort: DDD und Query Object Pattern). Soweit gut. Nun liest man mehr, findet man, dass es möglich ist, auch das ViewModel ins View zu injecten. Macht das Sinn? Oder ist es evtl. etwas übertrieben? Oder nur dann sinnvoll, wenn man das ViewModel auch in anderen Views verwenden möchte?

    Ich denke, das waren fürs Erste meine Fragen. Danke schon mal vorab für eure Antworten.
    Hallo vb_fan

    Ich versuche mal zu beantworten.

    Zu 1/2
    Das ist bis zu einem gewissen Teil geschmacksache bzw. kommt es auch auf das Projekt an.
    wie du richtig erkannt hast ist es erstmal wichtig INotifyPropertyChanged zu implementieren. Ob in der Modelklasse oder in der ViewModel-Klasse wäre im Grunde egal, sobald du auch im Setter des Properties das Event wirfst hast du den Benachrichtigungsmechanismus und Binding klappt wie erwünscht.... ABER....

    Oft kommt es vor das du im View nicht genau die Werte benötigst bzw. nicht genau die Struktur die das Model hat weil du z.b. Umrechnen willst/muss oder ein Passwort vor der Speicherung verschlüsseln willst usw.
    Da ist es eben flexibler wenn man an ein ViewModel Bindet anstatt an ein Model direkt da du hier machen kannst was du willst. Ausserdem benötigst du ja dann (siehe Frage 3) auch Commands usw. und somit ist eine Modelklasse "verunreinigt".

    Das widerspricht zwar teilweise dem DRY aber dann auch wieder nicht. Du benötigst ja dann im Model nur noch ein Property ohne BackingField.

    Ich mache stehts ein ViewModel für jede View. Immer. Ein Model direkt Binde ich niemals ein. Das WPF Projekt hat bei mir gar keinen Verweis auf das Model, das gibt mir wieder etwas flexibilität.

    Zu 3
    Ales was im ViewModel abgehandelt wird, wird mit Comands gelöst. In einem Projekt mit echter(!!) MVVM Struktur hast du auch keine andere Wahl da du keinen Verweis auf ein Projekt mit UI hast. Und nur auf Commands kannst du Binden.
    Events kannst du nutzen, aber nur innerhalb des CodeBehind der View und dies sollte nur gemacht werden wenn es rein die VIew betrifft.

    Zu 4

    Wie schon angesprochen hat das ViewModel keinen Verweis auf die View (bei korrektem MVVM) wodurch du keine Dialoge oder Messageboxen aufrufen kannst. Du benötigst also sogenannte Services. Hier gibt es verschiedenste Ansätze. Ein Beispiel habe ich hier im Forum gepostet. Bitte guck mal hier: [WpfNote2] Mehrschichtanwendungen mit MVVM und Generischem Repository mittels EF Core

    Hoffe ich konnte dir Helfen.

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

    zu 1)
    Ich fahre eine andere Strategie als NoFear, nämlich wenn ich eine Model-Klasse einfach nur anzeigen will, zeige ich es an - ein ViewModel spare ich mir dann.
    Das bedeutet, ich würde - wenn ich EFCore über localDB verwendete - meine Model-Klassen von einer ModelBase-Klasse erben lassen, die INotifyPropertyChanged implementiert, und IErrorInfo auch.
    Will ich mehr mit der Klasse anfangen, muss ich natürlcih auch eine Viewmodel-Klasse dazu basteln, und die leidige Aufdopplung von Code in Kauf nehmen.
    2)
    verstehe ich nicht. was meinst du mit "wo es eine eigene Modellklasse mit INotifyPropertyChanged erstellt wird und dann diese Klasse im ViewModel verwendet wird"?
    Meinst du "Beerben"? oder was? natürlich kann eine ViewmodelKlasse keine ModelKlasse beerben - bzw. das hätte nicht viel Sinn (glaub ich jedenfalls). Aber was meinst du dann?
    Also ums klarzustellen: ViewModel-Klassen brauchen INotifyPropertyChanged.
    Wird an Model-Klassen direkt vom View aus gebunden (was NoFears Definition von "richtigem" MVVM widerspricht, wovor ich aber keine Bange habe), so sind sie damit auch ViewmodelKlassen, und brauchen natürlich auch INotifyPropertyChanged.
    3)

    vb_fan schrieb:

    Man kann doch alles mit Commands lösen, oder?
    Hmm - zB beim Thema Drag'n Drop wird die Geschichte bisserl sehr ungemütlich, weil da sind viele Events beteiligt (MouseDown, DragStart, DragDrop, DragOver, MouseMove, MouseUp, etc).
    Klar, da gibts iwelche Nuget-Bibliotheken, wo man mit iwelchen String-Smells "EventToCommand" verwandeln kann, damit es eben Commands sind, die man im Viewmodel verarbeitet - damit MVVM glücklich ist.
    Grad bei DragnDrop ist mir da gelegentlich die MVVM-Moral flöten gegangen, und ich hab den Treeview selbst ins Viewmodel geholt, weil - zum Kuckuick! - ich brauchte seine Events.
    4)
    das Viewmodel ins View injekten?
    Ähm - bei mir erstellt das View sein Viewmodel - da muss es nix injecten.
    Klar, man kann iwas komisches treiben, damit das View das Viewmodel auch nicht kennt (wir sind ja sooo entkoppelt!). Aber das hat keinen Nutzen, und nur Nachteile.
    Grösster Nachteil ist, dass Grundlagen - MVVM: "Binding-Picking" im Xaml-Editor nicht möglich ist, wenn man dergestalt die Entkoppelei übertreibt.
    Guck dir das Tut mal an und überprüfe, ob deine Wpf-Anwendungen BindingPicking noch unterstützen.
    Weil es gibt erschreckend viele Tutorials, die Wpf ohne BindingPicking propagieren.
    Was ich grauenhaft finde, weil da sinkt die Hochsprache unter das Niveau von VB6.
    Danke für eure Antworten, sie haben vieles enthüllen können.

    Punkt 2: ich meine damit zb. das hier (Modellklasse):

    Spoiler anzeigen

    C#-Quellcode

    1. public class User : INotifyPropertyChanged
    2. {
    3. private int userId;
    4. private string firstName;
    5. private string lastName;
    6. public int UserId
    7. {
    8. get
    9. {
    10. return userId;
    11. }
    12. set
    13. {
    14. userId = value;
    15. OnPropertyChanged("UserId");
    16. }
    17. }
    18. public string FirstName
    19. {
    20. get
    21. {
    22. return firstName;
    23. }
    24. set
    25. {
    26. firstName = value;
    27. OnPropertyChanged("FirstName");
    28. }
    29. }
    30. public string LastName
    31. {
    32. get
    33. {
    34. return lastName;
    35. }
    36. set
    37. {
    38. lastName = value;
    39. OnPropertyChanged("LastName");
    40. }
    41. }
    42. #region INotifyPropertyChanged Members
    43. public event PropertyChangedEventHandler PropertyChanged;
    44. private void OnPropertyChanged(string propertyName)
    45. {
    46. if (PropertyChanged != null)
    47. {
    48. PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    49. }
    50. }
    51. #endregion
    52. }


    ViewModel:

    Spoiler anzeigen

    C#-Quellcode

    1. class UserViewModel
    2. {
    3. private IList<User> _UsersList;
    4. public IList<User> Users
    5. {
    6. get { return _UsersList; }
    7. set { _UsersList = value; }
    8. }
    9. public UserViewModel()
    10. {
    11. }
    12. }


    In diesem Beispiel wird der User object in einer separaten Modellklasse deklariert und im ViewModel verwendet. Es gibt aber Fälle, wo z. B. das ganze Modell im ViewModell deklariert wird.

    Punkt 4:

    Aktuell habe ich z. B. diesen Code:

    Spoiler anzeigen

    C#-Quellcode

    1. public CapacityTypeView(AppDataContext appDataContext = null)
    2. {
    3. InitializeComponent();
    4. _appDataContext = appDataContext;
    5. DataContext = new CapacityTypeViewModel(_appDataContext);
    6. }


    Klar wird hier das ViewModel im Codebehind des Views erstellt. Mit Injecten meinte ich, dass man das ViewModel im Konstruktor übergibt und es im Servicehost deklariert. Dazu müsste man ViewLocators verwenden, wenn ich richtig gelesen habe. Aktuell kenne ich diese Vorgehensweise nicht so richtig, möchte ich auch nicht anwenden, da aus meiner Sicht es Overkill ist. Ich habe es nur angesprochen, falls es irgendjemand bereits Erfahrungen damit hatte.

    ErfinderDesRades schrieb:

    Grad bei DragnDrop ist mir da gelegentlich die MVVM-Moral flöten gegangen

    finde ich ja auch nix einzuwenden. Drag&Drop Dinge betreffen rein die View und können somit dort bleiben, verletzt in keinster Weise das Pattern. Bin ich voll bei dir.

    Was ich nicht daran mag wenn man die Model-Klassen als ViewModel verwendet ist das man einen Mix bekommt.
    Wenn man nur anzeigen will nimmt man direkt die Model-Klasse. Brauch ich mal einen Command mach ich ne ViewModel-Klasse drumrum. Und im Endeffekt habe ich einen Mix, was ich unschön finde, ist Geschmacksache. Egel obs nun das Pattern verletzt oder nicht.

    Grüße
    Sascha

    Edit:

    vb_fan schrieb:

    Klar wird hier das ViewModel im Codebehind des Views erstellt. Mit Injecten meinte ich, dass man das ViewModel im Konstruktor übergibt und es im Servicehost deklariert.

    Wie gesagt, schau dir mein Beispiel mal an. da hast du von Services über DataTemplate alles dabei, inkl. Repository mit EF Core.

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

    vb_fan schrieb:

    Klar wird hier das ViewModel im Codebehind des Views erstellt.
    Ja, und das ist höchst nachteilhaft, weil verhindert das Bindingpicking wie im gegebenen Tutorial gezeigt.
    Ich erstelle das viewmodel im Xaml, nämlich in der App.Xaml.
    guck dir das Tut mal an.
    Gugge wenn wolle auch Grundlagen - MVVM-Anwendungs-Struktur