Blogs

Mostly, when we begin to develop a windows form application we start finding a pattern, there are some patterns: Model View Controller, Model View ViewModel, and Presentation Model.

Commonly, Windows Form developers face a problem that how do they test the UI of the Windows Form?

Some questions nowadays the WindowsForm developers are facing:

  • If a value of a textbox is changed, will the status of the button change correctly?
  • If a value of a textbox is changed, will the value of a label change correctly?
  • Am I writing too much business logic code in UI code?
  • Decoupling the View and the Business Logic, can I write the unit test first?
  • If data of the Form is being feed from another server in separate thread, am I updating the GUI in the right thread?
  • Am I flexible to change the UI without having refactored the business logic code?

To answer those questions above, Microsoft presented a pattern: MVVM (Model –View – ViewModel) that let you write Windows Form Application which help you to:

  • Add unit test to ensure View behaves exactly with the given data and logic
  • Separate the UI code and business logic code
  • And marshal the data from another thread to GUI thread.

The philosophy of this pattern is to separate the data, logic code and the UI presentation code by presenting an intermediate layer (ViewModel)

  • ViewModel is absolutely a passive view, it reflects all values and states of View
    • The key difference between MVVM and MVC is ViewModel
    • ViewModel does not control anything of View
    • In MVVM – a ViewModel is a reflector of View, a view is a passive view just display value, states from ViewModel.
    • Model is a data provider, communicates and providers data for ViewModel .
    • View is just a view with just absolute UI code, nothing else.

Wow, sounds interesting, let’s take a look of the real example:

Describe about an Order form to order a product

  • An order form with controls:
  • orderform_state1
  • When user click “Submit” the values will be sent to server and the server will send the result status to this form, we call it client from here.
  • The server will send status back to client, include: Working, Replace, ReplaceAccepted, Cancelled
  • The logic are:
    • User click the Submit button, it will send the values (Symbol, Account, Price, Volume) to server.
    • If the status returned is Working or Replace or ReplaceAccepted the Submit button will be disabled and the Cancel button will be enabled
    • If the status returned is Cancelled, the Submit button will be enabled and the Cancel button will be disabled.
    • When user change Symbol, the LabelTitle should display the corresponding Symbol
  • This screen is showed when user clicks the “Submit button” and the status returned from server is “Working”
  • orderform_state2

How does it work?

Now, let’s implement the MVVM.

I mostly create (ViewModel) first when I begin to implement MVVM but to make you easy in following the idea, I will introduce the Model first.

Model

  • This form needs data for Symbol and Account combo-boxes.
  • This form also receives data returned from server and Model catch that to provide to this form but in this example we do not have server, the Model will act like a server to return status to client

We will create an interface call: IModel

The reason to create the IModel interface is for testing later.


using System;
using System.Collections.Generic;
namespace MVVM
{
public interface IModel
{
List<KeyValuePair<string, string>> GetSymbols();

List<KeyValuePair<string, string>> GetAccounts();

Action StatusChanged { get; set; }

void Submit();

void Cancel();
}
}

And we will create a class OrderModel.cs that implements the IModel interface

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace MVVM
{
    public class OrderModel : IModel
    {
        private string[] status = new string[4]
                                      {
                                          "Working",
                                          "Replaced",
                                          "ReplaceAccepted",
                                          "Cancelled"
                                      };

        private readonly Timer _timer = new Timer();
        public Action StatusChanged { get; set; }

        public OrderModel()
        {
            _timer.Interval = 8000;
            _timer.Tick += TimerTick;
        }

        private void TimerTick(object sender, EventArgs e)
        {
            //Keep sending the status
            if(StatusChanged!=null)
            {
                var random = new Random();
                var state = status[random.Next(0, 4)];
                if(state == "Cancelled")
                {
                    _timer.Stop();
                }
                StatusChanged(state);
            }
        }

        public List<KeyValuePair<string, string>> GetSymbols()
        {
            return new List<KeyValuePair<string, string>>
            {

New KeyValuePair<string,string>("LLJC.MRVS.DQ4","LLJC.MRVS.DQ4"),
new KeyValuePair<string, string>("LLJC.BF5","LLJC.BF5"),
            };
        }

        public List<KeyValuePair<string, string>> GetAccounts()
        {
            return new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("ACETEST2","ACETEST2"),
new KeyValuePair<string, string>("BOACLRHCY","BOACLRHCY"),
new KeyValuePair<string, string>("CHEVBCB01","CHEVBCB01")
      };
        }

        public void Submit()
        {
            if (StatusChanged != null)
            {
                StatusChanged("Working");
            }
            _timer.Start();
        }

        public void Cancel()
        {
            _timer.Stop();
            if (StatusChanged != null)
            {
                StatusChanged("Cancelled");
            }
        }
    }
}
 

