Freigeben über


Reusing ViewModels in a Universal App – Part 2

To restate where part 1 ended - We have our Windows App, MyCalc, which currently has no ViewModel and instead has all the logic in the View layer.  Which means it can’t be reused for different views, such as one for phone.  Our goal is to move that logic into a shared ViewModel and add a Phone view.

Adding a ViewModel

The first step is to add an empty ViewModel class.  So in the shared code project we create a new class called StackCalculatorViewModel. 

One thing just about every ViewModel needs is a way to communicate data changes to the View layer.  In XAML there is already a standard way to do this that works with data binding (more about that later).  That way is the INotifyPropertyChanged interface.  It allows us to raise an event with the name of the property that has changed.  So lets implement that interface, and a helper method to raise the event.

        privatevoid RaisePropertyChanged(string propertyName)

        {

            var eh = this.PropertyChanged;

 

            if(eh != null)

            {

                eh(this, newPropertyChangedEventArgs(propertyName));

            }

        }

 

        publiceventPropertyChangedEventHandler PropertyChanged;

 

The next thing our ViewModel needs is a reference to the Model, which is our StackCalculator class.  So we add a private field of type StackCalculator and in the constructor initialize it to a new instance of that class.

Lastly we need to give the View access to our ViewModel.  So in our MainPage class we add a private field of type StackCalculatorViewModel.  In the constructor we assign it to a new instance of that class.

