简体   繁体   中英

Correct usage of the MVVM-Pattern

I hope this question isn't to general.

I was used to implement my WPF-Applications within the MVVM-Pattern. So I alwayse created one Folder for the View , one for the ViewModel and one for the Model (and some other folders for Behaviours, Converter, ...)

First I've put the MainWindowView.xaml into the View -Folder. Then I've added the MainWindowViewModel.cs to the ViewModel -Folder. And after this I've added a MainWindowModel.cs -File to the Model -Folder.

And the MainWindowModel.cs -File is where I have the question.

I'm used to use this class for my business-logic like loading data from a database or parse an xml-file and put the results into a collection.

For example if I want to load data from a XML-File on a Button-Click I did it with something like:

MainWindowView

<Window x:Class="MVVMExample.View.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModel="clr-namespace:MVVMExample.ViewModel"
        Title="MainWindowView" Height="300" Width="300">
    <Window.DataContext>
        <viewModel:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <Button Command="{Binding LoadDataCommand}" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10" Content="Load data" Width="120"/>
    </Grid>
</Window>

MainWindowViewModel

internal class MainWindowViewModel : ViewModelBase
{
    private readonly MainWindowModel mainWindowModel;

    public MainWindowViewModel()
    {
        mainWindowModel = new MainWindowModel();
        mainWindowModel.XmlEntriesLoaded += XmlEntriesLoaded;
        LoadDataCommand = new RelayCommand(LoadData);
    }

    private void XmlEntriesLoaded(object sender, GenericEventArgs<List<XmlEntry>> e)
    {
        List<XmlEntry> entries = e.Value;
        // Display entries in ObservableCollection
    }

    private void LoadData(object parameter)
    {
        mainWindowModel.LoadData();
    }

    private ICommand loadDataCommand;
    public ICommand LoadDataCommand
    {
        get { return loadDataCommand; }
        set
        {
            loadDataCommand = value;
            OnPropertyChanged();
        }
    }
}

MainWindowModel

internal class MainWindowModel
{
    public EventHandler<GenericEventArgs<List<XmlEntry>>> XmlEntriesLoaded;

    public void LoadData()
    {
        BackgroundWorker backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += (s, e) =>
        {
            // Do load the data here and assign the result to e.Result
        };
        backgroundWorker.RunWorkerCompleted += (s, e) =>
        {
            EventHandler<GenericEventArgs<List<XmlEntry>>> temp = XmlEntriesLoaded;
            if (temp != null)
            {
                temp(this, new GenericEventArgs<List<XmlEntry>>((List<XmlEntry>) e.Result));
            }
        };
        backgroundWorker.RunWorkerAsync();
    }
}

The class XmlEntry is also located in the Model -Folder.

Now I was told, that the Model-Layer only contains the Business-Objects and no logic like I did it in my MainWindowModel . And the interaction to load data or so should be located into the ViewModel . Is that rigth? So I'm doing it wrong?

I've had a fair few debates about this before. Your model is much more than a dumb data object, it should contain behaviors just like your ViewModel .

Consider the example:

class Camera
    {
        BitmapImage CurrentFrame { get; set; }
        BitmapImage CapturedFrame { get; set; }
        VideoCaptureDevice CaptureDevice { get; set; }

        void TakePhoto();
        void ClearFrame();
        void Reset();
    }

The above is just some example code with no implementation, but you get the idea.

The point here is that methods like TakePhoto and Reset make much more sense if they are contained within the Model as opposed to the ViewModel . One of the reasons why this is a good idea is because it ensures that your Models can be used by multiple ViewModels without having to implement the logic across multiple ViewModels, as the logic is contains within the Model.

That said, there are some scenarios where a Model does not have any behaviors and only serves as a data object.

To answer your question:

The Model only knows about itself, therefore in your scenario, it is the ViewModel's responsibility to load the Models. Putting your loading method in the ViewModel is correct.

Taking this a step further, it'd be an even better idea if your loading of models logic is contained within a Service class, where your ViewModel can reference the service in order to load the appropriate Models. This will allow multiple ViewModels to reference the Service and reduce code duplication.

I wouldn't have said you're doing it wrong per se. Normally my Model classes would be pretty basic things which just defined the objects required by my program. The ViewModel contains a set of methods and properties which are bound by the View, and which make use of the Model classes. The 'normal' MVVM pattern would have 1 View to 1 ViewModel, and then a number of Model classes which define your business objects, and then whatever supplementary classes you needed to support the operations defined in the ViewModel.

I would generally put your 'LoadData' method into its own class called something like 'DataService', which would be responsible for whatever external data operations your program needs. It's different to your Model classes, which just define objects, but doesn't necessarily fall into the tightly defined scope of youir ViewModel.

The relation between Model - ViewModel - View is not 1:1:1. It is rather M:N:O. So multiple Views can use one ViewModel and also one ViewModel can use multiple Models .

Usually Model contains classes definig data structures and their relationships, like Entity framework classes. I usually have separate VS projects for Model classes. So eg if your app uses 3 databases you can have 3 models in your VS solution. The model classes names should follow your app's UML class model or your database model also with proper names. MainWindowModel is not a good name for model class - it suggests, that it belongs to MainWindow , which is wrong - it should contain generally usable classes.

So if the code for LoadData() in your MainWindowModel is generally useful and will be used by multiple ViewModels or other components, then you should place it into your Model in a class named eg CustomerXMLData . But it is also possible to have a ViewModel that uses no Model classes at all . Ie you do not have to create XXXWindowModel class for every ViewModel - that is not the point of MVVM. The point is re-usability of modular code and good design without duplicate code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM