Autor Beitrag
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Di 29.03.16 12:26 
Mahlzeit,

ich habe heute endlich mal damit angefangen das MVVM Pattern zu lernen. Den Sinn und alles dahinter verstehe ich schon aber ich habe dann doch noch einige Grundlegende Fragen:
1. In den Tutorials die ich gesehen habe wird immer das ViewModel im XAML erzeugt, dass kann ja aber in einem richtigen Projekt kaum realistisch sein, da ja niemand sonst auf das ViewModel zugreifen kann. Wer sollte also die ViewModel-Instanz erzeugen?
2. Wie wird dann mit dem Backend (in meinem Fall ist alles lokal, also keine Server-Client Anwendung) kommuniziert, bzw. wie kann das Backend das ViewModel über Änderungen im Model informieren? Als konkretes Beispiel: Ich habe einen Task der im Hintergrund irgendwelche Operationen auf einem Array ausführt. Das dabei entstehende Resultat beeinflusst ein Teil des Model. Wie soll man nun die Änderung am Model an das ViewModel weiterleiten? Müssen im Model dann auch Notifications eingebaut werden (was meines Wissens ja nicht getan werden soll)? Oder muss das Backend mit dem ViewModel kommunizieren?

Mir ist noch nicht ganz klar wie das MVVM Pattern letztendlich mit dem Backend verbunden wird.

Bin für jede Hilfe dankbar.

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mi 30.03.16 00:19 
Frage 1:

Das ist völlig dir überlassen.
Das Einzige, was ich nicht machen würde: Es im CodeBehing erzeugen.
Manchmal ist es sinnvoll, die Instanz in XAML zu erzeugen, manchmal kommt es als Property von einem anderen ViewModel. Meistens aber Letzteres.

Frage 2:

Zitat:
wie kann das Backend das ViewModel über Änderungen im Model informieren?

INotifyPropertyChanged ;)
Oder andere Observer-Pattern-Umsetzungen.
Zitat:
was meines Wissens ja nicht getan werden soll

Warum nicht?
Zitat:
Oder muss das Backend mit dem ViewModel kommunizieren?

Bloß nicht :D Das ViewModel kennt das Model, Umgekehrt ist tödlich :D Oder es macht zumindest die Schichtentrennung kaputt.
Zitat:
Mir ist noch nicht ganz klar wie das MVVM Pattern letztendlich mit dem Backend verbunden wird.

Auch hier ist alles frei. Solange Das Model das ViewModel nicht kennt, ist alles erlaubt - solange Du andere Patterns, die Du anwenden willst, nicht verletzt.
Ich mag die Variante mit dem Business-Layer zwischen Model und ViewModel. Da würde auch jedes Business-Object INotifyPropertyChanged bzw. INotifyCollectionChanged implementieren.

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mi 30.03.16 09:25 
Zitat:
Bloß nicht :D Das ViewModel kennt das Model, Umgekehrt ist tödlich :D Oder es macht zumindest die Schichtentrennung kaputt.

Ja das weiß ich :D. Es soll ja nicht das Model mit dem ViewModel kommunizieren sondern das Backend (in meinem Fall eine Engine).

Der Ablauf meines Programms sieht in etwa so aus:
1. In der UI werden irgendwelche Komponenten erstellt (modulare Bausteine, wie in einem Schaltplan)
2. Diese Objekte müssen dann in der Engine registriert werden
3. Die Engine läuft und dabei ändern sich natürlich auch die Module.
4. Die Änderungen sollen dann natürlich in der UI wieder angezeigt werden.

Wenn ich dich richtig verstehe, soll ich die Engine an das Model binden und das Model mit Notifications ausstatten, wie im ViewModel.

Klingt eigentlich logisch :D

Bei dem von mir beschriebenen Ablauf sollte ich dann wohl in dem Host Control (oder Modul Container, Window, ...) das Control erzeugen. Dieses erzeugt dann im XAML das ViewModel welches dann wiederum das Model erzeugt. Und dass ViewModel bindet dann das Model an die Engine :gruebel:? Klingt irgendwie falsch.

Wie sieht es aus wenn ich eine Klasse vom Typ Application anlege und diese dann die Kommunikation managet (ich nenne die Klasse jetzt einfach mal AppManager). Die UI informiert den AppManager, dass ein neues Modul generiert werden soll. Der AppManager sagt dem Host Control, dass ein neues Objekt angelegt werden soll (Command Pattern). Das generierte Modul (in Form eines UserControls) erzeugt dann das ViewModel und der AppManager erzeugt das Model und bindet dieses an das ViewModel und die Engine. Klingt besser finde ich.

