DM-V-VM part 8: View Models

Ok, time to wrap this up :-)

Now, we'll finally build what I'm calling a view model for managing a portfolio of stocks (building on the StockModel example from previous posts). A view model is a class that will be used as the DataContext for a data template that will be used to provide the UI for the model. Ideally, there is no code behind required for the UI. In practice, some glue code is often required, but in this example, I won't need any.

First, let's come up with the model. Unlike the data model, there is no base class we'll use here. We just need to provide properties, events, etc. that the UI can bind to. In this example, none of our properties will ever change value, so we don't need to implement INotifyPropertyChanged, but often you will need to. Our UI is going to be pretty simple. We'll provide a list of StockModels for the UI to bind to and provide CommandModels to add and remove stocks. Let's start with the constructor where we set up some fields and some accessors:

        public PortfolioViewModel(IStockQuoteProvider quoteProvider)

        {

            _quoteProvider = quoteProvider;

            _stockModels = new ObservableCollection<StockModel>();

            _stockModels.Add(new StockModel("MSFT", _quoteProvider));

 

            _addCommand = new AddCommand(this);

            _removeCommand = new RemoveCommand(this);

        }

 

        public ObservableCollection<StockModel> Stocks

        {

            get { return _stockModels; }

        }

 

        public CommandModel AddCommandModel

        {

            get { return _addCommand; }

        }

 

        public CommandModel RemoveCommandModel

        {

            get { return _removeCommand; }

        }

 

        private ObservableCollection<StockModel> _stockModels;

        private CommandModel _addCommand;

        private CommandModel _removeCommand;

        private IStockQuoteProvider _quoteProvider;

 The quote provider lets us pass in any quote provider and makes it really easy to unit test the class. We populate the stock list with MSFT, but a real implementation may read it from some saved settings or something.

Next, let's define AddCommand. It will take the stock to add as its parameter:

        private class AddCommand : CommandModel

        {

            public AddCommand(PortfolioViewModel viewModel)

            {

                _viewModel = viewModel;

            }

 

            public override void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e)

            {

                string symbol = e.Parameter as string;

                e.CanExecute = (!string.IsNullOrEmpty(symbol));

                e.Handled = true;

            }

 

            public override void OnExecute(object sender, ExecutedRoutedEventArgs e)

            {

                string symbol = e.Parameter as string;

                _viewModel._stockModels.Add(new StockModel(symbol, _viewModel._quoteProvider));   

            }

 

            private PortfolioViewModel _viewModel;

        }

And, RemoveCommand, which will take a StockModel as its parameter:

        private class RemoveCommand : CommandModel

        {

            public RemoveCommand(PortfolioViewModel viewModel)

            {

                _viewModel = viewModel;

            }

 

            public override void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e)

            {

                e.CanExecute = e.Parameter is StockModel;

                e.Handled = true;

            }

 

            public override void OnExecute(object sender, ExecutedRoutedEventArgs e)

            {

                _viewModel._stockModels.Remove(e.Parameter as StockModel);

            }

 

            private PortfolioViewModel _viewModel;

        }

 Now, how do we use this? Well, if we define a data template for the PortfolioViewModel, then we can put a PortfolioViewModel in any ContentControl. We could even have a list of them in an ItemsControl! We can put the data template in App.xaml and it can be used anywhere in the application. Here's an ugly, but functional template on top of the view model:

    <DataTemplate DataType="{x:Type local:PortfolioViewModel}">

      <DockPanel LastChildFill="True">

        <TextBlock DockPanel.Dock="Top" TextAlignment="Center">Portfolio View</TextBlock>

        <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">

          <TextBox Name="AddSymbol" Width="100" />

          <Button Command="{Binding AddCommandModel.Command}"

                  CommandParameter="{Binding Path=Text,ElementName=AddSymbol}"

                  local:CreateCommandBinding.Command="{Binding AddCommandModel}"

                  >

            Add

            </Button>

          <Button Margin="50,0,0,0"

                  Command="{Binding RemoveCommandModel.Command}"

                  CommandParameter="{Binding Path=SelectedItem,ElementName=StockList}"

                  local:CreateCommandBinding.Command="{Binding RemoveCommandModel}"

                  >

            Remove

          </Button>

        </StackPanel>

        <ListBox Name="StockList" ItemsSource="{Binding Stocks}" />

      </DockPanel>

    </DataTemplate>

 The add button functionality is much like the way we dealt with commands in part 7. The remove button's command parameter is bound to the selected item in the list box - pretty cool, huh? It will be disabled if nothing is selected and use the selected StockModel if one is selected. The ListBox is bound to the list of StockModels. To make each item display correctly, we need a data template for StockModels. Here's a simple implementation:

    <DataTemplate DataType="{x:Type local:StockModel}">

      <StackPanel Orientation="Horizontal" local:ActivateModel.Model="{Binding}">

        <TextBlock Text="{Binding Symbol}" Width="100"/>

        <TextBlock Text="{Binding Quote}" />

      </StackPanel>

    </DataTemplate>

Now, we need to put a Portfolio view into our window. We can do so by using the following XAML:

<Window x:Class="ModelSample.Window1"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    Title="ModelSample" Height="300" Width="300"

    >

    <Grid>

      <ContentControl x:Name="_content" />

    </Grid>

</Window>

and, the following code behind:

    public partial class Window1 : Window

    {

 

        public Window1()

        {

            InitializeComponent();

 

            _content.Content = new PortfolioViewModel(new MockQuoteProvider());

        }

 

    }

The end result looks something like:

Ok, it's not very pretty, but the visuals are all in Xaml and a designer can use Expression or the raw HTML to improve the look. Meanwhile, the behavior is all in PortfolioViewModel, which is easily unit testable because it doesn't depend on any UI.

This example is slightly idealized. I was able to use the attached property tricks to completely eliminate the code behind, but that's not always possible. Sometimes you are left with some glue code that may or may not be easily unit testable. And, I've found in practice that deisgners often need behavior tweaks to implement the UI they want. For example, they may need events to trigger animations. But, I hope I've given the general idea in this series of posts.

I'm including a sample project with demonstrates this all. Unfortunately, I never got around to updating the unit tests after adding the activation stuff to the DataModel class, so there are no unit tests. If there is enough interest, I'll add unit tests. I hope it's easy to see how they would be written.

ModelSample.zip