やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

(WPF)タッチキーボードを表示する -その2-

先日、WPF でタッチキーボードを表示する方法について書いたのですが、実際にアプリに組み込む場合はこの方法だけでは不足していたので、もうちょい実用的なコードとしてまとめてみます。

iyemon018.hatenablog.com

動作環境

前回と同じ

問題点

  1. タッチキーボードを表示してフォーカスが離れてもタッチキーボードが表示されたままになる。
  2. TextBox がフォーカスを取得した際に何らかの原因でタッチキーボードが即非表示になる。
    →これについては明確な原因はわかっていません。2 in 1 タブレット環境のみ発生する(?)ようです。

対策

問題点1. については、TextBox のLostFocus 時にTabTip.exe プロセスへSendMessage すればOK です。
問題点2. は、原因がわかっていないのでベストプラクティスではない可能性がありますが、フォーカス取得時に起動中のTabTip.exe プロセスをKill することで回避可能です。

具体的なコード

今回は既存のプログラムにも追加が簡単にできるようにBehavior で作ってみました。
なお、Behavior は、System.Windows.Interactivity.dll の参照を追加する必要があります。

<TouchKeyboardBehavior.cs>

namespace KeyboardTest
{
    using System;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;

    public class TouchKeyboardBehavior : Behavior<Control>
    {
        private const int WindowMessageSystemCommand = 274;

        private const int SendCommandClose = 61536;
        
        private static IntPtr GetTabTipWindowHandle() => NativeMethods.FindWindow("IPTip_Main_Window", null);

        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.GotFocus += AssociatedObject_OnGotFocus;
            AssociatedObject.LostFocus += AssociatedObject_OnLostFocus;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            AssociatedObject.GotFocus  -= AssociatedObject_OnGotFocus;
            AssociatedObject.LostFocus -= AssociatedObject_OnLostFocus;
        }
        
        private void AssociatedObject_OnGotFocus(object sender, RoutedEventArgs e)
        {
            // ここで問題点2. を解決
            Process[] processes = Process.GetProcessesByName("TabTip");
            if (processes.Length > 0)
            {
                processes[0].Kill();
            }

            new Process {StartInfo = {FileName = @"C:\Program Files\Common Files\microsoft shared\ink\TabTip.exe"}}.Start();
        }
        
        private void AssociatedObject_OnLostFocus(object sender, RoutedEventArgs e)
        {
            // ここで問題点1. を解決
            NativeMethods.SendMessage(GetTabTipWindowHandle().ToInt32(), WindowMessageSystemCommand, SendCommandClose, 0);
        }
    }
}

<NativeMethods.cs>

namespace KeyboardTest
{
    using System;
    using System.Runtime.InteropServices;

    public static class NativeMethods
    {
        [DllImport("user32.dll")]
        public static extern IntPtr FindWindow(String className, String appName);

        [DllImport("user32.dll")]
        public static extern int SendMessage(int hWnd, uint msg, int wParam, int lParam);
    }
}

使用する際はこんな感じ。
<MainWindow.xaml>

<Window x:Class="KeyboardTest.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
      xmlns:local="clr-namespace:KeyboardTest"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      Title="MainWindow"
      d:DesignHeight="450"
      d:DesignWidth="800"
      mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBox Grid.Row="0"
                 InputScope="Number">
            <i:Interaction.Behaviors>
                <local:TouchKeyboardBehavior />
            </i:Interaction.Behaviors>
        </TextBox>

        <PasswordBox Grid.Row="1">
            <i:Interaction.Behaviors>
                <local:TouchKeyboardBehavior />
            </i:Interaction.Behaviors>
        </PasswordBox>
    </Grid>
</Window>

ちなみにタッチキーボードの入力切替はInputScope で変更することができます。
ただし、TextBox のみ有効で、PasswordBox はInputScope を設定しても反映されませんのでご注意を。(セキュリティ上の仕様なんでしょうか?)

https://msdn.microsoft.com/ja-jp/library/windows/apps/mt280229.aspx