Visual Basic 初級講座 |
Visual Basic 中学校 > 初級講座 >
第23回 コントロールの遷移制御
フォーカス移動際での入力チェック、入力後の処理…深く考えないで適当にプログラムしていませんか?今回はこの辺りの話題を掘り下げます。フォーカス関連のイベントやValidatingイベント等を一から説明しているので、初めての方およびフォーカス制御が苦手な方は必読です。
この回の要約 ・フォーカス関連のイベントはLeave → Validating → Validated → Enterの順で発生する。 ・個別に行うならば、入力チェックはValidatingイベント、入力後の処理はValidatedイベントで行う。 ・場合によってはLeaveイベントでこれらの処理を行うこともありえる。 ・CausesValidationプロパティを使ってValidatingイベントの発生を抑止できる。 ・Validatingイベントの動作にはくせがあってなかなか思い通りにいかない。 |
「遷移制御」(読み方:遷移制御 = せんいせいぎょ)とは耳慣れない言葉ですが、この場合は主にフォーカスの移り変わりをさしています。単純なアプリケーションを作るのならばフォーカスの移り変わりに特に注意を払う必要はありません。左上から右下にかけて順々にタブオーダーが移動するように構成してあるだけで完全 といえる場合が少なくないでしょう。
遷移制御が問題になるのは入力チェックや入力後の処理が必要な場合です。
たとえば、日付を入力・表示するテキストボックスを考えてみてください。日付の形式には和暦・西暦などいろいろな形式があります。同じ2005年5月22日でも、「H17/05/22」や「2005/5/22」、「2005年05月22日」など幾通りにも表現できてしまいます。
そこで、あるプログラムで日付の形式を yyyy/MM/dd形式(2005/05/22のような形式)で統一したいと考えたとしましょう。プログラム上この変換は非常に簡単にできます。次の通りです。
TextBox1.Text = CDate(TextBox1.Text).ToString("yyyy/MM/dd") |
■リスト1
でも、このプログラムをどこに書けばよいでしょうか?TextBox1の値が変更された後にこのコードが実行されるようにしなければいけないはずですが最も適切なイベントはなんでしょうか?
これが遷移制御の問題の1つ、入力後の処理の問題です。
また、ユーザーは常に正しい値を入力するとは限りませんから、このテキストボックスに「ABC」のようなおよそ日付とは無縁の値を入力してしまうかもしれません。そうすると、先ほどの yyyy/MM/dd形式に変換するプログラムはエラーになってしまいます。そこで、ユーザーの入力が正しいかどうかチェックする必要があります。
さて、チェックするプログラムはどこに書けばよいでしょうか?
これが遷移制御の問題のもう1つ、入力チェックの問題です。
この入力チェックの問題と入力後の処理の問題が解決できれば遷移制御の問題のほとんどは解決したも同然です。
それでは具体的に検討してみましょう。「入力チェック」と「入力後の処理」はどこに書くのが適切でしょうか?
結論を先に言いますと、入力チェックはValidatingイベント(読み方:Validating = ヴァリデイティング)で行います。入力後の処理はValidatedイベント(読み方:Validated = ヴァリデイテッド)が適切です。
けれども、この2つのイベントを使えばすべてが解決するわけではありません。ここで候補となるいくつかのイベントをチェックして問題点を明らかにしていきます。この作業はきわめて有益です。特に遷移関係のイベントが苦手な方は是非一読してください。
今、ここにコントロールの遷移関係のイベントを列挙してみます。
イベント | 読み方 | 説明 |
Enter | エンター | フォーカスを取得したときに発生する。 |
GotFocus | ゴットフォーカス | フォーカスを取得したときに発生する。通常このイベントは使用しない。 |
Leave | リーブ | フォーカスを失ったときに発生する。 |
Validating | ヴァリデイティング | フォーカスを失ったときに発生する。よく入力チェックに使用され、フォーカスの移動をキャンセルすることができる。 |
Validated | ヴァリデイテッド | フォーカスを失ったときに発生する。よく入力後の処理に使用される。 |
LostFocus | ロストフォーカス | フォーカスを失ったときに発生する。通常このイベントは使用しない。 |
■表1:フォーカス移動に関わるイベント
これらのイベントは発生するタイミングがとても紛らわしいのですが明確な使い分けが存在します。まずこれらのイベントが実際にどのように発生するか調べてみます。
TextBox1からTextBox2にフォーカスを移動したときのイベントの発生順序を表にまとめてみました。
マウス、またはFocusメソッドでフォーカスを移動する場合 | その他の方法でフォーカスを移動する場合 | 共通する法則 |
TextBox1.LostFocus ↓ TextBox1.Leave ↓ TextBox1.Validating ↓ TextBox1.Validated ↓ TextBox2.Enter ↓ TextBox2.GotFocus |
TextBox1.Leave ↓ TextBox1.Validating ↓ TextBox1.Validated ↓ TextBox2.Enter ↓ TextBox1.LostFocus ↓ TextBox2.GotFocus |
TextBox1.Leave ↓ TextBox1.Validating ↓ TextBox1.Validated ↓ TextBox2.Enter |
■表2:フォーカス移動時のイベントの発生順序
表をみるとわかるようにフォーカスの移動のさせ方によってイベントの発生順序が異なります。一番左にはマウスやFocusメソッドを使ってフォーカスを移動したときのイベントの発生順序が示されています。中央には[TAB]キーやSelectNextControlメソッドなどを使ってフォーカスを移動したときのイベントの発生順序が示されています。
それぞれ発生順序が異なるので嫌な感じがするかもしれませんが安心してください。表1に書いたようにLostFocusイベント とGotFocusイベントは通常は使用しないディープなイベントですのでとりあえず無視して構いません。そこで、これらのイベントの一覧からLostFocusイベントとGotFocusイベントを取り除くと表の一番右の列の通りにイベントが発生していることがわかります。マウスを使おうが[TAB]キーを使おうがこの発生順序は共通です。
では、これら4つのイベントの中で「入力チェック」と「入力後の処理」をするのに適切なイベントを探してみます。まず、TextBox1に関する処理はTextBox1の内部で済ませてしまいたいのでTextBox2.Enterイベントは除外します。
TextBox2_EnterイベントでTextBox1に関するチェックや処理を行うこともできますが、将来TextBox1とTextBox2の並び順を変更する必要が生じた場合などにプログラムの変更が面倒になる し、フォーカスの制御が極めて煩雑になるのでお勧めしません。
もしあなたの周りにEnterイベントで入力チェックや入力後の処理をしているプログラマがいたらよく事情を聞いて、納得できない事情ならば直ちにプログラムを変更するように忠告してあげてください。
さて、残ったのはLeaveイベントとValidatingイベントとValidatedイベントですが、入力のチェックについて考えるときチェックした結果「ダメ」だった場合の制御が重要です。チェックしてNGだった場合の処理としてよくあるのは入力した内容が間違っている旨のメッセージを表示することですが、メッセージだけ表示してそのまま後処理を実行してしまっては意味がありません。
つまり、「日付に変換できませんよ」とメッセージを表示しておきながら、後処理を実行してyyyy/MM/dd形式への変換を行ってしまうのでは結局エラーになるのですからよくありませんよね。
ということは入力チェックは単にメッセージを表示するだけでなく、その後の処理を実行しない機能が必要と言うことになります。
この点Validatingイベントは非常に有利です。Validatingイベントにはフォーカスの移動をキャンセルする機能があるのです。入力の結果NGであれば、メッセージを表示するだけでなくフォーカスの移動をキャンセルすることによりその後のイベントを発生させないようにすることができます。
ということは、チェックの後に行う入力後の処理はValidatedイベントで行うのが穏当のように思えます。
ここで話が終わればまだ良かったのですが、後で説明するようにValidatingイベントとValidatedイベントの発生にはクセがあって、発生して欲しくないときに発生したり、発生して欲しいときに発生しなかったりするのです。もちろんちゃんとした法則の下に動作しているのでまったくきまぐれに発生するという意味ではありませんが、このあたりのタイミングを良く知っているプログラマでもややこっと遷移制御が必要なプログラムになると頭を悩ませることがしばしばあります。
そこで、ここでは単純なケースを想定して、まずはValidatingイベントとValidatedイベントをお勧めしますが、結局のところLeaveイベントで入力チェックと入力後の処理の両方を行うケースもありえます。
それでは、実際に日付のチェックと形式の変換をプログラムしてみます。
フォームにテキストボックスを2つ貼り付けて次の通りプログラムしてください。
Private Sub
TextBox1_Validating(ByVal
sender As
Object,
ByVal e
As
System.ComponentModel.CancelEventArgs)
Handles
TextBox1.Validating If
Not
IsDate(TextBox1.Text) Then |
Private
Sub
TextBox1_Validated(ByVal
sender As
Object,
ByVal e
As System.EventArgs) Handles
TextBox1.Validated
TextBox1.Text = CDate(TextBox1.Text).ToString("yyyy/MM/dd") End Sub |
■リスト2:入力チェックと入力後処理の単純な実装
ご覧のようにプログラム自体はシンプルです。
実行してTextBox2にフォーカスを移動しようとするとちゃんと日付のエラーメッセージが表示されます。そして、日付を正しく入力するまではTextBox1から脱出できません。
また、TextBox1に「01/02/03」のように入力すると、これまたちゃんと「2001/02/03」と変換されます。Validatedイベントに記述した入力後の処理が正しく機能している結果です。
短いプログラムの中でも、入力チェックの中でフォーカスの移動をキャンセルしている e.Cancel = True は要注意です。このようにValidatingイベント内で e.Cancel = True と記述するとフォーカスの移動をキャンセルすることができるのです。
長い前置きに関わらず問題がシンプルに解決してほっとしていますか?
いいえ、 本当の問題はここから始まるのです。
問題の所在をはっきりさせるためにプログラムを改造します。
プログラムに実行を終了するためのボタンを追加して、そのクリックイベントに以下のコードを追加してみてください。
Private
Sub
Button1_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
Button1.Click End End Sub |
■リスト3
Endでプログラムを終了するのは本来お勧めできないのですが、ここは理由があってEndを使っています。
この状態でもう一度プログラムを実行してみます。少しいじっているとすぐに気が付くと思いますが、TextBox1に日付に変換できない値が入っている場合ボタンをクリックして プログラムを終了することができないのです。
なぜなら、TextBox1のValidatingイベントがフォーカスの移動をキャンセルしてしまうからです。
Validatingイベントが命令どおりに動いているのは嬉しいのですが、フォームを閉じる処理くらいは見逃して欲しいものです。
このように実際のアプリケーションではいつでも必ず入力チェックを実行されては返って困る場合もあります。
この問題は場合によってValidatingイベントを発生させないことによって解決できます。これは プロパティの設定だけで簡単に解決できます。
Button1のCausesValidationプロパティ(読み方:CausesValidation = コーズスヴァリデイション)をFalseにすると、TextBox1にどんな値が入っていてもButton1に移動するときはValidatingイベントとValidatedイベントが発生しないようになります。
実際に試してみてください。
ところがCausesValidationプロパティではうまくいかない場合があります。フォームを閉じる処理を次のように書き換えてください。
Private
Sub
Button1_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
Button1.Click Me.Close() End Sub |
■リスト4
さきほどはEndでプログラムを終了していましたが、Endは強制終了のようなものなので通常は使用しません。通常はCloseメソッドを呼び出してプログラムを終了させます。(→プログラムの終了のさせ方については第13回 取りこぼした重要なトピックをご覧下さい。)
この状態でプログラムを実行してみるとEndをMe.Closeに変えただけなのにたちまちプログラムはうまく動かなくなります。CausesValidationプロパティがFalseになっているにもかかわらずTextBox1のValidatingイベントが発生してしまうのです。なんということでしょう!
メモ:VB2005のベータ版ではこの現象は発生しなかったのですが、製品版では発生します。 |
この現象も含めて、予想に反してValidatingが発生する場合を表にまとめてみました。
状況 |
Me.Closeを呼び出す。 |
フォームの右上の X ボタンを押す |
[ENTER]キーでCausesValidation = Falseのボタンを押下する。 ([スペース]キーの場合はValidatingイベントが発生しない。) |
■表3:意外の場合に発生するValidatingイベント
ここで我々は選択をせまられます。
・入力チェックにValidatingイベントを使うのを止める。
・なんとかプログラムを工夫してうまくうごくようにする。
Validatingイベントを使うのを止めるのであればLeaveイベントで処理を行うことになります。その場合は次のようなコードになります。
Private
Sub TextBox1_Leave(ByVal
sender As Object,
ByVal e As
System.EventArgs) Handles TextBox1.Leave
If
Not IsDate(TextBox1.Text)
Then End Sub |
Private
Sub Button1_Click(ByVal
sender As System.Object,
ByVal e As System.EventArgs)
Handles Button1.Click Me.Close() End Sub |
■リスト5:Leaveイベントでの代用
このコードですと、フォーカスの移動をキャンセルすることができません。コード中にコメントとして埋め込んだTextBox1.Focusを有効にすればフォーカスの移動を阻止できますが、ボタンのクリックすらできなくなってしまいます。
やはりValidatingイベントでのフォーカス移動キャンセルには魅力があります。Validatingイベントにこだわって「なんとかプログラムを工夫してうまくうごくようにする」のでしたらプログラムは次のようになります。
Private Sub
TextBox1_Validating(ByVal sender
As
Object,
ByVal e
As System.ComponentModel.CancelEventArgs)
Handles TextBox1.Validating
If
ActiveControl.CausesValidation = False
Then If Not IsDate(TextBox1.Text) Then MsgBox("日付を 2005/05/22 のような形式で入力してください。", MsgBoxStyle.Information) e.Cancel = True End If End Sub |
Private
Sub
TextBox1_Validated(ByVal
sender As
Object,
ByVal e
As System.EventArgs) Handles
TextBox1.Validated
If
ActiveControl.CausesValidation = False
Then TextBox1.Text = CDate(TextBox1.Text).ToString("yyyy/MM/dd") End Sub |
Private
Sub
Button1_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
Button1.Click
Me.Close() End Sub |
■リスト6
ValidatingイベントとValidatedイベントにCausesValidationプロパティをチェックするコードを追加しました。不細工なプログラムになってしまいました。
一見これでうまく動くようですが、どうでしょうか?もし、このプログラムを実際のアプリケーションに組み込むときはうまく動作するかよく点検してください。