Click here to Skip to main content
14,493,794 members

Sinking RoutedCommands to ViewModel Commands in WPF

Rate this:
5.00 (3 votes)
Please Sign up or sign in to vote.
5.00 (3 votes)
17 Dec 2012CPOL
This is an alternative for "Using RoutedCommands with a ViewModel in WPF".

Introduction

I found myself searching for a solution to using the RoutedCommand and sinking it to a ViewModel's command. I found Josh Smith's solution, but I found it hard to read when the Xaml was done. I wanted something that read nicer.

Why don't I just always used view model commands? Well, sometimes I cannot get around it. When my products use Third Party Controls, they typically implement the standard RoutedUICommands, like those in the System.Windows.Input namespace, such as ApplicationsCommands, MediaCommands, and NavigationCommands. I want to use my controls that I bought, but I also want to stick with my MVVM design. 

Solution 

My solution entails adding an invisible UserControl called CommandSinkControl. That control associates RoutedCommands with view model ICommands. Then in your normal CommandBindings, you forward the event call to the CommandSinkControl's public methods DoExecuted and DoCanExecute to handle the event. 

The Code  

There is just one control that you have to add to your code base, CommandSinkControl.  

CommandSinkControl.xaml 

<UserControl x:Class="CommandSink.CommandSinkControl"

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

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

             Visibility="Collapsed">
</UserControl> 

CommandSinkControl.xaml.cs 

using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;

namespace CommandSink {
    [ContentProperty("CommandSinkBindings")]
    public partial class CommandSinkControl : UserControl {
        private static readonly DependencyPropertyKey SinksPropertyKey =
            DependencyProperty.RegisterReadOnly("CommandSinkBindings",
                                                typeof(CommandSinkBindingCollection),
                                                typeof(CommandSinkControl),
                                                null);

        public static readonly DependencyProperty SinksProperty =
            SinksPropertyKey.DependencyProperty;

        public CommandSinkBindingCollection CommandSinkBindings {
            get { return (CommandSinkBindingCollection)this.GetValue(SinksProperty); }
            private set { this.SetValue(SinksPropertyKey, value); }
        }

        public CommandSinkControl() {
            this.InitializeComponent();
            this.CommandSinkBindings = new CommandSinkBindingCollection();
            this.CommandSinkBindings.CollectionChanged += this.Sinks_OnCollectionChanged;
        }

        protected override IEnumerator LogicalChildren {
            get {
                if (this.CommandSinkBindings == null) {
                    yield break;
                }
                foreach (var sink in this.CommandSinkBindings) {
                    yield return sink;
                }
            }
        }

        private void Sinks_OnCollectionChanged(object sender,
                                               NotifyCollectionChangedEventArgs e) {
            switch (e.Action) {
                case NotifyCollectionChangedAction.Add:
                    foreach (var sink in e.NewItems) {
                        this.AddLogicalChild(sink);
                    }
                    break;
            }
        }

        public void DoCanExecute(object sender, CanExecuteRoutedEventArgs e) {
            var commandSinkBinding = this.CommandSinkBindings
                                         .FirstOrDefault(csb => csb.Faucet == e.Command);
            if (commandSinkBinding != null && commandSinkBinding.Drain != null) {
                e.Handled = true;
                e.CanExecute = commandSinkBinding.Drain.CanExecute(e.Parameter);
            }
        }

        public void DoExecuted(object sender, ExecutedRoutedEventArgs e) {
            var commandSinkBinding = this.CommandSinkBindings
                                         .FirstOrDefault(csb => csb.Faucet == e.Command);
            if (commandSinkBinding != null && commandSinkBinding.Drain != null) {
                e.Handled = true;
                commandSinkBinding.Drain.Execute(e.Parameter);
            }
        }
    }

    public sealed class CommandSinkBindingCollection
        : ObservableCollection<CommandSinkBinding> {
    }

    public class CommandSinkBinding : FrameworkElement {
        public static readonly DependencyProperty FaucetProperty =
            DependencyProperty.RegisterAttached("Faucet",
                                                typeof(RoutedCommand),
                                                typeof(CommandSinkBinding));

