[C # / WPF / MVVM] I researched MVVM now
Introduction
I’m ashamed to say that I’m an outdated programmer who hasn’t used WPF, let alone ** MVVM **, for WinForms projects. Embarrassing …
** A WPF project has finally come down to me. ** **
I started researching “How to make it with WPF” for the past few days, and now I know the existence of ** MVVM , and I’ve been researching “ MVVM **” for a long time …
** I’m not sure! ** **
“Let’s make it separately for View
-ViewModel
-Model
“
“It will be easier to test”
I understand roughly.
However, even if I’m investigating the specific way of dividing it, it’s slightly different depending on the person and I don’t know what the correct answer is. [^ 1]
There may not be a correct answer in the first place.
So, I thought too much and couldn’t understand the reason, so I decided to write about ** MVVM ** in order to organize my own thoughts.
** WinForms There may be a lot of mistakes because it’s just a few days of research by a brain-minded person about MVVM. I would be grateful if you could kindly teach me at that time. gently···**
Why divide it into MVVM?
If you just research how to divide V-VM-M
, you can’t see anything clear, so change your perspective.
** “Why divide it into MVVM?” **
I decided to think from the purpose.
Purpose
Improved testability
Unit testing is difficult when the screen and code are stuck together.
Therefore, it will be easier to do it if you separate the screen (View) and the code (ViewModel / Model).
I see, sure.
Increased productivity
When the screen (View) and the code (ViewModel / Model) are separated, the designer can clearly divide the work from the screen (View) and the programmer can clearly divide the work from the code (ViewModel / Model).
However, I don’t have a designer.
Improved portability (?)
If the screen (View) and code (ViewModel / Model) are separated, it may be possible to support other platforms just by preparing the screen (View).
That’s because I don’t see much information about this, and it’s just a selfish image.
However, it should be possible in theory … [^ 2]
Is this the reason why the division is ambiguous?
Probably these three purposes are roughly the purpose, and it seems that improving testability is especially important.
**that? Can these purposes be met if the screens are separated for the time being? ** **
Is it ambiguous how to divide V-VM-M
because of that?
Consider an ideal project structure
Based on the purposes listed in ↑, let’s first roughly think about what kind of project structure is ideal.
Like this?
- MvvmTest (.Net Core/WPF)
- Views
- MainView.xaml
- MainView.xaml.cs
- App.xaml
- App.xaml.cs
- AssemblyInfo.cs
- MvvmTest.MVM (.Net Standard)
- MainViewModel.cs
- MainModel.cs
The minimum basic configuration to the last. If you think of DI containers and screen transitions, this is probably not enough.
MvvmTest project
A project that defines and implements View and applications.
It may be possible to divide the View into a MvvmTest.Views
project. It is a mystery whether it makes sense.
Also, I would be happy if I could make this MvvmTest.NetCore
and make MvvmTest.iOS
with Xamarin.
MvvmTest.MVM project
A project that defines and implements ViewModel and Model.
I want to make it .Net Standard
! !![^ 3]
MVM … Isn’t there a better name?
It may be divided into MvvmTest.ViewModels
and MvvmTest.Models
. It is a mystery whether it makes sense.
Is it convenient for ViewModel and Model to be in the same hierarchy and close to each other?
Consider the role of each MVVM
Next, let’s roughly consider the roles of View
, ViewModel
, and Model
.
View
screen. More even no less.
Define the screen according to the properties provided by ViewModel
.
I don’t want to write anything other than design (XAML).
Model
logic. A person who does what he should do.
Process by receiving parameters from ViewModel
.
I don’t know what kind of design the screen is. [^ 4]
ViewModel
A bridge that connects the screen and logic.
Define what kind of information you want from the screen, pass the information in the form required by the logic, get the result from the logic, and pass the result to the screen.
I know what kind of screen it is [^ 5], and should this person do the processing near the screen (input check, etc.)? (Unclear)
Think about what you should have
Based on the role of ↑, let’s roughly think about what View
, ViewModel
, and Model
should have (what to do).
View
- Screen design
- Corresponding instance of
ViewModel
(View
depends onViewModel
, DataContext) - Which property of the
ViewModel
the control works with (Binding)
ViewModel
- Corresponding instance of
Model
(ViewModel
depends onModel
) - Receive screen input data [^ 6]
- Input check whether input data can be passed to
Model
[^ 7] - Notify
View
of an error (INotifyDataErrorInfo) - Event processing such as buttons (ICommand)
- Convert the input data to the form required by
Model
and pass it - Receive property change notification for
Model
- Notify
View
of property changes (INotifyPropertyChanged)
Model
- Receive parameters from
ViewModel
- Parameter error checking
- Notify
ViewModel
of an error (INotifyDataErrorInfo) - The process you actually want to do
- Store the result in the property
- Notify
View
of property changes (INotifyPropertyChanged)
[^ 7]: I don’t know the error that depends on the processing of Model
. Only “whether or not it can be handed over”.
I will actually write
Based on what I have thought so far, I will actually write the code.
I made a super-simple app with ** MVVM ** that returns the result of adding up when two numbers are passed and executed.
Model(MainModel.cs)
First, write the processing that should be done without worrying about the screen.
class MainModel : ViewModelBase, IMainModel
{
private Property<int> ans = new Property<int>();
public int ParamA { get; set; }
public int ParamB { get; set; }
public int Answer
{
get => this.ans;
set => this.ans.SetValue(value, this);
}
public void Sum()
=> this.Answer = this.ParamA + this.ParamB;
}
Just take the two numbers and add them together. There is no parameter error because it is complicated.
By the way, the property change notification is implemented in the ViewModelBase
class, and the SetValue
of Property <T>
gives a nice notification.
ʻIMainModel is the definition of
MainModel`.
ViewModel(MainViewModel.cs)
Next, while imagining the screen, write a ViewModel
that connects to theModel
.
Two text boxes for entering numbers (ParamA, ParamB), one execute button (SumCommand), and one label for displaying the result (Answer).
public class MainViewModel : ViewModelBase
{
private IMainModel Model { get; }
private Property<string> paramA = new Property<string>("0",
(value) =>
{
if (value.Length == 0)
return "Missing error";
else if (!int.TryParse(value, out int _))
return "Format error";
else
return null;
});
private Property<string> paramB = new Property<string>("0",
(value) =>
{
if (value.Length == 0)
return "Missing error";
else if (!int.TryParse(value, out int _))
return "Format error";
else
return null;
});
private Property<int> ans = new Property<int>();
public string ParamA
{
get => this.paramA;
set
{
//Set to Model if there is no input error
if (this.paramA.SetValue(value, this))
this.Model.ParamA = int.Parse(this.ParamA);
}
}
public string ParamB
{
get => this.paramB;
set
{
//Set to Model if there is no input error
if (this.paramB.SetValue(value, this))
this.Model.ParamB = int.Parse(this.ParamB);
}
}
public int Answer
{
get => this.ans;
set => this.ans.SetValue(value, this);
}
public ICommand SumCommand { get; }
public MainViewModel()
{
// TODO:Originally obtained from DI container
this.Model = new MainModel();
this.Model.PropertyChanged += Model_PropertyChanged;
//Sum button
this.SumCommand = new Command(() =>
{
//Run
this.Model.Sum();
}, paramA, paramB);
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(this.Model.Answer))
this.Answer = this.Model.Answer;
}
}
Receive data from View
, check if the data can be passed to Model
, and control whether SumCommand is enabled or disabled.
Execute by passing parameters to Model
in the processing of SumCommand.
The result is fetched by the property change notification of Model
and notified to ViewModel
.
There are many unique classes and it is difficult to understand …
For the time being, Property <T>
also has a verification function, and the Command
class has an image of switching between valid and invalid in conjunction with the error status of the specified Property <T>
. ・ ・ ・ [^ 8]
View(MainView.xaml)
Finally the screen.
Two text boxes for entering numbers according to ViewModel
, one execute button, and one label for displaying the results.
<Window x:Class="MvvmTest.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MvvmTest.Views"
xmlns:vm="clr-namespace:MvvmTest.VM;assembly=MvvmTest.VM"
mc:Ignorable="d"
Title="Main" SizeToContent="WidthAndHeight">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Grid MinWidth="300" MinHeight="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- ParamA -->
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,10,0" HorizontalAlignment="Right" VerticalAlignment="Center" Text="ParamA"/>
<TextBox Grid.Row="0" Grid.Column="1" MaxHeight="24" Margin="10, 0, 10, 0" VerticalContentAlignment="Center" Text="{Binding ParamA, UpdateSourceTrigger=PropertyChanged}" />
<!-- ParamB -->
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,10,0" HorizontalAlignment="Right" VerticalAlignment="Center" Text="ParamB"/>
<TextBox Grid.Row="1" Grid.Column="1" MaxHeight="24" Margin="10, 0, 10, 0" VerticalContentAlignment="Center" Text="{Binding ParamB, UpdateSourceTrigger=PropertyChanged}" />
<!-- Button -->
<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="10,10,10,10" Content="Sum" Command="{Binding SumCommand}"/>
<!-- Answer -->
<TextBlock Grid.Row="3" Grid.Column="0" Margin="0,0,10,0" HorizontalAlignment="Right" VerticalAlignment="Center" Text="Answer"/>
<TextBlock Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left" Margin="10, 0, 10, 0" VerticalAlignment="Center" Text="{Binding Ans}" />
</Grid>
</Window>
Just define the screen design, who the ViewModel is, and the bindings for each control.
Code behind (MainView.xaml.cs)
/// <summary>
/// MainView.xaml interaction logic
/// </summary>
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
}
}
The generated code as it is. ** Never touch ** (Iron will)
I uploaded all the source code to GitHub
mitsu-at3/MvvmTest: Mvvm Test Project
I use GitLab for work, but I used GitHub for the first time … (now)
Is it right?
Considering various investigations and the purpose of MVVM, this division method is the best, but I do not know if it fits …
In particular, is it correct to set the property type for receiving data from View
defined in ViewModel
to a type that takes into account the convenience of controlling View
, such as string type for text boxes even when inputting numerical values? Somehow a mystery.
Even when I looked it up, I couldn’t find any information that clearly had such an idea.
However, if you set the property of ViewModel
to int, an input error will occur at the stage of View
, and if it is left as it is, the error cannot be detected on the ViewModel
side, so tell from View
to ViewModel
. A mechanism is needed.
But I don’t want to write anything extra in View
.
Thinking about it, is it better to accept the ViewModel
side as a string for the time being and do the input check on the ViewModel
side? [^ 9]
What I was interested in while researching
The information I saw when I was looking up at the end made me wonder, “Is that right?”
I don’t remember where I saw it because it was the information I saw while I was just fishing for information here and there … [^ 10]
Model is just a data class
public class PersonModel
{
public string Name { get; set; }
public int Age { get; set; }
}
Did you misunderstand the meaning of Model, or did you intend to implement the logic from now on?
“ViewModel and Model have the same properties”
Isn’t it not always the same?
If they are the same, the meaning of the ViewModel
bridge will be lessened.
As long as the relationship between ViewModel
and Model
is not broken, I feel that it can be different.
ViewModel is completely passed to Model only
Similar to ↑, but …
public class PersonModelView
{
public string Name
{
get => this.Model.Name;
set => this.Model.Name = value;
}
public int Age
{
get => this.Model.Age;
set => this.Model.Age= value;
}
//・ ・ ・ Just receive a notification from Model or receive a command to execute Model
}
It may be possible if there is no input check on the screen, but it seems that there is no point in separating VM and M at this point …
Is there no Model
for a very small project?
Where is your model from?
public MainViewModel(IMainModel model)
{
this.Model = model;
}
I think this is also good if there is some kind of mechanism and it automatically crosses by dependency injection.
But I don’t like this (MainView.xaml.cs)
/// <summary>
/// MainView.xaml interaction logic
/// </summary>
public partial class MainView : Window
{
public MainView()
{
IMainModel model =DI container-like guy.GetService(typeof(IMainModel));
this.DataContext = new MainViewModel(model);
InitializeComponent();
}
}
** I don’t want to write in code behind! !! !! ** **
Should I make it with the ViewModel constructor?
public MainViewModel()
{
this.Model =DI container-like guy.GetService(typeof(IMainModel));
}
There is no doubt that ViewModel
depends on Model
, so it seems that this is better.
Model is a singleton
I don’t know if this is correct.
From the point of view so far, it seems more natural to consider View
-ViewModel
-Model
as one set and keep the survival time the same.
Isn’t it easier to understand the relationship between MVVM by defining the function you want to handle in singleton below Model
and using that singleton class in Model
?
But strictly speaking, it seems that everything except View
and ViewModel
is Model
[^ 11], so I wonder if a singleton is a mistake …
Personally, at least the Model
referenced by the ViewModel
wants to have the same lifetime.
in conclusion
I’ve written a long time and said, “Isn’t it correct to think this way?”, But I can’t say with certainty that “this idea is correct!” …
I’m coming nicely.
However, even if you clearly recognize the “purpose” mentioned at the beginning, it may be useful for judging “Is the shape wrong?”
Did you get a little closer to the latest development style …?
After investigating screen transitions, dialog displays, DI containers, etc., I found out why I wanted to rely on Prism and external frameworks …
It seems that I can implement it by myself, but it seems to be quite troublesome.
Bonus: What is a DI container?
I first learned about “DI container” while researching MVVM (now), but when I read the explanation, I remembered him first …
HRESULT CoCreateInstance(
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID *ppv
);
If you pass the information of the Interface you want to get, the instance will be returned.
When I thought about it, I suddenly became familiar with it. That’s it.