フォトアルバム

他のアカウント

更新ブログ

Powered by Six Apart
Member since 03/2005

« 2012年9月 | メイン

スレッドセーフな? 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 を作って渡せば、

メッセージを送らなくても動作が出来ます。

こんな感じで、メッセージのやり取りでスレッド間の同期をとる方法も有るなと言う事でした。

また多分使うだろうから、ココにメモっときました!!