[WinRT] ストアアプリで独自にキーコードを取得する方法

Surface の場合、ソフトウェアキーボードが出るのでぽちぽちと打てばいいのですが、PC 環境でストアプリを使う時にはキーボードを使ったほうが便利です。

が、XAML ではキー自体のイベントを拾うのはフォーカスのあるコントロールしか拾えません。KeyDown, KeyUp のイベントが発生しないんですよね。いきおい、テキストボックスがない画面にキーボードを使って入力するにはどうすればいいのか?と調べてみました。デスクトップアプリのように、フォーム自身(ストアアプリだから Page 自身)からキーイベントをフックしたいわけです。

■結論から言えばできない

Windowsストアアプリ: 極意7: アプリは、キーボード、マウス、タッチで機能する必要がある – Build Insider
http://www.buildinsider.net/mobile/winstoretips/07

色々探したのですが、結論から言えば「できません」。なんらかのフォーカスを持つコントロールを張り付けて、それに対してキーボード操作をすることになります。なので、画面にフォーカスを持つコントロールがあれば、それに Focus を当ててやって、そこからキーボードイベントを拾うことになります。

さて、自作の ChuPad は、TextBlock だけで構成されているためにフォーカスを付けることができません。表示部分なり入力部分なりを TextBox にしてもよいのですが、look & feel が悪いし、絵文字なり画像なりを貼り付けようと思うと結構面倒そうです(画像に関しては、RichTextBoxを使う方法もありますが)。
このパッド自体は、タブレットや Windows Phone を想定するので、本来ならばキーボードが無い環境でつかいます。通常のソフトウェアキーボードを出してもよいのですが、大きさがひどいのと、Chu-lang 特有の絵文字の順番にしたいという事情もあるので、キーボード部分を自作したいのです。

さて、どうすればよいのでしょうか?

■フォーカスを持つ自作 Control を作る

Focus メソッドを持てば良いということが分かったので、となれば、Focus メソッドを持つコントロールを自作してしまえばよいのです。どうせ画面にひとつしたフォーカスがないのですから、そのコントロールにあわせればよいでしょう、という発想をしました。
見ると、Focus メソッドを持つのは、Control クラスなので、自作コントロールを作ります。その中で、KeyDown/KeyUp の処理をすればよいわけです。

マウスや指で別の場所をタップする(この場合は、絵文字キーボード部分をタップする)と、フォーカスが外れれてイベントが取れなくなるので、無理やり戻してやります。タブ移動で止まるように IsTabStop = true にするのを忘れないでください。

//強制的にフォーカスを設定する
this.keycode.Focus(FocusState.Keyboard);
this.keycode.LostFocus += (s, e) => this.keycode.Focus(FocusState.Keyboard);
this.keycode.IsTabStop = true;
this.keycode.OnKeyPush += keycode_OnKeyPush;

ちなみに、自作絵文字キーボードからへの打ち込みは、それぞれの TextBlock に名前を付けておいて、コード上で一気に Tapped イベントを設定します。

var lst = new TextBlock[] {
    kLet, kLeft, kRight, kCat, kDog,
    kIf, kThen, kElse, kEqual, kNEqual,
    kFor, kTo, kFun, kTrue, kFalse,
    kList, kHead, kTail, kPrint, kCase,
};
foreach (var it in lst)
    it.Tapped += it_Tapped;

こんな風に配列を使って設定すればコードが短くなりますね。いちいち new しなくよい F# の影響です。

■KeyDown/KeyUp イベントを処理する

キーボードを押し下げ/離したときのイベントから仮想キーコードが取得できます。

VirtualKey Enumeration
http://msdn.microsoft.com/ja-jp/library/windows/apps/windows.system.virtualkey(v=win.10).aspx

この enum をざっと見てわかるんですが、アルファベットしかなくて記号がありません。「@」とか「”」とかは、どうやって判別すればよいのでしょうか?

WPF の場合には、

KeyInterop.KeyFromVirtualKey Method (System.Windows.Input)
http://msdn.microsoft.com/en-us/library/system.windows.input.keyinterop.keyfromvirtualkey(v=vs.110).aspx

を使ってコンバートするのですが、WinRT では使えない。

仕方がないので、シフトキーの押し下げなどを判別して、JISキーボードをエミュレートします(英語向けなので、ASCIIキーボードも用意しないと駄目ですが)。

void makeKeycode()
{
    KCs = new KC[] {
        new KC( VirtualKey.Number1, "1", "!"),
        new KC( VirtualKey.Number2, "2", """),
        new KC( VirtualKey.Number3, "3", "#"),
        new KC( VirtualKey.Number4, "4", "$"),
        new KC( VirtualKey.Number5, "5", "%"),
        new KC( VirtualKey.Number6, "6", "&"),
        new KC( VirtualKey.Number7, "7", "'"),
        new KC( VirtualKey.Number8, "8", "("),
        new KC( VirtualKey.Number9, "9", ")"),
        new KC( VirtualKey.Number0, "0", ""),
        new KC((VirtualKey)189 , "-", "="),
        new KC((VirtualKey)222, "^", "~"),
        new KC((VirtualKey)220, "", "|"),

...略

        // 特殊キー
        new KC( VirtualKey.Space, " ", " "),
        new KC( VirtualKey.Enter, "ENTER", "ENTER"),
        new KC( VirtualKey.Back, "BS", "BS"),
        new KC( VirtualKey.Insert, "INS", "INS"),
        new KC( VirtualKey.Delete, "DEL", "DEL"),
        new KC( VirtualKey.Home, "HOME", "HOME"),
        new KC( VirtualKey.End, "END", "END"),
        new KC( VirtualKey.PageUp, "PAGEUP", "PAGEUP"),
        new KC( VirtualKey.PageDown, "PAGEDOWN", "PAGEDOWN"),
        new KC( VirtualKey.Up, "UP", "UP"),
        new KC( VirtualKey.Down, "DOWN", "DOWN"),
        new KC( VirtualKey.Left, "LEFT", "LEFT"),
        new KC( VirtualKey.Right, "RIGHT", "RIGHT"),

        // 数字キーパッド
        new KC( VirtualKey.NumberKeyLock, "NUMLOCK", "NUMLOCK"),
        new KC( VirtualKey.Divide, "/", "/"),
        new KC( VirtualKey.Multiply, "*", "*"),
        new KC( VirtualKey.Subtract, "-", "-"),
        new KC( VirtualKey.Add, "+", "+"),
        new KC( VirtualKey.NumberPad0, "0", "0"),
        new KC( VirtualKey.NumberPad1, "1", "1"),
        new KC( VirtualKey.NumberPad2, "2", "2"),
        new KC( VirtualKey.NumberPad3, "3", "3"),
        new KC( VirtualKey.NumberPad4, "4", "4"),
        new KC( VirtualKey.NumberPad5, "5", "5"),
        new KC( VirtualKey.NumberPad6, "6", "6"),
        new KC( VirtualKey.NumberPad7, "7", "7"),
        new KC( VirtualKey.NumberPad8, "8", "8"),
        new KC( VirtualKey.NumberPad8, "9", "9"),
    };
}

こんな風にちまちまとテーブルを使って変換表を作っておいて、LINQ 使って検索します。
KeyDown/KeyUp 時にあれこれとやっているのは、要らないキー処理を捨てているところです。実際試してみると、キーリピートが必要そうなので、後で修正しておきます。

protected override void OnKeyDown(KeyRoutedEventArgs e)
{
    // base.OnKeyDown(e);
    OnKey(e);
}
protected override void OnKeyUp(KeyRoutedEventArgs e)
{
    // base.OnKeyUp(e);
    OnKey(e);
}
private void OnKey(KeyRoutedEventArgs e)
{
    // if (e.KeyStatus.WasKeyDown == true) return;

    if (e.Key == Windows.System.VirtualKey.Shift)
    {
        isShift = !e.KeyStatus.IsKeyReleased;
        return;
    }
    if (e.Key == Windows.System.VirtualKey.Menu)
    {
        isAlt = !e.KeyStatus.IsKeyReleased;
        return;
    }
    if (e.Key == Windows.System.VirtualKey.Control)
    {
        isCtrl = !e.KeyStatus.IsKeyReleased;
        return;
    }
    if (e.KeyStatus.WasKeyDown == true) return;
    if (e.KeyStatus.ScanCode == 0) return;  // Enter外し

    Debug.WriteLine("key {0} {1} {2} {3} {4}",
        e.Key,
        isShift, isCtrl, isAlt,
        e.KeyStatus.ScanCode
        );

    try
    {
        var key = KCs.First(kc => kc.Key == e.Key);
        if (OnKeyPush != null)
        {
            string ch = "";
            if ( key.Normal.Length == 1 ) {
                ch = isShift == false ? key.Normal : key.Shift;
            } else {
                ch = key.Normal;
            }
            OnKeyPush(ch, isShift, isCtrl, isAlt);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Error: keycode error {0} {1}", e.Key, ex.Message );
    }
}

■サンプルコード

http://github.com/moonmile/ChuLang/tree/master/ChuPad

カテゴリー: C#, WinRT パーマリンク