Traditional UI development to MVVM Transformation

All the code in this tutorial can be found here in my github.

Demo Application Introduction

Timer-tracker Application
   Here's the application we are going to use in this transformation. It's a simple code that can do following things;
  • A stopwatch that can start stop and clear the time
  • Get time will add the current stopwatch value into ListView on the left
  • Clear time will clear the ListView

Demo Application Component

Here is it's components;

1. Application UI View (XAML file)

In C# wpf application, the application's appearance will be described in an XAML file.

HERE IS THE CODE(MainWindow.xaml)

2. Application UI Logic (.xaml.cs)
The code describing the behavior of the UI.

Demo Application Explanation

In this application you can obviously see that it is tightly coupling together. For example you can not do a unit testing on this class MainWindow because all the function parameter is attached to the UI component name described in the view.


The application is quite small. So actually in this small scale application the needs of separation is really hard to see but, as the application grow bigger. this code behind will also grow bigger.
We haven't implement much on the logic part but, we actually use the library of timer and stopwatch inside this application and we can think of it as our model classes

ViewModel Implementation

Now we going to isolate glue code into the new component called View Model.


The interface between ViewModel and Model is simple. ViewModel just going to call methods or using class objects locate in our model classes. So, the thing we need to focus is the interface between View and ViewModel.

Three interface are;
  1. Data Binding: Bind the UI value to Property inside ViewModel.
  2. Command: When The user manipulate the UI it travel to ViewModel in the form of Commands which define a standard interface.
  3. Notifications: this is when data has been updated inside ViewModel and it send a notification event to the View and tell it the get the data to update it in The UI

Data Binding

Any UI element that needs to interact with data in ViewModel need to be data binding. From our Demo, the element that we need to bind are as following;
  • The stopwatch value (TextBlock text)
  • all the button (Button command)
  • List View

The modification in  UI View

Here's the data binding inside the UI View (xaml file)

<TextBlock Name="txtBlock_timer" Text="{Binding CurrentTime}"/>
<Button Name="btn_start" Command="{Binding StartCommand}" IsEnabled="{Binding btnStartEnable}"/>
<Button Name="btn_stop" Command="{Binding StopCommand}" IsEnabled="{Binding btnStopEnable}"/>
<Button Name="btn_clear" Command="{Binding ClearCommand}"/>

The modification in UI logic (code behind)

all the gluecode inside UI logic (xaml.cs file) is relocated what left is the method to tell where those data binding element will be connecting to which is our ViewModel


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

Data Context is the default source of your bindings so you have to assign our ViewModel to this data context.

The implementation of ViewModel

Here comes our new component. It's good practice to ending the file name with the word ViewModel.
in this demo application, I named it TimerTrackerViewModel.cs

what we going to implement first is all the declaration of all the data that can be bind.

class timerTrackerViewModel
{   #region Public Properties
 public bool btnStartEnable { get; private set; }
 public bool btnStopEnable { get; private set; }
 public ICommand StartCommand { get; private set; }
 public ICommand StopCommand { get; private set; }
 public ICommand ClearCommand { get; private set; }
 public ICommand GetTimeCommand { get; private set; }
 public ICommand ClearTimeCommand { get; private set; }
 public string CurrentTime { get; private set; }
    #endregion

for ICommand is a special interface that will used for our command (button click).
then the less of the ViewModel will be the relocated gluecode.

SEE THE FULL CODE HERE(timerTrackerViewModel.cs)

if you scroll down to GetTimeFunc and ClearTimeFunction which is the the function that will be use with our List View haven't been implement yet. The reason is that you have to understand the Notifcation part first so, wait for.

Commands

This section will be about how can we interface the command (or ui command like clicking button) to our ViewModel.

In the previous section, you should already spot  that the way we interface them is the same as what we do with data binding but with specific interface called ICommand.

What we have to do is implement this ICommand and just connect them together! simple isn't it.

The implementation we going to do now is the most simplest implementation. What it does is that when the command being triggered, it will called the method attached to it.

Here's the implementation, named RelayCommand.

These element: CanExecuteChanged, CanExecute, Execute is something the interface require us to implement.
  • For this implementation we will not interest in CanExecuteChanged so we just pass an event into empty method.
  • Our RelayCommand will always be able to execute so we just return true
  • And when Command being triggered we just going Execute the function that is passed to it when this RealayCommand object being created
And here is how we use it in ViewModel. We just pass in the method that will be execute for each command.

StartCommand = new RelayCommand(StartFunc);
StopCommand = new RelayCommand(StopFunc);
ClearCommand = new RelayCommand(ClearFunc);
GetTimeCommand = new RelayCommand(GetTimeFunc);
ClearTimeCommand = new RelayCommand(ClearTimeFunction);

Two component is done. One more to go. At this point you can try to run the application. if you put a breakpoint inside StartFunc and click start button in UI. You can see that the function being execute.
and the value of currentTime will be updated by the UpdateFunc
But the UI won't update.

Why? cause the UI didn't know that the data have been Update. We need to notify it.

Notification

To be able to do so we need to implement something called INotifyPropertyChanged
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
you can find Microsoft tutorial here.

For our Demo, we going to show the simple implementation and then we going to implement in a way we can be a reusable easy to implement one.

The Simple implementation

Here's the modify of our ViewModel Declaration;

class timerTrackerViewModel: INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };

Then we need to modify any property that we need to notify the UI if its data is updated.
In this Demo, it is the data for our stopwatch.

private string _currentTime = "00:00:00";
public string CurrentTime
{
 get
 {
  return _currentTime;
 }
 set
 {
  if (_currentTime == value)
   return;
  _currentTime = value;
  PropertyChanged(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
 }
}

But this way you going to need to modify every property that affect the UI. so let's improve it.

The improve implementation

We going to use Nuget package called PropertyChanged.Fody. Which will helps you detect which property is affect to UI and do this PropertyChangedEvent for you what that property value gets update.


But you still need to have that INotifyPropertyChanged so the package know which class it need to look for.

Another Improvement

A small improvement to this Notify improvement. we move some common declaration to another class and let every ViewModel inherit from it. Let's named it BaseViewModel.


 public class BaseViewModel : INotifyPropertyChanged
    {
        /// <summary>
        /// The event that is fired when any child property changes its value
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
    }

 Now the property declaration in our ViewModel looks clean again;

class timerTrackerViewModel: BaseViewModel
{
 #region Public Properties
 public bool btnStartEnable { get; set; }
 public bool btnStopEnable { get; set; }
 public ICommand StartCommand { get; private set; }
 public ICommand StopCommand { get; private set; }
 public ICommand ClearCommand { get; private set; }
 public ICommand GetTimeCommand { get; private set; }
 public ICommand ClearTimeCommand { get; private set; }
 public ObservableCollection<string> SavedTimeList { get; set; }
public string CurrentTime { get; set; }

Run the code, the start stop clear button in our code should work now.

You should spot in the property declaration above that there is a new item coming up.(I'm highlight it in yellow!)

So let's get to know it.

public ObservableCollection<string> SavedTimeList { get; set; }

Well it is the list to store the time and will be shown on the list view.

You can try to use the normal list but of course it won't be updated in the UI.

That is because the List act as a pointer and its value will never change after we create it.

So that's why we use this ObservableCollection. It is an implementation of INotifyCollectionChanged. It will notify the UI if that list(ObservableCollection) has been added, deleted, or clear.

and now we can use it in our left over buttons;

private void GetTimeFunc()
{
 SavedTimeList.Add(CurrentTime);
}
private void ClearTimeFunc()
{
 SavedTimeList.Clear();
}

Code Recap

Summary

It's quite a long transformation. So, I would like to sumup what we have done.

To Implement MVVM we have to do 3 thing
1. Data Binding, we Bind the UI value with Properties in Our ViewModel
2. Command, similar to Data Binding but we use an interface name ICommand. we attach a specific method with it and when the UI connect with it got manipulate that method will be execute.
3.Notification, when data in ViewModel has been update it will send the notification to UI using INotifyPropertyChanged and INotifyCollectionChanged.

And We've Done!!! Happy Coding Everyone (why this sentence is so popular in coding tutorial)

Reference




Deemarc Burakitbumrung

A software develper who graduated from Electronic and Communication Engineer. Have an interest in the field of programming and embedded system.

No comments:

Post a Comment