MVVM Creating the ViewModel

2019-05-28 17:39发布

问题:

Can somebody explain to me how exactly to create a ViewModel for the MVVM Pattern. I tried to understand the the tutorial here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx , but I was unable to understand what exactly is happening in the code.

Let's say we want to create a basic application about getting and adding people from and to a local database and displaying them in the View. How should the ViewModel look like and how to create the RelayCommands for it. First why do we set the variables twice: once privately and then again publicaly.

EDIT: Thanks for the help so far. I have one more thing that I don't know to do - how to bind the View to the ViewModel and Vice Versa

Here is the Model:

public class Student : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private string name;
    private string surname;
    private string age;

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
            OnPropertyChanged("Name");
        }
    }

    public string Surname
    {
        get
        {
            return surname;
        }
        set
        {
            surname = value;
            OnPropertyChanged("Surname");
        }
    }

    public string Age
    {
        get
        {
            return age;
        }
        set
        {
            age = value;
            OnPropertyChanged("Age");
        }
    }
}

and here is the ViewModel:

public class MainViewModel : ViewModelBase
{
    ObservableCollection<Student> studentList;
    Student selectedPerson;

    public MainViewModel()
    {
        //populate some sample data
        studentList = new ObservableCollection<Student>()
    {
        new Student(){Name="John", Surname="Smith", Age="28"},
        new Student(){Name="Barbara", Surname="Anderson", Age="23"}
    };
    }

    public ObservableCollection<Student> StudentList
    {
        get { return studentList; }
    }

    public Student SelectedPerson
    {
        get { return selectedPerson; }
        set
        {
            selectedPerson = value;
            RaisePropertyChanged("SelectedPerson");
        }
    }

    private RelayCommand _addStudentCommand;
    public ICommand AddStudentCommand
    {
        get
        {
            return _addStudentCommand
                ?? (_addStudentCommand = new RelayCommand(() =>
                {
                    Student student = new Student();
                    studentList.Add(student);
                }));
        }
    }
}

I have found a way to bind the ViewModel to the View using some code for the view in Csharp but the question how to bind the View to the ViewModel is still on my mind. To be more specific how to create a new student using the values a user has entered in the View.

Here is the View's XAML code

<Window x:Class="MVVMLight.View.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" 
    SizeToContent="WidthAndHeight">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <TextBlock x:Name="NameTextBlock"
               Text="Name"
               Style="{StaticResource TextBlockTextStyle}"/>
    <TextBlock x:Name="SurnameTextBlock"
               Grid.Row="1"
               Text="Surname"
               Style="{StaticResource TextBlockTextStyle}"/>
    <TextBlock x:Name="AgeTextBlock"
               Grid.Row="2"
               Text="Age"
               Style="{StaticResource TextBlockTextStyle}"/>
    <TextBox x:Name="NameTextBox"
             Grid.Column="1"
             Style="{StaticResource TextBoxTextStyle}"/>
    <TextBox x:Name="SurnameTextBox"
             Grid.Row="1"
             Grid.Column="1"
             Style="{StaticResource TextBoxTextStyle}"/>
    <TextBox x:Name="AgeTextBox"
             Grid.Row="2"
             Grid.Column="1"
             Style="{StaticResource TextBoxTextStyle}"/>
    <ListBox x:Name="StudentListBox"
             Grid.ColumnSpan="2"
             Grid.Row="4"
             Style="{StaticResource ListBoxStyle}"
             ItemsSource="{Binding StudentList}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="{Binding Name}"
                               Style="{StaticResource TextBlockTextStyle}"/>
                    <TextBlock Text="{Binding Surname}"
                               Grid.Column="1"
                               Style="{StaticResource TextBlockTextStyle}"/>
                    <TextBlock Text="{Binding Age}"
                               Grid.Column="2"
                               Style="{StaticResource TextBlockTextStyle}"/>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <Button x:Name="AddButton"
            Grid.Row="7"
            Grid.ColumnSpan="2"
            HorizontalAlignment="Center"
            Content="Add"
            Margin="7,7,7,7"
            Command="{Binding AddStudentCommand}"/>        
</Grid>

And here is the View's Csharp code

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
}

I have some questions concerning the Binding between the View and The ViewModel: What are the pros and cons of using this type of binding? What is the best way of binding if I am going to use a database?

  1. Is this how the ViewModel and Model should look like
  2. How to create a RelayCommand for adding a student to the ObservableCollection
  3. Why do we set things first privately and then again publically [Answered]
  4. How to bind the View to the ViewModel and Vice Versa

回答1:

in your property setters you should check to see if the new value is equal to the old value, if it is you should return and not fire the PropertyChanged event.

As for your questions:

  1. Yes this looks fine.
  2. There are a couple of ways to setup your relay commands. I prefer

    private RelayCommand<Student> _addStudentCommand;
    public ICommand AddStudentCommand
    {
        get
        {
            return _addStudentCommand
                ?? (_addStudentCommand = new RelayCommand<Student>((student) =>
                    {
                         studentList.Add(student);
                    }));
        }
    }
    

another way without passing in a student object

private RelayCommand _addStudentCommand;
    public ICommand AddStudentCommand
    {
        get
        {
            return _addStudentCommand
                ?? (_addStudentCommand = new RelayCommand(() =>
                    {
                        Student student = new Student(); 
                        studentList.Add(student);
                    }));
        }
    }
  1. That is how properties work in .net, You could use automatic properties, but since you need to fire change notification in the setter you have to declare the field that the property will work against.

Also since it looks like you are using mvvm light you should try the code snippets. They make properties very easy to create. type mvvvminpc then hit tab twice. then fill in the highlighted part and hit tab till you are finished.

You can bind the View To the Viewmodel a couple of ways. I know that it is an Antipattern but you could use a locator. The basic idea is to set the viewmodel as the views datacontext.

public class Locator
{
   public Viewmodel1 Viewmodel1
    {
       return new Viewmodel1();
    }   
}  

You then in you app.xaml you add this class

<Application.Resources>
   <Locator x:key="VMLocator" />
</Application.Resources>

Then in your view in the xaml

<Page  DataContext="{Binding Source="{StaticResource VMLocator}" Path=ViewModel1}">

</Page>