Click here to Skip to main content
15,896,269 members
Articles / Desktop Programming / WPF

Executing Command Logic in a View Model

Rate me:
Please Sign up or sign in to vote.
4.94/5 (54 votes)
9 Mar 2011CPOL14 min read 305.5K   2.8K   142  
Introduces the RoutedCommandBinding class as a new way for invoking methods in a View Model directly from XAML in an MVVM application.
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Input;

namespace RoutedCommandBindingSample.Commands
{
    /// <summary>
    ///     A markup extension that returns an <see cref="ICommand"/> that is capable of executing
    ///     methods of the DataContext of a target FrameworkElement.
    /// </summary>
    /// <remarks>
    ///     When the <see cref="ICommand.Executed"/> and <see cref="ICommand.CanExecute"/> methods
    ///     of the returned <see cref="ICommand"/> object are invoked, methods on the DataContext
    ///     whose names correspond to the values of the <see cref="Executed"/> and
    ///     <see cref="CanExecute"/> properties are invoked. See the <see cref="Executed"/> and
    ///     <see cref="CanExecute"/> properties for specifics on the allowable method signatures.
    /// </remarks>
    public class DataContextCommandAdapter : MarkupExtension, ICommand
    {
        private object _target;

        /// <summary>
        ///     Name of the method of the target object's DataContext that determines whether the
        ///     command can execute in its current state.
        /// </summary>
        /// <remarks>
        ///     The corresponding method must have one of two signatures below, with the first
        ///     taking precedence over the other:
        ///     <code>void MyCanExecuteMethod(object parameter);</code>
        ///     <code>void MyCanExecuteMethod();</code>
        /// </remarks>
        public string CanExecute { get; set; }


        /// <summary>
        ///     Name of the method of the target object's DataContext to be called when the command
        ///     is invoked.
        /// </summary>
        /// <remarks>
        ///     The corresponding method must have one of two signatures below, with the first
        ///     taking precedence over the other:
        ///     <code>void MyExecutedMethod(object parameter);</code>
        ///     <code>void MyExecutedMethod();</code>
        /// </remarks>
        public string Executed { get; set; }

        /// <summary>
        ///     Initializes a new instance of the DataContextCommandAdapter class.
        /// </summary>
        public DataContextCommandAdapter() { }

        /// <summary>
        ///     Initializes a new instance of the <see cref="DataContextCommandAdapter"/> class by
        ///     using the specified method name for the <see cref="Executed"/> property.
        /// </summary>
        /// <param name="executed">
        ///     The name of the <see cref="Executed"/> method.
        /// </param>
        public DataContextCommandAdapter(string executed)
        {
            this.Executed = executed;
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="DataContextCommandAdapter"/> class by
        ///     using the specified method names for the <see cref="Executed"/> and
        ///     <see cref="CanExecute"/> properties.
        /// </summary>
        /// <param name="executed">
        ///     The name of the <see cref="Executed"/> method.
        /// </param>
        /// <param name="canExecute">
        ///     The name of the <see cref="CanExecute"/> method.
        /// </param>
        public DataContextCommandAdapter(string executed, string canExecute)
        {
            this.Executed = executed;
            this.CanExecute = canExecute;
        }

        /// <summary>
        ///     Returns an <see cref="ICommand"/> that is capable of executing methods of the
        ///     DataContext of the target.
        /// </summary>
        /// <param name="serviceProvider">
        ///     Object that can provide services for the markup extension.
        /// </param>
        /// <returns>
        ///     The <see cref="ICommand"/> object.
        /// </returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (target == null)
                throw new Exception("IProvideValueTarget could not be resolved.");


            this._target =
                target.TargetObject is InputBinding
                ? this.GetInputBindingsCollectionOwner(target)
                : target.TargetObject;

            return this;
        }

        //
        // This method only works with the C# 4.0 XamlParser.
        // If there was another way to do this without reflection... I would do it that way
        // Regardless, this method will only be called once when the xaml is initially parsed, so its
        // not really a performance issue.
        private object GetInputBindingsCollectionOwner(IProvideValueTarget targetService)
        {
            var xamlContextField = targetService.GetType().GetField("_xamlContext", BindingFlags.Instance | BindingFlags.NonPublic);
            if (xamlContextField != null)
            {
                var xamlContext = xamlContextField.GetValue(targetService);
                var grandParentInstanceProperty = xamlContext.GetType().GetProperty("GrandParentInstance");
                if (grandParentInstanceProperty != null)
                {
                    var inputBindingsCollection = grandParentInstanceProperty.GetGetMethod().Invoke(xamlContext, null);
                    var ownerField = inputBindingsCollection.GetType().GetField("_owner", BindingFlags.Instance | BindingFlags.NonPublic);
                    if (ownerField != null)
                    {
                        var owner = ownerField.GetValue(inputBindingsCollection);
                        return owner;
                    }
                }
            }
            return null;
        }

        #region ICommand Implementation

        event EventHandler ICommand.CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        bool ICommand.CanExecute(object parameter)
        {
            var target = GetDataContext(this._target);
            if (this._target != null)
            {
                bool canExecute;
                if (CommandExecutionManager.TryExecuteCommand(target, parameter, false, this.Executed, this.CanExecute, out canExecute))
                    return canExecute;
            }
            return false;
        }

        void ICommand.Execute(object parameter)
        {
            var target = GetDataContext(this._target);
            if (this._target != null)
            {
                bool canExecute;
                CommandExecutionManager.TryExecuteCommand(target, parameter, true, this.Executed, this.CanExecute, out canExecute);
            }
        }

        private static object GetDataContext(object element)
        {
            var fe = element as FrameworkElement;
            if (fe != null)
                return fe.DataContext;
            else
            {
                var fce = element as FrameworkContentElement;
                return fce == null ? null : fce.DataContext;
            }
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Fidelity National Financial
United States United States
Doug Schott has been developing .NET applications for the past 8 years.

He is currently working as a senior software consultant at Fidelity National Financial.

Comments and Discussions