Toggle FullScreen
More Info

VBAでInternet Explorerの画面遷移をイベントドリブン的に制御する

おしらせ

この記事を書いたあと、VBAでInternetExplorerのインスタンスを捕まえる方法と、DocumentCompleteイベントの判別方法という記事で、補足説明をしていますので、こちらもご覧ください。


今回は、プログラムの話

この半年ほどVBAで、ExcelやAccessからInternet Explorerを制御しようと試みてきました。苦労の甲斐あって、ようやく満足できるものができたので、書きとめておくことにします。なお、私はプログラミングに関しては素人なので、間違った記載やいい加減な内容があるかもしれませんが、ご容赦ください。

VBAからブラウザ(Internet Explorer)を制御することにより、JAVAで動作するWEBアプリケーションの処理を自動化(オートクリック)する。

この記事で述べる内容は、以下のとおり。

  1. VBAでInternet Explorerのイベントを補足する為に、クラスモジュールを使った。
  2. Internet ExplorerのDocumentCompleteイベントを利用してイベントドリブンな制御をするために、業務アプリケーションのステータス的な発想を使って、条件分岐した。
  3. コードを一定時間止める必要があったが、Sleep関数やWaitメソッドではなく、OnTimeメソッドを使った。


ちなみに、動作環境は、Excel2003とInternet Explorer6で確認しています。



イントロダクション

個人的に、今回初めて使った技術、クリアしたこともたくさんありました。

今回の記事で書いておくのは、画面遷移のコントロールについて。DOMでターゲットのクリックをエミュレートした後、IEがドキュメントの読み込みを完了したことを確認して、次のターゲットをクリックさせたいわけだが、その判断が難しい。いや、難しいのは、対象にするサイトがフレームで構成されている場合。

通常はREADYSTATEプロパティがREADYSTATE_COMPLETE (4)になったことを確認できればいいのだが、フレームで(それも何重にも)構成されていると、READYSTATEプロパティはあてになりません。

どうしたものだろうと行き詰っていたところ、IEは読み込みを完了すると、DocumentCompleteイベントが発生することを知りました。さらに、マイクロソフトの説明では、発行されたIDispatch* parameterがWebBrowser controlのIDispatchと同じならば、最終的にページ全体が読み込まれたことになる・・・とありますが、私の理解の範疇を超えています。ということで、参考にさせていただいたのが、次の記事。

主に言語とシステム開発に関して(ブラウザのビジー状態を判定するための,より良い方法 (WSHでIEを自動操作する際,COMのアプリケーションイベントを利用する)

この記事では、WSHでIEのDocumentCompleteイベントをキャッチしてIEを制御する方法が説明されています。おお、やっぱできるのか、と思ったのもつかの間、WScriptのCreateObjectとVBAのCreateObjectは仕様が異なるので、同様の方法は使えないんですよねぇ。

で、次に参考にしたのが、WithEventsで、ユーザーフォーム内コントロールのイベントを拾う という記事。

この記事で、イベントをキャッチする方法をはじめて知りました。この手法では、クラスモジュールを使用するのですが、実際に利用したのは初めてです。

これらの情報を元に、VBAでInternet Explorerのイベントを捕捉するコードを書きました。


VBAでInternet Explorerのイベントを補足する為に、クラスモジュールを使った

まず、標準モジュールに次のようなプロシージャを記述します。

Option Explicit
Private My_IeEvent As clsIeEvent
——————————————————–
Sub test()

    ‘クラスを初期化
    Set My_IeEvent = New clsIeEvent

    Dim objTAG As Object

    ‘オブジェクトを格納する変数
    Dim objShell As Object, objWindow As Object

    ‘シェルのオブジェクトを作成する
    Set objShell = CreateObject("Shell.Application")

    ‘ウインドウの数だけ走査する
    For Each objWindow In objShell.Windows
        ‘TypeNameでオブジェクト変数のタイプを得る
        ‘HTMLDocumentだったら
        If TypeName(objWindow.document) = "HTMLDocument" Then

        ‘document.Titleが「Yahoo! JAPAN」ならば
            If objWindow.document.Title = "Yahoo! JAPAN" Then

                ‘クラスモジュールの「Init」を呼び出す
                Call My_IeEvent.Init(objWindow)

                ‘tag全てについて走査
                For Each objTAG In objWindow.document.all

                    ‘class属性の値がcbysC12だったら(Yahoo!サービスの中のニュース)、クリック
                    If objTAG.getAttribute("className") = "cbysC12" Then

                        objTAG.Click

                        ’for eachを抜ける
                        Exit For

                    End If
                Next
            End If
        End If
    Next
    Set objShell = Nothing

End Sub

次に、クラスモジュールに「clsIeEvent」という名前のモジュールを作成し、次のように記述します。

Option Explicit

Private WithEvents My_IeEvent As InternetExplorer
——————————————————–
Public Sub Init(ByVal In_IeObj As InternetExplorer)

    ‘objWindowをクラスの変数に格納する
    Set My_IeEvent = In_IeObj

End Sub

Private Sub My_IeEvent_DocumentComplete(ByVal pDisp As Object, URL As Variant)
    MsgBox "DocumentComplete"
End Sub

あと、全体の作業が終わった時点で、クラスを解放しなければいけないので、
Set My_IeEvent = Nothing
の一文が必要です。クラスを解放しないと、一連のプロシージャが終わっても、クラスは生きています。ちなみに、下のほうに記述したようにログを取るようにセットしておくと、ずーっとログをとり続けます。

IEを起動し、Yahoo!Japanのトップページを表示させた状態で、サブプロシージャ「test」を走らせると、Yahoo!サービスの中のニュースをクリックして、画面遷移が始まり、読み込みが完了した時点で、IEが発したDocumentCompleteイベントにより、メッセージボックスで「DocumentComplete」と表示されます。

フレームが使われているサイトでは、DocumentCompleteイベントが何度も発生しますが、イベント発生時にURLが引き渡され、このURLで判断できます。最後のDocumentCompleteイベント発生時には、ページの構成上トップに位置するURLが渡されるようです。ですので、URLを確認することにって、全体の読み込みが完了したか(最後に発生するDocumentCompleteイベントかどうか)の判断ができます。

余談ですが・・・
クラスモジュールに、上記のように記述すると、VBEのプロシージャボックスにはInternet Explorerのイベントがずらっと並び、DownloadBegin、NavigateComplete2、TitleChange、面白いものではProgressChangeなど、ありったけのイベントを補足できるようになります。EXCELのシートにイベントログを出力することも簡単です。

まず、標準モジュールに次のようなプロシージャを記述します。あ、当然lngCellYは宣言部に宣言してください。それと、「Log」という名前をつけたワークシートも準備してください。

Sub LogOutput(pDisp As Object, URL As Variant, strEvent As String)
        Worksheets("Log").Cells(lngCellY, 1) = strEvent
        Worksheets("Log").Cells(lngCellY, 2) = pDisp
        Worksheets("Log").Cells(lngCellY, 3) = URL

    lngCellY = lngCellY + 1
End Sub

クラスモジュール「clsIeEvent」のPrivate Sub My_IeEvent_DocumentCompleteに次の一行を追加します。

Call LogOutput(pDisp, URL, "DocumentComplete")

これで、ワークシートにログが出力されます。
探知できるイベント全てについてログを出してみるとなかなか面白いです。一度お試しあれ。


Internet Explorerのイベントを利用してイベントドリブンな制御をするために、業務アプリケーションのステータス的な発想を使って、条件分岐した

ここまでやると、Internet ExplorerのイベントをVBAで確実に補足できるので、VBA本来のイベントドリブンな制御ができそうです。

私が実際に構成したロジックはというと・・・ステータスで制御してみました。
基本になるのが次のプロシージャ。あ、strStatusは宣言部に文字列変数として、lngNoは長整数型として宣言済です。

Sub MAIN()

    If lngNo <= lngNo Then
        Select Case strStatus
        Case "Status1"
                CLICK1

        Case "Status2"
                CLICK2

        Case "Status3"
                CLICK3

        End Select

        lngNo=lngNo + 1

    Else
        Set My_IeEvent = Nothing        ‘クラスを廃棄
    End If

End Sub

CLICK1、CLICK2、CLICK3のプロシージャには、それぞれ冒頭のtestプロシージャのような、ターゲットをクリックするコードが記載してあります。
クリックをした直後、変数strStatusに次のステータスを代入し、オブジェクトを開放した上で、Exit Subでプロシージャを抜けます。

クラスモジュールのMy_IeEvent_DocumentCompleteで、URLを判断した後、ページの構成上トップに位置するURLだったなら、

Call MAIN

として、MAINを呼び出し、Select文の判断を経て、次のCLICK2に移る・・・。

コードを一定時間止める必要があったが、Sleep関数やWaitメソッドではなく、OnTimeメソッドを使った

大抵のWEBサイトならば、このようなロジックで対処できるでしょう。
しかし、私が操作しようとしているサイトでは、うまくいきませんでした。JAVAで動いているアプリケーションを、ブラウザをVBAから操作することで、処理を自動化しようとしています。

試行錯誤しながら上記の方法でとったイベントログを見ていると、特定のクリックのところで、クリックはされているのに、その先へ進まない。クリック直後に何もイベントが起こらないこともあれば、DownloadBeginや、FileDownloadまで進むこともある。でも、いってもそこまでという状態。ひょっとして、DocumentCompleteイベントの直後、アプリケーション側で何か処理が組まれている?
実は、クリック操作の後、Sleep関数やWaitメソッドを使って、処理を止めてみたりもしたのですが、効果なし。コードが「動いている」間はだめで、一旦止めてからなら、手動でもコードからでもクリックが効きます。

で、とった対策が、OnTimeメソッド。こんなメソッド、あったんですね。クラスモジュールに記述したDocumentCompleteイベント時に発生するMy_IeEvent_DocumentCompleteを次のように変更します。

Private Sub My_IeEvent_DocumentComplete(ByVal pDisp As Object, URL As Variant)

    If URL = hogehogehoge(ここにページの構成上トップに位置するURLを記載) Then

’2秒後にMAINを起動
        Application.OnTime Now + TimeValue("00:00:02"), "MAIN"

    End If

End Sub

My_IeEvent_DocumentComplete自体は、瞬時に完了しますので、実質DocumentCompleteイベント発生後、一旦全コードは終了し、2秒後にMAINプロシージャが起動します。

このように、DocumentCompleteイベントとOnTimeメソッドを組み合わせることで、クリック後も正常に作動し、JAVAで動くアプリを制御できるようになりました。

コードを一旦止めたいときどうしたらいいかという話題で、よくSleep関数やWaitメソッドが紹介されています。確かに使いやすい方法ですが、コードを「完全」に止める(操作する対象のオブジェクトを一定時間開放する)必要があるときには、OnTimeメソッドと判断・条件分岐を使って行う方法も、一考に値すると思います。
ただ、難点は、AccessにOnTimeメソッドがないこと。ExcelとWordにはあるのにね。だから、この方法はAccessでは使えませんorz。

今回は、適切に開放すべきオブジェクトは、適宜開放しているつもりなのですが、うまくいきませんでした。経験と知識がある人なら、実際のコードを見ておかしいところがあるかもしれません。でも、なぜこうなったのか、分からないんですよね・・・・・。

さて、またもや余談ですが、VBAで処理を自動化したときのイベントログと、手動でクリックしたときのイベントログを比較してみました。その結果、StatusTextChangeイベントのログから、手動でクリックしたときに発生しているアプレットのうちのいくつかが、自動処理したときに発生していないことが分かりました。まあ、そんなこともあるでしょう。きっとちゃんとエミュレートするには、クリックをエミュレートするだけでは杜撰でしょうね。でも、ユーザサイドから見ている限りでは、ちゃんと動いています。どこかにエラーログが大量に残っていたりするのかな?

いずれにしても、当初の目的を達成することができたので、一段落。これで繁忙期の業務が大幅に軽減できます。やれやれです。



Twitter from tokidokidokin
アーカイブ
Portfolio Categories
  • カテゴリーなし
Portfolio Tags