A Simple Calculator With MVVM Light framework

Rajnilari2015
Posted by in WPF category on for Beginner level | Points: 250 | Views : 10761 red flag

The MVVM Light framework is a set of components that helps to create WPF applications in Model-View-ViewModel (MVVM) pattern. It was created by Laurent Bugnion as a lightweight MVVM framework. In this article we will get ourself introduce to this framework by building a simple calculator application.


 Download source code for A Simple Calculator With MVVM Light framework

Introduction

The MVVM Light framework is a set of components that helps to create WPF applications in Model-View-ViewModel (MVVM) pattern. It was created by Laurent Bugnion as a lightweight MVVM framework. In this article we will get our self introduce to this framework by building a simple calculator application.

Create a WPF application

Open VS 2015 and create a WPF application. Then from the Nuget Package Manager, execute the below command

PM >Install-Package MvvmLight

This will add a ViewModel folder containing the MainViewModel.cs and the ViewModelLocator.cs file.

MainViewModel Class

This class contains properties that the main View can data bind to. This class inherits from ViewModelBase abstract class which is the base class for the ViewModel classes in the MVVM pattern. A typical MainViewModel Class looks as under

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;

namespace WpfApplication1.ViewModel
{    
    public class MainViewModel : ViewModelBase
    { 
        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            ////if (IsInDesignMode)
            ////{
            ////    // Code runs in Blend --> create design time data.
            ////}
            ////else
            ////{
            ////    // Code runs "for real"
            ////}
        }
    }
}

ViewModelLocator Class

This class contains static references to all the view models in the application and provides an entry point for the bindings.

A typical ViewModelLocator Class looks as under

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;

namespace WpfApplication1.ViewModel
{    
    public class ViewModelLocator
    {
        /// <summary>
        /// Initializes a new instance of the ViewModelLocator class.
        /// </summary>
        public ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            ////if (ViewModelBase.IsInDesignModeStatic)
            ////{
            ////    // Create design time view services and models
            ////    SimpleIoc.Default.Register<IDataService, DesignDataService>();
            ////}
            ////else
            ////{
            ////    // Create run time view services and models
            ////    SimpleIoc.Default.Register<IDataService, DataService>();
            ////}

            SimpleIoc.Default.Register<MainViewModel>();
        }

        public MainViewModel Main
        {
            get
            {
                return ServiceLocator.Current.GetInstance<MainViewModel>();
            }
        }
        
        public static void Cleanup()
        {
            // TODO Clear the ViewModels
        }
    }
}

Design the "MainWindow.xaml" as under

<Window x:Class="WpfApplication1.MainWindow"
        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:WpfApplication1"
        DataContext="{Binding Main, Source={StaticResource Locator}}"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Label x:Name="lblCaption" Content="Calculator Demo Using MVVM Light" HorizontalAlignment="Left" Margin="37,10,0,0" VerticalAlignment="Top" Width="253"/>
        <Label x:Name="lblNum1" Content="First Number" HorizontalAlignment="Left" Margin="27,57,0,0" VerticalAlignment="Top"/>
        <TextBox x:Name="txtNum1" HorizontalAlignment="Left" Height="23" Margin="128,57,0,0" TextWrapping="Wrap" Text="{Binding FirstNumber}" VerticalAlignment="Top" Width="162" />
        <Label x:Name="lblNum2" Content="Second Number" HorizontalAlignment="Left" Margin="27,111,0,0" VerticalAlignment="Top"/>
        <TextBox x:Name="txtNum2" HorizontalAlignment="Left" Height="23" Margin="128,111,0,0" TextWrapping="Wrap" Text="{Binding SecondNumber}" VerticalAlignment="Top" Width="162"/>
        <Button x:Name="btnAddition" Content="+" HorizontalAlignment="Left" Margin="27,168,0,0" VerticalAlignment="Top" Width="75" Command="{Binding Operation}" CommandParameter="+"/>
        <Button x:Name="btnSubtraction" Content="-" HorizontalAlignment="Left" Margin="128,168,0,0" VerticalAlignment="Top" Width="75" Command="{Binding Operation}" CommandParameter="-"/>
        <Button x:Name="btnMultiplication" Content="*" HorizontalAlignment="Left" Margin="226,168,0,0" VerticalAlignment="Top" Width="75" Command="{Binding Operation}" CommandParameter="*"/>
        <Button x:Name="btnDivision" Content="/" HorizontalAlignment="Left" Margin="324,168,0,0" VerticalAlignment="Top" Width="75" Command="{Binding Operation}" CommandParameter="/"/>    
    </Grid>
