Visual Basic 6.0 テクニック |
Visual Basic 中学校 > VB6 テクニック >
8.入力チェック論1 チェックするタイミング
ユーザーからの値の入力を受け付けるようにプログラムすることはよくあります。パスワードを入力させたり、ある種の数字を入力させたり、いろいろな設定やデータベースに接続しているならもっとさまざまな情報を入力させるシーンがあるでしょう。
ところが、「年齢」のところに文字を書き込まれたらどうしますか?メッセージボックスで警告を出すか、そもそも数字以外書き込めないようにするか・・・。今回と次回はこのようなユーザーの入力をチェックする方法の解説とともに私なりの入力チェックに対する提言を行います。
1.悪い例
世間では入力チェックというものはたくさんあるのですが、今まで私が出会ったプログラムのほとんどは「なんだこりゃ」というようなものでした。どうも入力チェックみたいなところはレベルの低いプログラマが担当しているようでとんでもないコード・構造続出です。
とくに私は次の一点は絶対にやめてほしいと思っています。それは「同じチェックをいろいろなところに書く。」ことです。
たとえば、数字か文字かチェックするようなコードは一箇所にまとめておいて必要なところはそれをCallすればいいだけのはずです。それをいちいちイベントプロシージャに書いているようではもう同じコードが随所にあふれかえって収拾がつかなくなってしまいます。
あとで仕様変更とかバグがあったら泣きが入りますね。しかも、きっとコピーと貼り付けでやってんでしょうが時間の無駄もはなはだしい。わざわざテキストボックスのところだけ名前が変えてある(きっとここだけ手作業で変えたのでしょう)のをみると「無知とは恐ろしい」と感じます。
さらに、まったく同一のテキストボックスに対するチェックを「入力直後」と「OKボタンをクリックしたとき」の2回行っているものにも出会ったことがあります。誤解しないでください。2箇所でまったく同じチェックが必要な場合があることは認めます。でも私が見たそのプログラムでは2箇所それぞれに同じコードが書いてあるのです。しかもその画面に張り付いている全部のコントロールの分が・・・・。Call や関数を知らないわけではないようなので本当に首をひねってしまいます。
2.入力チェックはいつするか?
具体的な話しに入る前に入力チェックはいつすればよいか考えましょう。
入力チェックをする時期としては次の3つがあります。(方法の違いでさらに「入力直後」を分けているの全部で4つになります)。
1.入力中
ChangeイベントやKeydownイベントなどを使えば入力中にチェックすることができます。
メリット:まちがった値を「そもそも入力できない」ようにすることが可能なのでユーザーからはスマートに写る。
デメリット:ユーザーはプログラマが想定した順番で入力するとは限らない。いきなり「OK」ボタンとかを押されたときのためにこれとは別にもう1つチェック機構が必要。
例:Text1は数字しか入力できなくなります。
Private Sub Text1_KeyPress(KeyAscii As Integer)
Select Case KeyAscii
Case vbKey0 To vbKey9
Case Else
KeyAscii = 0
End Select
End Sub
2.フォーカス移動前
Validateイベントを使うとフォーカスを移動しようとしたときフォーカス移動前にチェックをかけることができます。フォーカス移動前なのでフォーカスを制御する面倒なコードを書く必要がありません。
メリット:たいていの場合スマートにプログラムできる。プログラムもわかりやすい。
デメリット:これもいきなり「OK」ボタンとかを押されたときのためにこれとは別にもう1つチェック機構が必要。また、VB6でしか利用できない。
例:Text1は数字以外のものが入力された状態ではフォーカスを移動できない。
Private Sub Text1_Validate(Cancel As Boolean)
Cancel = Not IsNumeric(Text1.Text)
End Sub
3.フォーカス移動直後
LostFocusイベントやGotFocusイベントを利用するとフォーカス移動のタイミングでチェックをかけることができます。
メリット:伝統的手法。解説やサンプルがしばしばあるのでプログラマの技術力が低くてもなんとか書ける。
デメリット:プログラムが複雑になる。とくに複雑なフォーカス制御が必要になる場合が多く、一度破綻するとデバッグに相当な時間を割かれることになる。
例:Text1に数字以外のものを入力してフォーカスを移動すると、またフォーカスを戻される。
Private Sub Text1_LostFocus()
If Not IsNumeric(Text1.Text) Then Text1.SetFocus
End Sub
4.後でまとめて
ほとんどのプログラムは「OK」ボタンや「確認」「登録」「次へ」などのボタンがついているのでそのボタンをクリックしたときにまとめてチェックすることも可能です。
メリット:面倒くさいプログラムから開放される。構造もすっきりする。
デメリット:きめ細かい制御が必要なときには対応不能(?)。
どれを利用するかはまぁ状況に応じてといったところです。
しかし、私はここで提言します。「3.フォーカス移動直後」だけは絶対に使わないでください。私の書いた例を見ると結構シンプルで「どこが悪いの?」と思われるかもしれません。入力チェックをすべきなのがText1だけだったら確かにこれでもいいでしょう。しかし、Text1とText2の2つをそれぞれチェックしたいときに次のように書いたらどうでしょう?
Private Sub Text1_LostFocus() If Not IsNumeric(Text1.Text) Then Text1.SetFocus End Sub |
Private Sub Text2_LostFocus() If Not IsNumeric(Text2.Text) Then Text2.SetFocus End Sub |
このプログラムのどこが問題だかわかりますか?分からなかったら実行してみてください。(プログラムを途中で止めるには Ctrl + Break(Pause) を押します)。
これが噂の「フォーカスのキャッチボール」です。お互いが自分にフォーカスを引き寄せようとするものですから、交互にお互いのLostFocusイベントが発生しそれが連鎖するのですね・・・。これをどう回避するのですか?
フラグを立てたりして回避することはできます。しかし、私は言いたい。「フォーカス系のイベントでフォーカスを操作することはパラドックス的な危険を伴う」ということを。だから私は『「3.フォーカス移動直後」だけは絶対に使わないでください。』というのです。フォーカスのキャッチボールはその一例に過ぎません。
それでは、次にこれ以外の方法を見ていくことにします。
3.入力中のチェック
KeyPressイベントで KeyAscii = 0 と書くと入力を無効にすることができます。そして、KeyPressイベントはユーザーがキーボードから何か入力するたびに発生するのですから、入力される1文字1文字にチェックをかけて有効・無効をきめ細かく制御することができるのです。
この方法だとユーザーは間違った入力をすることが最初からできないのでかなり安全ですし、ユーザーも安心できます。
先ほどの例にもう一度登場してもらいましょう。
Private Sub Text1_KeyPress(KeyAscii
As Integer) Select Case KeyAscii Case vbKey0 To vbKey9 Case Else KeyAscii = 0 End Select End Sub |
このプログラムを試してもらえれば分かりますが確かにText1には数字以外は入力できなくなります。さて、このプログラムのバグに気がつきましたか?
実はこのプログラムではバックスペースキーが入力できないのです。わかってしまえばこのくらいの修正は簡単で、次のようにすればよいでしょう。
Private Sub Text1_KeyPress(KeyAscii
As Integer) Select Case KeyAscii Case vbKey0 To vbKey9, vbKeyBack Case Else KeyAscii = 0 End Select End Sub |
これで完璧だと思いますか?
実はこのText1に英語や日本語を入力する方法があるのです!わかりますか?
では答えを言いましょう。マウスで右クリックして貼り付けができてしまいます!
このときはキーボードが使用されないのでKeyPressイベントが発生しないのです。(IMEのソフトキーボードなどではマウスから入力していてもKeyPressイベントが発生します)。
では、どうやったらこれを防げるのでしょうか。Changeイベントを使うのが穏当でしょう。Changeイベントはキーボード・マウスの区別なくすべての入力に反応します。だったらはじめからChangeイベントを使えばいいと思われるかもしれませんがChangeイベントでは KeyAscii = 0 のように入力を無効化することができません。
さしあたってChangeイベントに次のように記述することでこの問題を回避できます。
Private Sub Text1_Change() Dim K As Long Dim NewString As String For K = 1 To Len(Text1.Text) If IsNumeric(Mid(Text1.Text, K, 1)) Then NewString = NewString & Mid(Text1.Text, K, 1) End If Next K Text1.Text = NewString End Sub |
このコードは数字以外のものが入力されたとき数字以外のものをText1から排除します。ただ、たとえば「2001年」 という文字列をマウスの右クリックで貼り付けた場合排除されるのは 「年」 だけで 「2001」は残ります。貼り付けた文字列をすべて排除するにはさらなる工夫が必要ということです。
さらに、貼り付けを行ってこのChangeイベントが実行された場合カーソルの位置が変化してしまいます。これはユーザーにとっては「あつかいにくい」という印象を与えることでしょう。
これらの問題ももちろん回避できます。その方法はここでは示しませんがそれほど難しくはないでしょう。けれど、こういったことをごちゃごちゃ書いていくと気がついたときにはText1の制御のためだけにたくさんのコードがあふれかえることになります。さらに、これをText2やText3でもやるとなったら・・・・・。(注:イベントプロシージャを共有させることでこの問題は回避できます。クラスとWithEventsキーワードを使うことによりこれは実現できます。その方法は次回、またはその次当たりに説明する予定です)。
視点をかえてマウスの右クリックを無効にするということもできそうですがこれは単純にはできないようでますます複雑化しそうです。それに右クリックを無効にされたらユーザーはコピーもできなくなるのでまたしても「あつかいにくい」という印象を与えてしまうことでしょう。
書き忘れましたが、ユーザーがそもそもText1にフォーカスを移動せずいきなり「OK」ボタンをクリックした場合は入力チェックをくぐらなかったことになります。この場合に備えてText1に規定値として何かをセットしておくとよいでしょう。規定値をあらかじめ設定できないような状況下では「OK」ボタンが押されたときにもチェックをかける必要があります。
すべての入力チェックを通らないとOKボタンを押せないというのも手です。これは多くのアプリケーションが採用している手法でユーザーにとっても扱いやすいようです。
結論:
入力中のチェックは何か必要のある場合に行う。他の方法で足りる場合は入力中のチェックは行わない。入力中のチェックを行う場合は不正な入力ができないようにプログラムが複雑化することを覚悟する。
4.フォーカス移動前のチェック
VB6から採用されたValidateイベントを使うとこのフォーカス移動前のチェックを行うことができます。Validateイベントは入力チェックのためにあるイベントといっても過言ではなく、このようなイベントが追加されたということはVB5以前のプログラマの多くが入力チェックに苦心していたということが示されています。
ここでも先ほどの例にもう一度登場してもらいます。
Private Sub Text1_Validate(Cancel
As Boolean) Cancel = Not IsNumeric(Text1.Text) End Sub |
実にシンプルです。Validateイベントでは引数 CancelにTrueをセットしてイベントプロシージャを抜けるとフォーカスの移動がキャンセルされるという便利な機能があります。
この方法で入力チェックをかけるとユーザーはフォーカスを移動しようとさえしなければ好きな文字を入力できます。それが「入力中のチェック」との最大の違いですが、実のところこのことはユーザーの負担にはなりません(と私は思います)。
入力内容が不正ならフォーカスを移動できないのでいつまでたってもText1から出られません。
このように欠陥らしい欠陥のないみごとなプログラムができることでしょう。
それでも難点を示すとすればVB6以降でないと使えないということでしょう。VB5以前にはそもそもValidateイベントがないし、Office2000・Office XPにもありません。
まさにVB6の専売特許技です。
結論:
VB6を使うならValidateイベントで入力チェックすることは極めて理にかなっている。逆にVB6なのにValidateイベントで入力チェックしない場合は「なぜ、Validateイベントを使わないのか?」という批判に明確に答えられるようにしなければならない。
5.後でまとめて入力チェック
この方法は私が一番お勧めするものです。なぜならこの方法ではプログラムが簡潔になるし、ユーザーに使いにくいという印象も与えない、さらにプログラムの実行もスムーズになるからです。
テキストボックス1つ1つに入力チェックをかけていたのではフォーカスを移動するたびに入力チェックが発動して時間がかかってしまいます。「そんなこといっても実際には入力チェックに0.1秒もかかってないんじゃないの?」という方もいるでしょう。たしかに単純なチェックならそうかもしれないのですが中にはテキストボックスに入力された値をデータベースと照合して入力チェックする場合もあるわけで、そういう場合は実際には1秒以内で入力チェックが終わるとしてユーザーには我慢できないような遅さに感じられてしまいます。
では、後でまとめてチェックする例をしめしましょう。フォームにはText1とText2、それにCommand1があります。Command1が「OK」ボタンで、Text1には数字のみ、Text2には日付のみが入力できるようにチェックをかけるとします。
Private Sub Command1_Click() Select Case False Case IsNumeric(Text1.Text) MsgBox "Text1には数字だけを入力して下さい。" Text1.SetFocus Case IsDate(Text2.Text) MsgBox "Text2には日付を入力して下さい。" Text2.SetFocus Case Else MsgBox "入力チェック完了。次の処理に移ります。" End End Select End Sub |
もちろんメッセージをいちいち表示しないようにもできます。
実際にはIsNumericやIsDateで単純にチェックできるものばかりではないのでこのプロシージャはもっと長くなるし、Select Case を使って構造化することもできないかもしれません。
でも、他の方法で入力チェックする場合でも入力をチェックする部分のコードはどうせどこかに書くのだからこのことはデメリットにはなりません。
ユーザーはOKボタンをクリックするまでは好きなものを入力できてしまいます。しかし、このとことはフォーカス移動前にチェックをする場合の状況と同じです。
6.順次制御
先に入力された内容によって後ろのコントロールの有効・無効を切り替えたりしたい場合もあるでしょう。
たとえば、「その他」が選択された場合にはその他の内容を入力するテキストボックスを有効にしたいが、そうでない場合はそのテキストボックスには何も入力できないように無効にしたいという場合です。
このようにプログラムされているアプリケーションはなかなか使いやすくミス入力も防げます。ユーザーにも評判がいいのでは?
ところがこのことは入力チェックと関係して問題を提起します。入力中のチェックを採用している場合には必要に応じて後ろのコントロールの状況をいつでも変更できるのでさほど問題はありません。フォーカス移動前のチェックをかけている場合もそれに準じます。
この両者の場合はユーザーが後ろのテキストボックスを先にマウスでクリックしてそこから入力しようとした場合が若干問題ですが、このようなコントロールを最初 無効 にしておけばこの心配は回避できるでしょう。
ところが後でまとめてチェックを採用している場合にはそんなわけにもいかず問題が残ります。これが私が最初に後でまとめてチェックするときのデメリットとしてあげた「きめ細かい制御」ができないというものです。
そうは言ってもまったく回避できないわけではありません。ここでは2つの回避策をしめしましょう。
1つ目は複合的にチェックする方法です。つまり、コントロールの状況が変更される可能性のあるような入力をするシーンでは入力中のチェックやフォーカス移動前のチェックを採用し、そうでないところに後でまとめてチェックを採用することです。
もう1つはウィザード形式の採用です。「次へ」ボタンを用意して入力をいくつかの段階に区切るのです。プログラムは「次へ」ボタンが押されるタイミングで入力をチェックし次の段階のコントロールの状況(有効・無効など)を切り替えます。ウィザードというとたくさんのフォームを用意しなければならないから面倒だと思われている方もあるでしょうがそんなことはありません。フォームがひとつでも「次へ」ボタンを押さない限り次の段階の入力ができないようにしてしまえば同じことなのです。
文章で書いても私の拙筆で分かりにくいかもしれません。具体例をダウンロードできるようにしておきます。
InputCheck.lzh 3.87KB
このプログラムではヘッダー部で「削除」を指定した場合は詳細部のコントロールが使用不可で、それ以外の場合は使用可になります。
7.最後に
今回は入力チェックをする時期について解説しました。みなさんはどうやっていましたか?もっといいタイミングがあるよとおっしゃる方はぜひ掲示板で教えてください。
次回は入力チェックの具体的な方法をコードを示しながら解説していく予定です。