[C # / WPF / MVVM] I researched MVVM now

11 minute read

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 on ViewModel, DataContext)
  • Which property of the ViewModel the control works with (Binding)

ViewModel

  • Corresponding instance of Model (ViewModel depends on Model)
  • 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.