Attribuer des ICommand à des raccourcis claviers...26 août 2011

Boîte à outils

Toujours dans l’optique de bien respecter ce merveilleux modèle MVVM dans le cadre de nos développements WPF, je vous propose aujourd’hui une extension pour les fenêtres (type Window) permettant d’associer à des raccourcis clavier des commandes de votre ViewModel par simple Binding dans le code XAML.

J’ai nommé cette extension : WindowExtensions. Le tout tient dans un fichier car je n’ai pas trouvé utile de créer un projet pour si peu de code.

Le fichier WindowExtensions.cs

using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;

namespace Dotnetnews.Extensions
{
    class ShortcutCommand : DependencyObject
    {
        public Key Key { get; set; }
        public ModifierKeys ModifierKeys { get; set; }

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(ShortcutCommand), new PropertyMetadata(null));

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object), typeof(ShortcutCommand), new PropertyMetadata(null));

        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

    }

    class ShortcutCommandCollection : Collection<ShortcutCommand>
    {

    }

    class WindowExtensions
    {
        public static readonly DependencyProperty GlobalShortcutsProperty =
            DependencyProperty.RegisterAttached("GlobalShortcuts", typeof(ShortcutCommandCollection),
                                                typeof(WindowExtensions),
                                                new FrameworkPropertyMetadata(null,
                                                                              GlobalShortcutsPropertyChanged));

        public static ShortcutCommandCollection GetGlobalShortcuts(UIElement window)
        {
            return (ShortcutCommandCollection)window.GetValue(GlobalShortcutsProperty);
        }

        public static void SetGlobalShortcuts(UIElement window, ShortcutCommandCollection shortcuts)
        {
            window.SetValue(GlobalShortcutsProperty, shortcuts);
        }

        private static void GlobalShortcutsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var window = (Window)d;
            window.PreviewKeyDown += WindowPreviewKeyDown;
        }

        private static void WindowPreviewKeyDown(object sender, KeyEventArgs e)
        {
            var window = (Window)sender;
            var shortcuts = GetGlobalShortcuts(window);

            var sc =
                shortcuts.SingleOrDefault(
                    s => s.Key == e.Key && ((Keyboard.Modifiers & s.ModifierKeys) == s.ModifierKeys));
            if (sc != null && sc.Command != null)
            {
                if (sc.Command.CanExecute(sc.CommandParameter))
                    sc.Command.Execute(sc.CommandParameter);
            }
        }
    }
}

Cadre d’utilisation dans notre code XAML

<Window x:Class="MyApplication.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ViewModels="clr-namespace:MyApplication.ViewModels"
        xmlns:DnE="clr-namespace:Dotnetnews.Extensions"
        DataContext="{DynamicResource ViewModel}">

    <Window.Resources>
        <ViewModels:MainViewModel x:Key="ViewModel" />
    </Window.Resources>

    <DnE:WindowExtensions.GlobalShortcuts>
        <DnE:ShortcutCommandCollection>
            <!-- Raccourci clavier Ctrl+R -->
            <DnE:ShortcutCommand Key="R" ModifierKeys="Control"
                                   Command="{Binding RefreshCommand, Source={StaticResource ViewModel}}" />
        </DnE:ShortcutCommandCollection>
    </DnE:WindowExtensions.GlobalShortcuts>

    ...

</Window>

Note : Il est important de placer le code de GlobalShortcuts après le code des ressources contenant le ViewModel.