</Window>

Running the application at this point yields

Then in the MainViewModel we need to define the properties viz. FirstNumber and SecondNumber of type string and also the Operation command which is of type ICommand as under

public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {            
    }
    public string FirstNumber { get; set; }
    public string SecondNumber { get; set; }
    public ICommand Operation { get; private set; }
}

The purpose is that, we need to get the values of the two textboxes viz. txtNum1 and txtNum2 which are bind to these View Model properties. All the button commands will be triggered through the ICommand interface. It defines a command. It's the standard way to trigger a button action following MVVM pattern.

Next we need to implement the Operation command as under

public MainViewModel()
{           
   Operation = new RelayCommand<string>((arg) => DoCalculation(arg));
}

private void DoCalculation(string @operator)
{
    var result = 0;
    switch (@operator)
    {
        case "+": result = Convert.ToInt32(FirstNumber) + Convert.ToInt32(SecondNumber); break;
        case "-": result = Convert.ToInt32(FirstNumber) - Convert.ToInt32(SecondNumber); break;
        case "*": result = Convert.ToInt32(FirstNumber) * Convert.ToInt32(SecondNumber); break;
        case "/": result = Convert.ToInt32(FirstNumber) / Convert.ToInt32(SecondNumber); break;
    }
    MessageBox.Show(result.ToString());
}	

In the MVVM Light open-source toolkit, the ICommand implementation is called RelayCommand. It is a generic command whose sole purpose is to relay its functionality to other objects by invoking delegates. The default return value for the CanExecute method is 'true'. This class allows application to accept command parameters in the Execute and CanExecute callback methods.It looks as under

using System;
using System.Windows.Input;

namespace GalaSoft.MvvmLight.Command
{    
    public class RelayCommand<T> : ICommand
    {
       //Below two are the class constructor
        
        public RelayCommand(Action<T> execute);        
        public RelayCommand(Action<T> execute, Func<T, bool> canExecute);

        //end of class constructor

        public event EventHandler CanExecuteChanged;       
        public bool CanExecute(object parameter);        
        public virtual void Execute(object parameter);       
        public void RaiseCanExecuteChanged();
    }
}

The constructor of this class has two parameters - The first parameter is compulsory. It is used for the Execute method that the ICommand interface requires. The second parameter is a delegate for the CanExecute method specified by ICommand. This delegate must return a Boolean. This parameter is optional.

In our case we are using the first Constructor. And in the DoCalculation method, we are performing the Calculator operation. The argument passed to the DoCalculation method from the view is via the CommandParameter.

Run the application and the output for the "Multiplication" operation is as under

Initially, the App.xaml contains an application wide instance of the ViewModelLocator class

<Application x:Class="WpfApplication1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication1" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
  <Application.Resources>
    <ResourceDictionary>
      <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:WpfApplication1.ViewModel" />
    </ResourceDictionary>
  </Application.Resources>
</Application>

The ViewModelLocator class then gets the instance of the MainViewModel class by using the Service Locator Pattern.

public MainViewModel Main
{
   get
   {
     return ServiceLocator.Current.GetInstance<MainViewModel>();
    }
}

The DataContext binding is then defered to the ViewModelLocator class in the MainWindow

Reference

MVVM Light Toolkit

MVVM - Messenger and View Services in MVVM

Conclusion

In this article we have seen the use of building MVVM application in WPF using MVVM Light framework. Hope this will be helpful. Thanks for reading. Zipped file attached.

Page copy protected against web site content infringement by Copyscape

About the Author

Rajnilari2015
Full Name: Niladri Biswas (RNA Team)
Member Level: Platinum
Member Status: Member,Microsoft_MVP,MVP
Member Since: 3/17/2015 2:41:06 AM
Country: India
-- Thanks & Regards, RNA Team


Login to vote for this post.

Comments or Responses

Login to post response

Comment using Facebook(Author doesn't get notification)