かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

UWPでキーボードショートカット

こんな感じのビヘイビアを定義しておきます。

using Microsoft.Xaml.Interactivity;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Xaml;

namespace App65
{
    public class KeyEventTriggerBehavior : DependencyObject, IBehavior
    {
        public DependencyObject AssociatedObject { get; private set; }

        public ActionCollection Actions
        {
            get
            {
                if (GetValue(ActionsProperty) == null)
                {
                    this.Actions = new ActionCollection();
                }
                return (ActionCollection)GetValue(ActionsProperty);
            }
            set { SetValue(ActionsProperty, value); }
        }

        public static readonly DependencyProperty ActionsProperty =
            DependencyProperty.Register(
                nameof(Actions), 
                typeof(ActionCollection), 
                typeof(KeyEventTriggerBehavior), 
                new PropertyMetadata(null));

        public bool ShiftKey
        {
            get { return (bool)GetValue(ShiftKeyProperty); }
            set { SetValue(ShiftKeyProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ShiftKey.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShiftKeyProperty =
            DependencyProperty.Register("ShiftKey", typeof(bool), typeof(KeyEventTriggerBehavior), new PropertyMetadata(false));

        public bool CtrlKey
        {
            get { return (bool)GetValue(CtrlKeyProperty); }
            set { SetValue(CtrlKeyProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CtrlKey.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CtrlKeyProperty =
            DependencyProperty.Register("CtrlKey", typeof(bool), typeof(KeyEventTriggerBehavior), new PropertyMetadata(false));

        public VirtualKey Key
        {
            get { return (VirtualKey)GetValue(KeyProperty); }
            set { SetValue(KeyProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Key.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty KeyProperty =
            DependencyProperty.Register("Key", typeof(VirtualKey), typeof(KeyEventTriggerBehavior), new PropertyMetadata(VirtualKey.None));

        public void Attach(DependencyObject associatedObject)
        {
            this.AssociatedObject = associatedObject;
            this.Register();
        }

        public void Detach()
        {
            this.Unregister();
            this.AssociatedObject = null;
        }

        private void Register()
        {
            var fe = this.AssociatedObject as FrameworkElement;
            if (fe == null) { return; }
            fe.Unloaded += this.Fe_Unloaded;
            Window.Current.CoreWindow.KeyDown += this.CoreWindow_KeyDown;
        }

        private void Unregister()
        {
            var fe = this.AssociatedObject as FrameworkElement;
            if (fe == null) { return; }
            fe.Unloaded -= this.Fe_Unloaded;
            Window.Current.CoreWindow.KeyDown -= this.CoreWindow_KeyDown;
        }

        private void CoreWindow_KeyDown(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.KeyEventArgs args)
        {
            if (this.ShiftKey && (Window.Current.CoreWindow.GetKeyState(VirtualKey.Shift) & CoreVirtualKeyStates.Down) != CoreVirtualKeyStates.Down)
            {
                return;
            }

            if (this.CtrlKey && (Window.Current.CoreWindow.GetKeyState(VirtualKey.Control) & CoreVirtualKeyStates.Down) != CoreVirtualKeyStates.Down)
            {
                return;
            }

            if (this.Key == args.VirtualKey)
            {
                Interaction.ExecuteActions(this, this.Actions, args);
                args.Handled = true;
            }
        }

        private void Fe_Unloaded(object sender, RoutedEventArgs e)
        {
            this.Unregister();
        }
    }
}

こういう風に使います。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App65"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
      xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
      x:Name="page"
      x:Class="App65.MainPage"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <Interactivity:Interaction.Behaviors>
            <local:KeyEventTriggerBehavior Key="A"
                                           ShiftKey="True">
                <local:KeyEventTriggerBehavior.Actions>
                    <Core:CallMethodAction TargetObject="{Binding ElementName=page}"
                                           MethodName="Alert" />
                </local:KeyEventTriggerBehavior.Actions>
            </local:KeyEventTriggerBehavior>
            <local:KeyEventTriggerBehavior Key="Enter"
                                           CtrlKey="True">
                <local:KeyEventTriggerBehavior.Actions>
                    <Core:CallMethodAction TargetObject="{Binding ElementName=page}"
                                           MethodName="Tweet" />
                </local:KeyEventTriggerBehavior.Actions>
            </local:KeyEventTriggerBehavior>
        </Interactivity:Interaction.Behaviors>
        <Button Content="Click" />
    </Grid>
</Page>

課題としては、TextBoxみたいにキーイベントをかっさらってしまう奴にフォーカスがあるとショートカットが効かないという点でしょうか…Preview系イベントが恋しい。