画面ベースの入力

.NET Framework によるスクリーン キーボードのカスタマイズ

Christopher Frenz

コード サンプルのダウンロード

近年、画面ベースの入力を必要とするアプリケーションの開発ニーズが、徐々に高まってきています。開発者は、これまで、コンピューター キーボードを使用できないユーザー (特にキオスク アプリケーションなど) がデータ入力や選択を行えるよう、画面上でのデータ入力を行えるようにしてきました。

タブレット デバイスやモバイル コンピューティング デバイスが急速に成長することにより、画面上での入力はさらに普及し、かつてないほど便利なものになっています。今では、近所のカフェや公共交通機関でも携帯電話やタブレット デバイスの最新 "アプリ" に夢中になっている人を見受けることができます。成長を続けるモバイル コンピューティング市場に対する期待を考えれば、どのような開発者でも、画面上での入力方式を理解することで大きなメリットを得ることは想像に難くありません。

また、画面ベースの入力デバイスは、ハードウェアベースのキーロガーによってユーザー情報が盗まれるのを防ぐのに役立つため、セキュリティが不可欠なアプリケーションに使用されることも多くなっています。たとえば TradeKing などのオンライン証券会社はセキュリティを強化にするため、すべてのパスワードをスクリーン キーボードを使用して入力するよう利用者に義務付けています。しかし、セキュリティの向上に役立つとは言え、スクリーン キーボードにも考慮すべき潜在的なリスクがあります。スクリーン キーボードは、ハードウェアベースのあらゆるキーロガーに対するリスクは軽減できますが、ソフトウェアベースで入力を記録する手法や、その手法よりもはるかに多用される、"ショルダー サーフィン" という技術を用いてユーザーが押した入力ボタンを表示させる手法の被害を受けやすい可能性があります。たとえばあるアプリケーションでは、iPad に入力しているユーザーを映したビデオを使用して、iPad キーボードの画像を使ってユーザーが押したキーの場所を調べることで、入力されたキーを特定できます (onforb.es/oobLp2 (英語) 参照)。

ショルダー サーフィンの有効性を弱める方法はいくつかあります。最も一般的な方法の 1 つは、キャプチャされたキー操作位置の座標がら特定のキーを判断されないように、キーの配置を定期的にランダムにする方法です。今回は、Microsoft .NET Framework を使用して、キーの配置をランダムにするスクリーン キーボードを作成します。このアプリケーションでは、画面入力を必要とする特定のアプリケーションにスクリーン キーボードをリンクできるようにします。ただし、サンプル アプリケーションは、スクリーン キーボードをビルドするのに必要な技法を紹介するために設計したものであり、十分な機能を備えたキーボード アプリケーションを提供するものではありません。

スクリーン キーボードの GUI

まず、各アルファベット文字と各数字の "キー" を含む GUI をレイアウトします (図 1 参照)。ただし、コントロールをフォームにドラッグする前に、考慮すべき問題がいくつかあります。ほとんどの .NET アプリケーションでは (およびその他のアプリケーションでも)、ユーザーがクリックすることで入力を送信する場合の標準コントロールはボタンです。しかし、ボタン コントロールはクリックされると自動的に Windows OS のフォーカスを得るため、スクリーン キーボードには使用できません。キーボード入力は、アクティブなフォアグラウンド アプリケーション (つまり、フォーカスのあるアプリケーション) に送るつもりであるため、スクリーン キーボードが OS のフォーカスを取得しないようにする必要があります。ラベル コントロールはボタン コントロールと同じように Click イベントをサポートしますが、ボタン コントロールとは違ってフォーカスを取得しないため、キーには、ボタン コントロールではなくラベル コントロールを使用します。つまり、ラベル コントロールは、(この後紹介する少量のコードを追加すれば) アプリケーションのフォーカスに影響を与えることなくクリック操作に反応するという理想的な動作を行います。サンプル アプリケーションでは、これらのラベルに Label1 ~ Label36 の名前を付けます。ユーザーがスクリーン キーボードに入力した内容を外部アプリケーションの表示と比較するために、テキストボックス (TextBox1) も作成します (図 1 参照)。

The Design View of the On-Screen Keyboard GUI
図 1 スクリーン キーボード GUI のデザイン ビュー

フォーカスを取得しない

ラベルなどのコントロールは、アプリケーションが OS のフォーカスを受け取らないようにするために使用します。しかし、スクリーン キーボード アプリケーションは、フォーム自体が読み込まれるときやフォームまたはフォーム上のコントロールをマウスでクリックするときにもフォーカスを受け取る場合があるため、ラベル コントロールを使用するだけでは不十分です。この状況を改めるには、スクリーン キーボード アプリケーションにいくつかコードを追加する必要があります。