This class provides two lists of Symbol and Account and a delegate StatusChanged to send status to ViewModel.

Now we will implement ViewModel

ViewModel

In the MVVM, the ViewModel takes the heaviest responsibilities to handle the business logic, and the state of View.

Let’s implement ViewModel

It has the properties and fields to hold the states of View controls and handles the business logic

This class provides two lists of Symbol and Account and a delegate StatusChanged to send status to ViewModel.

Implement INotifyPropertyChanged


using System.Collections.Generic;
using System.ComponentModel;

namespace MVVM
{
    public class OrderViewModel : INotifyPropertyChanged
    {
        private IModel _model;

        public event PropertyChangedEventHandler PropertyChanged;

        private string _symbol;

        private string _account;

        private double _price;

        private string _status;

        public double Price
        {
            get
            {
                return this._price;
            }
            set
            {
                this._price = value;
                OnPropertyChanged("Price");
            }
        }

        public string Symbol
        {
            get
            {
                return this._symbol;
            }
            set
            {
                this._symbol = value;
                OnPropertyChanged("Symbol");
            }
        }

        public string Status
        {
            get
            {
                return _status;
            }
            set
            {
                _status = value;
                OnPropertyChanged("Status");
            }
        }     

        public bool SubmitButtonStatus
        {
            get
            {
                return string.IsNullOrEmpty(Status)
                       || Status == "Cancelled";

            }
        }

        public bool CancelButtonStatus
        {
            get
            {
                return !string.IsNullOrEmpty(Status) && (Status == "Working"
                                                         || Status == "Replaced"
                                                         ||Status == "ReplaceAccepted"
                                                         );
            }
        }
        public List<KeyValuePair<string, string>> Accounts
        {
            get
            {
                return _model.GetAccounts();
            }
        }

        public OrderViewModel(IModel model)
        {
            _model = model;
            if(_model!=null)
            {
                _model.StatusChanged = (status) => { Status = status; };
            }
        }

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

This ViewModel absolutely holds every UI control values in properties:

For example for Account:


public string Account
        {
            get
            {
                return this._account;
            }
            set
            {
                this._account = value;
                OnPropertyChanged("Account");
            }
        }

And the datasource for the AccountComBoBox:


public List<KeyValuePair<string, string>> Accounts
{
            get
            {
                return _model.GetAccounts();
            }
}

It also implements INotifyPropertyChanged to notify to client when a property is changed, here is the magic:


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

It also holds the states of the control, as you can see, the status of the Submit button is calculated by the Status

public bool SubmitButtonStatus
{
            get
            {
                return string.IsNullOrEmpty(Status)
                       || Status == "Cancelled";

            }
}

Wow, that is so interesting. I cannot wait, I want to know how the View can stick together with ViewModel. Let’s talk about View:
The key of MVVM is DataBindings object of each control, that Microsoft supports it to make developer life is easier.

The View

 

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace MVVM
{
    public partial class OrderView : Form, IView
    {
        private IModel model = new OrderModel();
        private OrderViewModel _viewModel;

        public OrderView()
        {
            _viewModel = new OrderViewModel(model);
            InitializeComponent();
        }

        protected override void OnLoad(EventArgs e)
        {
            BindingControls();
            //Subscribe viewmodel PropertyChanged
            _viewModel.PropertyChanged += ViewModelPropertyChanged;
            UpdateView();
            base.OnLoad(e);
        }
        private void OrderViewFormClosing(object sender, FormClosingEventArgs e)
        {
            //Unsubscribe viewmodel PropertyChanged when form is closing
            _viewModel.PropertyChanged -= ViewModelPropertyChanged;
        }

        void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch(e.PropertyName)
            {
                case "Status":
                    UpdateView();
                    break;
            }
        }

        private void UpdateView()
        {
            ButtonSubmit.Enabled = _viewModel.SubmitButtonStatus;
            ButtonCancel.Enabled = _viewModel.CancelButtonStatus;
            TextBoxPrice.Enabled = _viewModel.PriceTextBoxStatus;
            ComboBoxSymbol.Enabled = _viewModel.SymbolComboBoxStatus;
            ComboBoxAccount.Enabled = _viewModel.AccountComboBoxStatus;
            NumericUpDownVolume.Enabled = _viewModel.NumericUpDownVolumeStatus;
        }

        public void BindingControls()
        {
            TextBoxPrice.DataBindings.Clear();
            TextBoxPrice.DataBindings.Add("Text", _viewModel, "Price", true, DataSourceUpdateMode.OnPropertyChanged);

            ComboBoxSymbol.DataBindings.Clear();
            ComboBoxSymbol.DataBindings.Add("Text", _viewModel, "Symbol", true, DataSourceUpdateMode.OnPropertyChanged);

            ComboBoxSymbol.DisplayMember = "Value";
            ComboBoxSymbol.ValueMember = "Key";
            ComboBoxSymbol.DataSource = _viewModel.Symbols;

            LabelSymbolTitle.DataBindings.Clear();
            LabelSymbolTitle.DataBindings.Add("Text",_viewModel,"Symbol",true,DataSourceUpdateMode.OnPropertyChanged);

            ComboBoxAccount.DataBindings.Clear();
            ComboBoxAccount.DataBindings.Add("Text", _viewModel, "Account", true, DataSourceUpdateMode.OnPropertyChanged);
            ComboBoxAccount.DisplayMember = "Key";
            ComboBoxAccount.ValueMember = "Value";
            ComboBoxAccount.DataSource = _viewModel.Accounts;

            TextBoxStatus.DataBindings.Clear();
            TextBoxStatus.DataBindings.Add("Text", _viewModel, "Status", true, DataSourceUpdateMode.OnPropertyChanged);

            LabelMessage.DataBindings.Clear();
            LabelMessage.DataBindings.Add("Text", _viewModel, "Message", true, DataSourceUpdateMode.OnPropertyChanged);

            NumericUpDownVolume.DataBindings.Clear();
            NumericUpDownVolume.DataBindings.Add("Value", _viewModel, "Volume", true, DataSourceUpdateMode.OnPropertyChanged);
        }

        private void ButtonSubmitClick(object sender, EventArgs e)
        {
            _viewModel.Submit();
        }

        private void ButtonCancelClick(object sender, EventArgs e)
        {
            _viewModel.Cancel();
        }
    }}
 

The magic is here:

