- PR -

DoEventsを使わずにWebBrowserの表示完了を待つ方法

1
投稿者投稿内容
hei
ベテラン
会議室デビュー日: 2006/09/07
投稿数: 78
投稿日時: 2007-01-17 19:58
お世話になります。
これまで、次のようなWebBrowserを継承したクラスを使って、
表示完了を待っていました。

Public Class Browser
Inherits WebBrowser
Public Sub Wait()
Application.DoEvents()
While Me.IsBusy Or Me.ReadyState <> WebBrowserReadyState.Complete
Application.DoEvents()
End While
End Sub
End Class

Public Class Form1
Private _browser As Browser
Private WithEvents _button As Button
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
_button = New Button
Me.Controls.Add(_button)
_browser = New Browser
Me.Controls.Add(_browser)
_browser.Top = _button.Top + _button.Height
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles _button.Click
_browser.Navigate("http://www.yahoo.co.jp")
_browser.Wait()
Dim ret As Boolean = (_browser.Url Is Nothing)
If ret Then
MessageBox.Show("同期失敗")
Else
MessageBox.Show("同期成功")
End If
End Sub
End Class

これまではこれでうまくいっていたのですが、
急にDoeventsの後で制御不能になるようになりました。

同じプログラムを4台のPCで実行してみましたが、
1台はほぼフリーズし(稀に動く)、2台は主に特定のサイトによってダメで、1台は全く問題なく動きます。
フリーズするPCでも、新しいプロジェクトで上記のコードを実行すると、問題なく動きます。
(いづれも常駐ソフトを切ってもやってみましたが、同じでした)
よく分かりませんが、DoEventsが主な原因と思うので、
DoEventsを使わずに同期をとることはできないかと考えたのですが、
なかなかうまくいきません。

Navigateしたら、Sleepさせるかロックオブジェクトのウエイトセットに入れておいて、
DocumentCompletedイベント発生時に起こすことができればいいと思ったのですが、
NavigateするのもDocumentCompleteが発生するのも同じスレッドのため
SleepさせてしまうとDocumentCompleteが発生せず、
私の力ではできませんでした。

次のようにもしてみましたが、ダメでした。
Public Class Browser
Inherits WebBrowser
Private _flg As Boolean
Public Sub Wait()
While Not _flg
'Application.DoEvents()を入れればうまくいく
End While
_flg = False
End Sub
Protected Overrides Sub OnDocumentCompleted(ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)
_flg = True
MyBase.OnDocumentCompleted(e)
End Sub
End Class

検索をかけるとDelegateを使えばいいという情報が見つかるのですが、
どのようにやればいいのかご存知の方いらっしゃいますか?
よろしくお願いします。
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2007-01-17 20:36
表示完了を待ってどうしたいんですか?
完了後に走らせたいコードはそれこそ DocumentCompleted イベントのイベントハンドラに書けばいいと思うのですが。
hei
ベテラン
会議室デビュー日: 2006/09/07
投稿数: 78
投稿日時: 2007-01-17 22:53
Hongliangさん、ありがとうございます。
単に表示して終わりならそれでいいと思いますが、
ブラウザと表示するURLや表示後の処理を別々にしたい、ということがあります。
任意の数のブラウザを生成し、
それぞれはどこへ飛ぶかもなにをするかも知らずに指示を受け取るだけです。
ブラウザは指示どうりのURLに飛びHTMLDocumentを生成して次の指示を受け取る、を繰り返します。
ブラウザにより生成されたHTMLDocumentをどう処理するかは指示役のクラスの役目です。

言われるまでDocumentCompletedハンドラに書くという考えはなかったのですが、
個人的にはそうするよりもこの様にした方がわかりやすいと思いますし、同期が取れれば割と間単に書けます。


Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-01-18 00:32
こんばんは。

解決策ではありませんが…

私の予感では
(違うかもしれませんが、
 最近私が似たような状況に陥りましたので。もしかして同じではないかなと)
DoEventsでフリーズしてしまう原因は
DoEventsの処理が終わらないからではないでしょうか?

DoEventsはいわゆるメッセージポンプというのを行います。
現在キューに溜まっている全てのメッセージの処理を終わらせようとしますが、
メッセージ処理中にも、どんどんメッセージが送りつけられていると
いつまでたってもメッセージ処理が追いつかない場合、
DoEventsの処理が終わらない状態になってしまいます。

引用:

heiさんの書き込み (2007-01-17 22:53) より:
単に表示して終わりならそれでいいと思いますが、
ブラウザと表示するURLや表示後の処理を別々にしたい、ということがあります。
任意の数のブラウザを生成し、
それぞれはどこへ飛ぶかもなにをするかも知らずに指示を受け取るだけです。
ブラウザは指示どうりのURLに飛びHTMLDocumentを生成して次の指示を受け取る、を繰り返します。
ブラウザにより生成されたHTMLDocumentをどう処理するかは指示役のクラスの役目です。



すいません。仰りたいことが理解できませんでした。

引用:

言われるまでDocumentCompletedハンドラに書くという考えはなかったのですが、
個人的にはそうするよりもこの様にした方がわかりやすいと思いますし、同期が取れれば割と間単に書けます。



たしかにメソッド内で完了待ちをしたほうが、一連の処理が簡単に記述しやすい場合は多々あります。
(.NETでは難しそうですが、なんらかのモーダルループとして処理したい場合もあります。)
なので、気持ちは良く分かります。

ただ、DoEventsなどのメッセージポンプ処理は、
ひとたび使えばいっきに処理の流れを複雑なものにしてしまうので
予期しないバグを生む原因になりやすいです。



[ メッセージ編集済み 編集者: Tdnr_Sym 編集日時 2007-01-18 00:39 ]
hei
ベテラン
会議室デビュー日: 2006/09/07
投稿数: 78
投稿日時: 2007-01-18 07:16
Tdnr_Symさん、ありがとうございます。
何がメッセージを送り続けているのかさっぱりわかりません。
かなり難しそうですね・・・

>すいません。仰りたいことが理解できませんでした。

別な例をあげます。
接続して終わりではなく、
複数のサイトからデータを取得して、
さらにその結果を加工するような場合です。
意味のないコードですが、
ヤフー・@IT Topページ・@IT会議室からデータを取得してそれをソートするとします。
WebbrowserのNavigateメソッドが同期であれば、
次のコードで問題ありません。
Public Class Form1
Private WithEvents _browser As WebBrowser
Private _htmlList As New List(Of HtmlDocument)
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
_browser = New WebBrowser
_browser.Navigate("http://www.yahoo.co.jp")
_htmlList.Add(_browser.Document)
_browser.Navigate("http://www.atmarkit.co.jp/index.html")
_htmlList.Add(_browser.Document)
_browser.Navigate("http://www.atmarkit.co.jp/bbs/phpBB/index.php")
_htmlList.Add(_browser.Document)
_htmlList.Sort()
End Sub
End Class

しかしWebbrowserのNavigateは非同期なのでこれではダメです(NothingがAddされるだけです)
次のようにDocumentCompletedを使った場合は、
最後のHTMLしか取れません。
Public Class Form1
Private WithEvents _browser As WebBrowser
Private _htmlList As New List(Of HtmlDocument)
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
_browser = New WebBrowser
_browser.Navigate("http://www.yahoo.co.jp")
_browser.Navigate("http://www.atmarkit.co.jp/index.html")
_browser.Navigate("http://www.atmarkit.co.jp/bbs/phpBB/index.php")
_htmlList.Sort()
End Sub
Private Sub _browser_DocumentCompleted(ByVal sender As Object, ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles _browser.DocumentCompleted
_htmlList.Add(_browser.Document)
End Sub
End Class

このような場合でもDocumentCompletedイベントを使って正しく処理することはできますか?

同期をとるWebRequest/WebResponseにすれば解決するかなと思いましたが、
POSTメソッドでのアクセスやHTMLの解析はかなりの手間ですし、
これまでのものの全面的な作り変えが必要になってしまいます。
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-01-18 09:00
引用:

heiさんの書き込み (2007-01-18 07:16) より:
何がメッセージを送り続けているのかさっぱりわかりません。
かなり難しそうですね・・・



yahooや@IT TopページをIEで表示した状態で、Spy++を使ってメッセージを監視してみると、
特にMacromediaFlashPlayerActiveXに対してWM_TIMERやWM_PAINTなどのメッセージが
連続して送られていることがわかります。
アニメーション表示のためのメッセージなのだと思います。
よねKEN
ぬし
会議室デビュー日: 2003/08/23
投稿数: 472
投稿日時: 2007-01-18 09:31
Hongliangさんのご指摘とかぶりますが、
2つ目以降のNavigateはDocumentCompletedイベントの中でやればよいのではありませんか?
うまくメソッドごとの役割を分ければ、特に複雑なコードにはならないと思います。

heiさんの提示されてた別の例のコードで書き換えて見ます。
(コンパイルはしてません。妄想プログラミングですが、雰囲気を感じ取っていただければと思います)

コード:

Public Class Form1

Private WithEvents _browser As WebBrowser
Private _htmlList As New List(Of HtmlDocument)
Private _urlStack As New Stack(Of String)

Private Sub Form1_Load( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
_browser = New WebBrowser
_urlStack.push("http://www.yahoo.co.jp")
_urlStack.push("http://www.atmarkit.co.jp/index.html")
_urlStack.push("http://www.atmarkit.co.jp/bbs/phpBB/index.php")
If ExistsNavigateUrl() Then
Navigate()
End If
End Sub

Private Sub _browser_DocumentCompleted( _
ByVal sender As Object, _
ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) _
Handles _browser.DocumentCompleted
_htmlList.Add(_browser.Document)
If ExistsNavigateUrl() Then
Navigate()
Else
_htmlList.Sort()
End If
End Sub

Private Function ExistsNavigateUrl() As Boolean
If _urlStack.Count > 0 Then
Return True
Else
Return False
End If
End Function

Private Sub Navigate()
_browser.Navigate(_urlStack.pop())
End Sub

End Class



-----
訂正:プログラムではStackを使ってますが、Queueが正しいですね。
おおぼけでした。ソースは面倒なので修正しませんので、
読み替えてください。m(_ _)m
--
さらに訂正:私のコードのせいでこのスレッドの枠が
えらく横長になっていたので、コードを折り返しました。


[ メッセージ編集済み 編集者: よねKEN 編集日時 2007-01-18 20:59 ]
hei
ベテラン
会議室デビュー日: 2006/09/07
投稿数: 78
投稿日時: 2007-01-18 17:41
>Tdnr_Symさん、
FlashPlayerをアンインストールしましたがダメでした。

>よねKENさん
確かにこうすれば出来ますね!
今のものには適用できませんが、
今後の参考にさせて頂きます。


で、上司が原因を見つけてくれました。
私はブラウザはOPERAを使っているのでIEのバージョンは6のままだったのですが、
7にすることで解決しました。
接続できなかったサイト側に何らかの変更があったようです。

みなさん、ありがとうございました!
1

スキルアップ/キャリアアップ(JOB@IT)