• ベストアンサー

画面の更新とスレッド

ボタンを押すと、テキストボックスに10msごとに文字列を追加する関数実行するプログラムを作成したのですが、実行している間プログラムの画面の操作を受け付けなくなってしまいます。 そこで、 http://msdn.microsoft.com/ja-jp/library/ms171728(VS.80).aspx を参考にして、メインスレッド以外のスレッドからテキストボックスに書き込むようにしました。 しかし、このようにしても、 Application::DoEvents(); を、forの中で実行して強制的に画面を再描画させないと、インターフェイスへの操作を一切受け付けなくなってしまいます。 Windowsフォームの描画は、いったいどの様なタイミングで行われているのでしょうか? また、このようなインターフェイスの操作を受け付けなくなることを回避するには、 Application::DoEvents(); 以外には良い方法は無いのでしょうか?

質問者が選んだベストアンサー

  • ベストアンサー
回答No.4

 こんにちは。  例外が発生しなかったので、其の侭進めてきてしまったのですが、御指摘を受け、スレッド内にデバッガを飛び込ませたら、例外が生じました・・・。  MSDNのメモまでは目を通しておりませんでした。嗚呼、何と運の悪い事か・・・。  で、此れでは駄目ですか? //同じ #include<windows.h> #include<mmsystem.h> #pragma comment(lib, "winmm.lib") //メンバ変数 private: System::ComponentModel::BackgroundWorker^ bkgWorker; //下ごしらえ private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) { this->button1->Tag = gcnew System::Boolean(false); this->bkgWorker = gcnew System::ComponentModel::BackgroundWorker(); this->bkgWorker->DoWork += gcnew System::ComponentModel::DoWorkEventHandler(this, &Form1::DoWork); this->bkgWorker->RunWorkerCompleted += gcnew System::ComponentModel::RunWorkerCompletedEventHandler(this, &Form1::RunWorkerCompleted); this->bkgWorker->ProgressChanged += gcnew System::ComponentModel::ProgressChangedEventHandler(this, &Form1::ProgressChanged); this->bkgWorker->WorkerReportsProgress = true; } //ボタンを押す private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { System::Boolean% rbRunning = *static_cast<System::Boolean^>(this->button1->Tag); rbRunning ^= true; if(rbRunning) { this->button1->Text = L"実行中"; this->bkgWorker->RunWorkerAsync(); } else { this->button1->Text = L"レディ"; } this->button1->Update(); } //スレッド中 private: System::Void DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) { DWORD dwStart = ::timeGetTime(); System::Int32 i32 = 0; while(*static_cast<System::Boolean^>(this->button1->Tag)) { const DWORD dwLast = ::timeGetTime(); if(dwLast - dwStart >= 10) { this->bkgWorker->ReportProgress(i32++); dwStart = dwLast; } } } //此処でテキスト変更 private: System::Void ProgressChanged(System::Object^ sender, System::ComponentModel::ProgressChangedEventArgs^ e) { this->textBox1->Text = e->ProgressPercentage.ToString(); } //スレッド終了 private: System::Void RunWorkerCompleted(System::Object^ sender, System::ComponentModel::RunWorkerCompletedEventArgs^ e) { System::Windows::Forms::MessageBox::Show(L"スレッドが停止しました"); }

glarelance
質問者

お礼

皆さんどうも有り難うございます。 C++.Net超初心者な私には、さっぱり分からない関数も多いのでこれを参考に少しずつ調べて、やってみます。 どうも有り難うございました。

その他の回答 (3)

  • hidebun
  • ベストアンサー率50% (92/181)
回答No.3

#2さんのコードは、問題があると思います。例外が発生しませんか? BackGroundWorkerは、メモとして、 「DoWork イベント ハンドラでユーザー インターフェイス オブジェクトを操作しないように注意する必要があります。 代わりに、ProgressChanged イベントと RunWorkerCompleted イベントを通じてユーザー インターフェイスと通信します。」 とあります。 http://msdn.microsoft.com/ja-jp/library/system.componentmodel.backgroundworker(VS.80).aspx つまりテキストボックスの内容を変更するような操作は、ProgressChangedイベントで行なわれなければなりません。 http://dobon.net/cgi-bin/vbbbs/cbbs.cgi?mode=al2&namber=23055&no=0&KLOG=1 かくして、UIの変更はProgressChangedで行われなければならないのですが、 やはり10msec毎にそれらの通知を処理できるとは思えません。 10msec精度で制御を行うことは、Windowsでは難しいと思いますし、 そんなに高速にUIを変更することが、それほど有意とは思えないのですが、 その辺りは質問者に委ねることにします。