        public RoutedCommand Faucet {
            get { return (RoutedCommand) this.GetValue(FaucetProperty); }
            set { this.SetValue(FaucetProperty, value); }
        }

        public static readonly DependencyProperty DrainProperty =
            DependencyProperty.RegisterAttached("Drain",
                                                typeof(ICommand),
                                                typeof(CommandSinkBinding));

        public ICommand Drain {
            get { return (ICommand) this.GetValue(DrainProperty); }
            set { this.SetValue(DrainProperty, value); }
        }
    }
} 

Implementation 

In the following example, I have hooked up the CommandSinkControl such that it sinks the ApplicationCommands.Open RoutedCommand to the OpenCommand that I've defined in my View Model. 

MainWindow.xaml

<Window x:Class="CommandSink.MainWindow"

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

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

        xmlns:local="clr-namespace:CommandSink"

        Title="CommandSink" SizeToContent="WidthAndHeight" ResizeMode="NoResize">
    
    <!-- Normal Command Bindings to RoutedCommands -->
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Open"

                        Executed="CommandBinding_OnExecuted"

                        CanExecute="CommandBinding_OnCanExecute"/>
    </Window.CommandBindings>

    <Window.InputBindings>
        <!-- Example KeyBinding with Command Binding to a Routed Command -->
        <KeyBinding Gesture="CTRL+O" Command="ApplicationCommands.Open"/>
    </Window.InputBindings>

    <StackPanel>
        <!-- Make sure this is at the top so that CanExecute events can use CommandSinkControl.
             It defaults to hidden, so you don't have to hide it.-->
        <local:CommandSinkControl x:Name="CommandSinkControl">
            <!-- Sinks the ApplicationCommands.Open RoutedUICommand to the OpenCommand -->
            <local:CommandSinkBinding Faucet="ApplicationCommands.Open"

                                      Drain="{Binding OpenCommand}"/>
        </local:CommandSinkControl>

        <CheckBox Margin="10"

                  Content="Toggles the Open button's enabled state"

                  IsChecked="{Binding OpenCanExecute}"/>

        <!-- Example Button with Command Binding to a Routed Command-->
        <Button Margin="10" Content="Open" HorizontalAlignment="Left"

                Command="ApplicationCommands.Open"/>
    </StackPanel>
</Window>

 MainWindow.xaml.cs 

using System.Windows;
using System.Windows.Input;

namespace CommandSink {
    /// <summary>
    ///   Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            this.InitializeComponent();
            this.DataContext = new MainViewModel();
        }

        private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e) {
            this.CommandSinkControl.DoExecuted(sender, e);
        }

        private void CommandBinding_OnCanExecute(object sender, CanExecuteRoutedEventArgs e) {
            this.CommandSinkControl.DoCanExecute(sender, e);
        }
    }
} 

 MainViewModel.cs 

using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using Microsoft.Practices.Prism.Commands;

namespace CommandSink {
    public class MainViewModel : INotifyPropertyChanged {
        public ICommand OpenCommand { get; private set; }

        private bool m_openCanExecute;
        public bool OpenCanExecute {
            get { return this.m_openCanExecute; }
            set {
                if (value != this.m_openCanExecute) {
                    this.m_openCanExecute = value;
                    this.OnPropertyChanged("OpenCanExecute");
                }
            }
        }

        public MainViewModel() {
            this.OpenCommand = new DelegateCommand(this.OpenCommand_OnExecuted,
                                                   this.OpenCommand_OnCanExecute);
        }

        private bool OpenCommand_OnCanExecute() {
            return this.OpenCanExecute;
        }

        private void OpenCommand_OnExecuted() {
            MessageBox.Show("Open Command");
        }

