画面遷移待ち受けの必要性 

'サンプル4.2.1_画面遷移を待ち受ける(失敗ケース)
Public Sub navigateAndSetValue()
    Dim ie As InternetExplorer
    'IE起動→Googleに遷移
    Set ie = CreateObject("InternetExplorer.Application")
    ie.Visible = True
    ie.Navigate2 "http://www.google.co.jp/"
    'Googleのフォームに値をセットして検索実行
    ie.document.forms("f").elements("q").Value = "ぼたもち おはぎ 違い"
    ie.document.forms("f").submit
End Sub
リンクやボタンをクリックしてから次の画面が完全に表示されるまでには一定の時間がかかる。この間に次画面の画面部品を操作しようとするとエラーになるわ。
ie.Navigate2 "http://www.google.co.jp/"
ie.document.forms("f").elements("q").Value = "ぼたもち おはぎ 違い"
このサンプルだと、テキストボックスにValueをセットする処理の時点で、その前のNavigate2が完了していない可能性がある。画面遷移が完了した状態になってからテキストボックスの操作を行うために、この2つの処理の間にはその待ち受け処理が必要になる。
IE制御はイレギュラーが多いのだけれど、動作安定化の鍵を握るひとつがこの画面遷移待ち受け処理だから、ここで紹介する方法以外にも環境に合わせて工夫することをオススメするわ。

秒数を指定した待ち受け 

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

'サンプル4.2.2_画面遷移を待ち受ける 秒数待ちうけ
Public Sub navigateAndSetValue2()
    Dim ie As InternetExplorer
    'IE起動→Googleに遷移
    Set ie = CreateObject("InternetExplorer.Application")
    ie.Visible = True
    ie.Navigate2 "http://www.google.co.jp/"
    '待ちうけ(とりあえず3秒。通信速度等に応じて調整)
    Sleep 3000
    'Googleのフォームに値をセットして検索実行
    ie.document.forms("f").elements("q").Value = "ぼたもち おはぎ 違い"
    ie.document.forms("f").submit
End Sub
もっとも原始的な待ち受け方法。今回の例では画面遷移開始後3秒待ってから次画面の処理を行っている。
ie.Navigate2 "http://www.google.co.jp/"
Sleep 3000
ie.document.forms("f").elements("q").Value = "ぼたもち おはぎ 違い"
気づいているとは思うけれど、ネットワーク速度やページサイズ、レンダリング処理性能によっては3秒で画面遷移が完了しないかもしれない。したがってより動的で客観的な指標を使った待ち受けが必要になる。
以下ではブラウザやHTML文書の状態に応じた待ち受け処理を紹介していくわ。

InternetExplorerの「Busy」を監視した待ち受け 

'サンプル4.2.3_画面遷移を待ち受ける Busyの監視
Public Sub navigateAndSetValue3()
    Dim ie As InternetExplorer
    'IE起動→Googleに遷移
    Set ie = CreateObject("InternetExplorer.Application")
    ie.Visible = True
    ie.Navigate2 "http://www.google.co.jp/"
    '待ちうけ
    Do While ie.Busy
        Debug.Print ie.Busy
        DoEvents
    Loop
    Debug.Print ie.Busy
    'Googleのフォームに値をセットして検索実行
    ie.document.forms("f").elements("q").Value = "ぼたもち おはぎ 違い"
    ie.document.forms("f").submit
End Sub
InternetExplorerには、処理を受け付けられる状態にあるか否かを取得する「Busy」という関数が用意されている。これの戻り値がTrueの場合はIEが処理中ということだから、HTML文書を操作することができない。
逆に言えばBusyな状態でなくなるということは、次画面への遷移処理が完了したと言い換えることもできるわ。
具体的には画面遷移処理と次画面操作処理との間に、Busyでなくなるまで何もせずにループする処理を追加すればいい。
ie.Navigate2 "http://www.google.co.jp/"
Do While ie.Busy
    Debug.Print ie.Busy
    DoEvents
Loop
Debug.Print ie.Busy
ie.document.forms("f").elements("q").Value = "ぼたもち おはぎ 違い"
これが状態に応じた画面遷移待ち受け処理の中でもっとも単純な例。でも、さらに安全性を期す方法は次以降のサンプル。

IEのBusyとReadyStateを監視した待ち受け 

'サンプル4.2.4_画面遷移を待ち受ける Busy属性とReadyState属性の監視
Public Sub navigateAndSetValue4()
    Dim ie As InternetExplorer
    'IE起動→Googleに遷移
    Set ie = CreateObject("InternetExplorer.Application")
    ie.Visible = True
    ie.Navigate2 "http://www.google.co.jp/"
    '待ちうけ
    Do While ie.Busy Or ie.ReadyState < READYSTATE_COMPLETE
        Debug.Print ie.Busy & "/" & ie.ReadyState
        DoEvents
    Loop
    Debug.Print ie.Busy & "/" & ie.ReadyState
    'Googleのフォームに値をセットして検索実行
    ie.document.forms("f").elements("q").Value = "ぼたもち おはぎ 違い"
    ie.document.forms("f").submit
End Sub
Busyに似ているけど、ReadyStateという関数も用意されている。InternetExplorerの状態をBusyのTrue/Falseよりも細かく取得することができる関数ね。
待ち受けの手法は同じで、ループの条件を以下のように修正する。
Do While ie.Busy Or ie.ReadyState < READYSTATE_COMPLETE
ReadyStateの戻り値は数値なのだけれど、READYSTATE_COMPLETEのように定数が用意されているからこれを利用する。READYSTATE_COMPLETE以外についても知りたければWeb検索して調べて。でもREADYSTATE_COMPLETE以外はめったに使わないと思うわ。

またBusyとReadyStateの大きな違いとして、Busyは親フレームの読み込みが完了した時点でFalseになるのに対して、ReadyStateは全フレームの読み込み完了後にREADYSTATE_COMPLETEになるといわれている。この点については次のサンプルコードで紹介するわ。

フレーム構造の画面を待ち受ける 

'サンプル4.2.5_フレーム構造の画面を待ち受ける
Public Sub waitFrame1()
    Dim ie As InternetExplorer
    'IE起動→フレーム待ちうけテスト画面に遷移
    Set ie = CreateObject("InternetExplorer.Application")
    ie.Visible = True
    ie.Navigate2 "http://macrogirls.net/sample/frame.html"
    '待ちうけ
    Do While ie.Busy Or ie.ReadyState < READYSTATE_COMPLETE
        Debug.Print ie.Busy & "/" & ie.ReadyState
        DoEvents
    Loop
    'フォームに値をセット
    ie.document.frames("Frame3").document _
    .forms("TargetForm").elements("TargetText").Value = "ぼたもち おはぎ 違い"
End Sub
待ち受けロジックは先のフレーム構造の無い場合とまったく同じだけど、待ち受けに成功することを確認して。

ここまでで概ね安定した待ち受け処理になると思うけれど、冒頭で言ったように待ち受け処理は動作安定化の鍵となる部分。だから可能な限り対象が操作可能かを待ち受ける方がベター。
そこで以下ではInternetExplorerだけではなく、HTML文書オブジェクトの待ち受け処理について紹介するわ。

操作対象HTMLドキュメント状態を含めて待ち受ける 

'サンプル4.2.6_フレーム内ドキュメント状態を含めて待ち受ける
Public Sub waitFrame2()
    Dim ie As InternetExplorer
    'IE起動→フレーム待ちうけテスト画面に遷移
    Set ie = CreateObject("InternetExplorer.Application")
    ie.Visible = True
    ie.Navigate2 "http://macrogirls.net/sample/frame.html"
    '待ちうけ(IE)
    Do While ie.Busy Or ie.ReadyState < READYSTATE_COMPLETE
        Debug.Print ie.Busy & "/" & ie.ReadyState
        DoEvents
    Loop
    '待ちうけ(操作対象Document)
    Do While ie.document.frames("Frame3").document.ReadyState <> "complete"
        Debug.Print ie.document.frames("Frame3").document.ReadyState
        DoEvents
    Loop
    Debug.Print ie.document.frames("Frame3").document.ReadyState
    'フォームに値をセット
    ie.document.frames("Frame3").document _
    .forms("TargetForm").elements("TargetText").Value = "ぼたもち おはぎ 違い"
End Sub
たとえば画面遷移後にFrame3の中のHTMLドキュメントを操作したい場合、明示的に当該ドキュメントの読み込みが完了していることを確認するほうがベター。
IEのReadyStateが数値を返すのに対して、HTMLDocumentオブジェクトのReadyStateは読み込み完了状態では「complete」という文字列を返す。
サンプルコードでは、これまでどおりIEの状態待ち受けの後、このHTMLDocumentのcompleteを待ち受けるようにしている。
Do While ie.document.frames("Frame3").document.ReadyState <> "complete"
今回はフレーム内のドキュメントに対してReadyStateのcompleteを待ち受けるようにしたけれど、フレームが無い場合は以下のようにIE直下のDocumentに対して確認を行う。
Do While ie.document.ReadyState <> "complete"
ここまでの方法を組み合わせれば、かなり確実な待ち受け処理を行うことができる。でもオンラインシステムではJavaScriptで再読込処理をしたりなど動作が複雑だから、環境に応じたプラスアルファが必要になるかもしれないことは忘れないで。ここまで読んでくれたご褒美として汎用待受処理をサブルーチン化しておくわ。これをベースにカスタマイズするといい。もちろん、そのままでも一定使い物になるけれど。

画面遷移待受サブルーチン 

'画面遷移待受サブルーチン
Public Sub waitBrowsing(ie As Object)
    '待ちうけ(IE)
    Do While ie.Busy Or ie.ReadyState < READYSTATE_COMPLETE
        Debug.Print ie.Busy & "/" & ie.ReadyState
        DoEvents
    Loop
    '待ちうけ(操作対象Document)
    Do While ie.document.ReadyState <> "complete"
        Debug.Print ie.document.ReadyState
        DoEvents
    Loop
End Sub

'呼び出し側
Public Sub Test()
    Dim ie As InternetExplorer
    'IE起動→フレーム待ちうけテスト画面に遷移
    Set ie = CreateObject("InternetExplorer.Application")
    ie.Visible = True
    ie.Navigate2 "http://macrogirls.net/sample/frame.html"
    waitBrowsing(ie)
    'フォームに値をセット
    ie.document.frames("Frame3").document _
    .forms("TargetForm").elements("TargetText").Value = "ぼたもち おはぎ 違い"
End Sub

参考:その他の待ちうけ方法 

'サンプル4.2.7_その他の待ちうけ方法
Public Sub waitFrame3()
    Dim ie As InternetExplorer
    'IE起動→フレーム待ちうけテスト画面に遷移
    Set ie = CreateObject("InternetExplorer.Application")
    ie.Visible = True
    ie.Navigate2 "http://macrogirls.net/sample/frame.html"
    '待ちうけ
    Do
        Err.Clear
        On Error Resume Next
        'フォームに値をセット
        ie.document.frames("Frame3").document _
        .forms("TargetForm").elements("TargetText").Value = "ぼたもち おはぎ 違い"
        DoEvents
        Debug.Print Err.Number
    Loop Until Err.Number = 0
End Sub
今まではIEやHTMLDocumentの状態から操作可否を見極めてきた。
それ以外のやり方として、行いたい操作を成功するまで施行する方法もある。
Loop Until Err.Number = 0
テキストボックスへ値を入力しようとしたとき、もしIEがBusyだったりReadyStateが受付可能な状態以外の場合などでは処理が失敗するから、エラー番号は0にならずに再度Do以後の処理が行われる。つまり、テキストボックスへの入力を再施行する。
もしテキストボックスへの値の入力が正常終了した場合、Loop時に評価されるエラー番号は0になるから、ここではじめてループを脱出する。

状態だけでは見極めきれない場合や、確実に値の入力完了を保証したい場合にはこのような処理を部分的に利用してもいいわ。もちろん無限ループしないような配慮も忘れないで。