月別アーカイブ: 2009年09月

5 posts

日本語入力でのエンターキーの処理

日本語入力でないことが分かっている場合、[Enter]キーが押されたら次のコントロールにフォーカスを移すプログラムは前回ご紹介しました。

日本語入力の途中では、これではいけません。
この場合の[キー]は、キーを押したとき発生する[KeyDown]、[KeyPress]、[KeyUp]イベントです。
(日本語入力でない)通常の場合は、[Enter]キーを押したときイベントハンドラーに送られてくる[KeyEventArgs]の[KeyValue]は[Keys.Enter]ですが、日本語入力の「途中では」(KeyUpイベント以外では)別のコードが送られてきます(この時のKeyValueの値が何であったか忘れました)。
日本語入力の途中で[Enter]を押して、漢字やひらがな等日本語を確認した後は、上でいう「通常の状態」になり、再度[Enter]を押すと[KeyDown]、[KeyPress]、[KeyUp]に[Keys.Enter]が送られてきます。

したがって、連続した[KeyDown]、[KeyPress]、[KeyUp]イベントで、何れも[Keys.Enter]が送られてくれば次のコントロールに移動し、それ以外は移動しないことにすればいいのです。

あとは、これをどのようにコード化するかです。

私たちはinteger二つが入るQueue(行列)を使いました。
[KeyDown]等のイベントが発生したとき、次々にこのキューに確認のコードを入れていきます。そして連続した[KeyDown]、[KeyPress]、[KeyUp]イベントが[Keys.Enter]を受け取ったことを確認すれば、コントロールを移動します。

Enum EnterKeyCode
    Down
    Press
    Up
    NG
End Enum

Public Class clsCheckEnterKey

    Private Shared que As New Queue(Of Integer)(2)

    Shared Sub TestEnter(ByVal sender As Object, ByVal KeyCode As Integer)
      If KeyCode = EnterKeyCode.Up Then
          If que.Count = 2 Then
              If que.Dequeue = EnterKeyCode.Down Then
                  If que.Dequeue = EnterKeyCode.Press Then
                      sender.findform.SelectNextControl(CType(sender, Control), _
                          True, True, True, True)
                   End If
              End If
          End If
      End If
      If que.Count = 2 Then
          que.Dequeue()
      End If
      que.Enqueue(KeyCode)
    End Sub
End Class

これをイベントハンドラーからコールする形にします。

    Public Shared Sub KeyDown _
	(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs)
        If e.KeyValue = Keys.Enter Then
            clsCheckEnterKey.TestEnter(sender, EnterKeyCode.Down)
        Else
            clsCheckEnterKey.TestEnter(sender, EnterKeyCode.NG)
        End If
    End Sub

    Public Shared Sub KeyUp _
	(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs)
        If e.KeyValue = Keys.Enter Then
            clsCheckEnterKey.TestEnter(sender, EnterKeyCode.Up)
        Else
            clsCheckEnterKey.TestEnter(sender, EnterKeyCode.NG)
        End If
    End Sub

    Public Shared Sub KeyPress _
	(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs)
        If Asc(e.KeyChar) = Keys.Enter Then
            clsCheckEnterKey.TestEnter(sender, EnterKeyCode.Press)
        Else
            clsCheckEnterKey.TestEnter(sender, EnterKeyCode.NG)
        End If
    End Sub

更に特定のコントロールにイベントハンドラーとして登録します。
このコードは(9月18日付けでご紹介しました)[CtrlBindingCollection]からコールします。

    Public Shared Sub SetEnterKeyHandler(ByRef ctrl As Control)
    AddHandler ctrl.KeyDown, AddressOf KeyDown
    AddHandler ctrl.KeyPress, AddressOf KeyPress
       AddHandler ctrl.KeyUp, AddressOf KeyUp
    End Sub

使い方は前回の単純なフォーカス移動のプログラムと同じです。

次のコントロールに移動するイベントハンドラ

テキストボックスにデータを入力し、[Enter]キーを押すと次のコントロールに移動する。という仕組みは頻繁に使います。

VB6(Visual Studio 6)では、フォームのデザイン時にこの動作を設定することができましたが、.NETではデザイン時でのこの設定がなくなっています(タブキーでの移動はあります)。

