VBAの勉強を始めてみた

色々試しています。

Word 非連続な位置にクリップボードのデータを貼り付ける

タイトルの通り、今回は Word の文書で非連続な位置にクリップボードのデータを貼り付けるということをやってみようと思います。

と、いっても非連続な位置に対してどうやってマクロで処理するのか・・・・・・。
少し考えた結果、以下のようにやってみることにしました。

まず、任意の位置に目印となる文字列を貼り付けておきます。ここでは、目印に (@_@;) を使っていますが、お好みで OK です。(Ctrl + V 等を使って効率よく貼り付けましょう)

f:id:kouten0430:20181006120939j:plain

 

次に、目印の位置に貼り付けるデータをクリップボードに取り込みます。
この状態で、後ほど掲載するマクロを実行します。

f:id:kouten0430:20181006121226j:plain

 

クリップボードデータの1行と、目印の1個が1対1の関係になっていることがポイントです。

 

コードはこちら。

Sub 非連続な位置にクリップボードのデータを貼り付け()
    'クリップボードのデータを貼り付ける位置にあらかじめ目印をつけておいて下さい
    '目印1個がクリップボードデータの1行分に対応します
    Dim Mejirushi As String
    Dim 全文字列 As String
    Dim 分割文字列 As Variant
    Dim i As Integer
    Dim myLib As Object
    Set myLib = CreateObject("new:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")  '参照設定なしでDataObjectのインスタンスを生成する
    
    Mejirushi = "(@_@;)"  '検索する文字列(目印)
    
    myLib.GetFromClipboard
    
    On Error Resume Next
    
    全文字列 = myLib.GetText
    
    On Error GoTo 0
    
    If 全文字列 <> "" Then
        分割文字列 = Split(全文字列, vbCrLf)  '全文字列を改行で分割し、配列に格納する
        i = 0
        
        ActiveDocument.Range(0, 0).Select   '文書の先頭から検索を開始する
    
        With Selection.Find
            .Text = Mejirushi
            
            Do While .Execute   '検索に一致する文字列が無くなるまで下方向に検索する
                If i <= UBound(分割文字列) Then
                    Selection.Range.Text = 分割文字列(i)
                    i = i + 1
                Else
                    Selection.Range.Text = ""   '検索の途中で配列の中身が無くなった場合、余った目印は空白に置換する
                End If
            Loop

        End With
    
    Else
        MsgBox "クリップボードにデータがありません!"

    End If
    
End Sub

 

※コードの大まかな流れ

  • まず、クリップボードの全文字列を変数に格納します。
  • 次に、Split関数を使って全文字列を改行(CrLf)で分割し、配列に格納します。
  • Range(0, 0).Selectで、カーソルを文書の先頭に移動させます。
  • Selection.Findで、カーソル位置から検索開始とし(検索文字列は Text プロパティで指定)、Executeメソッドで検索を1回実行します。
  • 上記を、検索に一致する文字列が無くなるまでループさせます。(Executeメソッドが、検索に一致するものがあれば True を、無ければ False を返すので、これをループの継続条件に利用します)
  • ループ内の処理:検索に一致した文字列は選択状態になるので、配列の文字列と入れ替えます(次のループで、検索は下方向に、配列は次の要素に進みます)。検索の途中で配列の要素が尽きたら、余った目印は空白に置換(要するに削除)します。

 

※コードの使用方法

  • SubからEnd Subまでをコピーし、標準モジュール等に貼り付けて使用して下さい。なお、マクロで実行した処理は「元に戻す」ことができません。実行前に一旦保存し、やり直しのできる状態にしておいて下さい。Wordで標準モジュールにコードを貼り付けてマクロを使用する方法はこちら

Word 標準テンプレート(Normal.dotm)にVBAのコードを記述してマクロを使用するには

今回は Word で

  • VBEを起動する方法
  • 標準モジュールにコードを記述する方法
  • マクロを実行する方法

を順番に説明したいと思います。この辺は Excel とほとんど同じなので安心して下さい。

 

Word のデフォルトでは VBE を起動するためのボタンが非表示になっています。まずは VBE を起動できるように設定変更しましょう。リボンのタブ内のどこでもいいので右クリックして「リボンのユーザー設定」をクリックします。(ファイル-オプション-リボンのユーザー設定からでも同じように入れます)

リボンのユーザー設定を開いたら、「開発」にチェックを入れて OK します。

f:id:kouten0430:20181001125643j:plain

 

これでリボンに開発タブが追加されます。※画像の「Visual Basic」のボタンが VBE を起動するためのボタンです。

f:id:kouten0430:20181001125726j:plain

 

さっそく VBE を起動してみましょう。最初は標準モジュールが無い状態なので、左側のプロジェクトエクスプローラーで Normal を選択し「挿入」→「標準モジュール」で、標準モジュールを挿入します。

f:id:kouten0430:20181001130346j:plain

 

Normal は標準テンプレートと呼ばれ、エクセルでいうところの個人用マクロブックにあたります。なので、標準テンプレートに記述されたマクロは、すべての文書で共通して使用することができます。
C:\Users\ユーザー名\AppData\Roaming\Microsoft\Templates に、Normal.dotm というファイル名で用意されており、Word を起動するたびに自動的にロードされます。
VBA のコードの他に、フォント、余白、間隔、およびその他の設定が保存されています。

(それぞれの文書でのみ使用可能なマクロを記述する場合は、Project(文書1)などに標準モジュールを挿入し、コードを記述します)

 

さて、話を元に戻しましょう。
挿入された標準モジュールのコードウィンドウに、SubからEnd Subまでのコードを記述します。SubからEnd Subまでをプロシージャーといい、ざっくり言うとこれが一つのマクロの単位になります。

f:id:kouten0430:20181001130621j:plain

 

これで標準モジュールへ VBA コードの記述が完了しました。VBE を閉じて(右上の「×」を押す)、Word の画面に戻りましょう。

さきほど標準モジュールに記述したマクロを実行するには、開発タブから「マクロ」のボタンを押します。

f:id:kouten0430:20181001130752j:plain

 

すると、マクロの一覧が表示される(マクロの名前はプロシージャー名)ので、目的のマクロを選択して「実行」を押します。これで、SubからEnd Subまでの間に記述されたコードの内容が実行される(マクロが実行される)ことになります。 

f:id:kouten0430:20181001131502j:plain

 

ただし、その都度マクロの一覧から選択して実行するのは時間がかかるので、マクロをリボンやクイックアクセスツールバーに登録しておくと楽チンですよ。

 

マクロをリボンに登録する方法はこちら

kouten0430.hatenablog.com

 

マクロをクイックアクセスツールバーに登録する方法はこちら

kouten0430.hatenablog.com

非表示セルや結合セルを無かったことに・・・選択範囲を見たままコピペ

非表示セルや結合セルを含んだ範囲を普通にコピーし、他のセルへ値貼り付けしたら・・・・・・。

下のように、値が飛び飛びになってしまいます。

f:id:kouten0430:20180922133855j:plain

 

これを、マクロで飛び飛びにならないようにしてみましょう。

f:id:kouten0430:20180922134031j:plain

 

コードはこちらです。 

Sub Tab改行区切りでクリップボードに格納()
    Dim i As Long
    Dim j As Long
    Dim myLib As Object
    Set myLib = CreateObject("new:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")  '参照設定なしでDataObjectのインスタンスを生成する
    
    If Selection.Areas.Count > 1 Then   '複数の矩形範囲が選択されている場合は終了する
        MsgBox "一つの矩形範囲のみ選択して再度実行して下さい。"
        Exit Sub
    End If

    For i = Selection.Row To Selection.Rows(Selection.Rows.Count).Row
        For j = Selection.Column To Selection.Columns(Selection.Columns.Count).Column
            If Cells(i, j).Address = Cells(i, j).MergeArea(1).Address And _
            Rows(i).Hidden = False And Columns(j).Hidden = False Then '結合セルの場合は左上の値のみ取り出す。非表示セルは処理しない
                If Cells(i, j).MergeArea(1).Address = Cells(i, Selection. _
                Columns(Selection.Columns.Count).Column).MergeArea(1).Address Then
                '選択範囲の最終列(最終列を含む結合セル)であれば末尾に改行を追加
                    If InStr(Cells(i, j), vbLf) = 0 Then
                        V = V & Cells(i, j).Value & vbCrLf
                    Else
                        V = V & """" & Cells(i, j).Value & """" & vbCrLf    'セル内改行があれば前後を""で囲む
                    End If
                Else
                '選択範囲の最終列(最終列を含む結合セル)以外は末尾にTabを追加
                    If InStr(Cells(i, j), vbLf) = 0 Then
                        V = V & Cells(i, j).Value & vbTab
                    Else
                        V = V & """" & Cells(i, j).Value & """" & vbTab   'セル内改行があれば前後を""で囲む
                    End If
                End If
            End If
        Next j
    Next i
    
    V = Left(V, Len(V) - 2) '最終行の改行区切りを取り除く(CrLfは2文字)
    
    myLib.SetText V  '変数の値をDataObjectに格納する
    myLib.PutInClipboard 'DataObjectのデータをクリップボードに格納する
    
End Sub

 

コピーしたい範囲を選択してから、マクロを実行します。

値が飛び飛びにならないよう加工され、クリップボードに転送されます。好きなセルへ値貼り付けして下さい。(コピーはマクロで、貼り付けは標準機能で行うという流れです)

 

※コードの大まかな説明

  • まず、複数の矩形範囲が選択されている場合は意図通りに処理することができないので、プロシージャを強制終了します。
  • 次に、下方向をi、右方向をjとして、選択範囲を左上から右下までループ処理します。処理内容は、各セルの値にTabまたは改行を付け足します。(列と列の間にはTabを、行と行の間には改行が入るようにする)
  • もし、上記の処理を非表示セルや結合セル(の左上以外)にも行うと・・・・・・最終的に下のようなデータが出来上がります。

    f:id:kouten0430:20180922134918j:plain

  • ・・・・・・が、これでは普通にコピーしたのと同じで、値貼り付けをすると「画像1」と同様になります。(逆にいうと、これが普通にコピーしたら飛び飛びになってしまう理由です)

  • なので、非表示セルや結合セル(の左上以外)には処理を行わないようにしましょう。そうすると、最終的に下のようなデータが出来上がります。

    f:id:kouten0430:20180922135300j:plain

  • これを値貼り付けすると「画像2」と同様になります。
  • 最後に、このデータをクリップボードへ転送します。

 

※コードの使用方法

  • SubからEnd Subまでをコピーし、標準モジュール等に貼り付けて使用して下さい。なお、マクロで実行した処理は「元に戻す」ことができません。実行前に一旦保存しやり直しのできる状態にしておいて下さい。標準モジュールにコードを貼り付けてマクロを使用する方法はこちら

指定位置(セル)に瞬間移動する方法

エクセルで「列がAAAで行が30000のセルを、表示して下さい」と言われたら、何秒以内に表示できるでしょうか?私がやってみたところ・・・・・・A1セルを表示した状態から、通常のスクロールのみで1分以上かかりました。
時間がかかることと、動く数字やアルファベットを目で追うのは、ストレスや眼精疲労の原因になります。

私はストレスや眼精疲労がこの世で一番嫌いです(@_@;)

なので、今回は指定位置(セル)に瞬間移動する方法を紹介しましょう。

 

標準機能で瞬間移動する

これはとてもカンタンです。
F5キーを押して表示されたボックスに、瞬間移動したい位置を入力してOKします。(A1形式で入力します)

f:id:kouten0430:20180914145359j:plain

 

一度移動した所へは、履歴から選んで瞬間移動することもできます。

f:id:kouten0430:20180914142707j:plain

 

ただし、エクセルを閉じると履歴はリセットされます。 

この標準機能での瞬間移動は、指定位置(セル)を表示し、かつ選択状態にします。


マクロで瞬間移動する

「表示はさせたいけど、セルの選択はしなくていい・・・・・・(>_<)」

という人は、VBAScrollRowプロパティScrollColumnプロパティを使ってみて下さい。ScrollRowプロパティでは指定行をウィンドウの上端に表示させることができ、ScrollColumnプロパティでは指定列をウィンドウの左端に表示させることができます。
この二つを組み合わせて、指定位置を左上に表示させます。