まず、フォームが最初に読み込まれたときにフォーカスを取得しないよう、アプリケーションに次のコードを追加します (ここでは、Visual Basic .NET を使用しています)。

Private Const WS_EX_NOACTIVATE As Integer = &H8000000
Protected Overrides ReadOnly Property CreateParams() As CreateParams
  Get
    CreateParams = MyBase.CreateParams
    CreateParams.ExStyle = CreateParams.ExStyle And WS_EX_NOACTIVATE
    Return CreateParams
  End Get
End Property

このコードは、フォーム オブジェクトの作成に使用する、フォームの CreateParams プロパティをオーバーライドします。WS_EX_NOACTIVATE ウィンドウ スタイルを適用してこのプロパティをオーバーライドし、読み込み時にフォームがフォアグラウンドになるのを防ぐことにより、スクリーン キーボードを読み込んでも、起動時にアクティブ状態だったアプリケーションからフォーカスが移動することがなくなります。コードを用意したら次に重要なのは、アプリケーションがマウス クリックによってフォーカスを取得しないようにすることです。これを実現するには次のコードを追加します。

Private Const WM_MOUSEACTIVATE As Integer = &H21
Private Const MA_NOACTIVATE As Integer = &H3
Protected Overrides Sub WndProc(ByRef m As Message)
  If (m.Msg = WM_MOUSEACTIVATE) Then
      m.Result = MA_NOACTIVATE
    Else
      MyBase.WndProc(m)
    End If
End Sub

このコードは、ユーザーからのすべての入力を受け取る、フォームの WndProc 関数をオーバーライドします。オーバーライド関数では、非アクティブ状態のウィンドウをクリックすると送信される WM_MOUSEACTIVATE メッセージをインターセプトし、関数の戻り値を MA_NOACTIVATE に設定することで、スクリーン キーボード アプリケーションがマウス クリックによってフォーカスを取得しないようにします。その他のマウス入力メッセージは、すべてこのコードの Else 条件に移動するため、スクリーン キーボード アプリケーションはフォーカスを取得せずにラベルがクリックされたかどうかを検出することができます。

キーボード機能を追加する

ここまでで、アプリケーションの GUI と、アプリケーションにフォーカスが移動しないようにするコードを用意しました。次は、いよいよ実際のキーボード機能を追加します。この機能では、最初に少しサブルーチン (AssignKeys) を作成し、一意なキーをランダムにラベルに割り当てます (図 2 参照)。

図 2 各ラベルへの一意キーのランダム割り当て

Private Sub AssignKeys()
  Dim Character() As Char =
    {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", _
     "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", _
     "A", "S", "D", "F", "G", "H", "J", "K", "L", "Z", _
     "X", "C", "V", "B", "N", "M"}
  Dim Keys(36) As Char
  Dim I, X As Integer
  Dim Rand As New Random()
  Dim Used(36) As Integer
  Dim Unique As Boolean = False
    Used(0) = -1
    For I = 0 To 35
      Unique = False
      X = Rand.Next(0, 36)
      If Character(X) <> " " Then
        Keys(I) = Character(X)
        Character(X) = " "
      Else
        Do Until Unique = True
          X = Rand.Next(0, 36)
          If Character(X) <> " " Then
            Keys(I) = Character(X)
            Character(X) = " "
            Unique = True
          End If
        Loop
        End If
  Next
    Label1.Text = Keys(0)
    Label2.Text = Keys(1)
    Label3.Text = Keys(2)
    Label4.Text = Keys(3)
    Label5.Text = Keys(4)
    Label6.Text = Keys(5)
    Label7.Text = Keys(6)
    Label8.Text = Keys(7)
    Label9.Text = Keys(8)
    Label10.Text = Keys(9)
    Label11.Text = Keys(10)
    Label12.Text = Keys(11)
    Label13.Text = Keys(12)
    Label14.Text = Keys(13)
    Label15.Text = Keys(14)
    Label16.Text = Keys(15)
    Label17.Text = Keys(16)
    Label18.Text = Keys(17)
    Label19.Text = Keys(18)
    Label20.Text = Keys(19)
    Label21.Text = Keys(20)
    Label22.Text = Keys(21)
    Label23.Text = Keys(22)
    Label24.Text = Keys(23)
    Label25.Text = Keys(24)
    Label26.Text = Keys(25)
    Label27.Text = Keys(26)
    Label28.Text = Keys(27)
    Label29.Text = Keys(28)
    Label30.Text = Keys(29)
    Label31.Text = Keys(30)
    Label32.Text = Keys(31)
    Label33.Text = Keys(32)
    Label34.Text = Keys(33)
    Label35.Text = Keys(34)
    Label36.Text = Keys(35)
  End Sub

