MVVM: complete understanding (+WPF) Part 1
this article involved my experience of bringing a certain number of students to complete and final understanding of the pattern MVVM and implement it in WPF. A pattern is described on examples of increasing complexity. First, the theoretical part that can be used regardless of specific language, then the practical part, which shows several options of communication between the layers using WPF and a little bit of Prism.
Why do you need to use MVVM pattern? It's extra code! To write the same thing much clearer and more straightforward.
Answer: in smaller projects the direct approach works. But it is worth it to become a little more — and program logic in the smeared interface so that then the whole project turns into a solid ball, which is easier to rewrite than to try to untangle. For clarity, you can see two pictures:

Image 1: code without MVVM.

Image 2: code with MVVM.
In the first case, the programmer, with the words "I just need to connect this port and this why I have all these clamps and labwiki?", simply connect patchcords couple of slots. In the second case, uses some formulaic approach.
the
Methodology:
The technique of writing programs using the approach "ModelFirst".
the
So, our assignment is to write a program that adds two numbers with the result. Performed first point: write model. A model is a place in the program, which may require some creative effort or Sodeistvie his creative abilities.
However, the work is fairly resource-intensive thought process, and avoid unnecessary treatment to him. First- not to overexert yourself. Second is to have your brother in the programming (Yes you are, a few weeks) looking to you in code, would not be forced to, too exciting at times, adherence to the creative flight of thought. All the places you suddenly were able to apply the work — it is necessary to provide detailed commentary. By the way, there are numerous patterns of programming that will help you is the most creativity — does not apply.
So the model in our problem is the addition of numbers returning the result. The model is, in principle, may not store any state. I.e. it may well be implemented as a static method of a static class. Like this:
the
The next step (see the method "ModelFirst") is to create a View or, easier — to draw interface. This is also the part that can hold creativity. But again, not worth him to overdo it. The user should not be shocked by the unexpectedness of the interface. The interface should be intuitive. Our View will contain three text fields that you can supply the labels the number number one, number two, amount.
The final step is connecting View and model through the VM. VM is a place that does not have to contain a creative element. Ie the part of the pattern the train is due to the View and should not contain ANY "business logic". That means causality from View? This means that if we have in View there is three text boxes, or three places that must enter/output — hence VM (a kind of substrate) should be at least three properties they will accept/provide.
Therefore, two properties of the View take the number one and two, and the third property causes our model to perform the business logic of our program. VM in any case does not add numbers alone, it is for this action only causes the model! This is a function of VM is to connect the View (which is also nothing but receiving input from a user and provide output not engaged) and the Model in which happens all the calculation. If you draw a picture of our puzzles, then get something like this:

Image 3: Diagram of Example No. 1
Green's View, the three green dots which are our three text fields. Blue is a VM to which the three green dots iron nailed (prebendary), but red cloud is a model that deals with calculation.
the
Specifically in WPF is implemented in "hardware support" of the pattern MVVM. View is implemented in XAML. I.e. the green layer (View) will be written in XAML. Green dots — these will be text fields. And the green line connects with blue — will be implemented through the mechanism of Binding. Green dotted line — link View and the entire VM is when we create a VM object and assign it to the DataContext property View.
Draw View:
the
Now execute the last paragraph of the methods — implement the VM. Our VM "automatically" updated View, you must implement the INotifyPropertyChange interface. It is through his View receives notification, in the VM, something changed and you want to refresh data.
This is done as follows:
the
Now the VM will provide three desired properties. (Requirements for communication and VM View is that it should be public properties)
the
The last property is the line dashed blue line VM and models:
the
We have implemented a full-fledged application with MVVM pattern.
Examination pattern for the example No. 2:
Now let's complicate our task. The program text field for entering numbers. Will a ListBox with a collection of values. The "Add" button, by pressing on which the number in the text box will be added to the collection of values. The delete button, by pressing which is allocated to ListBox'e number will be removed from the collection. And a text box with the sum of all values in the collection.

Image 4: the Interface for the Example No. 2
According to the technique — you need to first develop a model. Now the model can not be stateless and needs to store state. Then the model becomes a collection of elements. This time. Then the operation of adding a number to the collection is the responsibility of the model. VM can not get on the inside of the model and to add to the collection model, number, it must ask to do is the model itself. Otherwise it would be a violation of the principle of encapsulation. It's as if the driver is not flooded, as expected, the fuel in the fuel tank, etc. — and climbed under the hood and injected the fuel directly into the cylinder. That is, the method will "add to collection". That's two. And third, the model will provide the sum of the values of the collection and just notify about the changes through the interface INotifyPropertyChanged. Not going to breed controversy about the purity of the model, and we just use a notice.
Let's just implement the model:
The collection of elements required to notify subscribers about the change. And it should be read-only so that no one but the model had to change. Restricting access is the implementation of the principle of encapsulation, it is to be strictly followed: a) most do not accidentally create a situation of hard to debug, and b) instill confidence that the field is not changed from the outside — again, to facilitate debugging.
In addition, as we then still connect the Prism for DelegateCommand, let's just use BindableBase is independent of the implementation of INotifyPropertyChange. For this we need to connect via NuGet libraries Prism.Wpf (at the time of writing 6.3.0). Accordingly OnPropertyChanged() to change to RaisePropertyChanged().
the
According to the technique — draw the View. Before that, a few necessary explanations. In order to create a link button and the VM, you need to use DelegateCommand. Use this event and code for pure MVVM is not permissible. Used events need to be framed in a command. But in the case of a button, this frame is not needed, because there is a special its Command property.
In addition, the number, which we will add using DelegateCommand, we will not bindit to the VM, and will be passed as a parameter of the DelegateCommand, so as not to clutter the VM to avoid out of sync with the direct command and use parameter. Note the resulting schema, and, especially, to the place circled in red.

figure 5: Schema for Example # 2
Here the binding is not on View view View <=> ViewModel, view View <=> View. In order to achieve this we used the second type of binding, which specifies the name of the item and its properties to which you linked — "{Binding ElementName=TheNumber, Path=Text}".
the
Now, implement the ViewModel:
the
Note — it is important! Regarding the forwarding of notifications from the model. To report a change amount VM alone can not, because she doesn't need to know what to change in the model after calling its methods and whether to change at all. The model VM should be a black box. Ie it must pass the input and user actions to the model and if the model has anything changed (which it needs to notify the model itself), only then to notify next View.
We implemented the second full-fledged application with MVVM pattern, met with ObservableCollection, DelegateCommand, binding, view View <=> View and forwarding notifications in the View.
Article based on information from habrahabr.ru
Why do you need to use MVVM pattern? It's extra code! To write the same thing much clearer and more straightforward.
Answer: in smaller projects the direct approach works. But it is worth it to become a little more — and program logic in the smeared interface so that then the whole project turns into a solid ball, which is easier to rewrite than to try to untangle. For clarity, you can see two pictures:

Image 1: code without MVVM.

Image 2: code with MVVM.
In the first case, the programmer, with the words "I just need to connect this port and this why I have all these clamps and labwiki?", simply connect patchcords couple of slots. In the second case, uses some formulaic approach.
the
Examination pattern for the example No. 1: Addition of two numbers with the result:
Methodology:
The technique of writing programs using the approach "ModelFirst".
the
- 2. To draw the program interface.
1. To develop model programs. the
3. To connect the interface and model interlayer VM.
So, our assignment is to write a program that adds two numbers with the result. Performed first point: write model. A model is a place in the program, which may require some creative effort or Sodeistvie his creative abilities.
However, the work is fairly resource-intensive thought process, and avoid unnecessary treatment to him. First- not to overexert yourself. Second is to have your brother in the programming (Yes you are, a few weeks) looking to you in code, would not be forced to, too exciting at times, adherence to the creative flight of thought. All the places you suddenly were able to apply the work — it is necessary to provide detailed commentary. By the way, there are numerous patterns of programming that will help you is the most creativity — does not apply.
So the model in our problem is the addition of numbers returning the result. The model is, in principle, may not store any state. I.e. it may well be implemented as a static method of a static class. Like this:
the
static class MathFuncs {
public static int GetSumOf(int a, int b) => a + b;
}
The next step (see the method "ModelFirst") is to create a View or, easier — to draw interface. This is also the part that can hold creativity. But again, not worth him to overdo it. The user should not be shocked by the unexpectedness of the interface. The interface should be intuitive. Our View will contain three text fields that you can supply the labels the number number one, number two, amount.
The final step is connecting View and model through the VM. VM is a place that does not have to contain a creative element. Ie the part of the pattern the train is due to the View and should not contain ANY "business logic". That means causality from View? This means that if we have in View there is three text boxes, or three places that must enter/output — hence VM (a kind of substrate) should be at least three properties they will accept/provide.
Therefore, two properties of the View take the number one and two, and the third property causes our model to perform the business logic of our program. VM in any case does not add numbers alone, it is for this action only causes the model! This is a function of VM is to connect the View (which is also nothing but receiving input from a user and provide output not engaged) and the Model in which happens all the calculation. If you draw a picture of our puzzles, then get something like this:

Image 3: Diagram of Example No. 1
Green's View, the three green dots which are our three text fields. Blue is a VM to which the three green dots iron nailed (prebendary), but red cloud is a model that deals with calculation.
the
the implementation of the Example No. 1 in WPF
Specifically in WPF is implemented in "hardware support" of the pattern MVVM. View is implemented in XAML. I.e. the green layer (View) will be written in XAML. Green dots — these will be text fields. And the green line connects with blue — will be implemented through the mechanism of Binding. Green dotted line — link View and the entire VM is when we create a VM object and assign it to the DataContext property View.
Draw View:
the
<Window ....
xmlns:local "clr-namespace: MyNamespace"> <!-- This namespace with our VM -->
<Window.DataContext>
<local:MainVM/> <!-- Create a new VM and connect it with View -->
</Window.DataContext>
<StackPanel>
<!--Binding, in fact, connects the text box with the property in the VM -->
<!--UpdateSourceTrigger, in this case, performs transmission the value in the VM at the time of input -->
<TextBox Width="30" Text="{Binding Number1, UpdateSourceTrigger=PropertyChanged}">
<TextBox Width="30" Text="{Binding Number2, UpdateSourceTrigger=PropertyChanged}">
<!--Mode=OneWay necessary for prisadki properties are read-only -->
<TextBox Width="30" Text="{Binding Number3, Mode=OneWay}" IsReadOnly="True">
</StackPanel>
Now execute the last paragraph of the methods — implement the VM. Our VM "automatically" updated View, you must implement the INotifyPropertyChange interface. It is through his View receives notification, in the VM, something changed and you want to refresh data.
This is done as follows:
the
public class MainVM : INotifyPropertyChange
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now the VM will provide three desired properties. (Requirements for communication and VM View is that it should be public properties)
the
private int _number1;
public int Number1 { get {return _number1;}
set { _number1 = value;
OnPropertyChanged("Number3"); // notify View that has changed the amount
}
}
private int _number2;
public int Number2 { get {return _number2;}
set { _number1 = value; OnPropertyChanged("Number3"); } }
The last property is the line dashed blue line VM and models:
the
//property is readonly, it is read View every time it is refreshed either Number1 or Number2
public int Number3 { get; } => MathFuncs.GetSumOf(Number1, Number2);
We have implemented a full-fledged application with MVVM pattern.
Examination pattern for the example No. 2:
Now let's complicate our task. The program text field for entering numbers. Will a ListBox with a collection of values. The "Add" button, by pressing on which the number in the text box will be added to the collection of values. The delete button, by pressing which is allocated to ListBox'e number will be removed from the collection. And a text box with the sum of all values in the collection.
Image 4: the Interface for the Example No. 2
According to the technique — you need to first develop a model. Now the model can not be stateless and needs to store state. Then the model becomes a collection of elements. This time. Then the operation of adding a number to the collection is the responsibility of the model. VM can not get on the inside of the model and to add to the collection model, number, it must ask to do is the model itself. Otherwise it would be a violation of the principle of encapsulation. It's as if the driver is not flooded, as expected, the fuel in the fuel tank, etc. — and climbed under the hood and injected the fuel directly into the cylinder. That is, the method will "add to collection". That's two. And third, the model will provide the sum of the values of the collection and just notify about the changes through the interface INotifyPropertyChanged. Not going to breed controversy about the purity of the model, and we just use a notice.
Let's just implement the model:
The collection of elements required to notify subscribers about the change. And it should be read-only so that no one but the model had to change. Restricting access is the implementation of the principle of encapsulation, it is to be strictly followed: a) most do not accidentally create a situation of hard to debug, and b) instill confidence that the field is not changed from the outside — again, to facilitate debugging.
In addition, as we then still connect the Prism for DelegateCommand, let's just use BindableBase is independent of the implementation of INotifyPropertyChange. For this we need to connect via NuGet libraries Prism.Wpf (at the time of writing 6.3.0). Accordingly OnPropertyChanged() to change to RaisePropertyChanged().
the
public class MyMathModel : BindableBase
{
private readonly ObservableCollection<int> _myValues = new ObservableCollection<int>();
public readonly ReadOnlyObservableCollection<int> MyPublicValues;
public MyMathModel() {
MyPublicValues = new ReadOnlyObservableCollection<int>(_myValues);
}
//add a collection of numbers and a change notification of the amount
public void AddValue(int value) {
_myValues.Add(value);
RaisePropertyChanged("Sum");
}
//check for validity, remove from the collection and a change notification of the amount
public void RemoveValue(int index) {
//check for the validity of the removal from the collection - duty models
if (index >= 0 && index < _myValues.Count) _myValues.RemoveAt(index);
RaisePropertyChanged("Sum");
}
public int Sum => MyPublicValues.Sum(); //amount
}
According to the technique — draw the View. Before that, a few necessary explanations. In order to create a link button and the VM, you need to use DelegateCommand. Use this event and code for pure MVVM is not permissible. Used events need to be framed in a command. But in the case of a button, this frame is not needed, because there is a special its Command property.
In addition, the number, which we will add using DelegateCommand, we will not bindit to the VM, and will be passed as a parameter of the DelegateCommand, so as not to clutter the VM to avoid out of sync with the direct command and use parameter. Note the resulting schema, and, especially, to the place circled in red.

