VB のたまご

作成日: 2017/04/06, 更新日: 2017/05/14



IME制御

  •  ここでは IME 制御について学習します。 主に TextBox 等の入力欄コントロールで設定する、入力形式(ひらがな、英字、数字、カタカナ)の制御です。 この制御がされていると、例えば誕生日を入力する時に、フォーカスが当たった瞬間からすぐに数字を入力できますし、 住所や名前のフリガナとしてカタカナ入力する際も、すぐに入力開始することができ、ユーザーにストレスなく入力させることができます。 いちいち半角全角キーを押して切り替えるのは面倒ですからね。

  •  それではサンプルを見てみましょう。以下は .NET 標準の WPF アプリケーションで作成しています。 私の環境では、IME は Microsoft IME 2012 のみで確認しています。そのため、他のバージョンや ATOK など他の IME では未確認です。

  • <Window x:Class="Window1"
            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:w02_IMEs"
            mc:Ignorable="d"
            Title="Window1" Height="350" Width="525">
    
        <!-- 起動時、初期フォーカスを textbox1 コントロールに当てた状態にする -->
        <StackPanel Margin="10" FocusManager.FocusedElement="{Binding ElementName=textbox1}">
    
            <!--
            https://code.msdn.microsoft.com/windowsdesktop/XAML-WPF-IME-Windows-WPF-9ef457d7
            -->
    
            <TextBlock
                TextWrapping="WrapWithOverflow"
                Margin="0,0,0,20"
                Text="※初期状態のみの設定、半角/全角キーを押されると変えられてしまう。コピペ未対応。入力済み文字列を選択して再変換未対応(aaa を選択して全角のaaaにするなど)" />
    
            <!-- IME 切り替えを禁止する、IME 切り替えは不可能 -->
            <TextBox
                Text="IME 切り替えを禁止する、IME 切り替えは不可能"
                Name="textbox1" 
                InputMethod.IsInputMethodEnabled="False" />
    
            <!-- IME の状態を変えない、IME 切り替えは可能 -->
            <TextBox
                Text="IME の状態を変えない、IME 切り替えは可能"
                InputMethod.PreferredImeState="DoNotCare" />
    
            <!-- IME を有効にする、IME 切り替えは可能 -->
            <TextBox
                Text="IME を有効にする、IME 切り替えは可能"
                InputMethod.PreferredImeState="On" />
    
            <!-- IME を無効にする、IME 切り替えは可能 -->
            <TextBox
                Text="IME を無効にする、IME 切り替えは可能"
                InputMethod.PreferredImeState="Off" />
    
            <!-- IME を抑止する、IME 切り替えは不可能 -->
            <TextBox 
                Text="IME を抑止する、IME 切り替えは不可能"
                InputMethod.IsInputMethodSuspended="True"/>
    
    
    
            <!-- 分断線 -->
            <TextBlock Margin="0,0,0,10" />
    
    
    
            <!-- 全角ひらがな、IME 切り替えは可能 -->
            <TextBox
                Text="全角ひらがな、IME 切り替えは可能"
                InputMethod.PreferredImeState="On"
                InputMethod.PreferredImeConversionMode="Native, FullShape" />
    
            <!-- 全角カタカナ、IME 切り替えは可能 -->
            <TextBox
                Text="全角カタカナ、IME 切り替えは可能"
                InputMethod.PreferredImeState="On"
                InputMethod.PreferredImeConversionMode="Katakana, FullShape" />
    
            <!-- 半角カタカナ、IME 切り替えは可能 -->
            <TextBox
                Text="半角カタカナ、IME 切り替えは可能"
                InputMethod.PreferredImeState="On"
                InputMethod.PreferredImeConversionMode="Native" />
    
            <!-- 全角英字、IME 切り替えは可能 -->
            <TextBox
                Text="全角英字、IME 切り替えは可能"
                InputMethod.PreferredImeState="On"
                InputMethod.PreferredImeConversionMode="Alphanumeric, FullShape" />
    
            <!-- 半角英字、IME 切り替えは可能 -->
            <TextBox
                Text="半角英字、IME 切り替えは可能"
                InputMethod.PreferredImeState="On"
                InputMethod.PreferredImeConversionMode="Alphanumeric" />
    
        </StackPanel>
    
    </Window>
    

  •  WPF で初期フォーカスを当てた状態で起動したい場合は、FocusManager クラスの FocusedElement プロパティに、初期フォーカスを当てたいコントロール名を指定します。 ここでは「textbox1」という名前の TextBox を指定しています。

  •  IME は、InputMethod クラスのメンバーを使って制御します。ソースに書いてある通りなので1つ1つは説明は割愛します。 それぞれのメンバーとセットする値によって、その TextBox の入力がどうなるのか、ひらがな、カタカナ、英数字、半角全角切り替え、などおこない確認してみてください。

  •  下半分が、それぞれの入力モードに設定しています。これにより、半角全角キーを押して切り替えなくても最初から入力できますね。 ただこのままだと、半角全角キーを押されてしまうと他の形式が入力できてしまいます。

  •  個人的には、ユーザーに自由に入力してもらって、確定ボタンを押したときの入力チェックで、NGをひっかければいいかなと思ってしまいますが、 入力時に鉄壁対策を行うにはどうすればいいのか調べました。 以降は、以下のリンク先の記事を参考に解説していますので、まずは以下のリンク先を読まれることを推奨します。

  • 備忘録
    WPFで数字のみ入力可能なTextBox
    http://someprog.blog.fc2.com/blog-entry-14.html
    
    泥庭
    IMEで変換状態中でもTextBox.TextChangedが発生する
    https://yone64.wordpress.com/2010/10/25/ime%E3%81%A7%E5%A4%89%E6%8F%9B%E7%8A%B6%E6%85%8B%E4%B8%AD%E3%81%A7%E3%82%82textbox-textchanged%E3%81%8C%E7%99%BA%E7%94%9F%E3%81%99%E3%82%8B/#postcomment
    

  •  ということで、リンク先に書いてある通り、まずは TextBox.TextChanged イベント、TextBox.PreviewTextInput イベント。 そして、TextCompositionManager クラスから、TextCompositionManager.PreviewTextInputStart イベント、 TextCompositionManager.PreviewTextInputUpdate イベント、TextCompositionManager.PreviewTextInput イベント、 がどういうタイミングで、どの順番で発生するのか見てみましょう。

  • <Window x:Class="Window2"
            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:w02_IMEs"
            mc:Ignorable="d"
            Title="Window2" Height="350" Width="525">
    
        <StackPanel FocusManager.FocusedElement="{Binding ElementName=textbox1}">
    
            <TextBox
                Name="textbox1"
                Text=""
                TextChanged="textbox1_TextChanged"
                PreviewTextInput="textbox1_PreviewTextInput"
                TextCompositionManager.PreviewTextInputStart="TextCompositionManager_PreviewTextInputStart"
                TextCompositionManager.PreviewTextInputUpdate="TextCompositionManager_PreviewTextInputUpdate"
                TextCompositionManager.PreviewTextInput="TextCompositionManager_PreviewTextInput" />
    
        </StackPanel>
        
    </Window>
    

    Public Class Window2
    
        ' IMEで変換状態中でもTextBox.TextChangedが発生する
        ' https://yone64.wordpress.com/2010/10/25/ime%E3%81%A7%E5%A4%89%E6%8F%9B%E7%8A%B6%E6%85%8B%E4%B8%AD%E3%81%A7%E3%82%82textbox-textchanged%E3%81%8C%E7%99%BA%E7%94%9F%E3%81%99%E3%82%8B/#postcomment
    
        Private Sub textbox1_TextChanged(sender As Object, e As TextChangedEventArgs)
            Console.WriteLine($"textbox1_TextChanged / {e.Source}")
        End Sub
    
        Private Sub textbox1_PreviewTextInput(sender As Object, e As TextCompositionEventArgs)
            Console.WriteLine($"textbox1_PreviewTextInput / {e.Text}")
        End Sub
    
        Private Sub TextCompositionManager_PreviewTextInputStart(sender As Object, e As TextCompositionEventArgs)
            Console.WriteLine($"TextCompositionManager_PreviewTextInputStart / {e.Text}")
        End Sub
    
        Private Sub TextCompositionManager_PreviewTextInputUpdate(sender As Object, e As TextCompositionEventArgs)
            Console.WriteLine($"TextCompositionManager_PreviewTextInputUpdate / {e.Text}")
        End Sub
    
        Private Sub TextCompositionManager_PreviewTextInput(sender As Object, e As TextCompositionEventArgs)
            Console.WriteLine($"TextCompositionManager_PreviewTextInput / {e.Text}")
        End Sub
    
    End Class
    

  •  リンク先と同じ入力手順で入力すると、同じ結果となりますね。 これらの特徴を踏まえて、以下のサンプルでは、ひらがなのみ(あ~ん)しか入力できない制御をおこなっています。

  • <Window x:Class="Window3"
            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:w02_IMEs"
            mc:Ignorable="d"
            Title="Window3" Height="350" Width="525">
    
        <StackPanel FocusManager.FocusedElement="{Binding ElementName=textbox1}">
    
            <TextBlock Text="ひらがなのみ(あ-ん)入力できる制御" Margin="0,0,0,20" />
    
            <TextBox
                Name="textbox1"
                Text=""
                TextChanged="textbox1_TextChanged"
                DataObject.Pasting="DataObject_Pasting"
                TextCompositionManager.PreviewTextInputStart="TextCompositionManager_PreviewTextInputStart"
                TextCompositionManager.PreviewTextInputUpdate="TextCompositionManager_PreviewTextInputUpdate"
                TextCompositionManager.PreviewTextInput="TextCompositionManager_PreviewTextInput" />
    
        </StackPanel>
        
    </Window>
    

    Imports System.Text.RegularExpressions
    
    Public Class Window3
    
        ' 備忘録
        ' WPFで数字のみ入力可能なTextBox
        ' http://someprog.blog.fc2.com/blog-entry-14.html
    
        ' 泥庭
        ' IMEで変換状態中でもTextBox.TextChangedが発生する
        ' https://yone64.wordpress.com/2010/10/25/ime%E3%81%A7%E5%A4%89%E6%8F%9B%E7%8A%B6%E6%85%8B%E4%B8%AD%E3%81%A7%E3%82%82textbox-textchanged%E3%81%8C%E7%99%BA%E7%94%9F%E3%81%99%E3%82%8B/#postcomment
    
    
    
        Private Const pattern As String = "[^あ-ん]+"
        Private reg As Regex = Nothing
        Private imeFlag As Boolean = False
    
        Private Sub DataObject_Pasting(sender As Object, e As DataObjectPastingEventArgs)
    
            ' 貼り付け時に指定文字以外の入力を拒否
            Dim isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, True)
            If Not isText Then
                Return
            End If
    
            If reg Is Nothing Then
                reg = New Regex(pattern)
            End If
    
            Dim text = TryCast(e.SourceDataObject.GetData(DataFormats.UnicodeText), String)
            If reg.IsMatch(text) Then
    
                e.CancelCommand()
                e.Handled = True
    
            End If
    
        End Sub
    
        Private Sub textbox1_TextChanged(sender As Object, e As TextChangedEventArgs)
    
            Console.WriteLine($"    {Me.textbox1.Text}")
    
            If Me.imeFlag Then
                Return
            End If
    
            Console.WriteLine(Me.textbox1.Text)
    
        End Sub
    
        Private Sub TextCompositionManager_PreviewTextInputStart(sender As Object, e As TextCompositionEventArgs)
    
            Me.imeFlag = True
    
        End Sub
    
        Private Sub TextCompositionManager_PreviewTextInputUpdate(sender As Object, e As TextCompositionEventArgs)
    
            If 0 < e.TextComposition.CompositionText.Length Then
                Return
            End If
    
            Me.imeFlag = False
            If Not Me.IsValidInputData(e.Text) Then
                e.Handled = True
                ' IME 変換後に確定させた値がNG判定の場合、e.Handled = True をしているのに、
                ' TextBox.Text に入ってしまう不具合の対応
                Me.textbox1.Text = Me.textbox1.Text.Replace(e.Text, String.Empty)
                Me.textbox1.SelectionStart = Me.textbox1.Text.Length
            End If
    
        End Sub
    
        Private Sub TextCompositionManager_PreviewTextInput(sender As Object, e As TextCompositionEventArgs)
    
            Me.imeFlag = False
            If Not Me.IsValidInputData(e.Text) Then
                e.Handled = True
                ' IME 変換後に確定させた値がNG判定の場合、e.Handled = True をしているのに、
                ' TextBox.Text に入ってしまう不具合の対応
                Me.textbox1.Text = Me.textbox1.Text.Replace(e.Text, String.Empty)
                Me.textbox1.SelectionStart = Me.textbox1.Text.Length
            End If
    
        End Sub
    
        Private Function IsValidInputData(target As String) As Boolean
    
            If reg Is Nothing Then
                reg = New Regex(pattern)
            End If
    
            ' 【あ-ん】以外の文字があればNG
            If reg.IsMatch(target) Then
                Return False
            Else
                Return True
            End If
    
        End Function
    
    End Class
    

  •  まずは、コピペ対策として、DataObject.Pasting イベントを購読して、入力文字が NG パターンであれば貼り付けをキャンセルするように制御します。 続いて、TextBox.TextChanged イベントを購読して、入力チェックをパスした文字列を出力します。

  •  TextChanged イベントは未確定な入力も拾ってしまうため、確定後の入力のみ出力されるように、 TextCompositionManager.PreviewTextInputStart イベントと TextCompositionManager.PreviewTextInputUpdate イベントと、 TextCompositionManager.PreviewTextInput イベントを購読して、入力処理に割り込んで、入力縛りをしましょう。

  •  と言っても、フラグ処理そのままです。ただし、フラグが確定に切り替わったタイミングで、入力チェック処理を通すように追加しています。 PreviewTextInputUpdate イベントハンドラと PreviewTextInput イベントハンドラの個所で呼んでる IsValidInputData メソッドですね。

  •  これを実行して動作確認してみましょう。これで良い感じの機能を持たせることができます。 と言いたかったのですが、この制御では抜け道バグが含まれています。 例えば「あいう」と入力して変換して「アイウ」と選択して(確定はせず、下線がある状態のまま)、「あいう」と続けて入力すると、「アイウ」の入力を許してしまいますorz。 今のところ、対策が見つけられずですorz。

  •  それでは最後に、この処理をビヘイビアにまとめてビヘイビアとして利用しましょう。 以下では、ひらがなのみ作成していますが、GitHub にアップしたサンプルでは、半角英字、全角英字、半角数字、全角数字、半角カタカナ、全角カタカナ、ひらがな、の7つ用意していますので、 興味があればご覧ください。

  • GitHub
    https://github.com/sutefu7/VbWpfSamples.Other
    w02_IMEs
    

  •  参照設定の追加で「System.Windows.Interactivity.dll」と「Microsoft.Expression.Interactions.dll」を追加して、Window タグの名前空間に追加しています。

  • Imports System.Text.RegularExpressions
    Imports System.Windows.Interactivity
    
    ' バグあり
    ' 1.ひらがな「あいう」を入力、「アイウ」に変換(変換後のまま確定はしていない状態。文字の下に下線が引いている状態)
    ' 2.他のひらがな「あいう」を入力、そのまま確定する(下線が無い状態)。1.の「アイウ」が入力できてしまう
    
    Public Class WideHiraganaTextBoxBehavior
        Inherits Behavior(Of TextBox)
    
        Private Const pattern As String = "[^あ-ん]+"
        Private reg As Regex = Nothing
    
        Protected Overrides Sub OnAttached()
            MyBase.OnAttached()
    
            ' 入力時のイベント制御
            TextCompositionManager.AddPreviewTextInputHandler(Me.AssociatedObject, AddressOf Me.TextCompositionManager_PreviewTextInput)
            TextCompositionManager.AddPreviewTextInputUpdateHandler(Me.AssociatedObject, AddressOf Me.TextCompositionManager_PreviewTextInputUpdate)
    
            ' コピペ貼り付け時のイベント制御
            DataObject.AddPastingHandler(Me.AssociatedObject, AddressOf Me.DataObject_Pasting)
    
            ' IME 機能を有効にして、ひらがな(Native, FullShape)に設定
            InputMethod.SetPreferredImeState(Me.AssociatedObject, InputMethodState.On)
            InputMethod.SetPreferredImeConversionMode(Me.AssociatedObject, ImeConversionModeValues.Native Or ImeConversionModeValues.FullShape)
    
            ' 破棄処理を追加
            Dim w = Window.GetWindow(Me.AssociatedObject)
            If w IsNot Nothing Then
                AddHandler w.Closed, AddressOf Me.Window_Closed
            End If
    
            reg = New Regex(pattern)
    
        End Sub
    
        ' なぜか通らない。呼ばれることが無い?
        Protected Overrides Sub OnDetaching()
            MyBase.OnDetaching()
    
            ' 入力時のイベント制御
            TextCompositionManager.RemovePreviewTextInputHandler(Me.AssociatedObject, AddressOf Me.TextCompositionManager_PreviewTextInput)
            TextCompositionManager.RemovePreviewTextInputUpdateHandler(Me.AssociatedObject, AddressOf Me.TextCompositionManager_PreviewTextInputUpdate)
    
            ' コピペ貼り付け時のイベント制御
            DataObject.RemovePastingHandler(Me.AssociatedObject, AddressOf Me.DataObject_Pasting)
    
            ' IME を有効
            InputMethod.SetIsInputMethodEnabled(Me.AssociatedObject, True)
    
        End Sub
    
        ' OnDetaching メソッドを無理やり呼び出す
        Private Sub Window_Closed(sender As Object, e As EventArgs)
    
            Dim w = TryCast(sender, Window)
            If w IsNot Nothing Then
    
                RemoveHandler w.Closed, AddressOf Me.Window_Closed
                Me.OnDetaching()
    
            End If
    
        End Sub
    
        ' 入力文字が、入力欄にセットされる時に発生するイベント
        ' 全角文字の場合は、確定された後の入力文字
        Private Sub TextCompositionManager_PreviewTextInput(sender As Object, e As TextCompositionEventArgs)
    
            Me.InputCheck(e)
    
        End Sub
    
        ' 全角文字が入力された時{まだ未確定。文字の下に下線(.... や ___)がある状態}に発生するイベント
        Private Sub TextCompositionManager_PreviewTextInputUpdate(sender As Object, e As TextCompositionEventArgs)
    
            If 0 < e.TextComposition.CompositionText.Length Then
                Return
            End If
    
            Me.InputCheck(e)
    
        End Sub
    
        Private Sub InputCheck(e As TextCompositionEventArgs)
    
            ' 【あ-ん】以外の文字があればNG
            If reg.IsMatch(e.Text) Then
    
                e.Handled = True
    
                ' IME 変換後に確定させた値がNG判定の場合、e.Handled = True をしているのに、
                ' TextBox.Text に入ってしまう不具合の対応
                Me.AssociatedObject.Text = Me.AssociatedObject.Text.Replace(e.Text, String.Empty)
                Me.AssociatedObject.SelectionStart = Me.AssociatedObject.Text.Length
    
            End If
    
        End Sub
    
        ' コピペ貼り付け時
        Private Sub DataObject_Pasting(sender As Object, e As DataObjectPastingEventArgs)
    
            ' 貼り付け時に指定文字以外の入力を拒否
            Dim isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, True)
            If Not isText Then
                Return
            End If
    
            Dim text = TryCast(e.SourceDataObject.GetData(DataFormats.UnicodeText), String)
            If reg.IsMatch(text) Then
    
                e.CancelCommand()
                e.Handled = True
    
            End If
    
        End Sub
    
    End Class
    

    <Window x:Class="Window4"
            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:ei="http://schemas.microsoft.com/expression/2010/interactions"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:local="clr-namespace:w02_IMEs"
            mc:Ignorable="d"
            Title="Window4" Height="350" Width="525">
    
        <StackPanel FocusManager.FocusedElement="{Binding ElementName=textbox1}">
    
            <!-- 全角ひらがなのみ(あ-ん) -->
            <TextBox Text="あいう">
                <i:Interaction.Behaviors>
                    <local:WideHiraganaTextBoxBehavior />
                </i:Interaction.Behaviors>
            </TextBox>
    
        </StackPanel>
        
    </Window>