重要なお知らせ

「教えて! goo」は2025年9月17日(水)をもちまして、サービスを終了いたします。詳細はこちら>

電子書籍の厳選無料作品が豊富!

スレッド内でコントロールや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;
}

A 回答 (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を使った方が
# いい気がします。
# 安全にスレッドを中断できるので。
    • good
    • 0
この回答へのお礼

とりあえず内容としては解決できたのでBAとさせてもらいますね。
スレッドはどぼんの.NET Tipsに書いてあったものをベースに
少し書いていましたがその時のには全くコントロールが
出てこないで今回コントロールの制御はどうすれば良いのか
というところでつまづいてしまってこんな質問をすることとなりました。

まだスレッド稼働中に終了したときの処理は入れてないので
またその辺りのことの質問を再度したいと思います。

お礼日時:2010/04/22 00:21

#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);
}
    • good
    • 0

#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'です。」

補足日時:2010/04/21 14:57
    • good
    • 0

例外の通り、コントロールが作成されたスレッド以外から直接コントロールを操作してはいけません。


コントロールのメソッドやプロパティの中で別スレッドから直接呼んでもよいものは
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に入れてカウントアップをさせようという
ものなのですが…スレッドを実行してみると固まってしまいました。

補足日時:2010/04/21 01:14
    • good
    • 0

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(コントロール名)'がアクセスされました。

補足日時:2010/04/20 00:58
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!