donderdag 4 juni 2009

A Viewmodel for the ObservableCollection

Introduction

In my previous post I wrote about the MasterDetailViewModel. A viewmodel for the navigation of master-detail tables. I briefly mentioned you could extend its base class, the NavigationViewModel class, and create a viewmodel for an ObservableCollection. In this post I will describe such a viewmodel that I called the ObservableViewModel, and give an example of its usage. Because the ObservableViewModel shared a lot of functionality with the MasterDetailViewModel an intermediate viewmodel called CollectionViewModel is needed. The class diagram of the viewmodels now looks like the figure below: image
When I first started learning about the Model-View-ViewModel (MVVM) pattern, I saw this video of Jason Dollinger on how to create a program with such a pattern. I wanted to have a close look at the code to see how everything worked. But there was no source code available so I recreated the code by watching the video. I will use a changed version of this program to demonstrate the ObservableViewModel.

The CollectionViewModel

In my previous post about the Master-Detail ViewModel I already described the NavigationViewModel and explained that it is an abstract class that holds all the commands to navigate through a collection and change it. The CollectionViewModel is derived from it and the most important job for this class is to hold a property called ViewSource of type CollectionViewSource. CollectionViewSource has a View property that holds the actual view and a Source property that holds the source collection. The View property is of type ICollectionView. The microsoft documentation says about it: You can think of a collection view as a layer on top of a binding source collection that allows you to navigate and display the collection based on sort, filter, and group queries, all without having to manipulate the underlying source collection itself.

        /// <summary>
        /// Gets the view source.
        /// </summary>
        /// <value>The view source.</value>
        public CollectionViewSource ViewSource { get; private set; }
 
        /// <summary>
        /// Gets the View of the ViewSource.
        /// </summary>
        protected ICollectionView IView { get { return ViewSource.View; } }
So IView just returns ViewSource’s View property and is primarily used to shorten notation. The MasterDetailViewModel derives from the CollectionViewModel and has a property called View of type BindingListCollectionView. BindingListCollectionView is the default view for collections that implement IBindingListView or IBindingList. The source code from the MasterDetailViewModel for this View property looks like this:

        /// <summary>
        /// Gets the view.
        /// </summary>
        /// <value>The view.</value>
        public BindingListCollectionView View { get { return (BindingListCollectionView)IView; } }

The ObservableViewModel

The ObservableViewModel derives from the CollectionViewModel. Its View property is of type ListCollectionView this type is the default view for collections that implement IList. The code for this property you see below:

        /// <summary>
        /// Gets the view.
        /// </summary>
        /// <value>The view.</value>
        public ListCollectionView View { get { return (ListCollectionView)IView; } }
 
You see it just returns ViewSource’s View property as a ListCollectionView. The ObservableViewModel also implements the add and remove commands of the NavigationViewModel. The ICollectionView type doesn’t have Add and Remove methods. But the ListCollectionView and BindingListCollectionView have them so they can only be implemented in the ObservableViewModel.

Implementation example: A watch list for stock quotes