figure 5: Schema for Example # 2
Here the binding is not on View view View <=> ViewModel, view View <=> View. In order to achieve this we used the second type of binding, which specifies the name of the item and its properties to which you linked — "{Binding ElementName=TheNumber, Path=Text}".
the
<Window ...>
<Window.DataContext>
<local:MainVM/> <!-- Set the DataContext -->
</Window.DataContext>
<DockPanel>
<!-- The number to add to the collection -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBox x:Name="TheNumber" Width="50" Margin="5"/>
<Button Content="Add" Margin="5" Command="{Binding AddCommand}"
CommandParameter="{Binding ElementName=TheNumber, Path=Text}"/>
</StackPanel>
<!-- Sum -->
<TextBox DockPanel.Dock="Bottom" Text="{Binding Sum, Mode=OneWay}" Margin="5"/>
<!-- The delete button from the collection -->
<Button DockPanel.Dock="Right" VerticalAlignment="Top" Content="Remove"
Width="130" Margin="5"
Command="{Binding RemoveCommand}"
CommandParameter="{Binding ElementName=TheListBox, Path=SelectedIndex}"/>
<!-- Collection -->
<ListBox x:Name="TheListBox" ItemsSource="{Binding MyValues}"/>
</DockPanel>
</Window>
Now, implement the ViewModel:
the
public class MainVM : BindableBase
{
readonly MyMathModel _model = new MyMathModel();
public MainVM()
{
//in this simple way we probressive changed the model properties in the View
_model.PropertyChanged += (s, e) = > { RaisePropertyChanged(e.PropertyName); };
AddCommand = new DelegateCommand<string>(str = > {
//checking validity of the input - duty VM
int ival;
if (int.TryParse(str, out ival)) _model.AddValue(ival);
});
RemoveCommand = new DelegateCommand<int?>(i => {
if(i.HasValue) _model.RemoveValue(i.Value);
});
}
public DelegateCommand<string> AddCommand { get; }
public DelegateCommand<int?> RemoveCommand { get; }
public int Sum => _model.Sum;
public ReadOnlyObservableCollection<int> MyValues => _model.MyPublicValues;
}
Note — it is important! Regarding the forwarding of notifications from the model. To report a change amount VM alone can not, because she doesn't need to know what to change in the model after calling its methods and whether to change at all. The model VM should be a black box. Ie it must pass the input and user actions to the model and if the model has anything changed (which it needs to notify the model itself), only then to notify next View.
We implemented the second full-fledged application with MVVM pattern, met with ObservableCollection, DelegateCommand, binding, view View <=> View and forwarding notifications in the View.
private int _number2;
ОтветитьУдалитьpublic int Number2 { get {return _number2;}
set { _number1 = value; OnPropertyChanged("Number3"); } }
_number1
Удалить