Casablanca: MVVM

Hallo Fprum,

ich bräuchte nochmal eure Hilfe. Also, ich lerne gerade WPF-MVVM anhand eines Beispiels, das hier zu finden ist: https://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/. Ich verstehe da leider einiges noch nicht ganz. Ich hoffe, dass mir jemand kurz erklären kann, was da vor sich geht.

Das MVVM-Prinzip ist mir schon klar. Hier wird das ViewModel in die DataContext-Property gespeichert und in die MainWindow.xaml eingebunden. Ich nehme an, dass genau deswegen auch zunächst das "DataTemplate for Product ViewModel" in der ProductView.xaml geladen wird.

Mir ist aber an dieser Stelle die Funktionalität der RelayCommand-Klasse und das Prinzip von ICommand in dieser Klasse nicht ganz klar.

  readonly Action<object> _execute;
  readonly Predicate<object> _canExecute;
  1. Sind das delgates? Worauf bezeihen sie sich? Ich bin da etwas irritiert, weil in der CanExecute und Execute so etwas steht: _execute(parameters); Was bewirkt das.
  2. Diese Methoden werden aufgerufen, ich siehe aber nirgends, wo Command eingebunden ist.
  3. In der Methode CanExecuteChanged, wann wird add und wann wird remove angestoßen.