As I said in the introduction I will change the source code of the original stock quote watch list program by Jason Dollinger so that it will implement the ObservableViewModel. The original program just had a subscribe button. For that button a subscribe command was created. This command is now replaced with the add command of ObservableViewModel. I added a remove button so that a selected stock quote can be removed from the watch list. The Navigation commands like previous, next, first and last are commands that are part of the ObservableViewModel but are not implemented in my program. The model of the MVVM pattern is an object of type RandomQuoteSource. This object creates stock quotes randomly and has an event called QuoteArrived that is raised every time a quote is created. The view of the program you see below. image The viewmodel is called WatchListViewModel and as you can see below has the ObservableViewModel as its base class. In the constructor below you see that a qoute source of type IQuoteSource is passed in the constructor. The _quoteMap field is a dictionary that keeps track of the symbols that are used.

    /// <summary>
    /// Viewmodel for a watchlist of incoming stock quotes
    /// </summary>
    public class WatchlistViewModel : ObservableViewModel<Quote, QuoteViewModel>
    {
        IQuoteSource _source;
        Dictionary<string, Quote> _quoteMap = new Dictionary<string, Quote>();
        string _lastSymbol, _symbol, _deleteText;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="WatchlistViewModel"/> class.
        /// </summary>
        /// <param name="source">The source of incoming stock quotes.</param>
        public WatchlistViewModel(IQuoteSource source)
            : base(new ObservableCollection<Quote>())
        {
            _source = source;
 
            _source.QuoteArrived += new Action<Quote>(_sourceQuoteArrived);
            CollectionChanged += new NotifyCollectionChangedEventHandler(WatchlistViewModel_CollectionChanged);
            CurrentChanged += new EventHandler<CurrentChangedArgs<Quote, QuoteViewModel>>(WatchlistViewModel_CurrentChanged);
        }
You can see the method connected to the CollectionChanged event of the ObservableViewModel below. When items are added to the collection the quote source is notified that it should create a new quote source and if the symbol doesn’t already exist a new symbol is added to the _quoteMap field. When items are removed they also are removed in the quote source and in the _quoteMap dictionary.

        /// <summary>
        /// Handles the CollectionChanged event of the WatchlistViewModel object.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        void WatchlistViewModel_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                    foreach (object item in e.NewItems)
                    {
                        Quote q = (Quote)item;
                        _source.Subscribe(this.Symbol);
                        q.Symbol = this.Symbol;
                        this.LastSymbol = this.Symbol;
                        if (!_quoteMap.ContainsValue(q))
                            _quoteMap.Add(q.Symbol, q);
                    }
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                    foreach (object item in e.OldItems)
                    {
                        Quote q = (Quote)item;
                        _source.UnSubscribe(q.Symbol);
                        _quoteMap.Remove(q.Symbol);
                    }
                    break;
                default:
                    break;
            }
        }

Remarks

When you compare the WatchListViewModel with the original viewmodel you don’t see much change in the amount of code. But don’t forget that you have the navigation commands at you disposal now. Furthermore it becomes easy to add sorting, filtering and grouping capabilities to the view. I noticed I didn’t explain the ItemViewModel much in this post and the previous post about the Master-Detail ViewModel. For now you should know that viewmodels that derive from IItemViewModel have an Item property that always is the currently selected item in a collection. I think I will change the stock quote program to demonstrate its usage. The application will get a datagrid. A datagrid in WPF has an interesting feature called RowDetails which is an area of customizable content beneath each row. When you select an item in the datagrid the RowDetails will show. It would be nice to then show a graph of the stock quotes in the RowDetails. The QuoteViewModel which has an ItemViewModel as its base class will handle this behaviour. So expect an update of this article. You can find the source code here.


Update
2-3-2010
I have an updated .net 4 version of this code. It includes a chart that is displayed in the rowdetails. You can download it here It is not tested on another computer so please report any problems to me. Shout it kick it on DotNetKicks.com

2 opmerkingen:

Bill H. zei

Good series on ViewModels, I appreciate the time you've spent. I'll be working on a master-detail treeview with item properties in a Listview, so your samples will be a big help. I tried to compile the .Net 4 version of MVVM4, and there are missing dependencies. It looks like you expect the MS Enterprise Application Blocks (EAB) to be installed? At least the Unity Block (Microsoft.Practices.Unity), which I think is the Dependency Injector module? I have not installed the EAB on this computer (Windows 7 Enterprise, .Net 4.0.30319, VS 2010 Premium (Trial), and various VS add-ins), and so the build is failing. I'll be reading your code, so the failed build is no problem, just wanted to send you feedback.

Anoniem zei

aIt is a greate article. I managed to run the .NET 4 version.

I added new record, but it didn't save to database. Do we need to implement a save method for that?
If so could you please give me some guidance?