And here is the “tricky” part – we set the MainPage.DataContext to point to the ViewModel.  The DataContent property is inherited by all the child controls, so now all the controls can access the ViewModel.  Not only that but DataContext is the default data binding source, which will come in handy later.

    publicsealedpartialclassMainPage : Page

    {

        StackCalculatorViewModel _viewModel;

 

    public MainPage()

        {

            _viewModel = newStackCalculatorViewModel();

 

            _viewModel.PropertyChanged += _viewModel_PropertyChanged;

            var collectionChanged = (INotifyCollectionChanged)_viewModel.Calculator.Stack;

            collectionChanged.CollectionChanged += collectionChanged_CollectionChanged;

 

            this.DataContext = _viewModel;

 

            this.InitializeComponent();

 

            // initial to defaults

            DecimalSelection.IsChecked = true;

            UpdateEnterBoxButtons();

 

            UpdateStackBox();

        }

 

You’ll likely notice we removed the StackCalculator field from MainPage, and instead reference the StackCalculator object held in the ViewModel.  This is temporary code that will go away once the ViewModel is fully fleshed out. 

Moving Logic to the ViewModel

Now we have an empty view model and we need to pick some logic to move into it.   Buttons tend to be pretty easy to support in a ViewModel, so we’ll start with those.

To bite off a reasonable chunk of work we are going to limit this to the buttons 0-9, add, subtract, multiply, divide and drop.

Buttons are easy to support with a ViewModel because they support having a command – meaning we can provide an instance of ICommand which allows us to execute code when the button is clicked.  It also allows us to enable/disable the button when conditions change.

ICommand and DelegateCommand

ICommand, as an interface, is perfect for our use in the ViewModel.  However there isn’t an implementation of that interface that is ViewModel friendly.  So we need to code our own implementation.  The most common implementation is along the lines of what I call DelegateCommand.  Simply put when one constructs a DelegateCommand one provides delegates to the implementations you want of Execute and CanExecute.  Thus the implementation is a simple Adapter to the required interface.  Add a method to raise the CanExecuteChanged event when needed and you are done.

My particular implementation has 2 constructors:

· One that takes an ExecuteHandler

· Another that takes an ExecuteHandler and CanExecuteHandler

The version that just takes an ExecuteHandler supplies an implementation of CanExecute which always returns true.  This is makes it just a little easier for supporting buttons that are always enabled.

    delegatevoidExecuteHandler(object parameter);

    delegateboolCanExecuteHandler(object parameter);

 

    classDelegateCommand : ICommand

    {

        privateExecuteHandler _executeHandler;

        privateCanExecuteHandler _canExecuteHandler;

 

        public DelegateCommand(ExecuteHandler executeHandler):

            this(executeHandler, CanAlwaysExecute)

        {

            // nothing

        }

 

        public DelegateCommand(ExecuteHandler executeHandler, CanExecuteHandler canExecuteHandler)

        {

            _executeHandler = executeHandler;

            _canExecuteHandler = canExecuteHandler;

        }

 

        publicvoid RaiseCanExecuteChanged()

        {

            var eh = this.CanExecuteChanged;

 

            if(eh != null)

            {

                eh(this, EventArgs.Empty);

            }

        }

 

        publicbool CanExecute(object parameter)

        {

            return _canExecuteHandler(parameter);

        }

 

        publiceventEventHandler CanExecuteChanged;

 

        publicvoid Execute(object parameter)

        {

            _executeHandler(parameter);

        }

 

        privatestaticbool CanAlwaysExecute(object parameter)

        {

            returntrue;

        }

    }

 

Using DelegateCommand in our ViewModel

Ok, now that we have an implementation of ICommand we can use, lets wire it up in our ViewModel.

First we add private methods for Execute/CanExecute that we need for the commands we will expose.  As an example here is are the implementation for the add command:

        privatevoid OnAddExecuted(object parameter)

        {

            _calc.Add();

        }

 

        privatebool OnAddCanExecute(object parameter)

        {

            return _calc.CanAdd();

        }

 

We also expose the command as a property so the Button can bind to it, like this:

        publicICommand AddCommand

        {

   get

            {

                return _addCommand;

            }

        }

 

And in the constructor we create the DelegateCommand instance providing the right methods, like this:

        public StackCalculatorViewModel()

        {

            _enterText = String.Empty;

            _calc = newStackCalculator();

 

            var collectionChanged = (INotifyCollectionChanged)_calc.Stack;

            collectionChanged.CollectionChanged += OnStackChanged;

 

            _addCommand = newDelegateCommand(OnAddExecuted, OnAddCanExecute);

            _subtractCommand = newDelegateCommand(OnSubtractExecuted, OnSubtractCanExecute);

            _divideCommand = newDelegateCommand(OnDivideExecuted, OnDivideCanExecute);

            _multiplyCommand = newDelegateCommand(OnMultiplyExecuted, OnMultiplyCanExecute);

            _popCommand = newDelegateCommand(OnPopExecuted, OnPopCanExecute);

            _addDigitCommand = newDelegateCommand(OnAddDigitExecuted, OnAddDigitCanExecute);

            _backspaceCommand = newDelegateCommand(OnBackspaceExecuted, OnBackspaceCanExecute);

        }

 

Just one last thing and the AddButton can be complete – that is raising the OnCanExecuteChanged appropriately.  Our Model doesn’t expose an event directly for this, but does let us know when the stack contents have changed.  Since we know that impacts the value returned from CanAdd, we subscribe to that event and in the handler raise OnCanExecuteChanged for the appropriate commands, like this:

        void OnStackChanged(object sender, NotifyCollectionChangedEventArgs e)

        {

            _addCommand.RaiseCanExecuteChanged();

            _subtractCommand.RaiseCanExecuteChanged();

            _divideCommand.RaiseCanExecuteChanged();

            _multiplyCommand.RaiseCanExecuteChanged();

            _popCommand.RaiseCanExecuteChanged();

        }

 

Then we need to update the XAML to bind to the command, which is straightforward and works like this:

            <Button

                x:Name="_AddButton"

                Grid.Column="0"

                Grid.Row="1"

                Style="{StaticResource ButtonStyle}"

                Content="+"

                Command="{Binding AddCommand}"

                />

 

The event handler for Clicked event was removed, and we bind to the AddCommand from the View Model.  You’ll also notice that I added a _ in front of the name of the control.  I did this to ensure I caught all references to the control in the View, since the name change will cause a compiler break. 

In the view code we remove the event handler and all references to the button – ensuring as we got that we captured all the right functionality in the ViewModel for that button.

As you look at the code you’ll notice that Add, Subtract, Divide, Multiply and Pop commands are all very similar.  But AddDigitCommand is a bit different.

AddDigitCommand

Now for buttons 0-9 we could create 10 commands – one per button.  That would work, but would be a bit monotonous to code, as well as just require a bunch of code (although it would be simple code).

Instead we can take advantage of the parameter which ICommand’s Execute & CanExecute methods.  That means we can have a single ICommand which supports all 10 buttons (0-9) and eventually will even support A-F (we’ll first need to move the Hex/Decimal support into the ViewModel, which will happen in a future article).

The implementation of that command looks like this:

        privatevoid OnAddDigitExecuted(object parameter)

        {

            var stringParameter = (string)parameter;

 

      var newText = this.EnterText + stringParameter;

            this.EnterText = newText;

        }

 

        privatebool OnAddDigitCanExecute(object parameter)

        {

            var stringParameter = (string)parameter;

 

            switch(stringParameter)

            {

                case"0":

                case"1":

                case"2":

                case"3":

                case"4":

                case"5":

                case"6":

                case"7":

                case"8":

                case"9":

                    returntrue;

                default:

                    returnfalse;

            }

        }

 

As you can see the parameter is the character to add to the EnterText.  Right now CanExecute just verifies it is an expected value between 0 and 9.  This method will get more interesting when Hex support is added.

You’ll also notice that I’ve added another property, EnterText, which contains the string the user is composing to add a number to the stack.  This is required because the AddDigitCommand needs to modify it. 

        publicstring EnterText

        {

            get

            {

                return _enterText;

            }

            set

            {

                if (_enterText != value)

                {

                    _enterText = value;

                    _backspaceCommand.RaiseCanExecuteChanged();

                    RaisePropertyChanged("EnterText");

                }

            }

        }

 

The property is pretty simple.  You’ll notice the setter raises the PropertyChanged event and also tells the backspace command to re-evaluate if it can execute – this is because the BackspaceCommand being able to execute depends on the length of EnterText.

Because this is just one step in a multi-step refactoring we do need some temporary code to keep everything working.  Because we can’t fully move dealing with that text to the view model until the Hex support is done.  So you’ll notice in the View there is now code which updates EnterText on the ViewModel when the TextBox’s text is changed.  There is also code that does the reverse and updates the TextBox when EnterText changes.

Just like the other commands we need to update the XAML and remove code from the View.  The only real difference is we also set the CommandParameter property to define what should be passed into the Command’s methods.

            <Button

                x:Name="_FourButton"

                Grid.Column="2"

                Grid.Row="2"

                Style="{StaticResource ButtonStyle}"

                Content="4"

                Command="{Binding AddDigitCommand}"

                CommandParameter="4"

                />

Wrapping It Up

Lets review what we’ve done:

· Created a ViewModel and made it available to the XAML markup

· Created DelegateCommand to expose ICommand objects from the ViewModel

· Exposed commands from the ViewModel for most of the buttons

· Migrated the XAML to use the commands

· Removed code from the View

· Added some temporary code in the View for EnterText to keep things working until the next step.

Hopefully you can see that the net effect is logic that was previous in the View and coupled with the XAML markup is now in the reusable ViewModel and is completely decoupled from the markup.

One thing that might have escaped notice is that there are a variety of controls which accept an ICommand, beyond the simple button.  For example the HyperlinkButton takes an ICommand, so we could change the markup to use hyperlinks instead of traditional buttons.  Which gives us a glimpse of the flexibility a good ViewModel can give us when it comes to defining different markup over the same logic.

Complete code attached.

MyCalc - part 2.zip