マルチスレッド VB.NET クラスを強制的に単一フォームに結果を表示する
-
07-07-2019 - |
質問
Shared クラスを使用してアプリケーションのすべての共通オブジェクトを格納する Windows フォーム アプリケーションがあります。設定クラスには、定期的に処理を行うオブジェクトのコレクションがあり、メイン フォームに警告を発して更新させる必要があるため、何か興味深いことがあります。
現在、オブジェクトのイベントを通じてこれを行っており、各オブジェクトが作成されると、EventHandler を追加してイベントをフォームにマップし直します。ただし、これらのリクエストが常にフォームのメイン コピーに反映されるわけではないことを示すいくつかの問題が発生しています。たとえば、フォームには通知トレイ アイコンがありますが、フォームがイベントをキャプチャしてバブルを表示しようとしても、バブルは表示されません。ただし、そのコードを変更してアイコンを表示できるようにし (すでに表示されていますが)、バブルを表示すると、2 番目のアイコンが表示され、バブルが適切に表示されます。
これまでに誰かがこれに遭遇したことがありますか?すべてのイベントをフォームの単一インスタンスで強制的にキャプチャする方法はありますか、それともこれを処理するまったく別の方法はありますか?必要に応じてコードサンプルを投稿することもできますが、これは一般的なスレッドの問題であると考えています。
詳しくは: 現在、フォームのイベント ハンドラーで Me.InvokeRequired を使用していますが、この場合は常に FALSE を返します。また、このフォームから表示するときに作成される 2 番目のトレイ アイコンにはコンテキスト メニューがありませんが、「実際の」アイコンにはコンテキスト メニューがあります。これで誰かがわかるでしょうか?
髪の毛抜いちゃうよ!そんなに難しいことじゃないよ!
解決:ヒントをくれた novagz に感謝します。そのおかげで、私が現在使用しているコードにたどり着きました (これは見事に機能していますが、もっと良い方法があると思わずにはいられません)。「IsPrimary」というプライベート ブール変数をフォームに追加し、次のコードをフォーム コンストラクターに追加しました。
Public Sub New()
If My.Application.OpenForms(0).Equals(Me) Then
Me.IsFirstForm = True
End If
End Sub
この変数が設定され、コンストラクターが終了すると、イベント ハンドラーに直接進み、私はこのように処理します (注意:私が探しているフォームはアプリケーションのプライマリ フォームであるため、My.Application.OpenForms(0) は必要なものを取得します。非起動フォームの最初のインスタンスを探している場合は、それが見つかるまで繰り返す必要があります)。
Public Sub EventHandler()
If Not IsFirstForm Then
Dim f As Form1 = My.Application.OpenForms(0)
f.EventHandler()
Me.Close()
ElseIf InvokeRequired Then
Me.Invoke(New HandlerDelegate(AddressOf EventHandler))
Else
' Do your event handling code '
End If
End Sub
まず、正しいフォームで実行されているかどうかを確認します。そうでない場合は、正しいフォームを呼び出します。次に、スレッドが正しいかどうかを確認し、正しくない場合は UI スレッドを呼び出します。次に、イベント コードを実行します。3 回の呼び出しになる可能性があるのは気に入らないのですが、他に方法が思いつきません。少し面倒ではありますが、うまく機能しているようです。誰かがそれを行うより良い方法を持っているなら、私はそれを聞きたいです!
繰り返しになりますが、すべての助けに感謝します。これには私は気が狂いそうなほどでした。
解決
スレッドの問題でもあると思います。イベントハンドラーで Control.Invoke() を使用していますか?.NET は通常、アプリのデバッグ時に違反を検出しますが、検出できない場合もあります。NotifyIcon もその 1 つですが、スレッドのアフィニティをチェックするためのウィンドウ ハンドルがありません。
OPが質問を変更した後に編集します:
古典的な VB.NET のトラップは、型名で Form インスタンスを参照することです。Form1.NotifyIcon1.Something のように。スレッドを使用すると、期待どおりに機能しません。作成されます 新しい Form1 クラスのインスタンスを作成します。既存のインスタンスは使用しません。このインスタンスは表示されず (Show() が呼び出されなかった)、メッセージ ループをポンプしないスレッドで実行されているため、ドアネイルとして機能しません。2 番目のアイコンが表示されたら、それは間違いありません。スレッドから使用していることがわかっている場合に InvokeRequired = False が取得されることも同様です。
既存のフォーム インスタンスへの参照を使用する必要があります。それが難しい場合 (通常はクラス コンストラクターに引数として "Me" を渡します)、Application.OpenForms を使用できます。
Dim main As Form1 = CType(Application.OpenForms(0), Form1)
if (main.InvokeRequired)
' etc...
他のヒント
Control.InvokeRequiredを使用して適切なスレッド上にあるかどうかを判断し、そうでない場合はControl.Invokeを使用します。
フォームのInvokeメソッドのドキュメントをご覧ください。フォームを所有するスレッドでフォームを更新するコードを実行できるようにします(Windowsフォームはスレッドセーフではありません)。 何かのようなもの Private Delegate Sub UpdateStatusDelegate(ByVal newStatus as String)
パブリックサブUpdateStatus(ByVal newStatus as String) If Me.InvokeRequired Then Dim d As New UpdateStatusDelegate(AddressOf UpdateStatus) Me.Invoke(d、new Object(){newStatus}) その他 'フォームのステータスを更新する End If
サンプルコードを提供していただければ、さらにカスタマイズされた例を提供させていただきます。
OPがInvokeRequiredを使用していると言った後に編集します。
InvokeRequiredを呼び出す前に、フォームハンドルが作成されていることを確認してください。私が生きているHandleCreatedプロパティがあります。コントロールに現在ハンドルがない場合、InvokeRequiredは常にfalseを返します。これは、正しい操作を行ったにもかかわらず、コードがスレッドセーフではないことを意味します。見つけたら質問を更新してください。いくつかのサンプルコードも役立ちます。
c#では次のようになります。
private EventHandler StatusHandler = new EventHandler(eventHandlerCode)
void eventHandlerCode(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(StatusHandler, sender, e);
}
else
{
//do work
}
}