Oder ist mein Ansatz falsch bzw. gibt es bessere (im Sinne von Stil) Wege mein Vorhaben umzusetzen?

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mi 30.03.16 12:02 
Irgendwie klingt das nach Spiel und ich bezweifle, dass MVVM da das richtige Pattern ist ^^
MVVM entfaltet gerade bei umfangreicher Business-Software die Vorteile.



Zitat:
Wenn ich dich richtig verstehe, soll ich die Engine an das Model binden und das Model mit Notifications ausstatten, wie im ViewModel.


Ich weiß nicht genau, was die Engine tut, aber rein nach MVVM würde ich das zwischen Model und ViewModel ansortieren.
ViewModel nutzt dann Model oder Engine. Wobei hier vermutlich passender ist, sich die Engine neben dem Model vorzustellen. Model sind dann die reinen Daten während die Engine die Features bereit hält und dafür das Model vom ViewModel zugewiesen bekommt.
Sprich: Das ViewModel holt sich ein Model, es holt sich die Engine, gibt der Engine das Model und verlangt die Ausführung eines Features.

Zitat:
Wie sieht es aus wenn ich eine Klasse vom Typ Application anlege und diese dann die Kommunikation managet


Gibts schon, die Idee :P
Zumindest würde ich das mit dem Bootstrapper in Verbindung mit dem IoC-Pattern vergleichen.
Im Bootstrapper werden eine Typen, die Du vom IoC-Container gemanaged haben willst, registriert. Du kannst dann eine Instanz definieren oder einen Typen und angeben wann eine neue Instanz erzeugt werden soll.
Die einzelnen Komponenten fragen dann nur noch beim ServiceLocator nach einer Instanz des jeweiligen Typs und der IoC-Container, der dahinter versteckt ist, sorgt dafür, dass eine Instanz des Tys erzeugt und Abhängigkeiten, für die ebenfalls etwas registriert ist, entsprechend aufgelöst werden. So macht es zumindest Unity.


PS: Unity, der IoC-Container, nicht die Spiele-Engine ;)

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mi 30.03.16 12:45 
Zitat:
Irgendwie klingt das nach Spiel

:D für ein Spiel würde in XNA, FlatRedBall oder Unity (die Game Engine) verwenden und nicht WPF :D

Es handelt sich in meinem Fall um eine Simulationssoftware, ähnlich einer Simulation für elektrische Schaltungen. Die Engine ist komplett von der UI abgekoppelt und läuft in einem geschlossenen System (ich könnte das Teil theoretisch auch über ne Konsole steuern :mrgreen:). Was ich eigentlich suche, ist ein Verfahren, wie ich die Komponenten/Module/Operatoren der Engine parametrieren, den aktuellen Stand anzeigen und evtl. auch neue Komponenten (die Typen sind bekannt, also nur neue Instanzen) verknüpfen kann.

Mit Bootstrapper hab ich noch nie gearbeitet; werde ich mir mal anschauen.

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mi 30.03.16 13:01 
Ein Bootstrapper ist nichts besonderes, nur eine Klasse, mit einer Run-Methode (oder irgendwelchen ähnlichen Abwandlungen).
Der Start entspricht dann folgendem Code:

ausblenden C#-Quelltext
1:
new MyBootstrapper().Run();					


Darin passiert dann die registrierung im IoC-Container und es wird die eigentliche Anwendung gestartet.
Wie Du das aufbaust, ist komplett dir überlassen. Einziges Ziel ist es, den Start der Anwendung bzw. die Initialisierung in eine oder mehrere Klassen aufzuteilen, um die Main-Methode leer zu halten und alles zu strukturieren. So kannst Du z.B. für verschiedene Bestandteile (Datanbank-Verbindungs-Daten abfragen, Abhängigkeiten registrieren, irgendwelche Daten aus der Registry lesen, etc.) in eigene Bootstrapper-Klassen auslagern und die dann in einem MainBoostrapper nacheinander verwenden.


Das ganze Konzept ist an das Booten vom PC angelehnt.
Dort gibt es ja auch das Bios, was dann erst das eigentliche Programm startet.
Es liest benötigte Daten aus, sammelt die Daten, testet die Daten, initialisiert irgendwas, fragt eventuell ein Boot-Passwort ab und startet dann die Anwendung mit den gewonnen Daten.

Für diesen Beitrag haben gedankt: C#
jfheins
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 918
Erhaltene Danke: 158

Win 10
VS 2013, VS2015
BeitragVerfasst: Mi 30.03.16 13:28 
Also ich habe das damals auch so gemacht, dass im ViewModel die wesentliche Logik ist. Die Models implementieren dann INotifyPropertyChanged damit die GUI reagieren kann.
In den Models ist ein Minimum an Code, in den Views eben das, was direkt mit der View zusammenhängt. Die Models sind im Wesentlichen Klassen die 1:1 in eine XML oder Datenbank geschrieben werden können.

