
スレッド内でコントロールやWin32APIを使うには?
メールソフトを開発中にまたつまづいた事があったので質問します。
現状のプログラムでは単一スレッド上に書いているメールの受信
コードなのですが、これだとメール受信中にフォームをさわると
応答なしになってしまうので、スレッドとして受信コードを移動しようと
思って実験してみたのですが、コントロールを呼び出す部分で
例外が発生してしまい、どうすれば良いのかが分かりません。
少し調べてみたのですが、内容がよく分かりませんでした。
動かしたいのはプログレスバーとFlashWindowのAPIです。
notifyはなぜか問題なく動いていました。
開発環境はVisualStudio2005のC#(.NET2.0)です。
以下に簡略化したコードを載せます。
int mailCount = 0; // 未受信メール件数
try
{
// POPサーバの認証処理が入ります
// メール件数が1件以上の場合
if(pop.Count >= 1){
// プログレスバーを表示して最大値を未受信メール件数に設定する
// 以下のコードで例外が発生する
progressMail.Visible = true;
progressMail.Minimum = 0;
progressMail.Maximum = pop.Count;
}
else{
// 0件の場合はPOP3から切断する
pop.Close();
return;
}
// 取得したメールをコレクションに追加する
for(int no = 1; no <= pop.Count; no++){
// 受信したメールを配列に格納します
// メールの受信件数分増加させる
// 以下のコードで例外が発生する
progressMail.Value = no;
}
// メール受信後プログレスバーを非表示に戻す
// 以下のコードで例外が発生する
progressMail.Visible = false;
// POP3から切断する
pop.Close();
// 未受信メールが1件以上の場合
if(mailCount >= 1){
if(this.WindowState == FormWindowState.Minimized && Mail.minimizeTaskTray == true && Mail.autoMailFlag == true){
notifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
notifyIcon1.BalloonTipTitle = "新着メール";
notifyIcon1.BalloonTipText = mailCount + "件の新着メールを受信しました。";
notifyIcon1.ShowBalloonTip(300);
}
else{
// 画面をフラッシュさせる
// 以下のコードで例外が発生する
FlashWindow(this.Handle, false);
}
}
else{
// ステータスバーに状況表示する
return;
}
}
catch (nMail.nMailException nex)
{
// ステータスバーにエラー状況表示する
return;
}
catch (Exception exp)
{
// ステータスバーにエラー状況表示する
return;
}
No.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を使った方が
# いい気がします。
# 安全にスレッドを中断できるので。
とりあえず内容としては解決できたのでBAとさせてもらいますね。
スレッドはどぼんの.NET Tipsに書いてあったものをベースに
少し書いていましたがその時のには全くコントロールが
出てこないで今回コントロールの制御はどうすれば良いのか
というところでつまづいてしまってこんな質問をすることとなりました。
まだスレッド稼働中に終了したときの処理は入れてないので
またその辺りのことの質問を再度したいと思います。
No.4
- 回答日時:
#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);
}
No.3
- 回答日時:
#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'です。」
No.2
- 回答日時:
例外の通り、コントロールが作成されたスレッド以外から直接コントロールを操作してはいけません。
コントロールのメソッドやプロパティの中で別スレッドから直接呼んでもよいものは
Invoke、BeginInvoke、EndInvoke、InvokeRequiredなど限られていて、
特に別スレッドからコントロールを操作するのにはInvokeやBeginInvokeを使います。
使い方については下記のURLなどを参照してみてください。
http://www.atmarkit.co.jp/fdotnet/dotnettips/312 …
参考URL:http://www.atmarkit.co.jp/fdotnet/dotnettips/312 …
この回答への補足
これに関しては見てみましたが…SetFocus()を単純に呼ぶだけのようですね。
とりあえず私のやりたいことはメール受信時の受信件数情報をステータスバー
(StatusStrip)のプログレスバー(progressMail)に表示させたいのと、メールが
1件以上新規で着信していたときに画面を光らせるためにFlashWindowを
使いたいというのが現状の目的です。
Invokeを使ってちょっとサンプルを書いてみましたが、全く動きませんでした。
単純にworkerスレッドに重いforループを仕掛けて、中でforループで回している
カウンタの値をprogressBarのValueに入れてカウントアップをさせようという
ものなのですが…スレッドを実行してみると固まってしまいました。
No.1
- 回答日時:
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(コントロール名)'がアクセスされました。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- Visual Basic(VBA) 【Excel VBA】自動メール送信の機能追加 5 2022/09/29 12:53
- JavaScript ①入力フォーム→②確認表示画面→③送信完了画面のコードを書いているのです、 入力フォームから受け取っ 2 2022/05/10 16:45
- JavaScript フォームが空欄の時にフォームの外をクリックすると、エラーが出るコードを調べています。 1 2023/06/25 11:51
- その他(メールソフト・メールサービス) サンダーバードメールにて数万件の受信メール対応方法 2 2023/01/27 13:38
- Visual Basic(VBA) エクセル VBA メール本文に指定セルに記載されているURLをリンクとして記載する方法 8 2022/08/08 07:50
- PHP PHP MySql ページング 2 2022/09/20 06:38
- Visual Basic(VBA) 【追加】ファイルを閉じてダイアログで保存した時だけ処理の実行をする 3 2022/03/23 15:43
- Visual Basic(VBA) Outlook VBAについて 1 2023/07/10 12:41
- Visual Basic(VBA) ファイル全てを .xlsm に変更したところ、プログラムが途中で落ちてしまっています 17 2022/12/07 12:03
- Java java 引数 戻り値のあるメソッド 3 2023/02/12 06:23
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
MFC通信プログラムマルチスレッ...
-
VC++スレッドの正しい終了のさ...
-
スレッドの監視方法について
-
スレッドの監視方法
-
マルチスレッドプログラム
-
スレッドにて同一メモリの書き...
-
WaitForSingleObjectの使い方に...
-
別スレッドのデータを受信できない
-
スレッドの終了の仕方
-
C#でスレッド実行中のイベント...
-
Macターミナルで実行中のプログ...
-
家電製品の電力周波数を変える機械
-
ウインドウにデータを入力する...
-
VBSでのSendKeysでの画面の最小化
-
ACCESS側からEXCELの書式を設定...
-
第三者に画面を同期するソフト...
-
フィボナッチヒープです
-
3のつく数字と3の倍数のみを表...
-
n次元の正規直交基底ベクトルの...
-
メモリが不足しています(VBA)
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
スレッドの監視方法について
-
VC++スレッドの正しい終了のさ...
-
スレッドにて同一メモリの書き...
-
スレッドの終了の仕方
-
VB2005 シリアル通信のClose処理
-
WaitForSingleObjectの使い方に...
-
別スレッドのデータを受信できない
-
同一スレッドで、ロックをかけ...
-
CWnd::OnTimerのスレッドの取得
-
メインダイアログが最背面に表...
-
スレッドの安全な終了のさせ方
-
Windows上で、シグナル(SIGTERM...
-
スレッド一覧の取得
-
複数スレッドを動作させるのに...
-
C言語で一定時間待機後、再実行
-
C#でスレッド実行中のイベント...
-
.netアプリへのSendMessageでフ...
-
DirectX 11のConsntanBuffer
-
スレッド内でコントロールやWin...
-
DirectX LPDIRECT3DDEVICE9のマ...
おすすめ情報