ActiveWindow.ScrollRow = 行番号
ActiveWindow.ScrollColumn = 列番号

 

セルの選択状態は変えず指定位置を左上に表示させるだけなので、表示変更後に任意のセルを Shift + 左クリックすることで範囲選択することもできます。 

 

表示させる位置を、行番号と列番号で指定するサンプルコードはこちら

Sub 指定位置に瞬間移動()
    Dim y As Variant
    Dim x As Variant
    Dim flag As Boolean
    
    y = InputBox("表示する「行」を数値で入力" & vbCrLf & "(行はこのままで良い場合、空白 or キャンセル)")
    
    If y = "" Then
        y = ActiveWindow.ScrollRow
    ElseIf y > 1048576 Then
        y = 1048576
        flag = True
    ElseIf y < 1 Then
        y = 1
    End If
    
    x = InputBox("表示する「列」を数値で入力" & vbCrLf & "(列はこのままで良い場合、空白 or キャンセル)")
    
    If x = "" Then
        x = ActiveWindow.ScrollColumn
    ElseIf x > 16384 Then
        x = 16384
        flag = True
    ElseIf x < 1 Then
        x = 1
    End If

    ActiveWindow.ScrollRow = y
    ActiveWindow.ScrollColumn = x
    If flag Then MsgBox "いしのなかにいる!"
    
End Sub

 

表示させる位置を、行番号と列のアルファベットで指定するサンプルコードはこちら

Sub 指定位置に瞬間移動アルファベットで指定版()
    Dim y As Variant
    Dim x As String
    Dim flag As Boolean
    
    y = InputBox("表示する「行」を数値で入力" & vbCrLf & "(行はこのままで良い場合、空白 or キャンセル)")
    
    If y = "" Then
        y = ActiveWindow.ScrollRow
    ElseIf y > 1048576 Then
        y = 1048576
        flag = True
    ElseIf y < 1 Then
        y = 1
    End If
    
retry:
    x = InputBox("表示する「列」をアルファベットで入力" & vbCrLf & "(列はこのままで良い場合、空白 or キャンセル)")
    
    If x <> "" Then
        x = StrConv(x, vbNarrow)
        x = StrConv(x, vbUpperCase)
        If x Like "*[!A-Z]*" Then
            MsgBox "列はアルファベットのみ入力可"
            GoTo retry
        End If
        On Error GoTo ErrorHandler
        ActiveWindow.ScrollColumn = Range(x & "1").Column
        On Error GoTo 0
    End If

    ActiveWindow.ScrollRow = y
    If flag Then MsgBox "いしのなかにいる!"
    
    Exit Sub
    
ErrorHandler:
    x = "XFD"
    flag = True
    Resume
    
End Sub


は変わりますが・・・・・・、ウィザードリィというゲームで瞬間移動する際に座標を間違えると、通路以外の場所に飛んでしまい

f:id:kouten0430:20180914142403j:plain

 

というメッセージが表示され、パーティが全滅します。

子供の頃、なけなしの小遣いでファミコン版「ウィザードリィIII ダイヤモンドの騎士」を買ったけれど、これに心を折られ、ソフトを中古屋に瞬間移動させた記憶があります←(ぇ)

 

全然関係ない話ですけどね。

 

A1セルに瞬間移動する

表示を、A1セルに戻すサンプルコードはこちら

Sub A1に瞬間移動()
    ActiveWindow.ScrollRow = 1
    ActiveWindow.ScrollColumn = 1
    
End Sub

 

※コードの使用方法

  • SubからEnd Subまでをコピーし、標準モジュール等に貼り付けて使用して下さい。なお、マクロで実行した処理は「元に戻す」ことができません。実行前に一旦保存しやり直しのできる状態にしておいて下さい。標準モジュールにコードを貼り付けてマクロを使用する方法はこちら

VBAでIEを操る(フレームについて)

今回は、フレームについてです。
社内システムでは未だに使われていたりするので、いちおう書いときましょう。

 

フレームとは?

複数個に分割した画面に、それぞれ別のページを表示する機能です。下の例では、3つのHTMLファイルが用意されます。

f:id:kouten0430:20180909000454j:plain

・構成.html(画面表示なし)

  • ユーザーは、この構成用HTMLファイルを読み込むことになります。分割の縦横サイズの設定、分割画面に表示するHTMLファイルのURLなどが記されています。

・メニュー.html(画面左)

・内容.html(画面右)

 

フレーム構造をもったページを操作する

この場合、IE直下のdocumentプロパティでは、フレーム以下のDOMまでは取得できません。

  • 誤:

f:id:kouten0430:20180909001221j:plain

 

frameタグから、さらにdocumentプロパティを参照する必要があります。

  • 正:

f:id:kouten0430:20180909000832j:plain

 

構成.htmlが「親」だとしたら、メニュー.htmlと内容.htmlが「子」です。
もし「孫」「ひ孫」があっても、同じようにそれぞれdocumentプロパティを参照する必要があります。

 

 フレーム以下の要素を取得する場合の記述

一行で記述するならこうです。

  • Set myElem = ie.document.frames(0).document.getElementsByTagName("p")(0)

※取得したいフレーム、要素がそれぞれインデックス0である場合

VBAでIEを操る(ページ移動後にDOMや要素を再取得しなかったらどうなるのか?)

今回は、変数に取得したDOMや要素が、ページ移動後、どんな挙動になるか調べてみます。
ページを移動した後、DOMツリー構造が変わるので、再取得する必要があるだろう・・・・・・と予想できます。が、実際はどうなのか、書籍やネットから腑に落ちる情報を見つけられなかったので、実験してみます。

目次

 

実験に使うのは、以下のHTML文書です。

 

ページ移動前

f:id:kouten0430:20180906164812j:plain

↑このページのソース

<html>
<body>
<p id="ichibanjukusita">りんご</p>
<p>りんご</p>
<p>りんご</p>
<p>りんご</p>
<p>りんご</p>
</body>
</html> 

 

ページ移動後

f:id:kouten0430:20180906164905j:plain

↑このページのソース

<html>
<body>
<p id="ichibanjukusita">みかん</p>
<p>みかん</p>
<p>みかん</p>
<p>みかん</p>
<p>みかん</p>
</body>
</html>

 

この二つのHTML文書を使い、ページ移動前と移動後で、DOMと要素がどうなるのかローカルウィンドウで観察します。

結論から言うと

  • メソッドで取得する要素は、ページ移動後に再取得する必要がある
  • プロパティで取得するDOMは、ページ移動後に再取得する必要なし

 

この結論を得るために、過剰ですが、8パターンの実験をしてみました。

 

要素ひとつを変数に取得し、ページ移動後、再取得せずに使用した場合

Sub test()
    Dim ie As Object
    Dim sh As Object
    Dim win As Object
    Dim myElem As Object
    Dim ページ移動前 As String
    Dim ページ移動後 As String
    
    Set sh = CreateObject("Shell.Application")
    
    For Each win In sh.Windows
        If win.Name = "Internet Explorer" Then
            Set ie = win
            Exit For
        End If
    Next
    
    Set myElem = ie.document.getElementsByTagName("p")(0)
    
    ページ移動前 = myElem.innerText
    
    ie.navigate "C:\Users\みかん.html"
    
    ページ移動後 = myElem.innerText
    
End Sub

 

りんごの木になっている「りんご」を取りに登った、変数の myElem君。

f:id:kouten0430:20180906165300j:plain

 

「りんご」を取ったあとに「みかん」も欲しくなったので、みかんの木に移動したけれど、みかんの木の登りかた(メソッド)が分からなかったので

f:id:kouten0430:20180906165449j:plain

 

「みかん」を手に入れることはできなかった・・・・・・。

 

要素ひとつを変数に取得し、ページ移動後、再取得した場合

