Last active
January 4, 2019 13:02
-
-
Save SlyZ/ca7b03931412115cc5fb1416180ad1b4 to your computer and use it in GitHub Desktop.
MVVM-friendly RoutedCommands. Command to command binding can be used to redirect one command to another. The primary use case for it is a redirection of a RoutedCommand to a different ICommand implementation (e.g. DelegateCommand, RelayCommand, etc.). Unlike the standard WPF CommandBinding, CommandToCommandBinding is a DependencyObject with Depe…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Collections.Specialized; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using System.Windows; | |
using System.Windows.Input; | |
namespace Sly.Toolkit | |
{ | |
/// <summary> | |
/// Command to command binding can be used to redirect one command to another. The primary use case for it | |
/// is a redirection of a RoutedCommand to a different ICommand implementation (e.g. DelegateCommand, RelayCommand, etc.). | |
/// Unlike the standard WPF CommandBinding, CommandToCommandBinding is a DependencyObject with DependencyProperties, | |
/// meaning that the source and target commands can be easily bound to in Xaml, and the commands can be implemented in the View Model. | |
/// Example usage: | |
/// <![CDATA[ | |
/// <Window xmlns:st="clr-namespace:Sly.Toolkit" ...> | |
/// <st:CommandToCommand.Bindings> | |
/// <st:CommandToCommandBinding SourceCommand="Save" TargetCommand="{Binding MySaveDelegateCommand}" /> | |
/// </st:CommandToCommand.Bindings> | |
/// </Window> | |
/// ]]> | |
/// </summary> | |
public static class CommandToCommand | |
{ | |
/// <summary> | |
/// Identifies the CommandToCommand.Bindings attached property. | |
/// </summary> | |
public static readonly DependencyProperty BindingsProperty = | |
DependencyProperty.RegisterAttached("CommandToCommandBindingsInternal", typeof(CommandToCommandBindingCollection), typeof(CommandToCommand), | |
new UIPropertyMetadata(null)); | |
/// <summary> | |
/// Gets the value of the CommandToCommand.Bindings attached property from a given System.Windows.UIElement. | |
/// </summary> | |
/// <param name="uielement">The element from which to read the property value.</param> | |
/// <returns>The value of the CommandToCommand.Bindings attached property.</returns> | |
public static CommandToCommandBindingCollection GetBindings(UIElement uielement) | |
{ | |
if (uielement == null) | |
{ | |
throw new ArgumentNullException(nameof(uielement)); | |
} | |
var bindings = (CommandToCommandBindingCollection)uielement.GetValue(BindingsProperty); | |
if (bindings == null) | |
{ | |
bindings = new CommandToCommandBindingCollection(uielement); | |
uielement.SetValue(BindingsProperty, bindings); | |
} | |
return bindings; | |
} | |
/// <summary> | |
/// Sets the value of the CommandToCommand.Bindings attached property to a given System.Windows.UIElement. | |
/// </summary> | |
/// <param name="uielement">The element on which to set the attached property.</param> | |
/// <param name="value">The property value to set.</param> | |
public static void SetBindings(UIElement uielement, CommandToCommandBindingCollection value) | |
{ | |
if (uielement == null) | |
{ | |
throw new ArgumentNullException(nameof(uielement)); | |
} | |
var bindings = (CommandToCommandBindingCollection)uielement.GetValue(BindingsProperty); | |
if (bindings != null) | |
{ | |
bindings.Unhook(); | |
} | |
uielement.SetValue(BindingsProperty, value); | |
} | |
} | |
/// <summary> | |
/// A command binding encapsulating a single command-to-command mapping. | |
/// Unlike <see cref="System.Windows.Input.CommandBinding"/>, this binding is a DependencyObject, | |
/// and hence can be bound to in Xaml. | |
/// </summary> | |
public class CommandToCommandBinding : Freezable | |
{ | |
/// <summary> | |
/// A dummy command used as a placeholder when source command is not set. | |
/// </summary> | |
private static readonly RoutedCommand m_dummyCommand = new RoutedCommand(); | |
/// <summary> | |
/// Identifies the <see cref="SourceCommand"/> dependency property. | |
/// </summary> | |
public static readonly DependencyProperty SourceCommandProperty = | |
DependencyProperty.Register(nameof(SourceCommand), typeof(ICommand), typeof(CommandToCommandBinding), | |
new FrameworkPropertyMetadata(null, SourceCommandChanged)); | |
/// <summary> | |
/// Identifies the <see cref="TargetCommand"/> dependency property. | |
/// </summary> | |
public static readonly DependencyProperty TargetCommandProperty = | |
DependencyProperty.Register(nameof(TargetCommand), typeof(ICommand), typeof(CommandToCommandBinding), | |
new FrameworkPropertyMetadata(null)); | |
/// <summary> | |
/// Initializes a new instance of the <see cref="CommandToCommandBinding"/> class. | |
/// </summary> | |
public CommandToCommandBinding() | |
{ | |
CommandBinding = new CommandBinding(m_dummyCommand, OnSourceExecuted, OnSourceCanExecute); | |
} | |
/// <summary> | |
/// Gets a <see cref="System.Windows.Input.CommandBinding"/> instance representing command-to-command mapping | |
/// that will be added to the <see cref="System.Windows.UIElement.CommandBindings"/> collection to enable | |
/// listening to routed commands. | |
/// </summary> | |
public CommandBinding CommandBinding { get; private set; } | |
/// <summary> | |
/// Gets or sets the source command for the binding. | |
/// </summary> | |
public ICommand SourceCommand | |
{ | |
get { return (ICommand)GetValue(SourceCommandProperty); } | |
set { SetValue(SourceCommandProperty, value); } | |
} | |
/// <summary> | |
/// Gets or sets the target command for the binding. | |
/// </summary> | |
public ICommand TargetCommand | |
{ | |
get { return (ICommand)GetValue(TargetCommandProperty); } | |
set { SetValue(TargetCommandProperty, value); } | |
} | |
private static void SourceCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) | |
{ | |
var c2cBinding = (CommandToCommandBinding)obj; | |
c2cBinding.WritePreamble(); | |
c2cBinding.CommandBinding.Command = ((ICommand)e.NewValue) ?? m_dummyCommand; | |
c2cBinding.WritePostscript(); | |
} | |
private void OnSourceCanExecute(object sender, CanExecuteRoutedEventArgs e) | |
{ | |
var targetCommand = TargetCommand; | |
if (targetCommand != null) | |
{ | |
e.CanExecute = targetCommand.CanExecute(e.Parameter); | |
e.Handled = true; | |
} | |
} | |
private void OnSourceExecuted(object sender, ExecutedRoutedEventArgs e) | |
{ | |
var targetCommand = TargetCommand; | |
if (targetCommand != null) | |
{ | |
targetCommand.Execute(e.Parameter); | |
e.Handled = true; | |
} | |
} | |
protected override Freezable CreateInstanceCore() | |
{ | |
return new CommandToCommandBinding(); | |
} | |
} | |
/// <summary> | |
/// A specialised collection used by the CommandToCommand.Bindings property as a storage for the command mappings. | |
/// This collection automatically adds and removes its own CommandToCommandBindings to the | |
/// <see cref="System.Windows.UIElement.CommandBindings"/> collection of the UIElement to which CommandToCommand.Bindings is attached. | |
/// </summary> | |
public sealed class CommandToCommandBindingCollection : FreezableCollection<CommandToCommandBinding> | |
{ | |
private UIElement m_uielement; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="CommandToCommandBindingCollection"/> class. | |
/// </summary> | |
/// <param name="uielement">UIElement to which this collection should add command bindings.</param> | |
internal CommandToCommandBindingCollection(UIElement uielement) | |
{ | |
m_uielement = uielement; | |
Hook(); | |
} | |
private void Hook() | |
{ | |
((INotifyCollectionChanged)this).CollectionChanged += OnCollectionChanged; | |
} | |
internal void Unhook() | |
{ | |
((INotifyCollectionChanged)this).CollectionChanged -= OnCollectionChanged; | |
if (m_uielement != null) | |
{ | |
for (int i = 0; i < this.Count; ++i) | |
{ | |
m_uielement.CommandBindings.Remove(this.ElementAt(i).CommandBinding); | |
} | |
} | |
} | |
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) | |
{ | |
if (m_uielement != null) | |
{ | |
if (e.OldItems != null) | |
{ | |
foreach (CommandToCommandBinding c2cBinding in e.OldItems) | |
{ | |
m_uielement.CommandBindings.Remove(c2cBinding.CommandBinding); | |
} | |
} | |
if (e.NewItems != null) | |
{ | |
foreach (CommandToCommandBinding c2cBinding in e.NewItems) | |
{ | |
m_uielement.CommandBindings.Add(c2cBinding.CommandBinding); | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment