dedlfix: MVVM

Beitrag lesen

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.