-
フォーム上のコントロールへのイベント処理を宣言する場合には AddHandler メソッドを使うことを以下の記事で紹介しました。
⇒コントロールの同じイベント処理に複数の関連付けをテスト:[AddHandler,DirectCast]
この記事の中で、各イベントの処理を1個の関数で処理できることを示しましたが、そのなかでは同じ処理を行っていました。 今回はこの関数の中でそれぞれのコントロールに対して内部で処理を分けることで、 同じコントロールのイベント関数としては入口は1個ですが、別々の処理を記述できます。 これはコントロールを配列で処理する方法の逆のアプローチになると思います。
⇒コントロールを配列で処理する方法 :[AddHandler,DirectCast]
今回のソースでは KeyPress イベント用関数を追加し、テキストボックスコントロールのそのイベントに関連付けます。 KeyPress イベント用関数のなかでは、どのテキストボックスから呼ばれたのかを判別する為、 呼び出し元オブジェクトの sender の名前 name を使って場合分けします。
今後、テキストボックスが増えた場合にはこの判定と、その処理を追加することになります。フォーム上のコントロールのイベント処理の一括関連付けその2
Public Class frmAddHandler2 ' フォームロードイベント Private Sub frmAddHandler_Load(sender As Object, e As EventArgs) Handles Me.Load 'コントロールへのイベントハンドラ関連付け Call Me.SetEvent(Me.Controls) 'フォームがすべてのキー イベントを受け取る Me.KeyPreview = True End Sub ' コントロールへのイベントハンドラ関連付け ' param CtrlColl :コントロールコレクション 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 'KeyPressイベントの関連付け AddHandler objTextBox.KeyPress, AddressOf CtrlTextKeyPressEvent 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 ' コントロールのKeyPressイベント処理(1カ所で集中管理する) Private Sub CtrlTextKeyPressEvent(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) 'コントロールの名前により処理を分ける Select Case sender.name Case "TextBox1" '[TextBox1]は数字のみ入力とする If Not ("0" <= e.KeyChar And e.KeyChar <= "9") Then e.Handled = True End If Case "TextBox2" '[TextBox2]は英字大文字の A ~ Z のみ入力とする If Not ("A" <= e.KeyChar And e.KeyChar <= "Z") Then e.Handled = True End If Case "TextBox3" '[TextBox3]は英字子文字の a ~ z のみ入力とする If Not ("a" <= e.KeyChar And e.KeyChar <= "z") Then e.Handled = True End If End Select 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
これを実行すると以下の様に TextBox1 には数字のみ TextBox2 には英大文字("A"~"Z")のみ TextBox3 には英子文字("a"~"z")のみ、の入力ができます。
今回の方法は、とにかく同じコントロールの同じイベント処理を1個の関数で集中的に処理したい場合には有効だと思います。 テキストボックスのそれぞれのイベントで記述すれば同じじゃないかという突っ込みはあると思いますが...関連する記事
⇒フォーム上のコントロールのイベント処理の一括関連付け:[AddHandler,DirectCast]
PR -
あるEXEファイルから、他のEXEファイルの中のフォームを強制的に表示する方法として以下の記事で説明しましたが、
⇒他のEXEファイルにあるフォームを表示する方法(Activator.CreateInstanceの使い方)
この方法ではフォームを表示するだけで、面白味が在りませんので、今回はフォームにプロパティとメソッドを追加して それらに対するアクセス方法を説明します。
先ずは、フォームにテキストボックスを1個追加し、それに対して値を設定したり取得するプロパティと、 テキストボックスの内容を表示するメソッドを追加します。呼び出される側のフォーム
Public Class Form1 Private mstrParamData As String 'テキストボックスに値を設定するプロパティ Public WriteOnly Property SetParamData As String Set(value As String) Me.TextBox1.Text = value End Set End Property 'テキストボックスの内容を取得するプロパティ Public ReadOnly Property GetTextData As String Get Return Me.TextBox1.Text End Get End Property '外部からアクセス可能なメソッド Public Sub DspTextBox() MessageBox.Show("TextBox1.Text : " & Me.TextBox1.Text) End Sub 'ボタンクリックイベント Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click '自分を閉じる Me.Close() End Sub End Class
上記のフォームが含まれる REF0002.EXE を呼出す側のソースは以下の通りです。
こちらの方は、特にフォームを持たずにMain() 関数からの実行となっています。呼び出す側のMainソース
Public Module Main Sub Main() '自分自身の実行ファイルのパスを取得する Dim appPath As String = _ System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) 'アセンブリ"REF0002.exe"を読み込む(「REF0002.EXE」は「REF0001.EXE」が存在するフォルダに在ります) Dim asmExe As System.Reflection.Assembly = _ System.Reflection.Assembly.LoadFile(appPath & "\REF0002.exe") 'Form1のTypeを取得する(名前空間を指定しているのでフォーム名の前に名前空間を付加) Dim typForm As Type = asmExe.GetType("REF0002.Form1") 'フォーム作成用変数 Dim frm As Form 'フォームのインスタンスを作成 Dim obj As Object = Activator.CreateInstance(typForm) 'インスタンスをフォームとして扱う frm = DirectCast(obj, Form) 'プロパティの設定 Dim pinf As System.Reflection.PropertyInfo = typForm.GetProperty("SetParamData") pinf.SetValue(frm, "Set From REF0001", Nothing) 'フォームの ShowDialog メソッドを使用する frm.ShowDialog() 'プロパティの取得 pinf = typForm.GetProperty("GetTextData") Dim str As String = CStr(pinf.GetValue(frm, Nothing)) MessageBox.Show(str) 'メソッドの利用 Dim minf As System.Reflection.MethodInfo = typForm.GetMethod("DspTextBox") minf.Invoke(frm, Nothing) End Sub End Module
プロパティの情報(System.Reflection.PropertyInfo)を取得する為に、Typeの GetProperty メソッドを使います。 その PropertyInfo の SetValue メソッドでプロパティへの値を設定します。
また、 PropertyInfo の GetValue メソッドでプロパティの値の取得を行います。
さらにメソッドの実行は、プロパティと同様にTypeの GetMethod メソッドによりメソッド情報(System.Reflection.MethodInfo)を取得し PropertyInfo の Invoke メソッドで該当するメソッドの実行を行います。
これを実行すると以下の様な表示になります。関連する記事
⇒他のEXEファイルにあるフォームを表示する方法(Activator.CreateInstanceの使い方)
-
あるEXEファイルから、他のEXEファイルの中のフォームを強制的に表示するにはどうすれば出来るのかを調べてみました。
方法として、EXEファイルの中にあるアセンブリ名(フォーム名)を指定して型情報(Type)を取得し、 そのTypeを利用して Activator.CreateInstance メソッドでフォームのインスタンスが生成されます。
そのインスタンスが持っているフォームの表示メソッドを使えば、そのフォームの表示が行えます。
先ずは、呼び出されるフォーム側のソースです。こちらの方はEXE名を REF0002.EXE となる様に設定してます。 また、名前空間も同様に REF0002 としています。呼び出される側のフォーム
Public Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click '自分を閉じる Me.Close() End Sub End Class
上記のフォームが含まれる REF0002.EXE を呼出す側のソースは以下の通りです。
こちらの方は、特にフォームを持たずにMain() 関数からの実行となっています。呼び出す側のMainソース
Public Module Main Sub Main() '自分自身の実行ファイルのパスを取得する Dim appPath As String = _ System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) 'アセンブリ"REF0002.exe"を読み込む(「REF0002.EXE」は「REF0001.EXE」が存在するフォルダに在ります) Dim asmExe As System.Reflection.Assembly = _ System.Reflection.Assembly.LoadFile(appPath & "\REF0002.exe") 'Form1のTypeを取得する(名前空間を指定しているのでフォーム名の前に名前空間を付加) Dim typForm As Type = asmExe.GetType("REF0002.Form1") 'フォーム作成用変数 Dim frm As Form 'フォームのインスタンスを作成 Dim obj As Object = Activator.CreateInstance(typForm) 'インスタンスをフォームとして扱う frm = DirectCast(obj, Form) 'フォームの ShowDialog メソッドを使用する frm.ShowDialog() End Sub End Module
これを実行すると以下の様な表示になります。
ソースは特に難しいところは無いかと思います。 Form1のTypeを取得するところで asmExe.GetType("REF0002.Form1") としていますが、これは REF0002.EXE での名前空間を REF0002 に 設定してあるからです。 この名前空間を空白にすれば asmExe.GetType("Form1") で可能です。関連する記事
⇒他のEXEファイルにあるフォームを表示する方法その2(Activator.CreateInstanceの使い方)
-
業務システムでは似た様なフォームを作成することがありますが、 共通となる部分を基本的なフォームクラスとして作成しておき、 それを継承することでデザインの統一を図ったりします。
今回はフォームクラスの継承の方法について順を追って説明します。
まず最初に基本となるフォームに1個のパネルを設置し、その上に1個のボタンを置く様にしました。 (フォームの名前は「BaseForm」としました。) デザイン画面は以下の様な感じです。
その後、ボタンのクリックイベントで簡単なメッセージを表示する様にしました。最初のBaseFormのソース
Public Class BaseForm Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click MsgBox("BaseForm : Button1_Click") End Sub End Classここで一旦このプロジェクトのビルドを行います。 その後、プロジェクトにこの「BaseForm」を利用するフォームの追加を行ってみます。 プロジェクトへの項目の追加を行うために、 「新しい項目追加」ダイアログで 「Windows Forms」 の中の 「継承されたフォーム」 を選択します。 フォーム名は Form1 のままとします。
この後で「継承ピッカー」ダイアログが表示されますので 「BaseForm」 を選択して「OK」をクリックします。
結果的に以下の様にフォームデザイン画面が表示されます。 (フォーム上のパネルとボタンの所に小さいマークが付いていますが、このデザインからは修正できない状態であるロックが掛かった状態であることを示しています。)
この後、このプロジェクトのプロパティを開いて 「スタートアップ オブジェクト」 を 「Form1」 に設定します。
このプロジェクトを実行させると以下の様になります。画面はボタンをクリックした時の状態を示します。
Form1 では特に何もソースは記述していませんが、継承されたフォームの処理が出来ることが分かると思います。
このままでは面白くないので 「BaseForm」 にボタンのタイトルを変更するプロパティを追加してみます。タイトルプロパティの追加のBaseFormのソース
Public Class BaseForm ' ボタン1タイトルに文字列設定 Public Property Button1Title As String Get Return Me.Button1.Text End Get Set(value As String) Me.Button1.Text = value End Set End Property Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click MsgBox("BaseForm : Button1_Click") End Sub End ClassForm1のソース
Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load 'ボタンタイトル設定 Me.Button1Title = "ボタン" 'フォームタイトルも設定 Me.Text = "Form1 から設定されたタイトル" End Sub End Classこれを実行すると以下の様に、ボタンのタイトルが「ボタン」に変わったことが分かります。
さらに、ボタン処理を継承先から上書き変更できる様にしたいと思います。 BaseForm 側では Button1_Click メソッドに Overridable 修飾子を付加して上書き可能であることを宣言します。
Form1 側では Button1_Click メソッドを上書きすることを宣言するため Overrides 修飾子を付加します。
ボタン処理をオーバーライド指定としたBaseFormのソース
Public Class BaseForm ' ボタン1タイトルに文字列設定 Public Property Button1Title As String Get Return Me.Button1.Text End Get Set(value As String) Me.Button1.Text = value End Set End Property 'オーバーライド指定 Protected Overridable Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click MsgBox("BaseForm : Button1_Click") End Sub End ClassForm1のソース:ボタン処理をオーバーライド
Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load 'ボタンタイトル設定 Me.Button1Title = "ボタン" 'フォームタイトルも設定 Me.Text = "Form1 から設定されたタイトル" End Sub Protected Overrides Sub Button1_Click(sender As Object, e As EventArgs) '別の処理にする MsgBox("Form1 からのボタン処理") End Sub End Classこれを実行すると以下の様に、ボタンクリック処理が Form1 の処理しか行われないことが分かります。
もし、継承元のボタン処理も行いたい場合には Form1 を以下の様に変更します。 これを実行し、ボタンをクリックすると2回メッセージボックスが表示されます。Form1のソース:ボタン処理をオーバーライド2
Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load 'ボタンタイトル設定 Me.Button1Title = "ボタン" 'フォームタイトルも設定 Me.Text = "Form1 から設定されたタイトル" End Sub Protected Overrides Sub Button1_Click(sender As Object, e As EventArgs) '継承元のボタン処理 MyBase.Button1_Click(sender, e) '別の処理にする MsgBox("Form1 からのボタン処理") End Sub End Class
関連する記事
⇒フォームクラス生成時に設定値を同時に渡す方法
⇒フォーム上の複数のコントロール(TextBox)の入力変化を確認する方法について
⇒フォームのマウスカーソルを待機状態する方法について
⇒フォームクラスの継承の方法について
-
スレッドを以下のプログラムの様に作成し開始させると Main 関数は先に終了しますが、スレッドは約5秒後にしか終了しないため、 全体のプログラムの実行はスレッドが終了するまで続きます。
コンソールへの表示からも分かりますが「Main終了」が表示された後で「...スレッド」が5回表示されます。
これは何もおかしいことでは無く、プログラムは属する全てのスレッドが終了するまでは終われないのです。
Main 関数も1個のスレッド(メインスレッド)として実行されるのでが処理的に先に終わってしまいます。
しかし、別のスレッドとして起動された ThreadProc 関数が残っているので、このスレッドが終了するまで プログラムは終了しません。スレッドの実行プログラム
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module取敢えず、実行結果がコンソールに以下の様に表示されます。
スレッド開始 Main終了 ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド
それでは Main 関数の中でスレッドの終了を待つにはどうすればいいのでしょうか。 そのために Thread クラスには Join メソッドがあります。 この Join をスレッドの実行側で呼出せば、呼び出し側の処理がブロックされます。
(Join 実行した場合に制御が戻ってこない)
先ほどの例に Join を追加した例を示します。
スレッドの実行を待つプログラム
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() 'スレッド終了待機 pThread.Join() Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module実行結果は以下の様に「Main終了」が最後に表示されます。
スレッド開始 ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド Main終了
Join の替わりにスレッドが生きているかどうかのフラグを見て以下の様にしても同じです。
スレッドの実行を待つプログラム2
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() 'スレッド終了待機 While pThread.IsAlive = True Threading.Thread.Sleep(10) End While Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 5 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module
それでは標題にある様にスレッドを停止させる方法についてですが、 Abort メソッドを使用します。 スレッドの中で非常に時間の掛かる処理を行わせて、途中で停止させる例を以下に示します。スレッドの実行を途中で停止させるプログラム
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド生成 Dim pThread As Thread = New Thread(AddressOf ThreadProc) Console.WriteLine("スレッド開始") 'スレッド開始 pThread.Start() '10秒待つ Threading.Thread.Sleep(10000) 'スレッド停止 pThread.Abort() 'スレッド終了待機 pThread.Join() Console.WriteLine("Main終了") End Sub 'スレッド関数 Sub ThreadProc() For i As Integer = 1 To 1000 '時間の掛かる処理 Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Module実行結果は以下の様になり「...スレッド」が10回表示されてからスレッドが停止していることが分かります。
スレッド開始 ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド ...スレッド Main終了
スレッドをいきなり終了させるのも怖いので、スレッドに対してフラグで指示を行い、 スレッドの中でフラグを見て終了させるようにしてみます。
尚、スレッドを含む処理をクラスの中に入れ込んで、 その中で開始メソッドや停止メソッドを作成します。スレッドの実行を途中で停止させるクラスの導入
Imports System Imports System.Threading Module ThreadStop 'メイン関数(エントリーポイント) Sub Main() 'スレッド用クラスの生成 Dim pclsThread As New clsThread 'スレッド開始 pclsThread.Start() Console.WriteLine("スレッド開始") '10秒待つ Threading.Thread.Sleep(10000) 'スレッド停止 pclsThread.Abort() Console.WriteLine("Main終了") End Sub 'スレッド用クラス Class clsThread 'スレッド Private mThread As Thread = Nothing 'スレッド停止フラグ Private mblnStop As Boolean = False '開始メソッド Sub Start() mThread = New Thread(AddressOf ThreadProc) 'スレッド開始 mThread.Start() End Sub '停止メソッド Sub Abort() 'スレッド停止フラグON mblnStop = True 'スレッド停止を待つ mThread.Join() End Sub 'スレッド関数 Private Sub ThreadProc() For i As Integer = 1 To 1000 If Me.mblnStop = True Then 'スレッド停止フラグONならば処理中断 Exit For End If Threading.Thread.Sleep(1000) Console.WriteLine("...スレッド") Next End Sub End Class End Module