        #region Implementation of INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName) {
            var handler = this.PropertyChanged;
            if (handler != null) {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    }
}

Points of Interest 

While you have to add a little wire-up code to the code-behind of the view, I found this acceptable to gain the added readability. 

You might also ask why I created an invisible UserControl instead of just putting it in the Window.Resources. It was crucial that Drain="{Binding OpenCommand}" worked. To do that, I needed it to be part of the LogicalTree and inherit the DataContext such that I could use DataBinding. Do you have a better solution to do this? Please let me know! 

Thank You!

Thank you for reading my article. I hope I was clear and understandable. Please provide comments, suggestions, and/or concerns! I'd love to hear them! Thank you! 

License

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

Share

About the Author

Charles Mathis
Software Developer (Senior)
United States United States
Charles creates software for Windows and the Web. He works as a Senior Software Engineer at a leading Financial Services company.

Comments and Discussions

 
Questionthanks Pin
taleofsixstrings17-Sep-13 21:06
Membertaleofsixstrings17-Sep-13 21:06 
SuggestionCould also use System.Windows.Interactivity to create a behavior that maps the RoutedCommand to a ICommand Pin
Member 402078415-Feb-13 13:23
MemberMember 402078415-Feb-13 13:23 
Another way to do this in a declarative way in the XAML without the ViewModel knowing anything about the View is to create a custom interactivity behavior. You could then something like the following to your XAML. Of course, you will have to reference System.Windows.Interactivity to make this work.

<i:Interaction.Behaviors>
    <core:RoutedCommandBehavior RoutedCommand="Open" Command="{Binding OpenFileCommand}" />
</i:Interaction.Behaviors>


The code for the RoutedCommandBehavior class is as follows (I apologize in advance for the wrapping!):

/// <summary>
/// A interactivity behavior that maps a <see cref="RoutedCommand" /> to a <see cref="ICommand"/> implementation.
/// This is useful for mapping routed commands to command properties in a MVVM view model.
/// </summary>
public class RoutedCommandBehavior : Behavior<FrameworkElement>, ICommandSource
{
    private CommandBinding _commandBinding;

    #region RoutedCommand Dependency Property

    public static readonly DependencyProperty RoutedCommandProperty =
       DependencyProperty.Register("RoutedCommand", typeof(RoutedCommand), typeof(RoutedCommandBehavior));

    /// <summary>
    /// Gets or sets the RoutedCommand that is the trigger
    /// </summary>
    public RoutedCommand RoutedCommand
    {
        get { return (RoutedCommand)GetValue(RoutedCommandProperty); }
        set { SetValue(RoutedCommandProperty, value); }
    }

    #endregion

    #region Command Dependency Property

    public static readonly DependencyProperty CommandProperty =
       DependencyProperty.Register("Command", typeof(ICommand), typeof(RoutedCommandBehavior));

    /// <summary>
    /// Gets or sets the command to be executed
    /// </summary>
    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    #endregion

    #region CommandParameter Dependency Property

    public static readonly DependencyProperty CommandParameterProperty =
       DependencyProperty.Register("CommandParameter", typeof(object), typeof(RoutedCommandBehavior));

    /// <summary>
    /// Gets or sets the command parameter.  If this is not specified, the parameter from the routed command will be used.
    /// </summary>
    public object CommandParameter
    {
        get { return GetValue(CommandParameterProperty); }
        set { SetValue(CommandParameterProperty, value); }
    }

    #endregion

    #region CommandTarget Dependency Property

    /// <summary>
    /// CommandTarget Dependency Property
    /// </summary>
    public static readonly DependencyProperty CommandTargetProperty =
       DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(RoutedCommandBehavior));

    /// <summary>
    /// Gets or sets the object that the command is being executed on.
    /// </summary>
    /// <returns>
    /// The object that the command is being executed on.
    /// </returns>
    public IInputElement CommandTarget
    {
        get { return (IInputElement)GetValue(CommandTargetProperty); }
        set { SetValue(CommandTargetProperty, value); }
    }

    #endregion

    /// <summary>
    /// Called after the trigger is attached to an AssociatedObject.
    /// </summary>
    protected override void OnAttached()
    {
        if (AssociatedObject != null && !AssociatedObject.CommandBindings.Contains(_commandBinding))
        {
            _commandBinding = new CommandBinding(RoutedCommand, HandleExecuted, HandleCanExecute);
            AssociatedObject.CommandBindings.Add(_commandBinding);
        }
    }