Hier mal ein Bisschen Beispiel:

Der "Einstiegspunkt" ist das ViewModel der Startseite, der Konstruktor initialisiert alles und holt die Daten aus dem Backend:
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
public class MainMenuPageViewModel : BindableBase
  {
    private static readonly Random Prng = new Random();
    
    private readonly ObservableCollection<Category> _categories = new ObservableCollection<Category>(); // Wichtig: Keine normale Liste
    private readonly ObservableRangeCollection<SpecialOffer> _specialOffers =
      new ObservableRangeCollection<SpecialOffer>();

    public MainMenuPageViewModel()
    {
      Categories.Add(new Category
      {
        Title = "A",
        CoverImage = new BitmapImage(new Uri(NetworkDataManager.DataPath + @"Bilder\A.jpg")),
        ClickAction = param =>
        {
          Debug.WriteLine("A clicked");
          OnNavigationRequest(
            new SpecialOfferIndexPageViewModel("A"),
            null);
        }
      });
      Categories.Add(...);
      Categories.Add(...);

      // Daten vom Netzlaufwerk laden
      LoadData();
      // Regelmäßiges Update der Daten
      var timer = new DispatcherTimer(DispatcherPriority.ContextIdle)
      {
        Interval = TimeSpan.FromMinutes(10)
      };
      timer.Tick += (sender, args) => { LoadData(); };
      timer.Start();
    }

    public ObservableRangeCollection<SpecialOffer> SpecialOffers
    {
      get { return _specialOffers; }
    }


BindableBase habe ich dir angehängt. Davon sind alle ViewModels und fast alle Models abgeleitet. (Damit sie die GUI aktualisieren wenn sich etwas ändert)

Das Innere der Models sieht bspw. so aus:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
  public abstract class Offer : BindableBase
  {
    private Color _color;
    private Guid _contact;
    private DateTime? _expirationDate;

    public Offer()
    {
      Color = Colors.Black;
      ExpirationDate = DateTime.Today.AddMonths(6);
      Guid = Guid.NewGuid();
    }

    [XmlIgnore]
    public Color Color
    {
      get { return _color; }
      set { SetProperty(ref _color, value); }
    }

    [XmlElement("Color")]
    public string XmlColor
    {
      get { return Color.ToString(); }
      set
      {
        var tmp = ColorConverter.ConvertFromString(value);
        if (tmp != null) Color = (Color) tmp;
      }
    }

Die relevante Property für die GUI ist Color und wichtig ist, dass im Setter die SetProperty() Methode aufgerufen wird. (Die wiederum das PropertyChanged Event auslöst)

Damit man im Designer auch was sieht, kannst du in den Konstruktoren Beispieldaten erzeugen:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
  class SpecialOfferDetailPageViewModel : BindableBase
  {
    private SpecialOffer _model;

    public SpecialOfferDetailPageViewModel()
    {
      // Designer
      if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
      {
        var vm = new MainMenuPageViewModel();
        Model = vm.SpecialOffers.First();
        Model.ContactName = "MR. X";
        Model.ContactEmail = "mrx@example.com";
      }
    }

    public SpecialOffer Model
    {
      get { return _model; }
      set { SetProperty(ref _model, value); }
    }

  }


Es gibt wohl auch die Möglichkeit, diese Daten in einer separaten XML-Datei zu hinterlegen, aber damit habe ich schlechte Erfahrungen gemacht. Nach einer Modeländerung bekam man keinen Compilerfehler, sondern nur der Designer muckt auf, iirc.

Ich hoffe, das hat dir jetzt irgendwie geholfen, ich habe auch etwas gebraucht um das MvvM irgendwie zu verstehen. (Oder zumindest den Glauben zu bekommen, es zu verstehen)
Einloggen, um Attachments anzusehen!

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mi 30.03.16 13:51 
@jfheins
Das MVVM an sich hab ich schon Verstanden, mir war nur nicht klar wie ich mit dem Backend kommuniziere. Ich werde es wohl so wie in deinem Beispiel machen und die Engine an die ViewModels koppeln.

@Palladin007
Ich habe mich jetzt mal ein wenig in IoC, DI und Bootstrapper eingelesen. Bootstrapper könnte ich wirklich leicht verwenden, aber um mit IoC zu arbeiten brauche ich vermutlich mehr Zeit. Ich werde jetzt erst mal an meinem Projekt mit MVVM und ohne IoC arbeiten. Ich kann nicht alles auf einmal lernen :D

Danke euch beiden für die Hilfe.

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler