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 にするのを忘れないでください。
1 2 3 4 5 | //強制的にフォーカスを設定する 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 イベントを設定します。
1 2 3 4 5 6 7 8 | 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キーボードも用意しないと駄目ですが)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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 時にあれこれとやっているのは、要らないキー処理を捨てているところです。実際試してみると、キーリピートが必要そうなので、後で修正しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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 ); } } |