[2018/03/22] 複数のTimerコントロールの使い方 (No.26)
[2018/03/22] Timerコントロールの使い方 (No.25)
[2018/03/22] コントロールの同じイベント処理に複数の関連付けをテスト (No.24)
[2018/03/22] フォーム上のコントロールのイベント処理の関連付け (No.23)
[2018/03/22] フォーム上のコントロールで[Enter]キー押下で次のコントロールにフォーカス移動する (No.22)
-
×
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
-
Timerコントロールを複数使う場合の注意点などを例を挙げて説明します。
尚、単体でのTimerコントロールの使い方については以下のページを参照下さい。
⇒Timerコントロールの使い方
複数のTimerコントロールをフォームに張り付けた場合には、 タイマイベントの発生が同じ時間間隔を設定しておいても同時には発生しません。 同時に起こった場合には最初に発生したイベントの処理が終わった後でしか 次のイベントが発生しない様です。 フォーム上に貼られた複数のTimerコントロールは同一のスレッド内で 動作しているためだと思います。
実際のプログラムでそのことを見ていきます。
先ず、フォーム上にTimerコントロールを2個貼り、Timer1、Timer2とします。 さらに表示用にTextBoxコントロールを貼り、MultiLineをTrueとし、ScrollbarをVerticalに設定します。 また、タイマーの開始・終了を行うButtonコントロールを貼ります。
フォームロードイベントではTimerの間隔を100msec(0.1sec)に設定し、 「開始」「停止」ボタンクリックイベントでTimerを開始及び停止しています。
注目点は、タイマ1のTickイベントで、テキストボックスに現在時刻表示を行いますが、 その後にSleepによる待ち処理を行っています。 このSleepは待ち時間が終わるまでは戻ってこないので、Tickイベントが待たされることになります。複数のTimerコントロールの使い方
Public Class frmTimerMul '''
''' フォームロードイベント ''' Private Sub frmTimer_Load(sender As Object, e As EventArgs) Handles Me.Load 'ラベルに現在時刻表示 Me.TextBox1.Text = "" 'Timerの間隔を100msec(0.1sec)に設定 Me.Timer1.Interval = 100 Me.Timer2.Interval = 100 'Timerを停止状態にする Me.Timer1.Enabled = False Me.Timer2.Enabled = False End Sub '''''' タイマ1Tickイベント ''' Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick 'テキストボックスに現在時刻表示 Me.TextBox1.Text &= "Timer1 = " & Now.ToString("hh:mm:ss.fff") & vbCrLf Me.TextBox1.Refresh() '5秒待たせる System.Threading.Thread.Sleep(5 * 1000) End Sub '''''' タイマ2Tickイベント ''' Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick 'テキストボックスに現在時刻表示 Me.TextBox1.Text &= "Timer2 = " & Now.ToString("hh:mm:ss.fff") & vbCrLf End Sub '''''' 「停止」ボタンクリックイベント ''' Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click 'Timerを停止する Me.Timer1.Stop() Me.Timer2.Stop() End Sub '''''' 「開始」ボタンクリックイベント ''' Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click 'TextBoxクリア Me.TextBox1.Text = "" 'Timerを開始する Me.Timer1.Start() Me.Timer2.Start() End Sub End Classこのプログラムを実行すると以下の様な表示になります。
Timer1の時刻表示が行われ、5秒後にTimer2の時刻表示が行われます。 Timer1、Timer2ともに0.1秒毎のタイマイベントが発生するのですが Timer1のタイマイベントではSleepによる5秒の待ちが在るため、 Timer2のタイマイベントが待たされている様です。 尚、Timer2のタイマイベント処理はすぐに終わるため、 Timer2の時刻と次のTimer1の時刻はほぼ同じものとなります。
そこで以下の様にして考察してみます。 Timer1、Timer2ともに5秒毎のインターバルとし、 Timer1のタイマイベント処理にMsgBoxの待ちを入れてみます。Timerコントロールでの注意すること
Public Class frmTimerMul2 '''
''' フォームロードイベント ''' Private Sub frmTimer_Load(sender As Object, e As EventArgs) Handles Me.Load 'ラベルに現在時刻表示 Me.TextBox1.Text = "" 'Timerの間隔を5000msec(5sec)に設定 Me.Timer1.Interval = 5000 Me.Timer2.Interval = 5000 'Timerを停止状態にする Me.Timer1.Enabled = False Me.Timer2.Enabled = False End Sub '''''' タイマ1Tickイベント ''' Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick 'テキストボックスに現在時刻表示 Dim strTime As String = "Timer1 = " & Now.ToString("hh:mm:ss.fff") & vbCrLf Me.TextBox1.Text &= strTime '待たせる MsgBox("Waiting..." & strTime, MsgBoxStyle.OkOnly, "タイマ1Tickイベント") End Sub '''''' タイマ2Tickイベント ''' Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick 'テキストボックスに現在時刻表示 Me.TextBox1.Text &= "Timer2 = " & Now.ToString("hh:mm:ss.fff") & vbCrLf End Sub '''''' 「停止」ボタンクリックイベント ''' Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click 'Timerを停止する Me.Timer1.Stop() Me.Timer2.Stop() End Sub '''''' 「開始」ボタンクリックイベント ''' Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click 'TextBoxクリア Me.TextBox1.Text = "" 'Timerを開始する Me.Timer1.Start() Me.Timer2.Start() End Sub End Classこれを実行して10数秒そのままにしておくと、以下の様なことが起こります。
Timer1のタイマイベントの中にMsgBoxを入れたことによりこの様になりました。
MsgBoxはボタンを持った1個のウインドウの様なもので、 この処理の中でウインドウのメッセージ処理が行われているため Timer1の次のタイマイベントの処理が受け付けられてしまい、次々とMsgBoxの表示がされてしまいます。 (ウインドウのメッセージ処理については、ここでは説明しませんがMsgBoxが行われることで 他のイベント処理を受付可能になるということです)
結果、タイマイベント処理の中ではあまり時間のかかる処理や、別のイベントを誘発するような処理は行わない方が良い様です。 (このことは、複数のTimerだからということではありませんが)
タイマコントロールではなく、スレッドを使ったタイマ処理は以下を参照して下さい。関連する記事
⇒Timerコントロールの使い方 :[Timer,Interval]
⇒スレッドタイマの使い方 :[System.Threading.Timer,Delegate,Invoke]
⇒スレッドタイマの使い方(イベント処理が時間が掛かる場合):[System.Threading.Timer,Delegate,Invoke]
⇒引数があるスレッドの実行について(パラメータ付きスレッド)
⇒スレッドを停止させる方法についておすすめ本
PR -
標準コントロールの中では少し異質なTimerコントロールは、ある一定時間ごとに処理を行う時には必要になります。 今回の例は、そのTimerコントロールの簡単な使い方を説明します。
Timerコントロールを使うには、フォーム上にツールボックスからTimerを選択し貼り付けます。 以下の図は、Timerコントロールを貼り付けた後の様子です。
MSDNの説明によれば以下の様になっています。Timer コントロール・について
<プロパティ> ・Enabled :タイマーが実行されているかどうかを取得または設定します。 ・Interval:取得または設定の時間 (ミリ秒単位) 前に、 Tick の最後に見つかった位置を基準としたイベントは、 Tick イベントです。 <メソッド> ・Start() :タイマーを起動します。 ・Stop() :タイマーを停止します。 <イベント> ・Tick :指定したタイマーの間隔が経過し、タイマーが有効である場合に発生します。
今回の例としては、フォーム上に Timer を貼り付け Tick イベントで 現在の時刻を表示し、時計の様な動作をします。
Timerコントロールの使い方
Public Class frmTimer '''
''' フォームロードイベント ''' Private Sub frmTimer_Load(sender As Object, e As EventArgs) Handles Me.Load 'ラベルに現在時刻表示 Me.Label1.Text = Now.ToString("hh:mm:ss") 'Timerの間隔を1000msec(1sec)に設定 Me.Timer1.Interval = 1000 'Timerを開始する Me.Timer1.Enabled = True End Sub '''''' タイムTickイベント ''' Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick 'ラベルに現在時刻表示 Me.Label1.Text = Now.ToString("hh:mm:ss") End Sub End Class今回の例は、プログラム的には非常に簡単なものです。 一つ注意する点としては、Timerの間隔設定の値がミリ秒である点です。
秒単位での間隔を設定するには、その秒数を1000倍した値をTimer.Intervalに設定します。
それでは、少しプログラムに手を加えて、Timerの中断、開始が出来る様にします。 中断、開始を行う為に2個のボタンコントロールをフォームに張り付けて、処理を追加します。 上記のソースにボタンClickイベントを追加したものを、以下に示します。Timerコントロールの停止・開始の方法
Public Class frmTimer '''
''' フォームロードイベント ''' Private Sub frmTimer_Load(sender As Object, e As EventArgs) Handles Me.Load 'ラベルに現在時刻表示 Me.Label1.Text = Now.ToString("hh:mm:ss") 'Timerの間隔を1000msec(1sec)に設定 Me.Timer1.Interval = 1000 'Timerを開始する Me.Timer1.Enabled = True End Sub '''''' タイムTickイベント ''' Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick 'ラベルに現在時刻表示 Me.Label1.Text = Now.ToString("hh:mm:ss") End Sub '''''' 「停止」ボタンクリックイベント ''' Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click 'Timerを停止する Me.Timer1.Stop() End Sub '''''' 「開始」ボタンクリックイベント ''' Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click 'Timerを開始する Me.Timer1.Start() End Sub End ClassTimerの開始・停止メソッドのStart Stopを使用し処理していますが、 EnabledプロパティをTrue / Falseに設定することでも同じことが行えます。
ここで、Timerを使う上で注意することがあるのですが、タイムTickイベントの処理が Timerに設定したIntervalの値より掛かってしまったらどうなるかです。
そこで、タイムTickイベントの中に10秒の待ちを行う処理を入れてみました。タイムTickイベントのみ抜粋
'''
''' タイムTickイベント ''' Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick 'ラベルに現在時刻表示 Me.Label1.Text = Now.ToString("hh:mm:ss") '10秒待たせる System.Threading.Thread.Sleep(10 * 1000) End SubタイムTickイベントの中に10秒の待ちを行う処理を入れると、 時刻の表示が10秒毎にしか変化しません。 タイムTickイベントが呼び出されて処理が終わるまでは、 次のイベントが呼び出されないことになります。
Intervalを超える処理時間が掛かった時に、次のイベントが呼び出されないことが良いかどうかは 求める仕様に関わってくると思います。
常にIntervalの時間間隔でイベントが呼び出される様にしたいのであれば、 このTimerコントロールでは無く、別の方式で行う必要があります。 この件に付いては別のページで説明したいと思います。
タイマコントロールではなく、スレッドを使ったタイマ処理は以下を参照して下さい。関連する記事
⇒複数のTimerコントロールの使い方 :[Timer,Interval]
⇒スレッドタイマの使い方 :[System.Threading.Timer,Delegate,Invoke]
⇒スレッドタイマの使い方(イベント処理が時間が掛かる場合):[System.Threading.Timer,Delegate,Invoke]
⇒引数があるスレッドの実行について(パラメータ付きスレッド)
⇒スレッドを停止させる方法について
-
フォーム上のラベルコントロールのクリックイベント処理を複数宣言した場合には、 どの様になるのかをテストしてみました。
フォーム上にテキストボックスとラベルコントロールを貼り付けます。 フォームロードイベントでは、テキストボックスの属性を設定し、 最初のラベルクリックイベントを宣言します。
その後、最初のラベルクリックイベントをコピーして2回貼り付けし、 それぞれ別の関数名に直します。
結果、以下の様なソースになります。コントロールの同じイベント処理に複数の関連付けをテスト
Public Class frmHandler '''
''' フォームロードイベント ''' Private Sub frmHandler_Load(sender As Object, e As EventArgs) Handles MyBase.Load 'テキストボックスの設定 Me.TextBox1.Multiline = True '複数行許可 Me.TextBox1.ScrollBars = ScrollBars.Vertical 'スクロールバー:縦のみ End Sub '''''' ラベルクリックイベント1 ''' Private Sub Label1_Click(sender As Object, e As EventArgs) Handles Label1.Click Me.TextBox1.Text &= "最初のクリック" & vbCrLf End Sub '''''' ラベルクリックイベント2 ''' Private Sub Label1_Test2_Click(sender As Object, e As EventArgs) Handles Label1.Click Me.TextBox1.Text &= "2番目のクリック" & vbCrLf End Sub '''''' ラベルクリックイベント3 ''' Private Sub Label1_Test3_Click(sender As Object, e As EventArgs) Handles Label1.Click Me.TextBox1.Text &= "3番目のクリック" & vbCrLf End Sub End Classこのプログラムを実行し、ラベルをクリックすると以下の様な表示なります。
テキストボックスの表示には、思った様な結果にはなりませんでした。 イベントの発生が、ソースの順番になるのかと思っていたのですが、 イベントの発生順番は、それには依存しない様です。
そこで、Addhandlerでイベント処理を宣言した場合にはどうなるのかと 思い、以下の様にソースを変更してみました。コントロールの同じイベント処理に複数の関連付けをテスト2
Public Class frmHandler2 '''
''' フォームロードイベント ''' Private Sub frmHandler_Load(sender As Object, e As EventArgs) Handles MyBase.Load 'テキストボックスの設定 Me.TextBox1.Multiline = True '複数行許可 Me.TextBox1.ScrollBars = ScrollBars.Vertical 'スクロールバー:縦のみ 'ラベルクリックイベントに処理関数の関連付け AddHandler Me.Label1.Click, AddressOf Label1_Click AddHandler Me.Label1.Click, AddressOf Label1_Test2_Click AddHandler Me.Label1.Click, AddressOf Label1_Test3_Click End Sub '''''' ラベルクリックイベント1 ''' Private Sub Label1_Click(sender As Object, e As EventArgs) Me.TextBox1.Text &= "最初のクリック" & vbCrLf End Sub '''''' ラベルクリックイベント2 ''' Private Sub Label1_Test2_Click(sender As Object, e As EventArgs) Me.TextBox1.Text &= "2番目のクリック" & vbCrLf End Sub '''''' ラベルクリックイベント3 ''' Private Sub Label1_Test3_Click(sender As Object, e As EventArgs) Me.TextBox1.Text &= "3番目のクリック" & vbCrLf End Sub End Classこのプログラムを実行し、ラベルをクリックすると以下の様な表示なります。
テキストボックスの表示は、思った様な結果なったのですが、 イベントの発生順番についていろいろネットで調べたのですが、 Addhandlerでも保証はされない様です。
とにかく、イベント処理では発生順番に依存せずに処理が問題無く動作する様に組むべきだと思います。関連する記事
⇒フォーム上のコントロールで[Enter]キー押下で次のコントロールにフォーカス移動する
⇒フォーム上のコントロールのイベント処理の一括関連付け:[AddHandler,DirectCast]
⇒コントロールを配列で処理する方法 :[AddHandler,DirectCast]
-
フォーム上のコントロールへのイベント処理を宣言する場合には、 以下の図の様に各コントロールを選択し、右側のイベントの中から選択すると、 対応するイベントの処理関数の宣言が自動で生成を行ってくれます。
通常はこの方法で良いのですが、多くのコントロールに設定するには手間ですし、 同じ様な処理の場合には、処理関数を1個作成しそれを各コントロールに設定したいものです。
そこで AddHandler メソッドを使うことになります。
MSDNの説明によれば以下の様になっています。AddHandler ステートメントについて
AddHandler event, AddressOf eventhandler 指定項目 event 処理するイベントの名前。 eventhandler イベントを処理するプロシージャの名前。 解説 AddHandler ステートメントと RemoveHandler ステートメントを使うと、 プログラムの実行中にいつでもイベント処理を開始および停止できます。 eventhandlerプロシージャのシグネチャは、eventイベントのシグネチャと一致する必要があります。
今回の例としては、フォーム上に3個の TextBox を貼り付けています。
[Enter]キーを押下することで3個の TextBox のフォーカスが移動します。 更に、フォーカスが渡った TextBox の文字列が反転表示されます。フォーム上のコントロールのイベント処理の関連付け
Public Class frmAddHandler '''
''' フォームロードイベント ''' Private Sub frmAddHandler_Load(sender As Object, e As EventArgs) Handles Me.Load 'コントロールへのイベントハンドラ関連付け Call Me.SetEvent(Me.Controls) 'フォームがすべてのキー イベントを受け取る Me.KeyPreview = True End Sub '''''' コントロールへのイベントハンドラ関連付け ''' '''コントロールコレクション</param> Private Sub SetEvent(ByVal CtrlColl As Control.ControlCollection) 'コントロール変数 Dim objControl As Control 'テキストボックスコントロール変数 Dim objTextBox As TextBox 'コントロールがある分だけループ For Each objControl In CtrlColl If TypeOf objControl Is TextBox Then 'テキストボックスコントロールに変換 objTextBox = DirectCast(objControl, TextBox) 'Enterイベントの関連付け AddHandler objTextBox.Enter, AddressOf CtrlEnterEvent 'Changedイベントの関連付け AddHandler objTextBox.TextChanged, AddressOf CtrlTextChangedEvent Else End If Next End Sub '''''' コントロールのEnterイベント処理 ''' Private Sub CtrlEnterEvent(ByVal sender As Object, ByVal e As System.EventArgs) If TypeOf sender Is TextBox Then 'TextBoxのとき DirectCast(sender, TextBox).SelectAll() End If End Sub '''''' コントロールのTextChangedイベント処理 ''' Private Sub CtrlTextChangedEvent(ByVal sender As Object, ByVal e As System.EventArgs) If TypeOf sender Is TextBox Then 'TextBoxのとき Dim objTextBox As TextBox = DirectCast(sender, TextBox) '仮の処理として表示内容をTagに退避 objTextBox.Tag = objTextBox.Text End If End Sub '''''' フォームKeyDownイベント ''' Private Sub frmEnterNext_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown If e.KeyCode = Keys.Enter Then If e.Control = False Then '[Enter]キーで次の TabIndex があるコントロールへフォーカスを移す Me.SelectNextControl(Me.ActiveControl, Not e.Shift, True, True, True) End If End If End Sub '''''' フォームKeyPressイベント ''' Private Sub frmEnterNext_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles MyBase.KeyPress If e.KeyChar = ControlChars.Cr Then '[Enter]キーで音が出るので、キーイベントが処理されたことにして音を消す e.Handled = True End If End Sub End Class関連する記事
⇒フォーム上のコントロールで[Enter]キー押下で次のコントロールにフォーカス移動する
⇒コントロールの同じイベント処理に複数の関連付けをテスト:[AddHandler,DirectCast]
⇒コントロールを配列で処理する方法 :[AddHandler,DirectCast]
-
入力フォームで TextBox 等のフォーカス移動を[Enter]キー押下で行いたいという要望は結構あります。 汎用機などでは昔から[Enter]キー押下で入力域を移動できたため残ってきた入力方式です。 この方法をVB.NETで実現するには、各入力コントロールで[Enter]キー押下を判定して次のコントロールに フォーカスを移動すればいいのですが、各コントロールに制御をプログラムするのは手間です。 そのため、フォームで[Enter]キーの押下を検知して、そこで一括に制御すれば簡単に行えます。
フォームの KeyPreview プロパティを True に設定し、 フォームKeyDownイベントで SelectNextControl メソッドを使用しフォーカス移動をさせます。SelectNextControl メソッドについて
Public Function SelectNextControl(ctl As System.Windows.Forms.Control , forward As Boolean , tabStopOnly As Boolean , nested As Boolean , wrap As Boolean) As Boolean 概要: 次のコントロールをアクティブにします。 パラメーター: ctl: 検索を開始する位置にある System.Windows.Forms.Control。 forward: タブ オーダー内を前方に移動する場合は true。 後方に移動する場合は false。 tabStopOnly: System.Windows.Forms.Control.TabStop プロパティが false に設定されている コントロールを無視する場合は true。それ以外の場合は false。 nested: 入れ子になった(子コントロールの子)子コントロールを含める場合は true。 それ以外の場合は false。 wrap: タブ オーダーの最後のコントロールに到達した後、 タブ オーダーの最初のコントロールから検索を続行する場合は true。 それ以外の場合は false。 戻り値: コントロールがアクティブにされた場合は true。それ以外の場合は false。
今回の例としては、フォーム上に3個の TextBox を貼り付けています。
[Enter]キーを押下することで3個の TextBox のフォーカスが移動します。フォーム上のコントロールで[Enter]キー押下で次のコントロールにフォーカス移動する
Public Class frmEnterNext '''
''' フォームKeyDownイベント ''' Private Sub frmEnterNext_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown If e.KeyCode = Keys.Enter Then If e.Control = False Then '[Enter]キーで次の TabIndex があるコントロールへフォーカスを移す Me.SelectNextControl(Me.ActiveControl, Not e.Shift, True, True, True) End If End If End Sub '''''' フォームKeyPressイベント ''' Private Sub frmEnterNext_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles MyBase.KeyPress If e.KeyChar = ControlChars.Cr Then '[Enter]キーで音が出るので、キーイベントが処理されたことにして音を消す e.Handled = True End If End Sub '''''' フォームロードイベント ''' Private Sub frmEnterNext_Load(sender As Object, e As EventArgs) Handles Me.Load 'フォームがすべてのキー イベントを受け取る Me.KeyPreview = True End Sub End Class関連する記事
⇒フォーム上のコントロールのイベント処理の一括関連付け:[AddHandler,DirectCast]
⇒コントロールの同じイベント処理に複数の関連付けをテスト:[AddHandler,DirectCast]