キーボードショートカットを付ける

公式の方法

UWPの公式キーボードショートカットの方法はGridのKeyDownイベントをとってねという感じです。

なにやらAutomationPropertiesとかの方法もあるらしいですがよくわからないです。

Gridが(理想のタイミングで)フォーカスを受け取れない

受け取れないわけではないですが、例えばGridをクリックしてもGridのGotFocusイベントは発生しません。

Gridの子要素にTextBoxなど、フォーカスが当たるものにフォーカスがあったとき、GridのGotFocusイベントが発生します。

Gridにフォーカスが当たってないとKeyDownイベントが発生しないのですが子要素のTextBoxにフォーカスがあたってるときにショートカット発動してもうれしくないし誤動作の原因になります。

Dispatcher.AcceleratorKeyActivatedイベントを使う

Dispatcher.AcceleratorKeyActivatedイベントを使うと、現在のDispatcher内で押されたキーをすべて検知することができます。

しかしこれ単体で動かすと、例えばショートカットを適用したいGridからフォーカスがはずれて、別のページに行ったときもショートカットが発動してしまいます。

つまりDispatcher.AcceleratorKeyActivatedイベントでキーを検知したとき、Gridに(理想的な)フォーカスが当たっていればショートカットを発動すればよいわけです。

Gridのショートカットキー的に理想なフォーカスイベント

ではGridの理想的なフォーカスイベントとは何かというと調べた感じたぶんこれぐらいだとおもいます。

PointerPressedはGridをクリックしたときに発動するので理想的なフォーカスといえます。

Unloadedはもし別のページに遷移した、現在のXAMLページが破棄されるなどのときに必要になります。

GotFocusイベントは引数のOriginalSourceを見て、もしGridならフォーカスを、もしGridじゃない(例えば子要素のTextBoxなど)ならフォーカスをあてないことによってテキストボックス入力中にショートカットが発動することを防ぎます。

ビヘイビアを使う

いちいちGridのイベントをとっていてはめんどくさいので自作ビヘイビアを作って簡単にしましょう。

ビヘイビアを自作するためにはBehaviorSDKを参照する必要があります。

作ったビヘイビアがこちらになります。コピペすれば使えます。

using Microsoft.Xaml.Interactivity;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;

namespace Behaviors
{
    public class PressedKeyList
    {
        private static PressedKeyList _instance=null;

        public List<VirtualKey> PressedDic { get; set; }
        private PressedKeyList()
        {
            PressedDic = new List<VirtualKey>();
        }

        public static PressedKeyList GetInstance()
        {
            if (_instance == null)
            {
                return new PressedKeyList();
            }
            else
            {
                return _instance;
            }
        }

        public void SetKeyDown(VirtualKey key)
        {
            if (!PressedDic.Contains(key))
            {
                PressedDic.Add(key);
            }
        }

        public void SetKeyUp(VirtualKey key)
        {
            if (PressedDic.Contains(key))
            {
                PressedDic.Remove(key);
            }
        }

    }

    [ContentProperty(Name = "Actions")]
    public class KeyTriggerBehavior:DependencyObject,IBehavior
    {
        public event EventHandler OnShortcutDown;

        private PressedKeyList _pressedKeyList;
        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(KeyTriggerBehavior), new PropertyMetadata(VirtualKey.None));



        public VirtualKey ModifierKey1
        {
            get { return (VirtualKey)GetValue(ModifierKey1Property); }
            set { SetValue(ModifierKey1Property, value); }
        }

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



        public VirtualKey ModifierKey2
        {
            get { return (VirtualKey)GetValue(ModifierKey2Property); }
            set { SetValue(ModifierKey2Property, value); }
        }

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


        public ActionCollection Actions
        {
            get
            {
                var actions = (ActionCollection)GetValue(ActionsProperty);
                if (actions == null)
                {
                    actions = new ActionCollection();
                    SetValue(ActionsProperty, actions);
                }
                return actions;

            }
        }

        // Using a DependencyProperty as the backing store for Actions.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ActionsProperty =
            DependencyProperty.Register("Actions", typeof(ActionCollection), typeof(KeyTriggerBehavior), new PropertyMetadata(null));


        private bool _isPressed;
        private bool _isFocus;
        public KeyTriggerBehavior()
        {
            _isPressed = false;
            _isFocus = false;
            _pressedKeyList = PressedKeyList.GetInstance();
            
        }

        //ビヘイビアとして適用されるコントロールが入る
        private DependencyObject associatedObject;
        public DependencyObject AssociatedObject
        {
            get { return this.associatedObject; }
        }

        public void Attach(DependencyObject associatedObject)
        {
            this.associatedObject = associatedObject;
            var element = associatedObject as FrameworkElement;
            element.GotFocus += Element_GotFocus;
            element.Unloaded += Element_Unloaded;
            element.PointerPressed += Element_PointerPressed;
            
            Dispatcher.AcceleratorKeyActivated += OnKeyActivated;
        }

        private void Element_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            //GridなどのElementはFocusがあたらないため
            _isFocus = true;
        }

        private void Element_Unloaded(object sender, RoutedEventArgs e)
        {
            //Page遷移したときにFocusが外れないため
            _isFocus = false;
        }


        private void Element_GotFocus(object sender, RoutedEventArgs e)
        {
            //フォースがきたときフォーカスを得たものがアタッチしてるものと違うなら
            if (e.OriginalSource != this.AssociatedObject)
            {
                //フォーカスをはずす
                _isFocus = false;
            }
            else
            {
                _isFocus = true;
            }
        }

        public void Detach()
        {
            var element = associatedObject as FrameworkElement;
            element.GotFocus -= Element_GotFocus;
            element.Unloaded -= Element_Unloaded;
            element.PointerPressed -= Element_PointerPressed;
            this.associatedObject = null;
            Dispatcher.AcceleratorKeyActivated -= OnKeyActivated;

        }
 

        private void OnKeyActivated(CoreDispatcher dispatcher,AcceleratorKeyEventArgs e)
        {
            if (e.EventType == CoreAcceleratorKeyEventType.KeyDown)
            {
                _pressedKeyList.SetKeyDown(e.VirtualKey);
            }
            else if(e.EventType==CoreAcceleratorKeyEventType.KeyUp) 
            {
                _pressedKeyList.SetKeyUp(e.VirtualKey);
            }

            if (e.EventType == CoreAcceleratorKeyEventType.KeyDown&&e.VirtualKey==Key&&_isPressed==false&&_isFocus)
            {
                var modi1State = Window.Current.CoreWindow.GetKeyState(ModifierKey1);
                var modi2State = Window.Current.CoreWindow.GetKeyState(ModifierKey2);

                bool isModi1On = (modi1State == (CoreVirtualKeyStates.Down | CoreVirtualKeyStates.Locked) || modi1State == CoreVirtualKeyStates.Down);
                bool isModi2On = (modi2State == (CoreVirtualKeyStates.Down | CoreVirtualKeyStates.Locked) || modi2State == CoreVirtualKeyStates.Down);
            
                if (ModifierKey1 != VirtualKey.None && ModifierKey2 != VirtualKey.None && Key != VirtualKey.None&&_pressedKeyList.PressedDic.Count==3)
                {
                    //2つの修飾キーと1つめの修飾キーがある場合
                    if (isModi1On && isModi2On && e.VirtualKey == Key)
                    {
                        CommandExecute();
                        
                    }
                }
                else if (ModifierKey1 != VirtualKey.None && ModifierKey2 == VirtualKey.None && Key != VirtualKey.None && _pressedKeyList.PressedDic.Count == 2)
                {
                    //1つめの修飾キーがある場合
                    if (isModi1On && e.VirtualKey == Key)
                    {
                        CommandExecute();
                        
                    }
                }
                else if (ModifierKey1 == VirtualKey.None && ModifierKey2 == VirtualKey.None && Key != VirtualKey.None && _pressedKeyList.PressedDic.Count == 1)
                {
                    //2つの修飾キーがない場合(キーのみ)
                    if (e.VirtualKey == Key)
                    {
                        CommandExecute();
                    }
                }
                else
                {
                    //それ以外
                }
                _isPressed = true;
                
            }
            else if (e.EventType == CoreAcceleratorKeyEventType.KeyUp&&e.VirtualKey==Key&&_isFocus)
            {
                _isPressed = false;
                
            }
        }

        private void CommandExecute()
        {
            Interaction.ExecuteActions(AssociatedObject, Actions, null);
            
            if(OnShortcutDown!=null)
            {
                OnShortcutDown(this,new EventArgs());
            }
        }

        
    }
}

使い方

Pageタグの属性にxmlnsを付け加えます。

xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" 
xmlns:Behaviors="using:Behaviors"

あとはショートカットキーを適用したいFrameworkElementの子要素にこれを入れるだけ

Aキーを押すとバインドされているコマンドが発動します。

<Interactivity:Interaction.Behaviors>
    <Behaviors:KeyTriggerBehavior Key="A">
        <Core:InvokeCommandAction Command="{Binding SumCommand}" />
    </Behaviors:KeyTriggerBehavior>
</Interactivity:Interaction.Behaviors>

イベントも用意してるのでOnShortcutDownイベントをフックするとコードビハインドから簡単に使えます。

以下の場合はControl + A

<Interactivity:Interaction.Behaviors>
    <Behaviors:KeyTriggerBehavior Key="A" ModifierKey1="Control" OnShortcutDown="KeyTriggerBehavior_OnShortcutDown">
    </Behaviors:KeyTriggerBehavior>
</Interactivity:Interaction.Behaviors>

KeyやModifierKeyもVirtualKeyとしてDependencyPropertyで公開してるのでモデルとバインドすれば簡単にショートカットを変更できますし、変更結果を保存できます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください