- ベストアンサー
スレッド内でコントロールやWin32APIを使うには?
- メールソフトを開発中につまづいた事があったので質問します。
- 現状のプログラムでは単一スレッド上に書いているメール受信コードなのですが、フォームを操作すると応答なしになってしまいます。
- スレッドとして受信コードを移動しようと思って実験してみましたが、コントロールを呼び出す部分で例外が発生してしまいます。プログレスバーとFlashWindowのAPIを動かしたいのですが、うまく動作しません。
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
#2,3です。 > 破棄されたオブジェクトにアクセスできません。 すみません。#3のサンプルには別スレッドが実行中に ウインドウが閉じられた時の処理を書いていませんでした。 この例外は、 ・ウインドウが閉じられてFormオブジェクトが破棄される。 このとき別スレッドは動いたまま ・別スレッドからFormのInvokeが呼ばれるけれどFormはもう無い ということが起こったのだと思われます。 対策としては、Form.FormClosingイベントを拾い、 Formが閉じられるときにスレッドが実行中なら 手動で中断なり終了するまで待機させてやればいいかと思います。 例えば#3に追加するなら class MyForm : Form { volatile bool threadContinueFlg; Thread workerThread; public MyForm() { FormClosing += new FormClosingEventHandler(MyForm_FormClosing); // 略 } void goButton_Click(object sender, EventArgs e) { threadContinueFlg = true; workerThread = new Thread(new ThreadStart(DoWork)); workerThread.Start(); } void DoWork() { while(threadContinueFlg && ++cnt <= 0) { /* 略 */ } } void MyForm_FormClosing(objecr sender, FormClosingEventArgs e) { if (workerThread != null && workerThread.IsAlive) { threadContinueFlg = false; while(workerThread.IsAlive) Application.DoEvents(); } } # どんな方法で別スレッドを作っているかわかりませんが # System.ComponentModel.BackgroundWorkerを使った方が # いい気がします。 # 安全にスレッドを中断できるので。
その他の回答 (4)
- kenken-1
- ベストアンサー率43% (13/30)
#2です。 ProgressBarとかFlashWindowを 別スレットから呼ぶサンプルを書いてみました。 参考になるかどうかは分かりませんがとりあえず。 /// Form上のGoボタンを押すと別スレッドでDoWork()が実行されます。 /// DoWorkの中からInvoke()してProgressBarの値を変えたり、 /// FlashWindowしたりします。 public class MyForm : Form { FlowLayoutPanel panel; ProgressBar progressBar; Button goButton; delegate void ChangeProgressDelegate(int value); delegate void FlashWindowDelegate(); delegate void EnableButtonDelegate(); public MyForm() { panel = new FlowLayoutPanel(); goButton = new Button(); goButton.Text = "Go"; goButton.Click += new EventHandler(goButton_Click); progressBar = new ProgressBar(); progressBar.Minimum = 0; progressBar.Maximum = 100; progressBar.Value = 0; panel.Controls.Add(goButton); panel.Controls.Add(progressBar); Controls.Add(panel); } void goButton_Click(object sender, EventArgs e) { goButton.Enabled = false; new Thread(new ThreadStart(DoWork)).Start(); } void DoWork() { // ここに別スレッドでやりたい事を // ただしコントロールを直接操作してはいけない。 // ProgressBarのValueを変えたいときはそれ専用のメソッドを書いて // this.Invokeを使って呼ぶ。 ChangeProgressDelegate changeProgressDlg = new ChangeProgressDelegate(ChangeProgress); FlashWindowDelegate flashWindowDlg = new FlashWindowDelegate(FlashWindow); int cnt = 0; while (++cnt <= 100) { Invoke(changeProgressDlg, cnt); if (cnt % 20 == 0) Invoke(flashWindowDlg); Thread.Sleep(100); } Invoke(new EnableButtonDelegate(EnableButton)); } void ChangeProgress(int value) { progressBar.Value = value; } void EnableButton() { goButton.Enabled = true; } void FlashWindow() { Win32_FlashWindow(this.Handle, true); } [DllImport("user32.dll", EntryPoint = "FlashWindow")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool Win32_FlashWindow(IntPtr hWnd, [MarshalAs(UnmanagedType.Bool)]bool bInvert); }
補足
とりあえず掲示頂いた目標の達成はできそうなのですが…これを テストしているときに途中でウィンドウの終了をしてしまうとまた 別の例外が発生するのですがこれはどう回避すれば良いのでしょうか? 一応手前にtry文をかませてcatch内で拾った例外発生時のメッセージを 載せておきます。 「破棄されたオブジェクトにアクセスできません。 オブジェクトは'Form1'です。」
- kenken-1
- ベストアンサー率43% (13/30)
#2です。 ProgressBarとかFlashWindowを 別スレットから呼ぶサンプルを書いてみました。 参考になるかどうかは分かりませんがとりあえず。 /// Form上のGoボタンを押すと別スレッドでDoWork()が実行されます。 /// DoWorkの中からInvoke()してProgressBarの値を変えたり、 /// FlashWindowしたりします。 public class MyForm : Form { FlowLayoutPanel panel; ProgressBar progressBar; Button goButton; delegate void ChangeProgressDelegate(int value); delegate void FlashWindowDelegate(); delegate void EnableButtonDelegate(); public MyForm() { panel = new FlowLayoutPanel(); goButton = new Button(); goButton.Text = "Go"; goButton.Click += new EventHandler(goButton_Click); progressBar = new ProgressBar(); progressBar.Minimum = 0; progressBar.Maximum = 100; progressBar.Value = 0; panel.Controls.Add(goButton); panel.Controls.Add(progressBar); Controls.Add(panel); } void goButton_Click(object sender, EventArgs e) { goButton.Enabled = false; new Thread(new ThreadStart(DoWork)).Start(); } void DoWork() { // ここに別スレッドでやりたい事を // ただしコントロールを直接操作してはいけない。 // ProgressBarのValueを変えたいときはそれ専用のメソッドを書いて // this.Invokeを使って呼ぶ。 ChangeProgressDelegate changeProgressDlg = new ChangeProgressDelegate(ChangeProgress); FlashWindowDelegate flashWindowDlg = new FlashWindowDelegate(FlashWindow); int cnt = 0; while (++cnt <= 100) { Invoke(changeProgressDlg, cnt); if (cnt % 20 == 0) Invoke(flashWindowDlg); Thread.Sleep(100); } Invoke(new EnableButtonDelegate(EnableButton)); } void ChangeProgress(int value) { progressBar.Value = value; } void EnableButton() { goButton.Enabled = true; } void FlashWindow() { Win32_FlashWindow(this.Handle, true); } [DllImport("user32.dll", EntryPoint = "FlashWindow")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool Win32_FlashWindow(IntPtr hWnd, [MarshalAs(UnmanagedType.Bool)]bool bInvert); }
- kenken-1
- ベストアンサー率43% (13/30)
例外の通り、コントロールが作成されたスレッド以外から直接コントロールを操作してはいけません。 コントロールのメソッドやプロパティの中で別スレッドから直接呼んでもよいものは Invoke、BeginInvoke、EndInvoke、InvokeRequiredなど限られていて、 特に別スレッドからコントロールを操作するのにはInvokeやBeginInvokeを使います。 使い方については下記のURLなどを参照してみてください。 http://www.atmarkit.co.jp/fdotnet/dotnettips/312ctrlinvoke/ctrlinvoke.html
補足
これに関しては見てみましたが…SetFocus()を単純に呼ぶだけのようですね。 とりあえず私のやりたいことはメール受信時の受信件数情報をステータスバー (StatusStrip)のプログレスバー(progressMail)に表示させたいのと、メールが 1件以上新規で着信していたときに画面を光らせるためにFlashWindowを 使いたいというのが現状の目的です。 Invokeを使ってちょっとサンプルを書いてみましたが、全く動きませんでした。 単純にworkerスレッドに重いforループを仕掛けて、中でforループで回している カウンタの値をprogressBarのValueに入れてカウントアップをさせようという ものなのですが…スレッドを実行してみると固まってしまいました。
- Wr5
- ベストアンサー率53% (2173/4061)
C#は使ったことないので細かいところは不明ですが… まず、補足要求。 ・単一スレッドでは動作していたのか? ・発生した例外の内容は? >// 以下のコードで例外が発生する >progressMail.Visible = true; >progressMail.Minimum = 0; >progressMail.Maximum = pop.Count; >// 以下のコードで例外が発生する >progressMail.Value = no; >// 以下のコードで例外が発生する >progressMail.Visible = false; progressMailが不正だったりしませんか? ダイアログのコントロールかと思われますがスレッドが走っている間、そのコントロールの生存は保証されていますか? スレッド走るのが早すぎて、コントロールが生成されていない(正しく初期化できていない)状態のものを代入していたりしませんか? >// 以下のコードで例外が発生する >FlashWindow(this.Handle, false); this.HandleはHWNDになっていますか?
補足
例外はこんな文言です。 有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール'xxxxx(コントロール名)'がアクセスされました。
お礼
とりあえず内容としては解決できたのでBAとさせてもらいますね。 スレッドはどぼんの.NET Tipsに書いてあったものをベースに 少し書いていましたがその時のには全くコントロールが 出てこないで今回コントロールの制御はどうすれば良いのか というところでつまづいてしまってこんな質問をすることとなりました。 まだスレッド稼働中に終了したときの処理は入れてないので またその辺りのことの質問を再度したいと思います。