    /// <summary>
    /// Called when the trigger is being detached from its AssociatedObject, but before it has actually occurred.
    /// </summary>
    protected override void OnDetaching()
    {
        if (AssociatedObject != null && _commandBinding != null && AssociatedObject.CommandBindings.Contains(_commandBinding))
        {
            AssociatedObject.CommandBindings.Remove(_commandBinding);
        }

        _commandBinding = null;
    }

    /// <summary>
    /// Handles the <see cref="RoutedCommand"/>'s CanExecute event
    /// </summary>
    /// <param name="sender">
    /// The sender.
    /// </param>
    /// <param name="e">
    /// The <see cref="CanExecuteRoutedEventArgs"/>.
    /// </param>
    private void HandleCanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        if (Command == null)
        {
            e.CanExecute = false;
            return;
        }

        var parameter = CommandParameter ?? e.Parameter;
        RoutedCommand routedCommand = Command as RoutedCommand;
        if (routedCommand != null)
        {
            e.CanExecute = routedCommand.CanExecute(parameter, CommandTarget);
        }

        e.CanExecute = Command.CanExecute(parameter);
    }

    /// <summary>
    /// Handles the <see cref="RoutedCommand"/>'s Executed event.
    /// </summary>
    /// <param name="sender">
    /// The sender.
    /// </param>
    /// <param name="e">
    /// The <see cref="ExecutedRoutedEventArgs" />.
    /// </param>
    private void HandleExecuted(object sender, ExecutedRoutedEventArgs e)
    {
        if (Command == null)
        {
            return;
        }

        var parameter = CommandParameter ?? e.Parameter;
        RoutedCommand routedCommand = Command as RoutedCommand;
        if (routedCommand != null)
        {
            routedCommand.Execute(parameter, CommandTarget);
        }

        Command.Execute(parameter);
        e.Handled = true;
    }
}


I use this method all the time in our code to handle standard routed commands like Cut, Copy and Paste in our ViewModels.
Neal Borelli
Long time XAML fan!

GeneralRe: Could also use System.Windows.Interactivity to create a behavior that maps the RoutedCommand to a ICommand Pin
Charles Mathis30-Dec-13 3:11
MemberCharles Mathis30-Dec-13 3:11 
QuestionNice idea Pin
Sacha Barber18-Dec-12 3:25
MemberSacha Barber18-Dec-12 3:25 
AnswerRe: Nice idea Pin
Charles Mathis18-Dec-12 4:06
MemberCharles Mathis18-Dec-12 4:06 
GeneralRe: Nice idea Pin
William E. Kempf18-Dec-12 9:32
MemberWilliam E. Kempf18-Dec-12 9:32 
AnswerRe: Nice idea Pin
Charles Mathis18-Dec-12 13:13
MemberCharles Mathis18-Dec-12 13:13 
GeneralRe: Nice idea Pin
William E. Kempf19-Dec-12 2:48
MemberWilliam E. Kempf19-Dec-12 2:48 
GeneralRe: Nice idea Pin
Sacha Barber20-Dec-12 1:57
MemberSacha Barber20-Dec-12 1:57 
GeneralRe: Nice idea Pin
Charles Mathis20-Dec-12 9:37
MemberCharles Mathis20-Dec-12 9:37 
GeneralRe: Nice idea Pin
Sacha Barber21-Dec-12 22:54
MemberSacha Barber21-Dec-12 22:54 
GeneralRe: Nice idea Pin
Sacha Barber20-Dec-12 1:54
MemberSacha Barber20-Dec-12 1:54 
QuestionArticle should be "Syncing" Pin
FatCatProgrammer18-Dec-12 0:35
MemberFatCatProgrammer18-Dec-12 0:35 
AnswerRe: Article should be "Syncing" Pin
Charles Mathis18-Dec-12 4:15
MemberCharles Mathis18-Dec-12 4:15 
Generalcool! Pin
poi11917-Dec-12 23:54
Memberpoi11917-Dec-12 23:54 
GeneralRe: cool! Pin
Charles Mathis18-Dec-12 4:16
MemberCharles Mathis18-Dec-12 4:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Alternative
Article
Posted 17 Dec 2012

Stats

23.1K views
210 downloads
13 bookmarked