質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

3回答

6785閲覧

C# ループ中に発生したDocumentCompleted イベントは、どうしてループ終了後にまとめて発生してしまうのか?

KentarouOgura

総合スコア105

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2015/10/14 16:16

編集2015/10/14 16:25

すいません。

先ほど質問させて頂いた内容がまだ未解決のまま、
似たような質問をさせて頂きます。

ボタンクリックイベント中に、
ループでwebbrowserコントロールに、
連続してURLをセットし、
その都度、DocumentCompleted イベントを発生させて、
画面キャプチャを取得していくという処理を作成しようとしています。

けれども、

private void button_Click(object sender, EventArgs e) { for (int i = 0; i < 10; i++) { // Add an event handler that prints the document after it loads. webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(GetMonthDayCapcha); // コンボで選択されているデータにジャンプ webBrowser.Url = new Uri(@year_label_data[i].ToString()); //メッセージキューに現在あるWindowsメッセージをすべて処理する System.Windows.Forms.Application.DoEvents(); } } // キャプチャ処理 private void GetMonthDayCapcha(object sender, WebBrowserDocumentCompletedEventArgs e) { ここにキャプチャ処理がある。 }

このように書くと、URLがセットされるごとにキャプチャ処理が実行されず、
ループが終了し、クリックイベントが終了してから、
キャプチャ処理が連続で実行されます。

この場合だと、クリックイベント時に、
webbrowserに、連続して10回URLがセットされて、
クリックイベントが終了してから、
10回画面キャプチャ処理が走ってしまいます。

なので、結局最後にセットされたURLのキャプチャが10回とれてしまうという感じになってしまいます。

どうしてこんな風になってしまうのでしょう?

こういう場合は、正しくはどう書いたらいいのでしょう?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

こんにちは。

DocumentCompletedがループ終了後にまとめて発生している事の直接的な原因ですが、
webBrowser.Url = new Uri(...)によって、即時に指定ドキュメントの読み込みが開始される訳ではない事になると思います。

ご質問中コードのDoEventsでは回避できないと思います

このあたりの関連するイベントとしては、以下があります。
Navigating : 新しいドキュメントに移動する前に発生
Navigated : 新しいドキュメントに移動し、ドキュメントの読み込みを開始したときに発生
DocumentCompleted : ドキュメントの読み込みが終了したときに発生

正確な表現にはならないと思いますが、webBrowser.Url = ...の時点では読み込む(移動する)事が予約されているだけで、それが即時に開始される事は保障(?)されないと思います。

他のご質問も結果として同様の話だと思えます

【webBrowserイベント】
https://msdn.microsoft.com/ja-jp/library/system.windows.forms.webbrowser_events%28v=vs.110%29.aspx


簡単にイベント発生のタイミングを確認するコードを書いてみました。
イメージ説明

C#

1namespace WebBrowserEvent 2{ 3 public partial class Form1 : Form 4 { 5 6 public Form1() 7 { 8 InitializeComponent(); 9 webBrowser1.Navigating += new WebBrowserNavigatingEventHandler(webBrowser1_Navigating); 10 webBrowser1.Navigated += new WebBrowserNavigatedEventHandler(webBrowser1_Navigated); 11 webBrowser1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser1_DocumentCompleted); 12 13 System.Diagnostics.Trace.WriteLine("====================================================================="); 14 } 15 16 /// <summary> 17 /// button1のクリック 18 /// </summary> 19 /// <param name="sender"></param> 20 /// <param name="e"></param> 21 private void button1_Click(object sender, EventArgs e) 22 { 23 System.Diagnostics.Trace.WriteLine(">> button1_Click start.\n"); 24 25 webBrowser1.Url = new Uri(textBox1.Text); 26 27 // 良くないけれど10秒待機 28 System.Threading.Thread.Sleep(10 * 1000); 29 30 System.Diagnostics.Trace.WriteLine(">> button1_Click end.\n"); 31 } 32 33 // 新しいドキュメントに移動する前に発生する 34 private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e) 35 { 36 System.Diagnostics.Trace.WriteLine("[WebBrowserNavigating] URL : " + e.Url.ToString() + "\n"); 37 } 38 // 新しいドキュメントに移動し、ドキュメントの読み込みを開始したときに発生する 39 private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e) 40 { 41 System.Diagnostics.Trace.WriteLine("[webBrowserNavigated] URL : " + e.Url.ToString() + "\n"); 42 } 43 // ドキュメントの読み込みが終了したときに発生する 44 private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) 45 { 46 System.Diagnostics.Trace.WriteLine("[webBrowserDocumentCompleted] URL : " + e.Url.ToString()); 47 System.Diagnostics.Trace.WriteLine("> " + ((WebBrowser)sender).Url.ToString() + "\n"); 48 } 49 } 50}

(例)"http://www.google.co.jp"で確認した結果
イメージ説明

乱暴なやり方ですが。。button1_Clickの中で10秒待機(Sleep)させていますが、Navigated(読み込みを開始)が発生しているのは、button1_Clickが終わってからになっています。

これを回避して期待する動作をさせるためには、yubaさんから頂いているようなDocumentCompletedイベントを使って繋いでいく(次へ移動させる)か、スレッドを起こす必要があると思います。

DocumentCompletedをチェックしつつ、タイマーで刻んで処理を進める事でも実現可能とは思えます

あまり良くなさそうですが・・・

一点注意が必要なのは、DocumentCompletedが複数発生するサイトが存在することです。これに対処するためにはDocumentCompletedで受け取る事が出来る値(object, ...EventArgs)を確認する等が必要になるかも知れません。

((WebBrowser)object).Url」や「e.Url」あたり

基本的には待ち等含めて時間の掛かるような処理はClick他イベント内から外に出した方が良いと思います。

上記のコードでも10秒間のSleepがあり、その間フォームの操作(移動する等)が出来ないです。。

投稿2015/10/15 04:03

sgr-2

総合スコア294

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KentarouOgura

2015/10/15 04:21

うわぁ!わ、わかりやすい。。。 よ~くわかりました。。。 ありがとうございますありがとうございますm(_ _)m
guest

0

webbrowserオブジェクトは昔Webアプリテストの自動化のために使ったことがあるだけでもう細かい仕様は覚えていないのですがリクエストにお応えして。

クリックイベントの中で10回のループを回すのではなく、ループ変数(i)はフィールドにしてクリックイベントでは初回のURL呼び出しのみ行い、OnDocumentCompleteイベントの最後でi+1回目のURL呼び出しを行うようにしてはいかがですか。
もちろん、i==10なら次回のURL呼び出しはもうしません。

投稿2015/10/15 00:10

yuba

総合スコア5568

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KentarouOgura

2015/10/15 00:13

ありがとうございます! やってみます!!!
KentarouOgura

2015/10/15 00:38

すいません。。。 やりかたがよくわかりません。。。>_< >ループ変数(i)はフィールドにして これの意味がよくわかりません。。。 >OnDocumentCompleteイベントの最後でi+1回目のURL呼び出しを行う OnDocumentCompleteイベントの最後? OnDocumentCompleteイベント中でループするのではなくて??
yuba

2015/10/15 01:05

ループはしないんです。 iは今ローカル変数ですよね? そうでなく、今作っているクラスのフィールドにします。 ループで繰り返すのでなく、1回目の処理が終わったらその後片付けとして2回目の開始を指示する、2回目の後片付けとして3回目の開始を指示する、という構造にするのです。 その、今何回目かという情報をイベントハンドラ間でもちまわるため、ローカル変数でなくフィールドにするわけです。
KentarouOgura

2015/10/15 03:32

あぁ、なるほど、なんとなくわかりました。やってみます!
guest

0

C#初心者なので、外していたらゴメンなさい。

原因は、読み込み完了を待つ処理がどこにもないからだと思います。

おそらく、10回のループなんて一瞬で完了しますよね?なので、クリックイベントは画面キャプチャ処理を待たずに終了します。

WebBrowserDocumentCompletedEventHandler(GetMonthDayCapcha)webBrowser.DocumentCompleted へ10回追加してあるので、画面読み込みの完了が 10回通知 され ** 通知を受け取る度にキャプチャ** を実施するので、都合10回分のキャプチャ取得が実施されます。しかし、どのURL分かを区別する術はありません。

そして、取得先のURLは、画面の取得処理の進捗状況とは無関係に、webBrowser.Url に対して、ほぼ一瞬のうちに上書きされます。

ですので、実際に画面の取得処理が開始される時点では10番目のURLが書き込まれているので、10番目のURLを読み込む処理が1回実施され、その同じ画面に対して完了通知を受け取ってキャプチャする処理だけが10回繰り返されているのだと思います。

具体的な改善案をご提示できないのですが、たとえばループの中に、下記の実装例を参考に待つ処理を入れてみてはいかがでしょうか。

サイトを巡回して自動でクリックしたいなんてときに便利なWebBrowser

// URLを開く
webBrowser1.Navigate("http://www.google.co.jp/");

// 更新
webBrowser1.Refresh();

// 読み込み完了まで待つ
try
{
while (webBrowser1.IsBusy || webBrowser1.ReadyState != WebBrowserReadyState.Complete)
{
System.Threading.Thread.Sleep((int)(0.5 * 1000));
Application.DoEvents();
}
}
catch (Exception) { }

ReadyState プロパティを監視することで、画面の読み込みを待つことができます。

もし、画面読み込みとキャプチャの処理を非同期に10画面分並列して実施したいのであれば、webBrowserオブジェクトのインスタンスを10個生成しないとダメなのではないかと思います。(ご参考

投稿2015/10/14 22:05

pi-chan

総合スコア5936

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KentarouOgura

2015/10/14 22:32

ありがとうございます!やってみます!
KentarouOgura

2015/10/14 22:50

うまくいかないです。。。何故か、whileのループが抜けださず、ずっとループし続けます。。。 デバッグで止めてステータスを見ると、ReadStateはCompleteになってるんですが。。。 IsBusyを消して、ReadStyleだけ見る感じでも、やっぱり同じです。。。
pi-chan

2015/10/14 23:35

C#自体をきちんと理解できておらずごめんなさい。 下記ページは参考になりませんか? > http://ironcat.info/tamabukuro/?p=93 「senderをWebBrowserでキャストし」の部分が重要な気がします。
KentarouOgura

2015/10/14 23:58

なるほど。。。 つまり、ReadStyleを監視する処理を別関数(別スレッド?)にするという事のようですが、具体的な方法がよくわかりません。。。webbrowserを関数の引数で渡して、そこでループさせるとかでしょうかねぇ?>_<
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問