Sub test()
    Dim ie As Object
    Dim sh As Object
    Dim win As Object
    Dim myElem As Object
    Dim ページ移動前 As String
    Dim ページ移動後 As String
    
    Set sh = CreateObject("Shell.Application")
    
    For Each win In sh.Windows
        If win.Name = "Internet Explorer" Then
            Set ie = win
            Exit For
        End If
    Next
    
    Set myElem = ie.document.getElementsByTagName("p")(0)
    
    ページ移動前 = myElem.innerText
    
    ie.navigate "C:\Users\みかん.html"
    
    Set myElem = ie.document.getElementsByTagName("p")(0)
    
    ページ移動後 = myElem.innerText
    
End Sub

 

またまた、りんごの木になっている「りんご」を取りに登った myElem君。

「りんご」を取ったあとに「みかん」も欲しくなったので、みかんの木に移動し、みかんの木の登りかた(メソッド)を知っていたので

f:id:kouten0430:20180906165637j:plain

 

「みかん」も手に入れることができた。

 

同じ要素すべてを変数に取得し、ページ移動後、再取得せずに使用した場合

Sub test()
    Dim ie As Object
    Dim sh As Object
    Dim win As Object
    Dim myCollection As Object
    Dim ページ移動前 As String
    Dim ページ移動後 As String
    
    Set sh = CreateObject("Shell.Application")
    
    For Each win In sh.Windows
        If win.Name = "Internet Explorer" Then
            Set ie = win
            Exit For
        End If
    Next
    
    Set myCollection = ie.document.getElementsByTagName("p")
    
    ページ移動前 = myCollection(0).innerText
    
    ie.navigate "C:\Users\みかん.html"
    
    ページ移動後 = myCollection(0).innerText
    
End Sub

 

myCollection君は大きなハンマーでりんごの木を叩き、「りんご」をすべて落としてから、「りんご」をひとつ入手した。

f:id:kouten0430:20180906165958j:plain

 

しばらくして「みかん」も欲しくなったので、みかんの木に移動したけれど、myCollection君はハンマーを持ってこなかったので、

f:id:kouten0430:20180906170012j:plain

 

「みかん」を手に入れることはできなかった・・・・・・。

 

同じ要素すべてを変数に取得し、ページ移動後、再取得した場合

Sub test()
    Dim ie As Object
    Dim sh As Object
    Dim win As Object
    Dim myCollection As Object
    Dim ページ移動前 As String
    Dim ページ移動後 As String
    
    Set sh = CreateObject("Shell.Application")
    
    For Each win In sh.Windows
        If win.Name = "Internet Explorer" Then
            Set ie = win
            Exit For
        End If
    Next
    
    Set myCollection = ie.document.getElementsByTagName("p")
    
    ページ移動前 = myCollection(0).innerText
    
    ie.navigate "C:\Users\みかん.html"
    
    Set myCollection = ie.document.getElementsByTagName("p")
    
    ページ移動後 = myCollection(0).innerText
    
End Sub

 

myCollection君は「りんご」を取ったあと、みかんの木がある場所にハンマーを持ってきたので、「みかん」をすべて落とし、目的の「みかん」を入手することに成功した。

f:id:kouten0430:20180906170217j:plain

 

IDで指定した要素を変数に取得し、ページ移動後、再取得せずに使用した場合

Sub test()
    Dim ie As Object
    Dim sh As Object
    Dim win As Object
    Dim myId As Object
    Dim ページ移動前 As String
    Dim ページ移動後 As String
    
    Set sh = CreateObject("Shell.Application")
    
    For Each win In sh.Windows
        If win.Name = "Internet Explorer" Then
            Set ie = win
            Exit For
        End If
    Next
    
    Set myId = ie.document.getElementById("ichibanjukusita")
    
    ページ移動前 = myId.innerText
    
    ie.navigate "C:\Users\みかん.html"
    
    ページ移動後 = myId.innerText
    
End Sub

 

myId君は、目印のついている実を打ち落とすことができる魔法の銃を持っています。まず、「りんご」を打ち落とし、

f:id:kouten0430:20180906170407j:plain

 

次にみかんの木に移動しました。・・・・・・が、目印を忘れてしまったため、

f:id:kouten0430:20180906170424j:plain

 

「みかん」を打ち落とすことができませんでした・・・・・・。

 

IDで指定した要素を変数に取得し、ページ移動後、再取得した場合