(以下のコードは実際に動作していますが、説明の邪魔になる部分を削除したり手を加えています。従ってこのままでは正しく動作しないことがあります。当ブログのコードは今後とも同様です。
掲載しますコードはベストだと思っているわけではありません。私たちの試行錯誤の結果、ともかく要求機能を満足するものです。
コードがもし読者の皆さんのお役に立つのであれば、コードのダウンロードを別途検討したいと思います。)

プログラムでコントロールの[KeyUp]イベントハンドラーで、[Enter]キーが押されたら、次のコントロールに移動するコードを書きます。

    Public Shared Sub SimpleKeyUp(ByVal sender As Object, _
                  ByVal e As System.Windows.Forms.KeyEventArgs)
        If e.KeyValue = Keys.Enter Then
	 Dim frm As Form = sender.findform
          Dim ctrl As Control = CType(sender, Control)
          Dim blnForwd As Boolean = True

          frm.SelectNextControl(ctrl, blnForwd, True, True, True)
        End If
    End Sub

このプログラムで注目すべきは、[sender](実はコントロール)から、それが配置されている[Form]を補足でき、このフォームで次の[TabIndex]のコントロールにフォーカスを移すことができることです(TabIndexの順番はデザイン時に設定します)。

そしてこのコードは、下のように当該コントロールに[KeyUp]イベントハンドラーとして登録します。

    Public Shared Sub SetSimpleEnterKeyHandler(ByRef ctrl As Control)

        If TypeOf (ctrl) Is TextBoxBase Then
            RemoveHandler CType(ctrl, TextBoxBase).KeyUp, AddressOf SimpleKeyUp
            AddHandler CType(ctrl, TextBoxBase).KeyUp, AddressOf SimpleKeyUp
        ElseIf TypeOf (ctrl) Is ButtonBase Then
            RemoveHandler CType(ctrl, ButtonBase).KeyUp, AddressOf SimpleKeyUp
            AddHandler CType(ctrl, ButtonBase).KeyUp, AddressOf SimpleKeyUp
        ElseIf TypeOf (ctrl) Is ListControl Then
            RemoveHandler CType(ctrl, ListControl).KeyUp, AddressOf SimpleKeyUp
            AddHandler CType(ctrl, ListControl).KeyUp, AddressOf SimpleKeyUp
        End If

        If TypeOf (ctrl) Is CheckBox Then
            RemoveHandler CType(ctrl, CheckBox).KeyUp, AddressOf ChkInstKeyUp
            AddHandler CType(ctrl, CheckBox).KeyUp, AddressOf ChkInstKeyUp
        End If
    End Sub

さらにこのコードは、前回ご紹介しました、[CtrlBindingCollection]クラスの[Cell]サブクラスのコンストラクターでコールされています。

その結果、

Dim CBC As New CtrlBindingCollection
With CBC
  .Bind(TextBox1, “氏名”, “String”, True)
  .Bind(TextBox2, “郵便番号”, “String”, True)
  .Bind(TextBox3, “住所”, “String”, True)
  .Bind(TextBox4, “年齢”, “int”, False)
End With
CBC.テキストクリア
CBC.Display(Datarow)

で[TextBox1]、[TextBox2]、[TextBox3]で[Enter]キーを押すと次のコントロールに移動するイベントハンドラーを割り当て、全コントロールをクリアし、それらのコントロールに[Datarow]の値をデータ型を考慮しながら表示することができます(もちろん別途Displayメソッドと、インスタンスDatarowを準備しておきます)。

ところで、日本語入力しているとき[Enter]キーを押すと直ちに次のコントロールに移動されては困ります。
日本語入力の途中では、通常ひらがなで入力して漢字変換が正しく表示されたところで、確定の[Enter]を押します。漢字入力はまだ続きますので、ここで次に移動したのではいけません。

次回はこれを正しく処理するコードをご紹介します。

コレクション

考えてみれば私がもっとも重宝しているのは、Collectionかもしれません。最初はCollectionBaseの継承をつかっていましたが、今はList(Of T)をメンバー変数に持つクラスです。

最も基本的な使用法は、画面で使うADO.NETのDataTable一つに対してこのクラスのインスタンスを一つ作ります。メソッドBindでは、画面のコントロールとテーブルの列名と列のデータ型を関連付けています。