図 2 のルーチンでは、キーボード上に表示するよう選択した英数字をすべて含む配列 (Character) を作成後、乱数生成を適用して、その配列内の要素をランダムに選択します。選択した要素をそれ以前に選択していなければ、要素に格納された文字を Keys 配列にコピーします。36 文字すべてを Keys 配列に割り当てるまでこの処理を繰り返し、配列内の各文字の配置をランダムにします。配列をランダムにしたら、割り当てた文字を画面に表示するよう、各ラベル コントロールの Text プロパティに Keys 配列の要素を割り当てます。Form_Load イベントの実行では、最初にこの AssignKeys サブルーチンを呼び出します。

これで、それぞれのキーに文字を割り当てました。次はスクリーン キーボード アプリケーション上でのマウス クリック操作を、対象とするアプリケーションへのキー操作の送信と同等な処理に変換するコードを追加する必要があります。そのためには、ウィンドウ処理やその他のウィンドウ管理機能など多くの UI 関連の関数を処理するのに Windows で使用する、user32.dll API を使用する必要があります。この API 機能を適切に活用するようアプリケーションをセットアップするには、次の DLL Import ステートメントを Form クラスに追加します。

<DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function FindWindow(ByVal lpClassName As String, _
    ByVal lpWindowName As String) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function SetForegroundWindow(ByVal hWnd As IntPtr) As Boolean
End Function

Label_Click イベントなどのイベントはそれぞれ図 3 のコードのようになるため、user32.dll API が Label_Click イベントが発生するたびに使用されます。

図 3 Label_Click イベントに user32.dll API を使用

Private Sub Label1_Click(ByVal sender As System.Object,_
  ByVal e As System.EventArgs) Handles Label1.Click
  Dim X As Char
  X = CChar(Label1.Text)
  Dim theHandle As IntPtr
  theHandle = FindWindow(Nothing, "Untitled - Notepad")
  If theHandle <> IntPtr.Zero Then
    SetForegroundWindow(theHandle)
    SendKeys.Send(X)
  End If
  TextBox1.Text = TextBox1.Text & Label1.Text
  AssignKeys()
End Sub

ラベルのクリック イベントが発生すると、スクリーン キーボードが入力を送信するアプリケーションのアプリケーション ハンドルを変数 "theHandle" に格納します。ここでは、アプリケーション ハンドルに新しく読み込んだメモ帳のコピーを設定しています。これは、メモ帳がすべての Windows システムで広く普及しているためです。アプリケーション ハンドルが現在システムに存在している場合は、そのハンドル (メモ帳) を処理するアプリケーションをフォアグラウンドに移動し、ラベルに割り当てた文字をそのアプリケーションに送信します。メモ帳に表示されている文字がキーボード アプリケーションが受信した文字と同じであることを示すため、キーボード アプリケーションのテキストボックス内に入力されているテキストにこの文字を付加します。最後の手順として、AssignKeys サブルーチンを再度呼び出してキーの位置を再びランダムにすることで、ショルダー サーフィンが困難になるようにします。この手順を図 4図 5 に示します。図 4 は新しく読み込んだバージョンのアプリケーション、図 5 はスクリーン キーボードでいくつかのキーを押した後のスクリーン キーボードとメモ帳を示しています。

A New Instance of the On-Screen Keyboard
図 4 スクリーン キーボードの新しいインスタンス

The On-Screen Keyboard Sending Input to Notepad
図 5: メモ帳に入力を送信しているスクリーン キーボード

セキュリティの強化とモバイルへの移植

今回は、.NET Framework を使用したスクリーン キーボード開発について紹介しました。データ入力の特定の要素に関するセキュリティを向上させたり、.NET アプリケーションをモバイル プラットフォームに移植したりするのに使用するスクリーン キーボードの開発方法について理解する手助けとなればさいわいです。

Christopher M. Frenz は、プログラミングに関する書籍『Visual Basic and Visual Basic .NET for Scientists and Engineers』(Apress、2002 年) と『Pro Perl Parsing』(Apress、2005 年) の著者です。連絡先は、cfrenz@gmail.com (英語のみ) です。

この記事のレビューに協力してくれた技術スタッフの Robert Green に心より感謝いたします。