Sub test()
    Dim ie As Object
    Dim sh As Object
    Dim win As Object
    Dim myId As Object
    Dim ページ移動前 As String
    Dim ページ移動後 As String
    
    Set sh = CreateObject("Shell.Application")
    
    For Each win In sh.Windows
        If win.Name = "Internet Explorer" Then
            Set ie = win
            Exit For
        End If
    Next
    
    Set myId = ie.document.getElementById("ichibanjukusita")
    
    ページ移動前 = myId.innerText
    
    ie.navigate "C:\Users\みかん.html"
    
    Set myId = ie.document.getElementById("ichibanjukusita")
    
    ページ移動後 = myId.innerText
    
End Sub

 

myId君は「りんご」を取ったあと、みかんの木に移動しました。今度は、目印の"ichibanjukusita"を思い出したので、

f:id:kouten0430:20180906170558j:plain

 

「みかん」も打ち落とすことができました。

 

documentプロパティでDOMを変数に取得し、ページ移動後、再取得せずに使用した場合

Sub test()
    Dim ie As Object
    Dim sh As Object
    Dim win As Object
    Dim myDoc As Object
    Dim ページ移動前 As String
    Dim ページ移動後 As String
    
    Set sh = CreateObject("Shell.Application")
    
    For Each win In sh.Windows
        If win.Name = "Internet Explorer" Then
            Set ie = win
            Exit For
        End If
    Next
    
    Set myDoc = ie.document
    
    ページ移動前 = myDoc.getElementsByTagName("p")(0).innerText
    
    ie.navigate "C:\Users\みかん.html"
    
    ページ移動後 = myDoc.getElementsByTagName("p")(0).innerText
    
End Sub

 

myDocさんは、みんなに木の実の取り方を教えてくれる先生です。
myDocさんは、一つのから望遠鏡を使って、いろんな場所にある木のかたちを見ることができます。しかし、一回望遠鏡を使ってしまえば、あとは望遠鏡なしでも、いろんな場所にある木のかたちをライブで見ることができます。

f:id:kouten0430:20180906170740j:plain

 

これは、望遠鏡の使用で発動する myDocさんの特殊能力です。

 

documentプロパティでDOMを変数に取得し、ページ移動後、再取得した場合

Sub test()
    Dim ie As Object
    Dim sh As Object
    Dim win As Object
    Dim myDoc As Object
    Dim ページ移動前 As String
    Dim ページ移動後 As String
    
    Set sh = CreateObject("Shell.Application")
    
    For Each win In sh.Windows
        If win.Name = "Internet Explorer" Then
            Set ie = win
            Exit For
        End If
    Next
    
    Set myDoc = ie.document
    
    ページ移動前 = myDoc.getElementsByTagName("p")(0).innerText
    
    ie.navigate "C:\Users\みかん.html"
    
    Set myDoc = ie.document
    
    ページ移動後 = myDoc.getElementsByTagName("p")(0).innerText
    
End Sub

 

myDocさんは一回望遠鏡を使ってしまえば、あとは望遠鏡なしでもいろんな場所にある木のかたちをライブで見ることができます。だからその都度、望遠鏡を使っていろんな場所にある木を見ることができるのは当然ですね。

f:id:kouten0430:20180906170941j:plain

 

まとめ

8パターンの実験結果は、下記のようになりました。

方法 りんご入手 みかん入手
要素1個を変数に取得し、ページ移動後、変数をそのまま利用 OK NG
要素1個を変数に取得し、ページ移動後、変数に再取得して利用 OK OK
要素のすべてを変数に取得し、ページ移動後、変数をそのまま利用 OK NG
要素のすべてを変数に取得し、ページ移動後、変数に再取得して利用 OK OK
IDから要素1個を変数に取得し、ページ移動後、変数をそのまま利用 OK NG
IDから要素1個を変数に取得し、ページ移動後、変数に再取得して利用 OK OK
documentプロパティでDOMを変数に取得し、ページ移動後、変数をそのまま利用 OK OK
documentプロパティでDOMを変数に取得し、ページ移動後、変数に再取得して利用 OK OK

 
冒頭で結論を先に言ってしまったので繰り返しになりますが、