Public Class CtrlBindingCollection
    Private m_lst As List(Of Cell)
    Sub New()
        m_lst = New List(Of Cell)
    End Sub

    Public Sub Bind(ByRef ctrl As Control, ByVal colName As String, ByVal DataType As Integer, ByVal blnNext As Boolean)
        m_lst.Add(New Cell(ctrl, colName, DataType, blnNext))
    End Sub

    Public Sub モード設定(ByVal blnEdit As Boolean)
   ' 全Cellのモード設定
    End Sub

    Public Sub テキストクリア()
   ' 全Cellのテキストクリア
    End Sub

    Class Cell
        Private m_Control As Control
        Private m_列名 As String
        Private m_データ型 As Integer

        Sub New(ByRef ctrl As Control, ByVal colName As String, ByVal intDataType As Integer, ByVal blnNext As Boolean)

            Me.m_Control = ctrl
            Me.m_列名 = colName
            Me.m_データ型 = intDataType

            If blnNext Then
                If Me.m_データ型 = DT.Str OrElse Me.m_データ型 = DT.Dte Then
                    SubFuncs.SetEnterKeyHandler(ctrl)
                Else
                    SubFuncs.SetSimpleEnterKeyHandler(ctrl)
                End If
            End If
   End Sub

        Sub モード設定(ByVal blnEdit As Boolean)
     .....
        End Sub

        Sub テキストクリア()
     .....
   End Sub
    End Class
End Class

今たとえば、Form コードの中で、
Private instCtrlBindingCollection As New CtrlBindingCollection

が宣言されているとして、[instCtrlBindingCollection.テキストクリア]でコントロールをすべてクリアすることができますし、[instCtrlBindingCollection.モード設定]では、画面コントロールのEnableの可能/不可能の切り替えができます。

すなわち、このコレクションに登録されたコントロールをいっせいに「どうこう」したいときにこのクラスのメソッドを一回コールすることで事足ります。

次回は実際何をしているかご紹介します。

これから

何人かの方がつたない私のブログを読んでくださっています。

ところが私はLINQと格闘していて、ブログを書く余裕がありません。

LINQについては、もう少し習熟が進んでから私なりに内容のあるお話を書きたいと思います。

VS2002から10年近く、.NETの開発を続け、その過程で様々なプログラムの工夫をしてきました。
ベストというほどの自信家ではありません。
ご批判をいただければ、また多少の参考になればと思い、少しずつ皆様にご披露していきたいと思っています。

LINQ を使う

その後、[LINQ to Entity Framework]を使ってプロジェクトを進めています。

まだ、小学生程度の習熟度ですが、「総じてLINQはいい」というのが感想です(複雑なビジネスロジックの場合はどうなのか分かりません)。

私は、マイクロソフトが用意するデータバインドが気に入りません。
一言で言えば、カスタマイズについての柔軟性に欠けると思います。

具体的にいえば、データベースとコントロールでの表示の間に関数を入れたいのですが、どうすればいいのかよく分からない。たぶんできない(と思います)(もちろん、ストアドプロシージャを使うとかまったく方法がない訳ではありません。データバインドでのカスタマイズができません)。

したがって、データバインドを一切つかわないで、コントロールへの表示と、コントロールデータのデータベースへの格納の部分をすべてコーディングしています。

これを統一的に書くために、ADO.NETではコントロールとデータベースの列名のマップを定義していますが、LINQを使うと厳密に型づけされていますので、列名とEntity Objectとのマップをどう定義すればいいのかわかりません。

やむなくその都度、コントロールへの表示とデータベースへの保存をいちいち書いているというわけです。
ここが一番気にいらないのですが、Entity Frameworkをもっと研究すれば、解決策があるのではないかと思っています。

LINQのいいところは、先にも書きましたが、ADO.NETではデータベースへのコネクションを書き(忘れず終了し)、トランザクションを書き、複数テーブルを保存するときは、Insert、Update、Deleteとテーブルの依存関係を考慮したコードを書かなければいけません。

これらをLINQが全部やってくれるので、LINQのプログラミングはADO.NETに比べずいぶん楽です。

まだまだ研究中ですが、研究する価値が十分あると思います。