Danke im Voraus.

  1. Hallo Fprum,

    ich bräuchte nochmal eure Hilfe. Also, ich lerne gerade WPF-MVVM anhand eines Beispiels, das hier zu finden ist: https://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/. Ich verstehe da leider einiges noch nicht ganz. Ich hoffe, dass mir jemand kurz erklären kann, was da vor sich geht.

    Steht weiter unten unter Notes: <cite>There are many other ways to do the things shown here, but I wanted to give you a good starting point before you start diving into the confusing world of MVVM.</cite>

    Also ab damit in die Mülltonne würde ich spätestens hier entscheiden. pl

    PS: bei mir hats weiter oben schon geklingelt: Haufenweise redundanter Code. OOP zwischen Klischee und Selbstherrlichkeit.

    1. Hallo,

      vielen Dank für die Antwort. Es ist mir schon klar, dass es andere, besser Ansätze für die Realisierung gibt. Ich möchte aber gerne zunächset diesen einfachen wenn auch nicht ganz sauberen Ansatz verstehen, bevor ich mich mit anderen weitermache.

      Gruß

  2. Tach!

    Das MVVM-Prinzip ist mir schon klar. Hier wird das ViewModel in die DataContext-Property gespeichert und in die MainWindow.xaml eingebunden. Ich nehme an, dass genau deswegen auch zunächst das "DataTemplate for Product ViewModel" in der ProductView.xaml geladen wird.

    Ein DataTemplate-Element ist eigentlich nur ein Container, der die Darstellungsvorschrift für Items enthält. Typischerweise setzt man DataTemplate da ein, wo eine Menge Items darzustellen sind, beispielsweise in einer ListBox oder einem ItemControl. Diese Elemente bekommen als Items eine Collection (ObservableCollection, damit das darstellende Element auf Änderungen der Menge reagieren kann) übergeben. Wenn die Items nur einfache Strings wären, gibt es nicht viel mehr Möglichkeiten, als diese darzustellen. Aber bei komplexen Objekten sieht das anders aus. Die eine Eigenschaft ist beisielsweise ein Verweis auf ein Bild, eine andere ein Text und eine dritte ein Boolean. Das DataTemplate legt fest, dass das Icon links hinkommt, dann der Text und rechtsbündig eine Checkbox für das Boolean - oder auch was ganz anderes, kommt ja drauf an, was man ins Template reinpackt.

    Mir ist aber an dieser Stelle die Funktionalität der RelayCommand-Klasse und das Prinzip von ICommand in dieser Klasse nicht ganz klar.

      readonly Action<object> _execute;
      readonly Predicate<object> _canExecute;
    
    1. Sind das delgates? Worauf bezeihen sie sich? Ich bin da etwas irritiert, weil in der CanExecute und Execute so etwas steht: _execute(parameters); Was bewirkt das.

    Sozusagen ja, es sind aber keine Delegates im herkömmlichen .NET-Sinne, sondern Variablen, um anonyme Funktionen/Callbacks im Lambda-Stil aufnehmen zu können. Die gezeigten Eigenschaften sind private und nehmen das auf, was die Klasse über Konstruktor-Parameter übergeben bekommt. Diese Callbacks werden dann aufgerufen, um zu ermitteln, ob das Kommando ausgeführt werden kann und wenn ja, was ausgeführt werden soll. Das RelayCommand ist nur ein Gerüst, das eigentliche Leben übergibst du selbst im Konstruktor. Und _execute(parameters) bewirkt, dass die übergebene Funktion aufgerufen wird.

    1. Diese Methoden werden aufgerufen, ich siehe aber nirgends, wo Command eingebunden ist.

    Das Innenleben vom RelayCommand ist für den Anfang nicht weiter wichtig für das Verständnis. Das verwirrt nur, vor allem, wenn man die Anwendung nicht deutlich genug und vor allem nachvollziehbar zeigt. (Vielleicht geht das ja besser aus dem vollständigen Code hervor, der da noch am Schluss velinkt ist.) Der XAML-Code ist reichlich unvollständig, denn es ist nur der von Window.Resources gezeigt, nicht aber der Rest mit dem eigentlichen Inhalt der Seite.

    Zudem ist die Verwendung der RelayCommands reichlich umständlich in dem ProductViewModel gelöst. Es reicht, wenn man die als Property (mit private set) hat und direkt im Constructor die RelayCommand-Instanzen zuweist, statt noch ein privates Feld extra dafür anzulegen und verwalten zu müssen.

    public class ProductViewModel : ObservableObject
    {
        #region Fields
     
        private int _productId;
        private ProductModel _currentProduct;
     
        #endregion
    
        public RelayCommand GetProductCommand { get; private set; }
        public RelayCommand SaveProductCommand { get; private set; }
     
        public ProductViewModel {
            GetProductCommand = new RelayCommand(
                param => GetProduct(),
                param => ProductId > 0
            );
    
            SaveProductCommand = new RelayCommand(
                param => SaveProduct(),
                param => (CurrentProduct != null)
            );
        }
        
        #region Public Properties/Commands
     
        public ProductModel CurrentProduct
        {
    [gekürzt]
        }
     
        public int ProductId
        {
    [gekürzt]
        }
     
        #endregion
     
        #region Private Helpers
     
        private void GetProduct()
        {
    [gekürzt]
        }
     
        private void SaveProduct()
        {
            // You would implement your Product save here
        }
     
        #endregion
    }
    
    1. In der Methode CanExecuteChanged, wann wird add und wann wird remove angestoßen.

    Das kannst du als Verwender eigentlich komplett ignorieren. Für dich ist nur interessant, dass es ein Event namens CanExecuteChanged gibt, an das du dich bei Bedarf hängen kannst. Falls du doch diese Frage beantwortet sehen möchtest, dann solltest du dich ganz allgemein mit der Erstellung von Events beschäftigen. Das ist nichts MVVM-spezifisches.


    Meine Empfehlung, lass den Artikel links liegen und geht mal direkt zu dem darin erwähnten und verlinkten MVVM Light. Vom Autor (Laurent Bugnion) gibt es auch Videos. Zudem ist das recht verbreitet und damit gibt es reichlich Informationsquellen bei Problemen. Es gibt auch noch eine Menge Aspekte mehr, die wissenswert sind und einem das Leben als Programmierer erleichtern. Beispielsweise ist das Schreiben von observierbaren Propertys nur eine Fleißarbeit und durch die Verwendung von Strings, die den Eigenschaftsnamen an das OnPropertyChanged weitergeben tippfehleranfällig.

        public ProductModel CurrentProduct
        {
            get { return _currentProduct; }
            set
            {
                if (value != _currentProduct)
                {
                    _currentProduct = value;
                    OnPropertyChanged("CurrentProduct");
                }
            }
        }
    

    Stattdessen geht das mit dem MVVM Light deutlich kürzer:

        public ProductModel CurrentProduct
        {
            get { return _currentProduct; }
            set { Set(ref _currentProduct, value); }
        }
    

    Voraussetzung ist, dass das ViewModel von ViewModelBase erbt. Das Implementieren von INotifyPropertyChanged sowie die gezeigte Klasse ObservableObject kann man sich dann auch sparen. Wichtig ist nur die Erkenntnis, dass Änderungen an den ViewModel-Eigenschaften per OnPropertyChanged an den Verwender gemeldet werden müssen, damit das Binding dort die Anzeige aktualisieren kann. Set() erledigt das für dich. Den Namen der Eigenschaft findet es übrigens seit .NET 4.5 selbst raus.

    Weiterhin missfällt mir, dass das ProductModel observable ist. Damit bekommt es Eigenschaften, die für die Darstellung wichtig sind, aber in einem reinen Datenobjekt eigentlich nichts verloren haben. Der Vorteil ist zwar, dass man das direkt an die View durchreichen kann. Der Nachteil eines reinen Datenobjekts wäre, dass man im Viewmodel alle Eigenschaften nochmal aufführen muss (dann aber observable). Allerdings hat man so eine bessere Trennung zwischen Model und ViewModel. Üblicherweise erzeugt man das Model mit dem EntityFramework oder einer ähnlichen Datenhaltung im Hinterkopf und da stört das Observable doch eher als es nützt.

    Auch muss Property-Validation nicht mit ins Model rein (es sei denn EF-gemäß als Attribute). Für Dienste wie Validierung erstellt man sich besser einen Service (falls es über die Möglichkeiten der attributbasierten Validierung hinausgeht).

    dedlfix.

    1. Hallo,

      vielen herzlichen Dank für deine ausführliche Antwort. Wie immer hast du dir viel Zeit genommen, um meine Fragen zu beantworten.

      Es ist mir klar, dass in ProductViewModel:

       public ICommand GetProductCommand
       {
           get
           {
              if (_getProductCommand == null)
              {
                _getProductCommand = new RelayCommand(param => GetProduct(), param => ProductId > 0);
              }
      
              return _getProductCommand;
           }
       }
      

      die Methode GetProduct() und der boolsche Wert ProductId > 0 als Parameter an der RelayCommand-Konstruktor übergeben und in die Variablen _execute und _canExecute gespeicher werden. Werden diese in Execute und CanExecute:

       public bool CanExecute(object parameters)
       {
           return _canExecute == null ? true : _canExecute(parameters);
       }
      
       public void Execute(object parameters)
       {
           _execute(parameters);
       }
      

      ausgeführt? _canExecute ist kann doch nur true oder false sein. Welche Rolle spielt an dieser Stelle der Parameter "parameters"? Ist das eine Übergabeparameter an die GetProduct()? Ich verstehe das nicht ganz.

      Das ist klar. Ich kann sofort mit "MVVM Light" oder "Prism" anfangen. Ich gehe davon aber aus, dass meine Probleme da erheblich größer werden, wenn ich die Zusammenhänge nicht versetehe, die z.B. in diesem Beispiel zum Tragen kommen. Daher möchte ich zunächst mal dieses Beispiel mehr oder weniger verstehen, bevor ich mit etwas anderem anfange.

      Kennst du eventuell ein MVVM Beispiel, in dem auch die Handhabung mit den Menüs (in der Menüleiste meine ich) behandelt werden?

      Danke im Voraus.

      1. Tach!

        Es ist mir klar, dass in ProductViewModel:

         public ICommand GetProductCommand
         {
             get
             {
                if (_getProductCommand == null)
                {
                  _getProductCommand = new RelayCommand(param => GetProduct(), param => ProductId > 0);
                }
        
                return _getProductCommand;
             }
         }
        

        die Methode GetProduct() und der boolsche Wert ProductId > 0 als Parameter an der RelayCommand-Konstruktor übergeben und in die Variablen _execute und _canExecute gespeicher werden.

        Nein, das ist nicht richtig. Da werden Funktionen übergeben.

        param => GetProduct() ist die Kurzform von

        noname(object param) {
          GetProduct();
        }
        

        Und beim zweiten Parameter ähnlich. Da wird auch nicht nur ein Ergebnis übergeben sondern eine Funktion, die das Ergebnis berechnet.

        Dabei seh ich grad, dass es zumindest bei _execute sinnlos ist, den Typ Action<object> zu verwenden, denn das Object wird nie verwendet. Also kann man auch gleich Action nehmen. Bei Predicate<T> ist hier ebenso der Parameter unverwendet, aber das gibt nicht ohne <T>.

        An dieser Stelle, so scheint mir, fehlt dir grundlegendes Wissen zu C#. Siehe beispielsweise Anonyme Funktionen.

        Werden diese in Execute und CanExecute:

         public bool CanExecute(object parameters)
         {
             return _canExecute == null ? true : _canExecute(parameters);
         }
        
         public void Execute(object parameters)
         {
             _execute(parameters);
         }
        

        ausgeführt? _canExecute ist kann doch nur true oder false sein.

        Nicht "kann sein" sondern "kann zurückgeben". Es ist ein Verweis auf eine Funktion.

        Welche Rolle spielt an dieser Stelle der Parameter "parameters"? Ist das eine Übergabeparameter an die GetProduct()? Ich verstehe das nicht ganz.

        Ah, hab ich ja grad schon so beantwortet: keine Rolle, kann (teilweise) weg.

        Ich kann sofort mit "MVVM Light" oder "Prism" anfangen. Ich gehe davon aber aus, dass meine Probleme da erheblich größer werden, wenn ich die Zusammenhänge nicht versetehe, die z.B. in diesem Beispiel zum Tragen kommen. Daher möchte ich zunächst mal dieses Beispiel mehr oder weniger verstehen, bevor ich mit etwas anderem anfange.

        Ok, aber der Artikel hilft dir nur bedingt. Der Autor gesteht ja selbst, dass er auf dem Gebiet Anfänger ist. Und da macht er einen Haufen Dinge umständlicher als notwendig und das Wichtigste lässt er so gut wie unerwähnt: die Verbindung zum XAML. Das macht man mit diesen Binding-Direktiven.

        Kennst du eventuell ein MVVM Beispiel, in dem auch die Handhabung mit den Menüs (in der Menüleiste meine ich) behandelt werden?

        Menü-Elemente klickt man an und dann wird ein Kommando ausgeführt. Dabei kommt nur ein Teil vom MVVM zum Tragen.

        <Window Vorspann weggelassen>
          <DockPanel>
            <Menu DockPanel.Dock="Top">
              <MenuItem Header="_Edit">
                <MenuItem Header="_Cut" Command="{Binding Cut}" />
                <MenuItem Header="C_opy" Command="{Binding Copy}" />
                <MenuItem Header="_Paste" Command="{Binding Paste}" />
              </MenuItem>
            </Menu>
          </DockPanel>
        </Window>
        

        Das ist mal eine ganz simple View mit Menu. Die braucht jetzt noch das ViewModel an den DataContext übergeben. Der MVVM-Light-Weg wäre deklarativ und mit Dependency Injection. Man kann das aber auch wie im Artikel unter "Starting the Sample" beschrieben im Code zuweisen.

        Cut, Copy und Paste sind die gleichnamigen Eigenschaften aus dem nachfolgenden ViewModel, das - nur für das Menü - so aussieht:

        public YourViewModel : ViewModelBase {
          public RelayCommand Cut { get; private set; }
          public RelayCommand Copy { get; private set; }
          public RelayCommand Paste { get; private set; }
        
          public ViewModel() {
             Cut = new RelayCommand(() => cut());
             Copy = new RelayCommand(copy);
             Paste = new RelayCommand(delegate { paste(); });
          }
        
          private void cut() { }
          private void copy() { }
          private void paste() { }
        }
        

        Ich hab mal drei verschiedene Varianten genommen, wie man die Callback-Funktionen übergeben kann. Und ich hab das CanExecute weggelassen. In der Praxis braucht man das aber doch, denn Cut und Copy darf nur ausgeführt werden, wenn man was markiert hat und Paste nur, wenn was in der Zwischenablage ist.

        Die Cut-Variante zeigt eine anonyme Funktion, die keinen Parameter übergeben bekommt. Die Copy-Variante übergibt einfach eine Funktionsreferenz und Paste nimmt einen herkömmlichen Delegate. Seit es anonyme Funktionen gibt, sind aber die Delegates am Aussterben. In dem Fall wäre die Copy-Variante vorzuziehen, weil die Vorgänge vermutlich umfangreicher als ein Einzeiler werden. Ansonsten nehme ich eher die Cut-Variante.

        Neben den Kommandos könnte man in dem Beispiel auch noch die Header-Eigenschaften ans ViewModel binden, um programmatisch den Text ändern zu können. Üblicherweise wird man das in dem Fall wohl kaum machen, sondern Ressourcen (resx-Dateien) anbinden, in denen die jeweilige Lokalisierung enthalten ist. Sei's drum, hier das erweiterte Beispiel

        <Window Vorspann weggelassen>
          <DockPanel>
            <Menu DockPanel.Dock="Top">
              <MenuItem Header="{Binding EditHeader}">
                <MenuItem Header="{Binding CutHeader}" Command="{Binding Cut}" />
                <MenuItem Header="{Binding CopyHeader}" Command="{Binding Copy}" />
                <MenuItem Header="{Binding PasteHeader}" Command="{Binding Paste}" />
              </MenuItem>
            </Menu>
          </DockPanel>
        </Window>
        
        public YourViewModel : ViewModelBase {
          private string editHeader;
          private string cutHeader;
          private string copyHeader;
          private string pasteHeader;
        
          public RelayCommand Cut { get; private set; }
          public RelayCommand Copy { get; private set; }
          public RelayCommand Paste { get; private set; }
        
          public string EditHeader {
             get { return editHeader; }
             set { Set(ref editHeader, value); }
          }
        
          // Cut-, Copy- und PasteHeader analog zu EditHeader
        
          public ViewModel() {
             Cut = new RelayCommand(() => cut());
             Copy = new RelayCommand(copy);
             Paste = new RelayCommand(delegate { paste(); });
        
             EditHeader = "_Edit";
             CutHeader = "_Cut";
             CopyHeader = "C_opy";
             PasteHeader = "_Paste";
          }
        
          private void cut() { }
          private void copy() { }
          private void paste() { }
        }
        

        Das Beispiel zeigt die MVVM-Light-Variante mit dem Set(). Ansonsten müsstest du das Interface INotifyPropertyChanged einbinden. Beispiele dazu (außer denen im Artikel) sollten sich genügend finden lassen.

        dedlfix.

        1. Hallo,

          danke. Ich habe MVVMLight installiert. Das Menu funktioniert auch gut. Aber:

          1. die zwei Instanzen von RelayCommand funktionieren nicht so:

          Alternativ-Text Alternativ-Text

          Es sei denn, man ändert auch die RealayCommand-Kalsse. Ich habe dies wie folge umgeändert:

             Cut = new RelayCommand(param => cut());
             Copy = new RelayCommand(param => copy());
          

          Wie kann man an dieser Stelle mehrere ViewModels registrieren? Das Beispiel tut dies in App-Klass:

                  protected override void OnStartup(StartupEventArgs e)
                  {
                      base.OnStartup(e);
          
                      MainWindow app = new MainWindow();
                      ProductViewModel context = new ProductViewModel();
                      app.DataContext = context;
                      app.Show();
                  }
          

          Oder darf man da nur ein ViewModel besitzen, was ich mir schwer vorstellen kann. Nach der Installation sind zwei Klassen ind das Projekt hinzugefügt: MainViewModel und ViewModelLocator. Müssen sie an dieser Stelle angefasst werden?

          Gruß

          1. Tach!

            danke. Ich habe MVVMLight installiert. Das Menu funktioniert auch gut. Aber:

            1. die zwei Instanzen von RelayCommand funktionieren nicht so:

            Alternativ-Text Alternativ-Text

            Du machst da was nicht richtig.

            Es sei denn, man ändert auch die RealayCommand-Kalsse.

            Hast du den Code von RelayCommand aus dem Artikel genommen? Wenn ja, dann solltest du den löschen und GalaSoft.MvvmLight.CommandWpf einbinden. Da ist das richtige RelayCommand drin.

            Wie kann man an dieser Stelle mehrere ViewModels registrieren? Das Beispiel tut dies in App-Klass:

                    protected override void OnStartup(StartupEventArgs e)
                    {
                        base.OnStartup(e);
            
                        MainWindow app = new MainWindow();
                        ProductViewModel context = new ProductViewModel();
                        app.DataContext = context;
                        app.Show();
                    }
            

            Ich sehe da nur ein ViewModel (ProductViewModel), und das wird dem DataContext vom MainWindow zugewiesen. Eine Variable dafür mit app zu benennen ist nicht richtig, denn die Application ist nochmal was anderes als das Haupt-Fenster.

            Oder darf man da nur ein ViewModel besitzen, was ich mir schwer vorstellen kann.

            Du darft so viele besitzen, wie du magst. Aber jedes WPF-Element hat nur einen DataContext, an den man ein ViewModel üblicherweise bindet. Dazu brauchst du Grundlagenwissen zum DataContext und wie der sich in der Hierarchie der Elemente im XAML vererbt.

            In aller Regel wirst du pro View genau ein ViewModel verwenden wollen. Zumindest für nicht allzu komplexe Szenarien. Das bindest du dann an den DataContext vom Haupt-Element in der View. An die anderen Elemente vererbt sich das, wenn du den DataContext nicht individuell überschreibst.

            Ein Beispiel für ein solches Überschreiben ist das DataTemplate einer ListBox oder ItemsControl. Da wird der DataContext der einzelnen Elemente auf das jeweilge Element der übergebenen ItemsSource gesetzt (macht das Element von selbst). Du bindest nur die ItemsSource an eine Eigenschaft deines ViewModels, das eine ObservableCollection ist.

            Ich fand eine StackOverflow-Frage, in deren ersten beiden Antworten zusammen mit dem Code in der Frage beispielhaft zu sehen ist, wie man das macht: http://stackoverflow.com/questions/18119900/wpf-listbox-bind-itemssource-with-mvvm-light

            Nach der Installation sind zwei Klassen ind das Projekt hinzugefügt: MainViewModel und ViewModelLocator. Müssen sie an dieser Stelle angefasst werden?

            Schau mal da rein, da sind Kommentare drin, wie man die verwendet. MainViewModel ist ein Gerüst für ein ViewModel. Das kannst du verwenden, kannst es aber auch ignorieren oder löschen. Aber dann fehlt dir ein Beispiel, wie man ViewModels für den MVVM-Light-Weg erstellt und verwendet. Außerdem ist auch noch was in die App.xaml eingefügt worden. Das brauchst du, um in der View (im XAML-Code) den DataContext so wie in ViewModelLocator.cs im Kommentar beschrieben deklarativ festlegen zu können. Wenn du das so machst, musst oder besser gesagt kannst du allerdings auf das händische Setzen des DataContextes so wie im Beispiel des Artikels verzichten.

            dedlfix.

            1. Hallo,

              vielen Dank nochmals.

              Hast du den Code von RelayCommand aus dem Artikel genommen? Wenn ja, dann solltest du den löschen und GalaSoft.MvvmLight.CommandWpf einbinden. Da ist das richtige RelayCommand drin.

              Das hat funktioniert.

              Ich sehe da nur ein ViewModel (ProductViewModel), und das wird dem DataContext vom MainWindow zugewiesen. Eine Variable dafür mit app zu benennen ist nicht richtig, denn die Application ist nochmal was anderes als das Haupt-Fenster.

              Das ist richtig. Es ist so, weil ich nicht wusste, wie ich das zweite VieModel anbinden soll. Ich habe zwar nur eine View, ich wollte aber nur testen, wie das mit zwei ViewModels geht. Ich habe ein zweites ViewModel namens MenuViewModel angelegt, in dem die Logik vom Menu drin steht.

                      protected override void OnStartup(StartupEventArgs e)
                      {
                          base.OnStartup(e);
              
                          MainWindow app = new MainWindow();
                          //ProductViewModel context = new ProductViewModel();
              
                          MenuViewModel context = new MenuViewModel();
              
                          app.DataContext = context;
                          app.Show();
                      }
              

              Außerdem ist auch noch was in die App.xaml eingefügt worden. Das brauchst du, um in der View (im XAML-Code) den DataContext so wie in ViewModelLocator.cs im Kommentar beschrieben deklarativ festlegen zu können.

              Soweit ich sehen kann, ist bei mir nichte weiteres in App.xaml eingefügt worden.

              Wie werden eigentlich die Events gehandhabt, die zur Laufzeit und ohne Benutzeraktivität ausgelöst werden müssen. Z.B. ich möchte an einer Stelle eine Messagebox anzeigen oder eine bestimmte Teil-View in eine bestimmte Stelle einfügen und Anzeigen.

              Gruß

              1. Tach!

                Ich sehe da nur ein ViewModel (ProductViewModel), und das wird dem DataContext vom MainWindow zugewiesen. Eine Variable dafür mit app zu benennen ist nicht richtig, denn die Application ist nochmal was anderes als das Haupt-Fenster.

                Das ist richtig. Es ist so, weil ich nicht wusste, wie ich das zweite VieModel anbinden soll. Ich habe zwar nur eine View, ich wollte aber nur testen, wie das mit zwei ViewModels geht. Ich habe ein zweites ViewModel namens MenuViewModel angelegt, in dem die Logik vom Menu drin steht.

                Mehrere ViewModels in einer View sind kein Problem. "In aller Regel wirst du pro View genau ein ViewModel verwenden wollen. Zumindest für nicht allzu komplexe Szenarien." Das schrub ich gestern, und hier hast du nun einen Fall, in dem das Szenario komplexer wird. Du kannst ja jedem Element einen eigenen DataContext zuweisen. Dein Beispiel mal etwas gekürzt und umgeschrieben:

                MainWindow mainWindow = new MainWindow();
                mainWindow.DataContext = new ProductViewModel();
                mainWindow.Menu.DataContext = new MenuViewModel();
                

                Das MainWindow bekommt das ProductViewModel und das Menu ist Nachfahre von diesem Window und bekommt sein eigenes Viewmodel. "Menu" steht hier für den x:Name des Menu-Elements. Du kannst aber auch ohne x:Name zu vergeben auskommen, wenn du die ViewModels deklarativ zuweist, so wie es im Kommentar von ViewModelBase.cs beschrieben ist. Dann kannst du die Code-Behind-Dateien unbeachtet lassen.

                Außerdem ist auch noch was in die App.xaml eingefügt worden. Das brauchst du, um in der View (im XAML-Code) den DataContext so wie in ViewModelLocator.cs im Kommentar beschrieben deklarativ festlegen zu können.

                Soweit ich sehen kann, ist bei mir nichte weiteres in App.xaml eingefügt worden.

                Der Key Locator, um den ViewModelLocator deklarativ ansprechen zu können? Die eingefügte Zeile ist in der ViewModelLocator.cs dokumentiert.

                Wenn das nicht eingefügt wurde, solltest du das noch machen, sonst geht die deklarative Zuweisung an den DataContext nicht.

                Wie werden eigentlich die Events gehandhabt, die zur Laufzeit und ohne Benutzeraktivität ausgelöst werden müssen. Z.B. ich möchte an einer Stelle eine Messagebox anzeigen oder eine bestimmte Teil-View in eine bestimmte Stelle einfügen und Anzeigen.

                MessageBoxen lassen sich auch aus dem ViewModel heraus aufrufen, genauso wie es im Code-Behind geht. Ob das allerdings guter Stil ist, aus dem ViewModel heraus direkt die Anzeige zu steuern, statt indirekt nur Propertys zu beschreiben, auf deren Änderung die View reagieren kann, darf man bezweifeln, wenn man das mag. Für den Anfang kannst du das aber erstmal so machen.

                "Eine Teil-View einfügen", das kommt drauf an. Ein Weg ist, die Teil-View bereits in der View zu haben und über ein Boolean vom ViewModel aus die Sichtbarkeit umzuschalten. Dazu braucht es in der View einen Boolean-zu-Visibility-Konverter. Beispiele dazu findest du recht einfach über eine Suchmaschine, das spar ich mir an dieser Stelle.

                Bisher habe ich auch zu allen Aufgabenstellungen eine Lösung gefunden, weil schon irgendwer anderes das Problem hatte. WPF, DataBinding und MVVM gibts ja schon ziemlich lange. Die Chance, dass du auf ein neues Problem stößt, ist ziemlich gering. Dieser Wink mit dem Zaunspfahl soll dich aber nicht davon abhalten bei Verständnisproblem hier nachzufragen.

                dedlfix.

                1. Hallo,

                  vielen Dank. Ich habe viel gelernt und bin jetzt restlos glücklich. :-)

                  Gruß