Second Stanza

July 26, 2010

MVVM – MEF – Reactive Data Grid – Part IV

Filed under: MEF, MVVM, Rx Extensions, Silverlight — Tags: — dfbaskin @ 7:01 am

In MVVM, the View is meant to be isolated from other parts of the model. The goal for the View is that It contains user interface and not code. This is a good goal, obviously, since it allows designers to work separately from developers and allows for unit testing other parts of the model separate from the design.

In MVVM, events that originate in the view are usually handled in the view model. The idea of event delegation through a command interface has become popular for addressing this issue within MVVM. For example, the Prism framework provides a DelegateCommand that can be bound from the View which in turn executes a method implemented within the View Model. The MVVM Light Toolkit expands event delegation more by supporting a wider array of events. These solutions allow the user to declaratively express how events should be handled.

And anybody who has used these knows the difficulties in addressing more complex eventing scenarios, beyond just doing something when the user clicks a button.

One of the benefits of the Reactive Extensions for .NET (Rx) is the ability to do interesting things with events. You can compose events from multiple sources.  You can filter and transform events. You can orchestrate different UI elements. Rx gives you a great tool for managing the asynchronous nature of event handling.I think the Rx extensions provides a better mechanism for dealing with UI events that the current MVVM solutions. Yes, a developer is putting code into the code behind for the View, but the benefits of Rx outweigh this in my mind.

In the MVVMGrid application, the columns to be displayed in the grid are stored in settings files on the web server, but these settings, in fact, map to a very specific configuration of the controls contained within the View. The View knows about these controls (the DataGridColumn controls) so it makes sense that the View should be the one to manipulate these controls.

Let’s examine how this is used in the MVVMGrid application.

First, the GridViewModel exposes a ViewSettings property that contains the settings for the view the user has currently selected. The GridView needs to know when this property changes so that it can update the view. The bindings expressed in the GridView use the INotifyPropertyChanged interface to determine when the value of a property has changed. The GridView itself can use this same interface to determine when the view needs updating.

        [Import]
        public GridViewModel ViewModel
        {
            get { return DataContext as GridViewModel; }
            set { DataContext = value; OnViewModelChanged(); }
        }

        private IDisposable notifyChangedSubscription;

        private void OnViewModelChanged()
        {
            if( notifyChangedSubscription != null )
            {
                notifyChangedSubscription.Dispose();
                notifyChangedSubscription = null;
            }
            if( ViewModel != null )
            {
                notifyChangedSubscription =
                    Observable
                        .FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
                            h => new PropertyChangedEventHandler( h ),
                            h => ViewModel.PropertyChanged += h,
                            h => ViewModel.PropertyChanged -= h )
                        .Where( evt => evt.EventArgs.PropertyName == "ViewSettings" )
                        .Subscribe( evt =>
                        {
                            UpdateColumns();
                        } );
            }
        }

When the ViewModel property is initially set, the OnViewModelChanged method subscribes (using the Rx extensions) to the PropertyChanged event. It filters out the other property changed notifications except for the one we need.  Finally, when the event occurs, the UpdateColumns method is called to update the view.

The UpdateColumns method uses a LINQ query to map from the column definitions in the view settings to the DataGridColumn controls contained within the data grid, showing or hiding the columns and setting the prescribed width.

        private static string GetColumnID( DataGridColumn gridColumn )
        {
            return (string)gridColumn.GetValue( FrameworkElement.NameProperty );
        }

        private void UpdateColumns()
        {
            int displayIndex = 0;
            ViewModel.ViewSettings.Columns
                .GroupJoin(
                    dataGrid.Columns,
                    vc => vc.ColumnID,
                    gc => GetColumnID( gc ),
                    ( vc, gc ) => Tuple.Create( vc, gc.FirstOrDefault() ) )
                .Concat(
                    dataGrid.Columns
                        .Where( gc => !ViewModel.ViewSettings.Columns
                                            .Where( vc => GetColumnID( gc ) == vc.ColumnID )
                                            .Any() )
                        .Select( gc => Tuple.Create( ViewColumnSettings.Null, gc ) ) )
                .ToList()
                .ForEach( t =>
                {
                    ViewColumnSettings viewCol = t.Item1;
                    DataGridColumn gridCol = t.Item2;

                    if( viewCol == null )
                        gridCol.Visibility = System.Windows.Visibility.Collapsed;
                    else
                    {
                        gridCol.Visibility = System.Windows.Visibility.Visible;
                        gridCol.Width = new DataGridLength( viewCol.ColumnWidth, DataGridLengthUnitType.Pixel );
                        gridCol.DisplayIndex = displayIndex++;
                    }
                } );
        }

I grant that this may not be the best example of the benefit of using the Rx extensions within the View, but it shows the basic idea. The bottom line is that I think that there are some great benefits of using Rx in this way and perhaps we should take a second look our View implementations.

Agree/Disagree with me? Please add your comments!

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: