スレッドセーフな? Form へのEvent 渡し
ひっさびさにBlog 更新!!
FB に移行してからもう、コッチは・・・
ですが^^; プログラムメモとしてはやはりBlog でしょう!!
さて、本題!
私は、Form に機能を持たせません。
殆ど、クラスに機能を持たせ、そのクラスだけで実動作を行うように作ります。
なので、メインのプログラムは、クラス内でスレッドでグルグル回ってる作り方が多いです。
で、そのクラスをForm に渡し、メソッドを叩いたり、プロパティを参照する訳ですが、
クラスからForm へ通知したい時にイベントやデリゲートを使います。
所が・・・
クラスからのスレッドでイベントやデリゲートを発生させて、Form で受けて、
Form 内のコントロールにアクセスすると、「スレッドセーフがなにがし山」って言う例外が発生します!!
おっしゃる事は分かりますが、別に良いじゃんって思うのに・・・
で、良くForm 内に変数を作りイベントで内容を一時的にソイツに保存し、
Form 内でRefresh タイマーかなんかを仕掛けて、ソイツで読んだりしていましたが、
今回試したのは、ウィンドウメッセージを使って、処理をして見ました。
そうすると、そのForm でのスレッドでイベントが走るので、
無事に同期がとれた訳です。
Form の上にコイツを一応^^;
Imports System.Runtime.InteropServices
適当な場所に、メッセージ送信用の関数を準備
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function PostMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Boolean
End Function
メッセージを受ける方は、下記のクラスをNew します。
Imports System
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Imports System.Runtime.Serialization.Formatters.Binary
Public Class clsWindowMessage
Inherits NativeWindow
Private prvtargetForm As Form = Nothing
Public Sub New(ByVal targetForm As Form)
prvtargetForm = targetForm
AddHandler prvtargetForm.HandleCreated, AddressOf Me.OnHandleCreated
AddHandler prvtargetForm.HandleDestroyed, AddressOf Me.OnHandleDestroyed
End Sub
Public Sub OnHandleCreated(ByVal sender As Object, ByVal e As EventArgs)
Me.AssignHandle(prvtargetForm.Handle)
End Sub
Public Sub OnHandleDestroyed(ByVal sender As Object, ByVal e As EventArgs)
Me.ReleaseHandle()
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
MyBase.WndProc(m)
End Sub
End Class
上記クラスをForm のNew の時に、
prvWindowMessage = New clsWindowMessage(Me)
します。
※因みに、Form 内に
Private prvWindowMessage As clsWindowMessage
と変数を定義してあります。
受信は、下記のメソッドが呼ばれます。
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If ReciveWindowMessage(m) Then
Exit Sub
End If
MyBase.WndProc(m)
End Sub
Private Function ReciveWindowMessage(ByRef m As System.Windows.Forms.Message) As Boolean
If ref***** Is Nothing Then
Return False
End If
Select Case m.Msg
Case WM_USER_EXEC_INSPECTION : ref*****.ReciveWindowMessage(m)
Case WM_USER_WORK_DIRECTION : ref*****.ReciveWindowMessage(m)
Case WM_USER_CHANGE_RECIPE : ref*****.ReciveWindowMessage(m)
Case WM_USER_CHANGE_CAMERA : ref*****.ReciveWindowMessage(m)
Case WM_USER_CHANGE_USER_LEVEL : ref*****.ReciveWindowMessage(m)
Case WM_USER_SETTING_MODE : ref*****.ReciveWindowMessage(m)
Case WM_USER_DISPLAY_REFRESH : ref*****.ReciveWindowMessage(m)
Case Else
Return False
End Select
Return True
End Function
コイツで受けてます。
んで、ソレを上のクラスにぶっ飛ばしてます。
Public Const WM_USER As UInteger = &H400
Public Const WM_APP As UInteger = &H8000
Public Const WM_USER_EXEC_INSPECTION As UInteger = WM_USER + 1000
Public Const WM_USER_WORK_DIRECTION As UInteger = WM_USER + 1001
Public Const WM_USER_CHANGE_RECIPE As UInteger = WM_USER + 1002
Public Const WM_USER_CHANGE_CAMERA As UInteger = WM_USER + 1003
Public Const WM_USER_CHANGE_USER_LEVEL As UInteger = WM_USER + 1100
Public Const WM_USER_SETTING_MODE As UInteger = WM_USER + 1101
Public Const WM_USER_DISPLAY_REFRESH As UInteger = WM_USER + 1090
メッセージは数字なので、私はConst で上記のように定義しました。
ココで注意は、XP ならWM_USER でそのまま受けれますが、
XP 以降のWindows はフィルタがかかってるため、解除する必要が有ります。
#Region "DllImport"
Private Const MSGFLT_ADD As Integer = 1
Private Const MSGFLT_REMOVE As Integer = 2
<DllImport("user32.dll", EntryPoint:="ChangeWindowMessageFilter", SetLastError:=True, CharSet:=CharSet.Unicode, ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function ChangeMessageFileter(ByVal message As UInteger, ByVal dwflag As Int32) As Boolean
End Function
#End Region
下記のコードをForm のNew に追加し、XP の場合はCatch し、ソレ以降のWindows の場合は、フィルタが解除されます。
Try
ChangeMessageFileter(WM_USER_EXEC_INSPECTION, MSGFLT_ADD)
ChangeMessageFileter(WM_USER_WORK_DIRECTION, MSGFLT_ADD)
ChangeMessageFileter(WM_USER_CHANGE_RECIPE, MSGFLT_ADD)
ChangeMessageFileter(WM_USER_CHANGE_CAMERA, MSGFLT_ADD)
ChangeMessageFileter(WM_USER_CHANGE_USER_LEVEL, MSGFLT_ADD)
ChangeMessageFileter(WM_USER_SETTING_MODE, MSGFLT_ADD)
ChangeMessageFileter(WM_USER_DISPLAY_REFRESH, MSGFLT_ADD)
Catch ex As Exception
'
End Try
基本的には、コレで準備完了となり、後は、そのForm へメッセージを送ります。
メッセージ送信には、そのForm のHandle が必要ですので、
Private prvMainFormHndl As IntPtr
この様な変数に、
prvMainForm = New frmMain(refMasterSetting, Me, refLogClass)
prvMainFormHndl = prvMainForm.Handle
ハンドルを渡しておきます。
そして、スレッドでも何処からでも、
PostMessage(prvMainFormHndl, WM_USER_DISPLAY_REFRESH, 0, 0)
コレを打てば、Form 側が受信してForm 側でイベント処理を行い始めます。
また、上の受信部から飛ばすref*****.ReciveWindowMessage(m) にも、
Dim m As System.Windows.Forms.Message = New System.Windows.Forms.Message()
m.Msg = WM_USER_CHANGE_USER_LEVEL
m.WParam = 0
ref*****.ReciveWindowMessage(m)
こんな感じでSystem.Windows.Forms.Message を作って渡せば、
メッセージを送らなくても動作が出来ます。
こんな感じで、メッセージのやり取りでスレッド間の同期をとる方法も有るなと言う事でした。
また多分使うだろうから、ココにメモっときました!!
最近のコメント