回答No.2

 こんにちは。C/C++CLIの様ですので、  「System::ComponentModel::BackgroundWorker」クラス  http://msdn.microsoft.com/ja-jp/library/system.componentmodel.backgroundworker(VS.80).aspx  で出来ませんか(特に固まる事はありませんでした)。  以下はボタン1が押される度に、スレッド「開始・停止」を繰り返します。フラグにはボタン1のTagプロパティを使用してます。  後、ボタン1、テキストボックス1共に、Form1内のプロパティです(staticではない)。  参考程度でお願いします。 //此れが無いと駄目 #include<windows.h> #include<mmsystem.h> #pragma comment(lib, "winmm.lib") //此処で下ごしらえする private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e) { this->button1->Tag = gcnew System::Boolean(false); this->bkgWorker = gcnew System::ComponentModel::BackgroundWorker(); this->bkgWorker->DoWork += gcnew System::ComponentModel::DoWorkEventHandler(this, &Form1::DoWork); this->bkgWorker->RunWorkerCompleted += gcnew System::ComponentModel::RunWorkerCompletedEventHandler(this, &Form1::RunWorkerCompleted); } //ボタン1がクリックされた private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { System::Boolean% rbRunning = *static_cast<System::Boolean^>(this->button1->Tag); rbRunning ^= true; if(rbRunning) { this->button1->Text = L"実行中"; this->bkgWorker->RunWorkerAsync(); } else { this->button1->Text = L"レディ"; } this->button1->Update(); } //スレッド中 private: System::Void DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) { DWORD dwStart = ::timeGetTime(); System::Int32 i32 = 0; while(*static_cast<System::Boolean^>(this->button1->Tag)) { const DWORD dwLast = ::timeGetTime(); if(dwLast - dwStart >= 10) { this->textBox1->Text = i32.ToString(); dwStart = dwLast; ++i32; } } } //スレッドが終わると呼ばれる private: System::Void RunWorkerCompleted(System::Object^ sender, System::ComponentModel::RunWorkerCompletedEventArgs^ e) { System::Windows::Forms::MessageBox::Show(L"スレッドが停止しました"); }

  • hidebun
  • ベストアンサー率50% (92/181)
回答No.1

10msec毎ですか、Windowsで正確に10msecの精度で実行を制御するのは難しいな。まあそれはさておき。 >Windowsフォームの描画は、いったいどの様なタイミングで行われているのでしょうか? マルチスレッド化したなら、タイミングはアプリケーションのメインスレッドに制御が戻り、再描画イベントを処理したとき。 テキストボックスに書き込むスレッドは、CPUリソースを使い切るほどに処理が走っているなら、再描画されなくなるかもしれません。 たとえば、10msec毎ではなく、100msec毎にすれば、再描画されますか? 再描画されなければ、時間の取り方に問題がありそうです。 適当なタイミングでスレッドを停止させないと、他のスレッドに制御がわたりませんよ。 (それほどCPUを占有すると、OSが強制的に切り替えるかもしれませんが) ぱっと思いつく問題の解決策は3つぐらい。 1.Application:DoEvents()で強制的にイベントを処理させる 2.10msec毎の描画をあきらめて、適当なタイミングでスレッドを休ませる(Sleepさせる) 3.スレッドのプライオリティを下げる 私なら2.かな。 10msecの精度で実行を制御しようとすると、while文やfor文で常にタイマを監視するようなコードになってるでしょ? マルチコアのCPUなら、SetThreadAffinityMask()とかSetThreadIdealProcessor() で処理スレッドを特定のコア上で実行させられる。 けど、やったことない。試してみるかな。

関連するQ&A

専門家に質問してみよう