Monday 13 December 2010

WCF & MVVM

I was playing around attempting to implement course grained MVVM in WPF. I used the Prism Application Block to achieve the most part.

I came up with a few resulting base classes that formed the crux of the application I was deving at the time.

ViewModelBase does what it says on the tin. Your base class for ViewModels. Nothing really special apart from a couple of things. It contains a DelegateCommandManager which is a dictionary of commands that the ViewModel can support. Normally the implementors of the ICommand interface (i.e. RoutedCommand) has an event that gets fired internally within WPF when the result of the CanExecute has changed, it’s called 'CanExecuteChanged'. Surprise surprise.

I've addressed that in the following way. You'll notice an abstract property called 'AutoRaiseCanExecuteChangedOnPropertyChanged'. This means the derived the ViewModel gets to choose whether the event gets raised automatically once the OnPropertyChanged event is raised. You can inspect this in the RaisePropertyChanged method below.

Secondly the Dispatcher. Most of the work in the application is actually done on a background worker thread and some updates to the UI originate from there. To address this I've initialised the DisatcherSynchronizationContext within the base constructor below. I've made the basic assumption that the ViewModel should be newed up by the UI thread. Even when taking into consideration IOC, the first thread that runs is the WPF UI Thread and that should be the thread that new's up the IOC container. So I've covered all bases on that one.

        public abstract class ViewModelBase : DomainObject, IDisposable
    {
        protected ViewModelBase()
        {
            this.CommandManager = new DelegateCommandManager();
            this._Context = new DispatcherSynchronizationContext();
        }
        protected DelegateCommandManager CommandManager { get; private set; }
        protected virtual bool AutoRaiseCanExecuteChangedOnPropertyChanged
        {
            get { return false; }
        }
        protected override void RaisePropertyChanged(string propertyName)
        {
            base.RaisePropertyChanged(propertyName);
            if (!this.AutoRaiseCanExecuteChangedOnPropertyChanged) return;
            foreach(var command in this.CommandManager.Commands.Values)
            {
                command.RaiseCanExecuteChanged();
            }
        }
        protected DispatcherSynchronizationContext _Context;
       
        #region IDisposable
        private bool _Disposed; // to detect redundant calls
        protected virtual void Dispose(bool disposing)
        {
            if (this._Disposed) return;
            this._Disposed = true;
            if (disposing)
            {
                // dispose-only, i.e. non-finalizable logic
            }
            // shared cleanup logic
        }
        ~ViewModelBase()
        {
            Dispose(false);
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }

You might have noticed the DomainObject class. Here’s the class definition -

    public abstract class DomainObject : NotificationObject, IDataErrorInfo
    {
        /// <summary>
        /// WPF does not use this property.
        /// </summary>
        public virtual string Error
        {
            get { return null; }
        }
        /// <summary>
        /// Use this to add custom validation for properties that are bound "OneWay". This is from source to target.
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public virtual string this[string columnName]
        {
            get { return null; }
        }
    }

Now this class can also be used to derive your Models. Pretty simply and self explanatory really.

Some of the obstacles I acountered during this was using third party controls that didn't support the WPF Command model. It’s a real pain in the butt when you're trying to implement pure MVVM. I came across the Expression Blend Behaviour Framework. It's pretty neat. Its a mechanism which allows you to extend controls to support new behaviour. Long story short, when you have controls which support .Net events and no WPF Commands we can use this framework to hook onto those events and invoke a WPF Command. All setup in XAML! I took this concept a step further and implemented a generic DotEventCommandBinder class which uses reflection to bind to an event and invoke a command.

Few limitations, it works only against 'Dot Net'events which supports the traditional Microsoft EventHandler Delegate only - void (object, EventArgs). It can support WPF Routed Events, but they need to be wrapped up in a Dot Net event wrapper. Luckily I think all Routed Events in the WPF framework are wrapped up with Dot Net events anyway. Finally it does not support AttachedRoutedEvents thats for sure !

/// <summary>
    /// This class binds a standard dot net event to a command.
    /// Supports delegate - 'void (object, EventArgs)'
    /// It does not bind to routed events that have not exposed a dot net event wrapper
    /// or attached routed events.
    /// </summary>
    public class DotNetCommandBinder : Behavior<UIElement>
    {
        public string EventSource
        {
            get { return (string)GetValue(EventSourceProperty); }
            set { SetValue(EventSourceProperty, value); }
        }
        // Using a DependencyProperty as the backing store for EventSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EventSourceProperty =
            DependencyProperty.Register("EventSource", typeof(string), typeof(DotNetCommandBinder));
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }
        // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(DotNetCommandBinder));
        public object CommandParameter
        {
            get { return GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }
        // Using a DependencyProperty as the backing store for CommandParameter.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object), typeof(DotNetCommandBinder));
        private Delegate _commandHandler;
        private EventInfo _eventInfo;
        protected override void OnAttached()
        {
            if (string.IsNullOrEmpty(this.EventSource))
            {
                throw new NullReferenceException("EventSource cannot be null");
            }
            this._eventInfo = this.AssociatedObject.GetType().GetEvent(this.EventSource);
            if (this._eventInfo == null)
            {
                throw new NullReferenceException(string.Format("Could not find event on associated object with name {0}", this.EventSource));
            }
            try
            {
                var methodInfo = this.GetType().GetMethod("ActionCommand", BindingFlags.Instance|BindingFlags.NonPublic);
                this._commandHandler = Delegate.CreateDelegate(this._eventInfo.EventHandlerType, this, methodInfo, true);
                this._eventInfo.AddEventHandler(this.AssociatedObject, this._commandHandler);
            }
            catch(Exception ex)
            {
                throw new InvalidOperationException("Could not associate event handler to event. Please make sure your delegate signiture is 'void(object, EventArgs)'", ex);
            }
            base.OnAttached();
        }
        private void ActionCommand(object sender, EventArgs e)
        {
            if (this.Command != null && this.Command.CanExecute(this.CommandParameter))
            {
                this.Command.Execute(this.CommandParameter);
            }
        }
        protected override void OnDetaching()
        {
            if (this._eventInfo != null && this._commandHandler != null)
            {
                this._eventInfo.RemoveEventHandler(this.AssociatedObject, this._commandHandler);
            }
            base.OnDetaching();
        }
    }

Anyway I hope you've found that interesting. I'm going to take a look at the other frameworks outs there to check out if they offer any new fresh ideas into the world of WPF & MVVM.

  • MVVM Toolkit
  • MVVM Foundation
  • WPF Application Framework (WAF)
  • Light MVVM
  • Caliburn
  • Cinch

No comments:

Post a Comment