[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]
『IE制御で画面遷移のイベント待受けについて』(かず)
IE制御で画面遷移のイベント待受けについて
VBAでWebページの読み込み完了をCOMイベント
で取得しようとしています。
Webページ「IE で,フレームのある WEB ページの読み込み完了
まで待機する方法」
http://www5f.biglobe.ne.jp/~f-lap/tips_wait_documentcomplete.html
を参考にさせて頂いて、フレームのあるページでもフレームのないページでも
同じような扱いでページの状態遷移を待ち受けができるよう(注 一部のコードを32ビット版動作に割り切って修正)になりました。
(クラスモジュール CIEWaitを参照ください)
そこで、実際に会社のイントラページ(フレームあり)でCIEWaitで
待受けさせると、待受け前にすでにページを表示し終わっていて、待ち受
けの時点ですでにDocumentCompleteが終わっているように見えるます。
待受けの制御ができたと思ったら、待受け前の段階でドキュメントを
捕まえられないという事態で対応に悩んでいます。
後述のsheet1モジュールを参照)
以下の詳細(1)〜(7)に現状と(8)〜(10)推定原因と確認方法を記載しました
IE制御に経験のある方にぜひアドバイスを頂きたいと思います。
どうぞよろしくお願いいたします。
詳細
(1)ページ社内のイントラページで、ログイン済み。
(2)WaitIEメソッドでフレーム有、フレーム無のページで
待受けはできるようになった。 URL(a)(b)(c)は問題なし
(3)(A)Navigate直後には .visibleをTrueにしていないので
通常ページは表示されないはず(あまり自信なし)
(4)実際の動作では(A)Navigate直後に画面は表示されている→リロードなのでは?
(5)waitIE に入っても DocumentCompleteイベントが発生せず タイムアウトする
→すでに読込完了しているので、待受け対象のイベントが発生していない
(6)(A)直後の(C)では実行時エラー'-2147023706 が発生する
(7)VBEで(D)をブレークポイントにして確認 docはnothingになっている
→ 現状理由不明
(8) 推定原因 oobjIE.Visible を実行していないのにページが
表示されているということは、Webページを「リロード」しているのではないか。
リロードならページの画面完了はDocumentCompleteではなくDownloadComplete
になるので今のWaitIEメソッドではこのDownloadCompleteではずっとループする
のは当然。しかしURL(a)(b)では、(C)の所でdocを正しく取得できている
(9) 確認方法 会社のページ(d)と他のページで何が違うのか F12ツールで
objIEの.ReadyStateプロパティでどんなイベントが発生しているか調べる。
[ツール] IE コンポーネントにおけるイベントの発生順序
http://d.hatena.ne.jp/dayflower/20070926/1190787926
→ネットワークやUIでイベントの詳細がわかるのでしょうか
URL(d)をNavigateした際に 画面上半分にIE、下半分にF12ツールでイベント発生順
をモニターできれば確かに状況が明確かもしれない
(10)推定原因2
Excel VBAでIE操作時にオートメーションエラーが出た時は、対象のURLが
「信頼済みサイト」に登録されているからかもしれない
https://qiita.com/3mc/items/da045e86d25ef697ec43
(C)の所でエラーになるのがイントラサイトの場合だけなので、確かに
InternetExplorerMediumを使うことで対応できるのかもそれない
'--------
'sheet1 モジュール
'--------
Option Explicit
Dim doc As HTMLDocument
Dim m_isRunning As Boolean
Private Sub CommandButton1_Click()
If m_isRunning Then Exit Sub m_isRunning = True
Dim objIE As SHDocVw.InternetExplorer Set objIE = New SHDocVw.InternetExplorer
Dim ieWait As CIEWait Set ieWait = New CIEWait Set ieWait.ie = objIE
ieWait.frame = True
'○問題なし objIE.navigate "http://www5f.biglobe.ne.jp/~f-lap/index.htm" '(a) フレームあり '○問題なし objIE.navigate "https://dev.to/" ' (b)フレームあり 表示が非常に早いWebページ '○問題なし objIE.navigate "http://www.t3.rim.or.jp/~buchi/procobol/sld024.htm" ' (c)フレームなし
'×問題あり
objIE.navigate "https:// 会社のページ/" ’★(A) フレームあり URL(d)
On Error Resume Next Set doc = objIE.document ’★(C) On Error GoTo 0 ' ★(D)
If Err.Number <> 0 Then If Not ieWait.WaitIE(5000, False) Then ’★(B) MsgBox "IEのDocumentCompleteの待機に失敗。" m_isRunning = False Exit Sub End If End If
Debug.Print "After ieWait.WaitIE()"
If Not objIE.Visible Then objIE.Visible = True End If
m_isRunning = False
End Sub
'IEを削除された場合にオブジェクトを開放する
'これがないと「Excelを終了できません」となる
Private Sub objIE_OnQuit()
Set objIE = Nothing '握っていた IE への参照を解放しておきます。 End Sub
’--------
'クラスモジュール CIEWait
' 出典 IE で,フレームのある WEB ページの読み込み完了まで待機する方法
' http://www5f.biglobe.ne.jp/~f-lap/tips_wait_documentcomplete.html
’LongPtr → Longに変更
'--------
Option Explicit
Private Const MWMO_WAITALL As Long = &H1
Private Const MWMO_ALERTABLE As Long = &H2
Private Const MWMO_INPUTAVAILABLE As Long = &H4
' Q1 クラス化するとなぜ MsgWaitForMultipleObjects を
' MsgWaitForMultipleObjectsExにするのか不明
Private Declare _
Function MsgWaitForMultipleObjectsEx Lib "user32.dll" ( _
ByVal nCount As Long, _ ByRef pHandles As Long, _ ByVal dwMilliseconds As Long, _ ByVal dwWakeMask As Long, _ ByVal dwFlags As Long _ ) As Long ' 第2引数の型 LongPtr → Long
Private Const STATUS_WAIT_0 As Long = 0&
Private Const WAIT_OBJECT_0 As Long = (STATUS_WAIT_0) + 0&
Private Const QS_ALLINPUT As Long = &H4FF&
Private Const INFINITE As Long = &HFFFFFFFF ' Infinite timeout
Private Const WAIT_TIMEOUT As Long = 258&
Private Const STATUS_USER_APC As Long = &HC0&
Private Const WAIT_IO_COMPLETION As Long = STATUS_USER_APC
Private Const WAIT_FAILED As Long = &HFFFFFFFF
'Private Declare Function MsgWaitForMultipleObjects Lib "user32.dll" ( _
' ByVal nCount As Long, _
' ByRef pHandles As LongPtr, _
' ByVal fWaitAll As Long, _
' ByVal dwMilliseconds As Long, _
' ByVal dwWakeMask As Long _
') As Long
' VBA 32ビットと64ビットの違い
' https://www.saka-en.com/office/vba-32bit-64bit-declare-statement-branch/
'64Bit 版 Office では、32Bit 版に比べて使用できるメモリ容量が大きくなり、2GB
'を超えるサイズのファイルを扱うことができるようになりました。
'これに伴い、VBA も VBA7 (Microsoft Visual Basic for Applications 7.0) に
' バージョンアップされました。
'64Bit 版では、ポインターやハンドルのサイズが 32Bit 版に比べて大きくなったため
'、Long 型を使用すると予期しないエラーが発生する可能性があります。
'そのため、Office 2010 以降ではその問題に対応した新しいデータ型として、
'LongPtr 型(ポインターデータ型)と LongLong型(64Bit 数値型)が追加されています。
'64Bit 版では、Declare ステートメントを使って Windows API を呼び出す場合、これ
'まで使っていた Long 型を適切な形で置き換える必要があります。
'そうなると、32Bit 版なのか、64Bit 版なのかを判別する方法が必要になりますね。
'Office 2010 以降、VBA7 と Win64 という条件付きコンパイル定数が追加されました。
' 第2第4引数、戻り値の型 LongPtr → Long に変更
Private Declare _
Function CreateEvent Lib "kernel32.dll" Alias "CreateEventW" ( _
ByVal lpEventAttributes As Long, _ ByVal bManualReset As Long, _ ByVal bInitialState As Long, _ ByVal lpName As Long _ ) As Long
' 第1引数の型 LongPtr → Long に変更
Private Declare _
Function SetEvent Lib "kernel32.dll" ( _
ByVal hEvent As Long _ ) As Long
' 第1引数の型 LongPtr → Long に変更
Private Declare _
Function ResetEvent Lib "kernel32.dll" ( _
ByVal hEvent As Long _ ) As Long
' 第1引数の型 LongPtr → Long に変更
Private Declare _
Function CloseHandle Lib "kernel32.dll" ( _
ByVal hObject As Long _ ) As Long
Dim m_hEvent As Long 'イベントハンドル用 型をLongPtr→Longに変更
Dim m_topFrame As Object '今回のトップレベルウィンドウ/フレーム
Dim WithEvents m_IE As SHDocVw.InternetExplorer
Dim frame_ As Boolean
Dim DocComplete_flg As Boolean
' 全入力のウィンドウメッセージを処理しつつ,
' 引数で渡されたイベントが発生するまで待機。
Private Function VBAWaitWithDoEvents( _
Optional ByVal dwMilliseconds As Long = INFINITE) As Boolean
' 省略可能な引数の場合、オブジェクト型でない引数は ' = デフォルト の形でデフォルト値を設定可能 ' オブジェクト型の引数の場合はIf MISSING(obj) Then で判定
Const nCount As Long = 1&
Dim prevTime As Single ' Timer関数は単精度浮動小数点(single)を返す ' PrevTime Single ' dwMilliseconds タイムアウト時間
If dwMilliseconds <> INFINITE Then prevTime = Timer() End If
Do Dim rc As Long rc = MsgWaitForMultipleObjectsEx(nCount, _ m_hEvent, _ dwMilliseconds, _ QS_ALLINPUT, _ MWMO_ALERTABLE Or MWMO_INPUTAVAILABLE) If rc = WAIT_OBJECT_0 Then ' イベント発生。 Debug.Print "WAIT_OBJECT_0" VBAWaitWithDoEvents = True Exit Function ElseIf rc = WAIT_OBJECT_0 + nCount Then ' メッセージキューに入力あり。 Debug.Print "WAIT_OBJECT_0 + nCount" DoEvents ElseIf rc = WAIT_TIMEOUT Then ' タイムアウト Exit Function ElseIf rc = WAIT_IO_COMPLETION Then ' APC is queued. DoEvents ElseIf rc = WAIT_FAILED Then ' Failed. Exit Function Else ' One or more mutex objects were abandoned. DoEvents End If
If dwMilliseconds <> INFINITE Then Dim currentTime As Single currentTime = Timer() If prevTime > currentTime Then ' 仕切り直しとする。 prevTime = currentTime End If dwMilliseconds = dwMilliseconds - (currentTime - prevTime) * 1000 If dwMilliseconds <= 0 Then Exit Do prevTime = currentTime End If Loop
End Function
Private Sub Class_Initialize()
m_hEvent = CreateEvent(0, 0, 0, 0) End Sub
Private Sub Class_Terminate()
CloseHandle m_hEvent End Sub
Private Sub m_IE_NavigateComplete2(ByVal pDisp As Object, URL As Variant)
If frame_ = True And m_topFrame Is Nothing Then
' トップレベルの NavigateComplete2 イベントは最初に発生。 Debug.Print "frame あり NavigateComplete2"
Set m_topFrame = pDisp
End If
End Sub
Private Sub m_IE_DocumentComplete(ByVal pDisp As Object, URL As Variant)
If frame_ = True And pDisp Is m_topFrame Then
Debug.Print "frame あり m_IE_DocumentComplete"
' 関連フレームワーク群におけるトップレベルの DocumentComplete イベントは最後に発生
SetEvent m_hEvent ' Event オブジェクトを signaled 状態にする。 Set m_topFrame = Nothing
ElseIf frame_ = False Then
Debug.Print "frame なし DocumentComplete" SetEvent m_hEvent ' Event オブジェクトを signaled 状態にする。 DocComplete_flg = True
End If
End Sub
Public Property Set ie(ByVal ie As SHDocVw.InternetExplorer)
Set m_IE = ie End Property
Public Function WaitIE(Optional ByVal dwMilliseconds As Long = INFINITE, _
Optional ByVal Release As Boolean = False) As Boolean If m_IE Is Nothing Then MsgBox "想定外エラー InternetExplorerオブジェクトが設定されていません。" Exit Function End If
WaitIE = VBAWaitWithDoEvents(dwMilliseconds)
If Release Then Set m_IE = Nothing End If End Function
Private Sub m_IE_NewWindow2(ppDisp As Object, Cancel As Boolean)
Set m_IE_NEW = New SHDocVw.InternetExplorer
Set ppDisp = m_IE_NEW ' 作ったオブジェクトを代入
Debug.Print "m_IE_NEW_NewWindow2"
m_IE_NEW.Visible = True
End Sub
Private Sub m_IE_NEW_DocumentComplete(ByVal pDisp As Object, URL As Variant)
If frame_ = True And pDisp Is m_topFrame Then
Debug.Print "frame あり m_IE_DocumentComplete"
' トップレベルの DocumentComplete イベントは最後に発生 SetEvent m_hEvent ' Event オブジェクトを signaled 状態にする。 Set m_topFrame = Nothing
ElseIf frame_ = False Then
Debug.Print "frame なし DocumentComplete" SetEvent m_hEvent ' Event オブジェクトを signaled 状態にする。 DocComplete_flg = True
End If
End Sub
Property Let frame(ByVal new_frame As Boolean)
frame_ = new_frame End Property
Property Get frame() As Boolean
frame = frame_ End Property
'要否検討中
Property Get DocCmplt() As Boolean
DocCmplt = DocComplete_flg End Property
Private Sub m_IE_OnQuit()
Set m_IE = Nothing End Sub
< 使用 Excel:Excel2016、使用 OS:Windows7 >
また、従来の定番だったInternetExplorerですが、IEのデフォルトセキュリティが厳しくなったので、表示できない問題が出たときは、InternetExplorerMedium を試してみてください。
(???) 2018/06/19(火) 09:14
コメント有難うございます。非常に有難いです。今回
クラスモジュールのなかのウェイト処理の意味がなあかなか理解でき
ませんでした。
今回の件は、InternetExplorerMediumを使うことで解決できました
navigateの直後にreadyStateを調べたところ DocumentCompleteも
帰ってきていました。
(7)docがnothingになったのはアートメーションエラーが原因
(8)リロードではありませんでした。objIE.visibleでないのに表示
されるはおそらく事実誤認でした。お恥ずかしい限りです。
(9)F12ツールの使ったデバッグはまだ習得できていません。
(10)これが原因 InternetExplorerMedium を使いました。
>名前からおそらくreadyStateとかBusyを調べているのだろうと推測
この点は、ieWaitの元ページではreadyStateとかBusyは使ってないです。
基本的に DocumentComplete が来たら特定のイベントを発行
それとは別に、MsgWaitForMultipleObjectsEx と言う関数でイベントを
他のイベントも含めて特定のイベントを待ち受けるという仕組みだと理解
しています
振り返りとして、以下の点はもっと調べたいと思っています。
お気づきの点があればご指摘お願いいたします。
Q1 まだHTMLのいろいろな機能に詳しくないのですがインライン
フレームiframe を使ったページも フレームを使ったページ
と同じように扱えばいいんでしょうか?
Q2 COMイベントでの待受けできない制約があるページには
例えばどんなページでしょうか。DOMやgetElementByTagNameなど
の関数で操作できななそうなのはどういうページなのでしょうか
以上
(かず) 2018/06/20(水) 01:30
Set doc = objIE.Document.Frames(1).Document
なお、フレーム数は objIE.Document.Frames.Length で得られますが、対象となるURLのフレーム構成なんて事前に判っているでしょうし、決め打ちで良いと思います。
待ち受けできないページとは、通常は表示完了を先に書いたようなreadyStateとかBusyでチェックするのですが、ページが更にJS等で表示更新するような場合(google mapとか?)、これだと最初のページの完了で条件成立してしまうので、その後の表示更新完了を拾えないものの事かと思います。 でも、今回かずさんはクラスモジュールでコールバックに対応しているので、m_IE_DocumentCompleteの中で、目的のURLにたどり着くまで完了イベントを読み捨てれば、どんなページでも完了検出できると思いますよ。
(???) 2018/06/20(水) 09:33
[ 一覧(最新更新順) ]
YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki.
Modified by kazu.