ページを移動したら

  • 要素は再取得する必要がある
  • DOM は(変数に入っていても)再取得しなくてよい。常に最新の状態である

 

ということが、実験で分かりました。

 

VBAでIEを操る(ポップアップや通知バーをSendKeysで操作する)

VBAIEを操作している途中、ポップアップや通知バーが出てきたらどうすればいいでしょうか?
今回は、その辺に焦点をあててみたいと思います。

ポップアップや通知バーが出たら、選択肢を選んで次へ進んだり、ファイルを保存したりする・・・・・・
これをオブジェクトとして操作する方法もあるようですが、正直、ちょっと煩雑です。

もともと、楽がしたくてIEの自動操作を行っているのに、そのコードを書くために長い時間を割いたり、結局上手くいかなくて何回も試行錯誤したり・・・・・・。
そのうち、本来の目的以上に時間を浪費してしまう矛盾に陥ってしまいます。まぁ、コードの技術は上がりますが(@_@;)

うー・・・ん。

まあ、その都度悩むのはやめにして、ポップアップや通知バーの操作はもれなく SendKeysメソッド で記述する作戦にしようと思います。

SendKeys とはなんぞや?

ということですが、これは、アクティブなアプリケーションにキーストロークを送信することができるメソッド・・・・・・
もうちょっと噛み砕いて言うと、人間が行うキーボード入力を再現することができるメソッド、とゆーことになります。

  • SendKeys 引数

引数には、送信するキーストロークを文字列式で指定します。

例えば、IEでファイルメニューを開くなら、Alt + F なので、下記のようにします。

  • SendKeys "%f"

 % はAltキーを押しながら、ということを表す特殊文字です。

キー操作をどのように表現するかは、MSDN 又はその他のサイトを参照下さいませ。

 

上記をふまえて、通知バーでファイル保存する操作を再現してみたいと思います。

と、その前に、IEがそもそも画面の前面にないと、キーストロークが他のアプリケーションに対して送信されるので、下記のWin APISetForegroundWindow関数IEを最前面に表示して、アクティブにしておきます。(下記は、Win32 API での例)

Declare Function SetForegroundWindow Lib "user32" (ByVal hWnd As Long) As Long

    SetForegroundWindow (ie.hWnd)

太字の行は、モジュールの先頭(宣言エリア)に記述します。

 

さて・・・・・・、話を戻しましょう。

ファイルを保存するリンクやボタンをクリックすると、下記のような通知バー(ポップアップの場合もある)が表示されます。

f:id:kouten0430:20180901155050j:plain

 

画像に表示されているように、「保存」にはショートカットキー"S"が割り当てられているのがわかります。ショートカットキーはAltキーとの同時押しなので、

  • SendKeys "%s", True

とすることで、名前を指定せずに保存できます。(第二引数の True は、キーストロークが渡るまで処理を中断することを表します)

ショートカットキーが効かないページや、名前を付けて保存したいような場合は、Alt + N で通知バーにフォーカスを当ててから、TABキーとEnterで操作することもできます。

まずは、Alt + N で通知バーにフォーカスを当てます。

  • SendKeys "%n", True

f:id:kouten0430:20180901155126j:plain

 

最初に、「実行」が選択されていることがわかります。次に、TABキーで移動し「保存」を選択します。

  • SendKeys "{TAB}", True

f:id:kouten0430:20180901155202j:plain

 

「名前を付けて保存」は、この状態で下方向を押すと現れ、さらに「保存」の下なので、下方向を2回送信します。

  • SendKeys "{DOWN 2}", True

f:id:kouten0430:20180901155228j:plain

 

あとは、Enterで名前を付けて保存します。

  • SendKeys "{ENTER}", True

このあと表示されるダイアログボックスで、SendKeys のみでファイル名を打ち込むことも可能です。(SendKeysの引数には変数を使うこともできるので、連番などを付加することも可能)

一見、メンドくさそうですが、キー操作を模擬するだけのことなのでカンタンです。

※ファイルを保存するには、URLを直接指定する専用のメソッドもありますが、CGIなどで都度ファイルが生成されるようなものはURLが特定できないので、今回のような SendKeys が有効だと思います。