 TextBoxPrice.DataBindings.Clear(); TextBoxPrice.DataBindings.Add("Text", _viewModel, "Price", true, DataSourceUpdateMode.OnPropertyChanged); 

 

The DataBindings will bind the datasource to the control, so when the value of the Control is changed, the value of _viewModel is changed correspondingly, you do not need to do anything else.

Forget about the EditValueChanged() event like this:


void EditValueChanged(object sender, EventArgs e)
{
	_viewModel.Price = Int32.Parse(TextBoxPrice.Text);
}

 

These code lines above are so boring. Microsoft presented the Binding instead, that saves your time.

As you see

The view handles the event


void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch(e.PropertyName)
            {
                case "Status":
                    UpdateView();
                    break;
            }
}

That will update every state of every controls based on ViewModel, that it is.
The method UpdateView(); will update the state of buttons for each changed in ViewModel

Now, again, as you can see, the View is just a View, its states are from ViewModel. I can have a Windows Form View, WebView, WPF View for this just one ViewModel.
If I want to test my View, I just need to test my ViewModel.
Let’s talk about UnitTest for ViewModel.

Adding UnitTest:

Let’s add a UnitTest project called:

MVVM.Tests and add a test class with the two methods:


 [TestMethod]
        public void Test_ButtonSubmitStatus_StatusIsReplace_ReturnTrue()
        {
            var viewModel = CreateFakeViewModel();
            viewModel.Status = "Replace";
            Assert.AreEqual(false, viewModel.SubmitButtonStatus);
        }

        [TestMethod]
        public void Test_ButtonSubmitStatus_StatusIsCancelled_ReturnTrue()
        {
            var viewModel = CreateFakeViewModel();
            viewModel.Status = "Cancelled";
            Assert.AreEqual(true, viewModel.SubmitButtonStatus);
        }

You can add more test cases for you real case.

Conclusion

  • With MVVM you actually separate the UI code and the Business Code and the UI code does not handle its states. It just gets the values and states from ViewModel.
  • The ViewModel does not control View like in MVC model, so it actually decoupling pattern. You can have multiples kind of View (WindowsForm, WebForm, Silverlight, WPF) for just one ViewModel.
  • This pattern provides a way to write you unit test for your UI code, to make sure your UI code behaves correctly with the business code.
  • This pattern provides a way to ensure your Windows Form (GUI thread) and your data which is in another thread is marshaling in GUI thread (I will present in another post).
  • Please get the full code demo for this article at:MVVM