DEV Community

Cover image for Lumi4 -- MVVM
Thomas Brittain
Thomas Brittain

Posted on • Originally published at ladvien.com

Lumi4 -- MVVM

One of the issues I've had in the past with the Lumi projects is manageable UI.  The project will start out pretty straight foward, but soon, I'm switching between device types, checking if hardware is ready, and routing callbacks based upon the device selected.  It becomes spaghetti code quick.  On Lumi4, I've decided to bite the bullet and implement MVVM.

After about 20 hours struggling with setting up Lumi4 as an MVVM project I've dervied two conclusions:

  1. It's possible
  2. Apple spoils developers with MVC baked into Xcode

MVVM in C# and UWP isn't simple.  It seems like there is a lot more boiler-plate coding necessary for MVVM than MVC in Xcode.  Eventually, I broke down and downloaded the NuGet package Prism, as a MVVM helper.  This helped alleviate some of the code necesssary for Commanding (which I still don't understand well enough to implement without Prism). 

Below I'm going to take a look at a couple of controls I've written MVVM on.  Finding documentation for MVVM in Universal Windows Plateform (UWP) is tricky.  It is similar to XamarinForms and WPF, but overall, there are syntax differences which make generalizing the documentation difficult.

First though, here's how I structured my project:

*MainPage.xaml = View
*MainViewViewModel = ViewModel
*[Not Yet Written] = Model

Below is some code for a couple of UI controls.

First, taking a look at the four text boxes which will hold the network IDs and host IDs the user is to type in:

MainPage.xaml

    ....
    <TextBox x:Name="NetworkIDOne" Text="{Binding HostIDOne, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextAlignment="Center" Grid.Column="1"/>
    <TextBox x:Name="NetworkIDTwo" Text="{Binding HostIDTwo, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextAlignment="Center" Grid.Column="2"/>
    <TextBox x:Name="HostIDOne" Text="{Binding NetworkIDOne, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextAlignment="Center" Grid.Column="3"/>
    <TextBox x:Name="HostIDTwo" Text="{Binding NetworkIDTwo, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextAlignment="Center" Grid.Column="4"/>
    ....

Enter fullscreen mode Exit fullscreen mode

This sets up the binding to the variables HostIDOne, HostIDTwo, NetworkIDOne, NetworkIDTwo.  However, there's plenty more boilerplate code before things start working. 

To setup a ViewModel it's best to setup an abstract class which can be inherited.  This saves on creating boilerplate.  Below is the abstract class the internet told me to make:

MainViewModelBase.cs

    public abstract class MainViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Enter fullscreen mode Exit fullscreen mode

This code handles the property changing notification for all properties declared in classes which inherit from the MainViewModelBase.  Such as the MainViewViewModel which is used in Lumi4

MainViewViewModel.cs

    ....
        public class MainViewViewModel: MainViewModelBase
        {
    ....

Enter fullscreen mode Exit fullscreen mode

Moving on to the actual implementation of the bound text boxes.  Each text box will have a property associated with the string value in the Text attribute.  However, before this will work, the DataContext must be set for the MainPage.xaml.  This is done in the MainPage.xaml.cs file.

MainPage.xaml.cs

    public MainPage()
    {
        this.InitializeComponent();
        DataContext = new Lumi4App.ViewModels.MainViewViewModel();

Enter fullscreen mode Exit fullscreen mode

(I told you it was a lot of work to setup.  Well, compared to Xcode's MVC.)

Ok, everything should be in place, time to implement the bound properties.  In the MainViewViewModel I've the following code:

    private string _HostIDOne;
    public string HostIDOne
    {
        get { return _HostIDOne; }
        set {
            if (_HostIDOne != value)
            {
                _HostIDOne = value;
            }
        }
    }

    private string _HostIDTwo;
    public string HostIDTwo
    {
        get { return _HostIDTwo; }
        set {
            if (_HostIDTwo != value)
            {
                _HostIDTwo = value;
            }
        }
    }

    private string _NetworkIDOne;
    public string NetworkIDOne
    {
        get { return _NetworkIDOne; }
        set {
            if (_NetworkIDOne != value)
            {
                _NetworkIDOne = value;
            }
        }
    }

    private string _NetworkIDTwo;
    public string NetworkIDTwo
    {
        get { return _NetworkIDTwo; }
        set {
            if (_NetworkIDTwo != value)
            {
                _NetworkIDTwo = value;
            }
        }
    }

Enter fullscreen mode Exit fullscreen mode

The if statement under the setter checks if the value about to be set is the same as the value is currently.  Note, there is a helper in Prism which will prevent one from having to rewrite this for every attribute.  However, I implemented the above code before I downloaded Prism and decided to write this article while it was still fresh in my mind.  I'll probably correct these to use the Prism helper before moving.

The code so far should allow for the properties setter to be called whenever the user types something in one of the four textboxes. On caveat, it seems the setter is not called until the user removes focus from the textbox.  I'll probably need to change the binding of the properties, but for now, it works well enough.

Well, this is all fine and gone, but what about Commands such as Button Click Events?  Pretty simple, but with more boilerplate code.

First, bind to the command in the View

MainPage.xaml

    <Button x:Name="Search" Command="{Binding SearchCommand, Mode=TwoWay}" Padding="2" >

Enter fullscreen mode Exit fullscreen mode

  Next, and this is the part I don't understand without Prism helping, there needs to be a delegate which will fire an event whenever the commad is called.  In Prism there is the DelegateCommand type which takes care of a lot of the work.  The DelegateCommand has to be initialized with two event handlers CanExecute and Execute.  These methods will be called in that order every time the DelegateCommand property is accessed.

MainViewViewModel.cs

    public DelegateCommand SearchCommand { get; set; }
    private bool SearchCanExecute()
    {
        return (CentralDeviceTypeSelected == CentralDeviceType.Http) ? true : false;
    }
    private void SearchExecute()
    {
        [Add Code to Do Stuff when the Search Button is pressed here]
    }

    ....

    public MainViewViewModel()
    {
        SearchCommand = new DelegateCommand(SearchExecute, SearchCanExecute);
    }

Enter fullscreen mode Exit fullscreen mode

And that's it for now.  Had to make sure I jotted down my notes while the challenge was still fresh in my head.

Update: 4-16-2017

Apparently, a Prism ViewModel should inherit from BindableBase instead of the custom ViewModelBase the internet told me to write.  Also, to get the prismProp code snippet it looks like you have to download the Prism template packet:

*Prism Templates

And the intellisense phrase for the PrismProperty is "pprop".

Also, Brian Laguna (maintainer of Prism) has a video using Prism 6:

*MVVM Made Simple with Prism

Oh, and to make Prism work you need to get NuGet packages:

  1. Prism.Core
  2. Prism.Windows (contains ViewModelLocator)

Top comments (0)