[2019/10/29] テキストボックスの Leave イベントでのエラー処理でフォーカス強制移動する方法について (No.135)
[2019/10/11] コレクション「List」をコントロール配列の様に使う方法 (No.132)
[2019/10/08] 現在のイベント処理が終了するまで他のイベント処理は実行されない (No.129)
[2019/10/03] パネル上にピクチャボックスを貼付て画像スクロールさせる方法ついて(Panel、PictureBox) (No.128)
[2019/10/03] 標準コントロールのパネルの使い方について2(複数Panelの表示切替) (No.127)
-
×
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
-
テキストボックスは各種のデータ入力に使いますが、その入力データのエラーチェックを行うことが普通です。
エラーチェックの方法はいろいろありますが、例えばテキストボックス毎にエラーチェックを行ったり、 登録処理などのタイミングで入力項目を一括でエラーチェックを行い、エラーが在った項目の背景色を変えたりすることなどがあります。
今回は、テキストボックスに個別にエラー処理を行った場合にフォーカス移動させたい場合の方法について説明したいと思います。■単純にテキストボックスでエラー処理を行う(「Leave」イベント)
先ずフォーム上に1個のテキストボックスと、2個のボタンを張り付けます。 (以下の図参照) 1個のボタンは btnTest の名前でテキストボックスに入力された文字列を表示します。 もう1個のボタンは btnClose の名前で自分のフォームを閉じる様にします。
処理のソースは以下の通りです。テキストボックスのデータのエラー処理を Leave イベントの中で行っています。
数値チェック標準関数の IsNumeric を使って、エラーの場合にはメッセージを表示し、再度フォーカスをテキストボックスに設定しています。 尚、データをより細かくチェックする場合は自分でチェック処理を用意する必要があります。Public Class frmTextBoxExit '「Leave」イベントで個別の入力チェック Private Sub TextBox1_Leave(sender As Object, e As EventArgs) Handles TextBox1.Leave Dim str As String = Trim(Me.TextBox1.Text) '数値チェック If IsNumeric(str) = False Then MsgBox("数字のみ入力して下さい!") Me.TextBox1.Focus() End If End Sub '「Close」ボタンクリックで自分のウインドウを閉じる Private Sub btnClose_Click(sender As Object, e As EventArgs) Handles btnClose.Click Me.Close() End Sub '「Test」ボタンクリックで入力値を表示する Private Sub btnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click Dim str As String = Trim(Me.TextBox1.Text) MsgBox("入力文字列:" & str, , "Test Button") End Sub End Class
テキストボックスに数字「123」を入力し「Test」ボタンをクリックすると以下の表示になります。
さらにテキストボックスに数字ではない文字「qq」を入力すると、「Test」「Close」ボタンのどちらをクリックしても以下の表示になります。
ウインドウを閉じようとして「Close」ボタンをクリックしても、ボタンクリックイベントは発生しません。
これは、「Close」ボタンをクリックで一旦フォーカスがボタンに移動するのですが、 その後発生するテキストボックスの「Leave」イベントが走ってフォーカスが強制的に、テキストボックスに戻されてしまうからです。
これでは、このままの状態ではフォームは閉じなくなります。 (テキストボックスの入力を数字にすれば閉じます。また、ウインドウの右上の「×」をクリックすれば閉じますが)
■「Leave」イベントのエラー処理を強制的に抜け出る方法
「Leave」イベントのエラー処理の中では、フォーム上のアクティブなコントロールは、テキストボックスではなく既に「Close」ボタンの方に移っています。 フォーム上のアクティブなコントロールは ActiveControl プロパティに持っています。 このプロパティはフォーム上のアクティブなコントロールの情報を持っています。(と言うかデータ型がControlです)
このプロパティからコントロールの名前を取得し、「btnClose」であればエラー処理をスキップする様にします。
以下に修正したソースを示します。Public Class frmTextBoxExit '「Leave」イベントで個別の入力チェック Private Sub TextBox1_Leave(sender As Object, e As EventArgs) Handles TextBox1.Leave '「アクティブなControl」が「Close」ボタン?? If Me.ActiveControl.Name = "btnClose" Then Exit Sub End If Dim str As String = Trim(Me.TextBox1.Text) '数値チェック If IsNumeric(str) = False Then MsgBox("数字のみ入力して下さい!") Me.TextBox1.Focus() End If End Sub '「Close」ボタンクリックで自分のウインドウを閉じる Private Sub btnClose_Click(sender As Object, e As EventArgs) Handles btnClose.Click Me.Close() End Sub '「Test」ボタンクリックで入力値を表示する Private Sub btnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click Dim str As String = Trim(Me.TextBox1.Text) MsgBox("入力文字列:" & str, , "Test Button") End Sub End Class
このソースで実行すると、テキストボックスに数字ではない文字を入力しても、「Close」ボタンのクリックでウインドウが閉じます。
■フォーム上の入力データのエラー処理の考察
上記の様にテキストボックスの個別のエラー処理をスキップするには ActiveControl の名前などを確認すればできることが分かりましたが、 他にボタンなどがあれば、それぞれをチェックする必要があります。
尚、他にテキストボックスが在って、それぞれにエラーチェック処理が在る場合は、 それぞれで ActiveControl でボタンの場合エラー処理をスキップする必要があります。
また、フォームの入力データのチェックには一括で行う方法もあります。
「登録」などのボタンを設置して、フォームの入力データを全て取得して、 登録処理等を行うのが一般的だと思いますので、 そのクリックイベントで各入力データ毎に全てのチェック処理を行います。
各データの中でエラーが在ったコントロールにはその証拠となる様に、 背景色などを変更し見た目でエラーが在ったことを表示した方が良いと思います。 (フォーカスは最初にエラーを発見したコントロールに移動すべきです)
最後に、強制的にフォームを閉じる「Close」ボタン等があれば、 フォーム上のデータを1個でも修正したことがあればその旨を表示し 「修正した個所がありますが、フォームを閉じても宜しいでしょうか?」 などのメッセージで聞くことも必要かと思います。関連する記事
⇒テキストボックス拡張クラスにプロパティを追加する方法
⇒フォーム上のコントロールで[Enter]キー押下で次のコントロールにフォーカス移動する
⇒フォーム上のコントロールのイベント処理の一括関連付け:[AddHandler,DirectCast]
⇒コントロールの同じイベント処理に複数の関連付けをテスト:[AddHandler,DirectCast]
PR -
フォーム上にある同じ種類のコントロールを配列に入れて処理する方法は以下の記事で紹介しましたが、 今回はそれをコレクションクラスの List で行ってみます。
⇒コントロールを配列で処理する方法 :[AddHandler,DirectCast]
以下の画像は、今回のフォームを実行した場合の表示で、Button3 を押下した時の様子です。
以下のソースは、フォームロード時に「コントロール配列」に TextBox1~5 及び Button1~5 を設定し 各ボタンのクリックイベントの処理関数を AddHandler で設定しています。
ボタンのクリックイベントではイベント発生が在ったオブジェクト sender がどのボタンに該当するのかの配列の指標を取得します。 その指標を使って、テキストボックスを List から取得しそのデータを表示しています。■コントロールを List で処理する方法
Public Class frmCtrlList 'TextBoxコントロールのList Private ListTextBox As List(Of TextBox) 'ButtonコントロールのList Private ListButton As List(Of Button) ' フォームロードイベント Private Sub frmCtrlList_Load(sender As Object, e As EventArgs) Handles Me.Load 'コントロールListに各コントロールを設定 ListTextBox = New List(Of TextBox) From {Me.TextBox1, Me.TextBox2, Me.TextBox3, Me.TextBox4, Me.TextBox5} ListButton = New List(Of Button) From {Me.Button1, Me.Button2, Me.Button3, Me.Button4, Me.Button5} 'ボタンコントロールのクリックイベント処理を定義 For Each btn As Button In ListButton AddHandler btn.Click, AddressOf ButtonClick Next End Sub ' ボタンクリックイベント Private Sub ButtonClick(ByVal sender As Object, ByVal e As System.EventArgs) 'ボタンのオブジェクト名から指標文字列を取得(Nameが[ButtonNN]の形式が前提) Dim strTitleIdx As String = DirectCast(sender, Button).Name.Replace("Button", "") '指標が数値?? If IsNumeric(strTitleIdx) = True Then 'テキストボックスをListから取得 Dim TextBox As TextBox = ListTextBox.Item(CInt(strTitleIdx) - 1) 'テキストボックスの入力値を取得 Dim strTextBox As String = TextBox.Text 'ボタンのCaptionを取得 Dim strTitle As String = DirectCast(sender, Button).Text '表示 MsgBox(strTextBox, MsgBoxStyle.OkOnly, strTitle) End If End Sub ' フォームClosedイベント Private Sub frmCtrlList_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed 'コントロールListの廃棄 ListButton.Clear() ListButton = Nothing ListTextBox.Clear() ListTextBox = Nothing End Sub End Class
更に、チェックボックスを List を使って制御する例を示します。
フォームロード時にチェックボックスを List を生成し、ボタンをクリックした時にチェックボックスのチェックを反転処理しています。 チェックボックスの繰り返し処理は For Each文 で行っています。■チェックボックスを List を使って制御
Public Class frmCtrlList2 ' CheckBox の Listの宣言 Private list As List(Of CheckBox) Private Sub frmCtrlList2_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' Listの生成(初期化) list = New List(Of CheckBox) From {Me.CheckBox1, Me.CheckBox2, Me.CheckBox3} End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' For Each文でのチェックボックス処理 For Each chk As CheckBox In list chk.Checked = Not chk.Checked 'チェック状態反転 Next End Sub End Class
下図はチェックの反転の様子です。
尚 List の ForEach メソッドを使う方法は以下の通りです。■List の ForEach メソッドを使う方法
Public Class frmCtrlList2 ' CheckBox の Listの宣言 Private list As List(Of CheckBox) Private Sub frmCtrlList2_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' Listの生成(初期化) list = New List(Of CheckBox) From {Me.CheckBox1, Me.CheckBox2, Me.CheckBox3} End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' ForEachメソッドで匿名メソッドを宣言して処理する list.ForEach(Sub(chk) chk.Checked = Not chk.Checked Console.WriteLine("Name[{0}]...Checked:{1}", chk.Name, chk.Checked) End Sub) ' 1行の処理ならば以下の方法でもOK 'list.ForEach(Sub(chk) chk.Checked = Not chk.Checked) End Sub End Class
上記の匿名メソッドの宣言方法では、メソッドの名前を宣言せずに、通常の Sub を宣言する様にします。 (複数行の処理を行う場合を示しています)
コメント行で記述した方法でもOKです。簡単に1行で済ます場合には有効ですが、パッと見た感じでは分かりにくいですが。 (匿名メソッドはJavascriptなどではよく出てきます)関連する記事
⇒コントロールを配列で処理する方法 :[AddHandler,DirectCast]
⇒コレクション「List」の使い方について
⇒コレクション「List」と配列の相互変換について
⇒コレクション「Dictionary」の使い方について
⇒コレクション「Dictionary」から配列及び List への変換について
-
一般的に VB.NET において現在実行しているイベント処理が終了するまでは、他のイベント処理は実行されない様になっています。
イベント処理は実行しているプログラムから見て、外部から引き起こされる要因により発生します。 例えば、マウスのクリックイベント、キー入力のキーダウンイベント、タイマコントロールからのチックイベントなどがあります。
それぞれのイベントが発生した順番にイベント処理は順次実行されていきます。 同時にキーやマウスが動作しても、内部的には必ず順番が振られて、わずかな時間差で順次の処理になってしまいます。 多くのイベントが発生した場合はそれぞれが持ち行列的に待たされて順次処理されます。
それでは簡単な例で順次示していきます。- フォームにタイマコントロールを1個設置しタイマイベント時刻表示
- タイマイベント内に時間の掛かる処理をさせる
- タイマイベント内に再入を引き起こす処理をさせてみる
- タイマコントロールを2個設置して1個のタイマイベント内に時間の掛かる処理をさせる
- ボタンコントロールを2個設置して1個のクリックイベント内に時間の掛かる処理をさせる
■フォームにタイマコントロールを1個設置しタイマイベント時刻表示
先ずはフォームにタイマコントロールを1個設置し、タイマイベントでデバッグ出力ウインドウへの時刻表示を行います。 ソースは以下の様になります。
Public Class frmMulEvent Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 「Timer1」設定・開始 Me.Timer1.Interval = 2000 ' 2秒毎のイベント発生 Me.Timer1.Start() End Sub Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick ' 出力ウインドウに現在時刻表示 Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss")) End Sub End Class
実行結果が出力ウインドウには以下様に2秒毎に時刻が表示されます。
Start... Timer1 : 10:28:23 Timer1 : 10:28:25 Timer1 : 10:28:27 Timer1 : 10:28:29
■タイマイベント内に時間の掛かる処理をさせる
タイマイベントの中に時間のかかる処理を追加して、出力ウインドウへの表示を見てみます。
Public Class frmMulEvent Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 「Timer1」設定・開始 Me.Timer1.Interval = 2000 ' 2秒毎のイベント発生 Me.Timer1.Start() End Sub Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick ' 出力ウインドウに現在時刻表示 Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss")) ' 時間の掛かる処理を[Sleep]で行う System.Threading.Thread.Sleep(1000 * 5) '5秒待つ処理 End Sub End Class
タイマコントロールのインターバルは2秒ですが、タイマイベント処理では5秒待つ処理を行っていますので、 出力ウインドウには以下様に5秒毎に時刻が表示されます。
Start... Timer1 : 10:55:43 Timer1 : 10:55:48 Timer1 : 10:55:53 Timer1 : 10:55:58
タイマイベント処理がインターバルよりも超えているためにイベントは発生せずに、5秒毎の時刻表示になります。 この5秒間に発生しているタイマイベントはキャンセルされている様に見えます。
本当のことを言えば、この様な処理は問題があります。実際にはイベント内の処理時間はインターバルの時間内に終わる様にするべきだと思います。
但し逆に言えば、タイマイベント処理自身が処理しているときは再度自分自身が呼ばれる(このことを再入と言います)ことは無いことになります。 つまり二重に処理されることはありませんので、これを逆手に取ってもいい場合があるかもしれません。
■タイマイベント内に再入を引き起こす処理をさせてみる
タイマイベントの処理で System.Threading.Thread.Sleep メソッドで時間待ちをしていた部分をメッセージボックスの表示にしてみます。 ソースは以下の通りです。
Public Class frmMulEvent Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 「Timer1」設定・開始 Me.Timer1.Interval = 2000 ' 2秒毎のイベント発生 Me.Timer1.Start() End Sub Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick ' 出力ウインドウに現在時刻表示 Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss")) ' メッセージBOXを表示させる MsgBox("Timer1 のイベント処理中") End Sub End Class
これを実行すると以下の様にメッセージボックスが順次表示されてしまいます。 このままにしておくと無限に繰り返されて、やがて限界が来たところでエラーで止まると思います。
この様な現象が起こるのは、メッセージボックスの処理の中でイベントを受け付ける様になっているためです。 メッセージボックス自身にボタンコントロールが在るため、このボタンのクリックイベントを受け付けるためで、その他のイベントも拾ってしまう為です。
■タイマコントロールを2個設置して1個のタイマイベント内に時間の掛かる処理をさせる
Public Class frmMulEvent Private Sub frmMulEvent_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' ラベルに現在時刻表示 Debug.Print("Start...") ' 「Timer1」設定・開始 Me.Timer1.Interval = 2000 ' 2秒毎のインターバル Me.Timer1.Start() ' 「Timer2」設定・開始 Me.Timer2.Interval = 4000 ' 4秒毎のインターバル Me.Timer2.Start() End Sub Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick ' 出力ウインドウに現在時刻表示 Debug.Print("Timer1 : " & Now.ToString("hh:mm:ss")) System.Threading.Thread.Sleep(1000 * 5) End Sub Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick ' 出力ウインドウに現在時刻表示 Debug.Print("...Timer2 : " & Now.ToString("hh:mm:ss")) End Sub End Class
このソースの実行結果は以下の通りです。Timer2 のタイマイベントは時々阻止されているのがわかると思います。
Start... Timer1 : 02:02:15 ...Timer2 : 02:02:20 Timer1 : 02:02:20 ...Timer2 : 02:02:25 Timer1 : 02:02:25 Timer1 : 02:02:30 ...Timer2 : 02:02:35 Timer1 : 02:02:35 Timer1 : 02:02:40 ...Timer2 : 02:02:45
■ボタンコントロールを2個設置して1個のクリックイベント内に時間の掛かる処理をさせる
以下の様にフォーム上にボタンコントロールを2個設置して Button1 のクリックイベント内に時間の掛かる処理をさせる様にします。
Public Class frmMulEventBtn Private Sub frmMulEventBtn_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' 開始メッセージ出力ウインドウに表示 Debug.Print("...Start") End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' 出力ウインドウに現在時刻表示 Debug.Print("Button1 : " & Now.ToString("hh:mm:ss")) System.Threading.Thread.Sleep(1000 * 5) End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click ' 出力ウインドウに現在時刻表示 Debug.Print("Button2 : " & Now.ToString("hh:mm:ss")) End Sub End Class
...Start Button1 : 02:40:41 ⇒この時点で「Button2」を3回クリック Button2 : 02:40:46 Button2 : 02:40:46 Button2 : 02:40:46
Button1 をクリックした直後に Button2 を連続してクリックします。 Button2 をクリックしたすぐには時刻表示が無く Button1 のクリックイベント処理が終わった直後、 3個連続して時刻表示が行われます。
Button1 のイベント処理中には処理が他には移らないのですが、ボタンのクリックは覚えている様で連続して3回の時刻表示がされます。
これは結構問題で Button1 の処理が終わったと勘違いして Button2 の処理が行われない様にする必要があります。 解決方法としては、ボタンイベント処理中には全てのボタンをクリック不可の状態にするか、 もしくはフラグを導入してボタンクリック処理が連続して行わない様にするかではないでしょうか。
尚、キー入力などでも同様なことが起きるはずですので、この様な現象が起きない様に考える必要があります。関連する記事
⇒フォーム上のコントロールのイベント処理の一括関連付け:[AddHandler,DirectCast]
⇒フォーム上のコントロールのイベント処理の一括関連付けその2:[AddHandler,DirectCast]
⇒コントロールの同じイベント処理に複数の関連付けをテスト:[AddHandler,DirectCast]
⇒Timerコントロールの使い方 :[Timer,Interval]
⇒複数のTimerコントロールの使い方 :[Timer,Interval]
⇒コントロールを配列で処理する方法 :[AddHandler,DirectCast]
-
フォーム上にピクチャボックスのみ配置しても、フォームが画像よりもサイズが小さい場合、スクロールはできません。 そこで、フォーム上にパネルを貼付て、そのパネル上にピクチャボックスを全面表示で貼り付けます。
画像イメージは System.Drawing.Image.FromFile メソッドで設定します。 尚、画像ファイルは以下のものを使います。
ソースは以下の様になります。
フォームロード時にパネルを生成しスクロール許可を与えます。 更に、ピクチャボックスを生成し、各種の設定を行ってからパネルのコントロールに追加し、 そのパネルをフォームのコントロールに追加します。パネルとピクチャボックスによる画像表示
Public Class frmPanelPic ' パネルの静的変数宣言 Dim Panel1 As Panel ' ピクチャボックスの静的変数宣言 Dim Pic1 As PictureBox 'フォームロード時イベント処理 Private Sub frmPanelPic_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' パネルの生成 Panel1 = New Panel ' [Dock]:フォーム全体にFill Panel1.Dock = DockStyle.Fill ' スクロール許可 Panel1.AutoScroll = True ' ピクチャボックスの生成 Pic1 = New PictureBox ' [SizeMode]:ピクチャボックスの大きさに合わせる指定 Pic1.SizeMode = PictureBoxSizeMode.AutoSize ' 画像イメージ Pic1.Image = System.Drawing.Image.FromFile("Desert.jpg") ' ピクチャボックスをパネル上に追加表示 Panel1.Controls.Add(Pic1) ' パネルをフォーム上に追加 Me.Controls.Add(Panel1) End Sub End Class
これを実行すると以下の様な表示になります。 左側は初期表示で、右側はフォームのサイズを大きくした場合の状態です。
関連する記事
⇒標準コントロールのパネルの使い方について(Panel)
⇒標準コントロールのパネルの使い方について2(複数Panelの表示切替)
-
フォーム上に複数のパネルを配置して、ある条件のもとで各パネルを表示することで異なる処理が行えます。
今回は2個のパネルをフォーム上に貼り、それぞれのパネルは異なるコントロールを配置します。
1個目はテキストボックスを5個配置し、2個目のパネルはチェックボックスを5個配置します。
更に、パネルの切り替え用にラジオボタンを2個設置します。 デザイン画面では以下の様になります。
尚、ソースは以下の様になります。
フォームロード時にフォームの幅を1個のパネルのみを表示する幅に設定し、パネル2の座標位置をパネル1と同じにします。 また、初期のパネル切替を行う為にパネル1の状態を「ON」にします。
パネルそれぞれに対応するラジオボタンのクリック時に、対応するパネル切替を行う様にしています。2個のパネルの表示切替
Public Class frmPanelM1 ' フォームロード時処理 Private Sub frmPanelM1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 'ウインドウの横幅を1個のパネルのみ表示する「300」にする Me.Width = 300 'パネル2の横位置をパネル1と同位置にする Me.Panel2.Left = Me.Panel1.Left '最初のパネル1に設定 Me.RadioButton1.Checked = True End Sub ' ラジオボタン1変化時処理 Private Sub RadioButton1_CheckedChanged(sender As Object, e As EventArgs) Handles RadioButton1.CheckedChanged If DirectCast(sender, RadioButton).Checked = True Then ' ラジオボタン1がONの場合、パネル1表示 Call Me.DisplayPanel(1) End If End Sub ' ラジオボタン2変化時処理 Private Sub RadioButton2_CheckedChanged(sender As Object, e As EventArgs) Handles RadioButton2.CheckedChanged If DirectCast(sender, RadioButton).Checked = True Then ' ラジオボタン2がONの場合、パネル2表示 Call Me.DisplayPanel(2) End If End Sub ' パネル表示切替処理 Private Sub DisplayPanel(ByVal intPanelNo As Integer) Select Case intPanelNo Case 1 Me.Panel1.Visible = True Me.Panel2.Visible = False Case 2 Me.Panel1.Visible = False Me.Panel2.Visible = True End Select End Sub End Class
これを実行すると以下の様な表示になります。右側はラジオボタン2をクリックしてパネル2を表示した状態です。
(ラジオボタンを交互にチェックすれば、パネル1⇔パネル2が交互に表示されます)関連する記事
⇒標準コントロールのパネルの使い方について(Panel)
⇒パネル上にピクチャボックスを貼付て画像スクロールさせる方法ついて(Panel、PictureBox)