【備忘録】複数行テキストボックスで改行とIME確定を見分ける


要件は以下の通り。

  1. 複数行入力可能なTextBoxで、
  2. 日本語入力と英語入力が混ざった状態で、
  3. IME入力の確定には反応せず、
  4. 改行を表すEnterキーが押されたことを検知する

(Visual Studio 2019・.NET Framework4.7.2)

要件1

作るだけ。

MainWindow.xaml

<Window x:Class="sample.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:sample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBox x:Name="Txtbox" Margin="20,20,20,20" Padding="5,5,5,5" Text=""
                 AcceptsReturn="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto"/>
    </Grid>
</Window>

できた。

要件2~4

確認のため、とりあえず普通に、KeyDownを検知してみる。

MainWindow.xaml

    <Grid>
        <TextBox x:Name="Txtbox" Margin="20,20,20,20" Padding="5,5,5,5" Text=""
                 AcceptsReturn="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto"
                 KeyDown="Txtbox_KeyDown"/>
    </Grid>

MainWindow.xaml.cs

private void Txtbox_KeyDown(object sender, KeyEventArgs e)
{
    Console.WriteLine("{0}が押されました!", e.Key.ToString());
}

これでよし。

なんでやねん。

英語入力しか検知できないようです。

作戦変更

Enterキーが物理的に押されているかどうか確認する。CtrlキーやShiftキーが押されていた場合の動作は別途用意すれば良い。

MainWindow.xaml.cs

private void Txtbox_KeyDown(object sender, KeyEventArgs e)
{
    if (Keyboard.IsKeyDown(Key.Return))
    {
        Console.WriteLine("Enterキーが押されました!");
    }
}

これでよし。

なんでやねん。

先ほどの結果と合わせて考えると、IME確定も改行もKeyDownイベントとして認識されていないっぽい。

さらに作戦変更

検知するイベントを変えます。

MainWindow.xaml

    <Grid>
        <TextBox x:Name="Txtbox" Margin="20,20,20,20" Padding="5,5,5,5" Text=""
                 AcceptsReturn="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto"
                 PreviewKeyDown="Txtbox_PreviewKeyDown"/>
    </Grid>

MainWindow.xaml.cs

private void Txtbox_PreviewKeyDown(object sender, KeyEventArgs e)
{
    Console.WriteLine("{0}が押されました!", e.Key.ToString());
}

いけそう。

MainWindow.xaml.cs

private void Txtbox_PreviewKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key.ToString() == "Return")
    {
        Console.WriteLine("改行されました!");
        // ここ、正しくは、「改行されそう!」ですが、そんなことは気にしない。
    }
}

完璧です。

結局

PreviewKeyDownイベントで、e.Key.ToString() == "Return"を判定してやればよい。

ちなみになぜこれを備忘録として残す事になったかというと、ネットで調べるとTextChangedイベントで、テキストボックスの行数が変わったら改行するというコードが先に出てきて、謎実装しかけるはめになったから。
ちなみに、TextChangedイベントを使うと、最後のEnterが改行として使われてからイベントが発火することになるため、それを許さない要件の場合そもそも使えない。(つまり、「あいうえお」の「い」と「う」の間にカーソルがある状態で、イベントを発火させようとしてEnterすると、先に改行が入るため、「あい\r\nうえお」を処理する羽目になる。)
その点、PreviewKeyDownイベントを使えば、e.Handled = true;としてやることで、キーイベントの処理を明示的に終了できるので、その心配も無い。

さらにちなみに、KeyEventArgs.ImeProcessedKeyというのもあるみたいだが、これは英語圏で使われる文字を打ったときは全部Noneを返してくるので、今回のように改行を検知したいなら、Keyboard.IsKeyDown(Key.Return) && e.ImeProcessedKey.ToString() == "None"とすれば使えないこともない。
そうではなく、IME確定を検知したいなら、e.ImeProcessedKey.ToString() == "Return"となるので、強い味方となりそうだ。