テキストボックス内のハイライト部分をそれぞれ配列に格納して、Collectionにする(Word)
テキストボックス内のハイライト部分をそれぞれ配列に格納して、Collectionにする
意味の取りづらいタイトルですまない。
テキストボックス内のハイライト部分(Range
オブジェクト)をテキストボックスごとに配列にして、それぞれの配列をCollection
にぶち込もう、ということ。
余計にわからなくなったとしたら申しわけない。
ハイライト部分を保持するクラス
なぜかクラスモジュールを使う。
クラス名は「HighLightedRange
」にした。相変わらずクラスの命名のこつがわからない。
すでに作成済みのクラスモジュールのコードを全て載っけると異様に長くなるので、必要な部分のみ掲載する。
リスト1 クラスモジュール
'Variable' Private ParentDoc_ As Document Private HighLightedRangesInTextBox_ As Collection 'Properties' Public Property Get ParentDoc() As Document Set ParentDoc = ParentDoc_ End Property Public Property Get HighLightedRangesInTextBox() As Collection Set HighLightedRangesInTextBox = HighLightedRangesInTextBox_ End Property 'Constructor' Private Sub Class_Initialize() Set ParentDoc_ = ActiveDocument End Sub Public Sub init(ByVal targetDocument As Document) Set ParentDoc_ = targetDocument End Sub 'Methods' Public Sub getHighLightedRangesInTextBox() Dim targetShape As Word.Shape 'Collectionオブジェクトのインスタンスを準備' Set HighLightedRangesInTextBox_ = New Collection 'Shapeオブジェクトを一つづつスキャン' For Each targetShape In ParentDoc_.Shapes Application.ScreenUpdating = False Dim ar() As Range Dim i As Long i = 0 '捕まえたShapeオブジェクトがテキストボックスだったら処理' If targetShape.Type = msoTextBox Then 'テキストボックスが文字列を持っていたら処理' If targetShape.TextFrame.HasText Then '一旦文字列全体を選択' targetShape.TextFrame.TextRange.Select '選択範囲を先頭方向に向って潰す' Call Selection.Collapse(Direction:=wdCollapseStart) 'Findオブジェクトを初期化' Call resetFindObject(Selection.Find) '必要なプロパティを設定' Selection.Find.Highlight = True '検索実行' Call Selection.Find.Execute Do While Selection.Find.Found '配列の容量を拡張' ReDim Preserve ar(i) '現在の選択範囲を配列に格納' Set ar(i) = Selection.Range '変数をインクリメント' i = i + 1 '現在の選択範囲を終端方向に潰す' Call Selection.Collapse(Direction:=wdCollapseEnd) '検索実行' '次のマッチ部分があれば、そこが選択状態になる' Call Selection.Find.Execute Loop End If End If '上のDoループで作成された配列をCollectionに追加' Call HighLightedRangesInTextBox_.Add(ar) Application.ScreenUpdating = True Next End Sub 'Findオブジェクトのリセット用Privateメソッド' Private Sub resetFindObject(ByRef targetFind As Find) With targetFind Call .ClearFormatting Call .Replacement.ClearFormatting .Text = "" .Replacement.Text = "" .Forward = True .Wrap = wdFindContinue .Format = False .Highlight = False .MatchCase = False .MatchWholeWord = False .MatchByte = False .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With End Sub
コード中のresetFindObject
メソッドについては、
コチラをご覧ください。
getHighLightedRangesInTextBox
メソッドが、テキストボックスのハイライト部分を配列化→配列のコレクション化を行う。処理の内容については、コード中にかなり細かくコメントを入れたので、そちらをよく読んでくだされ。
簡単に説明すると、
コチラでご紹介したやり方でもって、テキストボックスの文字列の先頭にカーソルを置いて、あとは
コチラでご紹介したやり方でハイライト部分(Range
オブジェクト)を配列にぶち込んでいっている。
このときと違うのは、さらにその配列をCollection
にぶち込んでいる、というだけ。
使ってみる
このように、テキストボックスを二つ設置したDocumentを用意して、次のコードを実行してみる。
リスト1 標準モジュール
Public Sub test() Dim highLightedRange_ As New HighLightedRange With highLightedRange_ .getHighLightedRangesInTextBox End With End Sub
もちろん、単にこのコードを実行しただけだと、表面上何も起こらない。
配列を作ってCollection
にぶち込んでいるだけだし、そのCollection
にしたところで、実行後は破棄されてしまうのだから。
従って、End Sub
のところにブレークポイントを設定してコードの実行を中断し、イミディエイト・ウインドウに次のように打ち込んで[Enter]し、変数の内容を調べてみる。
?highLightedRange_.HighLightedRangesInTextBox(1)(0).Text
?highLightedRange_.HighLightedRangesInTextBox(1)(1).Text
?highLightedRange_.HighLightedRangesInTextBox(1)(2).Text
?highLightedRange_.HighLightedRangesInTextBox(1)(3).Text
?highLightedRange_.HighLightedRangesInTextBox(2)(0).Text
?highLightedRange_.HighLightedRangesInTextBox(2)(1).Text
鋭い方なら三つ目と四つ目に「?」となるだろうが、実行結果を見てほしい。
実行結果
なぜか、このような結果になる。
一つ目のテキストボックスは、ハイライト箇所が2箇所しかないのに、その2箇所が二重に取得されてしまっている。
二つ目のテキストボックスはちゃんと取得されているのに。
なぜこうなるのか、全然わかりません。
おわりに
実は、今回のHighLightedRange
クラスに、次のメソッドを追加して、上記getHighLightedRangesInTextBox
メソッド実行後に実行すると、おかしなことが起こる。
toggleHighLightInTextBoxメソッド
Public Sub toggleHighLightInTextBox() Dim i As Long For i = 1 To HighLightedRangesInTextBox_.Count Dim j As Long For j = LBound(HighLightedRangesInTextBox_(i)) To _ UBound(HighLightedRangesInTextBox_(i)) With HighLightedRangesInTextBox_(i)(j) If .HighlightColorIndex = wdYellow Then .HighlightColorIndex = wdNone Else .HighlightColorIndex = wdYellow End If End With Next Next End Sub
getHighLightedRangesInTextBox
メソッドで取得したRange
オブジェクト一つ一つのHighlight
プロパティの値を調べて、必要に応じて設定し直すだけのしょうもないメソッドなのに、なぜかこれを実行すると、私の環境(Windows10 64Bit & Word2013 32Bit)ではWordが強制終了されてしまうのである。
しかも、Wordが自動的に再起動した後、標準モジュールやクラスモジュールの復元に失敗したらしく、間違えて上書き保存してしまってせっかく書いたコードが一度失われるという悲劇に……。
ホントにわけわかんないんですけど……。
転記用マクロをサクッと書いてみた(Excel)
転記用マクロをサクッと書いてみた
Twitterで話題になっていたのでちょっと乗っかってみた。
転記処理は、基本的に各転記元のブックの方で、集計(積み上げ)対象のデータ(笑)だけを別シートに転記しておいて、集計(積み上げ)時には、そのシートのデータ(笑)をまるごと集計(積み上げ)用シートに積み上げていくことしかやったことがなかったので、ちょっと腕試しに……。
お題
- 転記元のブックは複数ある(たぶん、ファイル名もでたらめ。)。
- 転記元ブックのシートのデータ(笑)を全て転記するわけではなく、特定の条件に当てはまっている行のデータ(笑)のみ転記する。
だいたいこういう条件だと思った。
実験の準備
次のように、テスト用のデータ(笑)を準備した。
転記先ブックと転記元ブック
画像のように、フォルダ内に、転記先ブック一つと転記元ブック複数を用意した。
転記先ブック
転記先ブックには、このように項目のラベルだけを作った。
ファイル名が「★転記先ブック.xlsm
」となっていることからお分かりのように、マクロはこのブックに書く。
転記元ブック
転記元ブックは、次のように複数準備した。
転記元01.xlsx
転記元02.xlsx
転記元03.xlsx
転記元04.xlsx
このように、実に詳細なデータ(笑)の入った四つのブックを用意した。あーしんど。
コーディング
とりあえず、再利用性や保守(保守は英語でメンテナンス)性は無視。心にうつりゆくよしなしコードを、そこはかとなく書きつけたものが次のコード。
リスト1 標準モジュール
Public Sub transferData() '転記先シートを変数にぶち込む' Dim mainSh As Worksheet Set mainSh = Sheet1 '現在のフォルダパスを変数にぶち込む' Dim folderPath As String folderPath = ThisWorkbook.Path & "\" '" '処理済みファイル用フォルダの準備' Dim saveFolder As String saveFolder = folderPath & "処理済み\" '" '「処理済み」フォルダがなかったら作る' If Dir(saveFolder) = "" Then Call MkDir(saveFolder) '一つ目の転記元ブックのファイル名を取得' Dim targetFileName As String targetFileName = Dir(folderPath & "*.xlsx", vbNormal) '該当ファイルにヒットしなければ煽って終了w' If targetFileName = "" Then _ Call XlsCommon.makeUserSick("ファイルがないんじゃぼけー!"): _ Exit Sub '転記先シートの転記対象行番号用変数の準備&初期化' Dim n As Long n = 2 Do While targetFileName <> "" '転記元シートを取得して変数にぶち込む' Dim targetSh As Worksheet Set targetSh = Workbooks.Open(folderPath & targetFileName).Worksheets(1) '転記元シートのデータ(笑)最終行を取得' Dim maxRow As Long maxRow = targetSh.Cells(Rows.Count, 1).End(xlUp).Row '転記元シートをスキャン' Dim i As Long For i = 2 To maxRow With mainSh '条件に当てはまったら転記&行番号用変数インクリメント' If targetSh.Cells(i, 7).Value = "先捲" Or _ targetSh.Cells(i, 7).Value = "捲先" Then .Range(.Cells(n, 1), .Cells(n, 8)).Value = _ targetSh.Range(targetSh.Cells(i, 1), targetSh.Cells(i, 8)).Value n = n + 1 End If End With Next '転記元ブックを保存せずに閉じる' Call targetSh.Parent.Close(SaveChanges:=False) '「処理済み」フォルダへ移動' Name folderPath & targetFileName As _ saveFolder & targetFileName '次の転記元ブックのファイル名を取得' targetFileName = Dir() Loop 'データ(笑)の範囲に格子罫線を施す' mainSh.Range("A1").CurrentRegion.Borders.LineStyle = xlContinuous End Sub
処理の手順は全てコード中にコメントで記した。転記元の表のデータ(笑)の中から、「戦法」が「先捲」か「捲先」の行のデータ(笑)だけを転記先ブックに積み上げていくマクロ。我ながらReadableなコードだとqあsうぇdrftgyふじこlp……あっ、物を投げないでくださいよ!
そうそう、コード中のXlsCommon.makeUserSick
メソッドについては、
コチラをどうぞ。
実行してみる
「★転記先ブック.xlsm
」だけを開いた状態で実行。
まず、
フォルダ内に「処理済み」フォルダが生成され、
画面がボカチカチカチカした後、転記が完了。
ちなみに、「処理済み」フォルダ内は、
こんな感じ。
おわりに
コードそのものをもっとReadableにする手はあるし、エラー対応もほとんど考えていないコードだけれど、即興だったらこんなもんかなあ。
ちなみに、フォルダ内に処理対象ブック(っていうか、.xlsx
ファイル)がない場合にこのマクロを実行すると、
こうなりますw
テキストボックスの先頭にカーソルを置く(Word)
テキストボックス内の先頭にカーソルを置く
Documentオブジェクトの場合
Document
オブジェクトの先頭位置にカーソルを置くのは簡単。
Document.Range(0, 0).Select
とすれば、ドキュメントの先頭位置にカーソルを置くことができる。
テキストボックスの場合
ところが、テキストボックスの場合、Range
メソッドを持つオブジェクトが見当たらない(ですよね?)ので、そもそも
Object.Range(0, 0)
で先頭位置のRange
オブジェクトを取得することができない。
テキストボックスはWord.Shape
オブジェクト。テキストボックスに書き込まれたテキストを取得するには、
Document.TextFrame.TextRange.Text
と、非常に多くの階層をたどる必要がある。
「TextRange
」というのがRange
オブジェクトを返しそうな名前のプロパティなので調べてみると、確かにRange
オブジェクトを返すらしい。
で、イミディエイト・ウインドウに、次のように打ち込んで[Enter]してみる。
ActiveDocument.TextFrame.TextRange.Select
このように、テキストボックス内のすべてのテキストが選択された。
これにCollapse
メソッドを組み合わせたら、カーソルを先頭に置くことは可能だろう。
カーソルをテキストボックスの先頭に置くコード
リスト1 標準モジュール
Public Sub test() ActiveDocument.Shapes(1).TextFrame.TextRange.Select Call Selection.Collapse(Direction:=wdCollapseStart) End Sub
選択範囲を、開始位置方向に向けて潰す、といったイメージのコード。
TextFrame
オブジェクトの段階でRange
メソッド使わせてくれたらいいのに……。
こいつを実行すると、
わかりづらい画像ですまない。
カーソルが先頭に来ている。
これで、Find
オブジェクトを使って検索することができる。
配列を配列の要素にする
配列の配列を作ってみる
配列の要素を配列にすることはできるのか。
やってみた。
準備
Split
関数をラップしたFunctionを作って、簡単にString
型の配列を作れるようにした。
リスト1 標準モジュール
Private Function setArray( _ ByVal spaceSeparatedWords As String) _ As String() Dim ar As Variant ar = Split(spaceSeparatedWords) setArray = ar End Function
単語ごとに半角スペースで区切った文字列を渡したら、それらの単語を格納した配列を返すFunction。
配列を返り値として受け取るときのルールがイマイチよくわかっとらんので、たぶん変なことをしているのだと思う。
これで準備はおしまい。
配列を配列にぶちこんでみる
次のようなコードで実験してみた。
リスト2 標準モジュール
Public Sub test() Dim ar1() As String '……(1)' ar1 = setArray("アホ ボケ カス") Dim ar2() As String ar2 = setArray("クズ ドロボー ロクデナシ") Dim ar3() As String ar3 = setArray("デコスケ ボーフラ ウジムシ") Dim ars(2) As Variant '……(*)' ars(0) = ar1 '……(2)' ars(1) = ar2 ars(2) = ar3 Dim i As Long '……(3)' Dim j As Long For i = 0 To 2 For j = 0 To 2 Debug.Print ars(i)(j) Next Debug.Print String(10, "=") Next End Sub
(*)のところでは、
Dim ars(2) As String
とすると、「型が一致しません」エラーになる。
ぶち込みたいのはString
型の値なのではなく、String
型の配列なのだから当り前だ。
かといって、
Dim ars(2) As String()
としたのではもっとダメ。そもそもコンパイルが通らない。
そんなわけで、今のところVariant
にしている。
見てもらったらわかると思うけれど、(1)からの6行
Dim ar1() As String '……(1)' ar1 = setArray("アホ ボケ カス") Dim ar2() As String ar2 = setArray("クズ ドロボー ロクデナシ") Dim ar3() As String ar3 = setArray("デコスケ ボーフラ ウジムシ")
では、「ar1
」、「ar2
」、「ar3
」というString
型の配列を作っている。
それを(2)からの3行
ars(0) = ar1 ars(1) = ar2 ars(2) = ar3
でVariant
型の配列にぶち込んでいる。
あとは、(3)からの8行
Dim i As Long Dim j As Long For i = 0 To 2 For j = 0 To 2 Debug.Print ars(i)(j) Next Debug.Print String(10, "=") Next
の二重For
ループで、それぞれの配列の値をイミディエイト・ウインドウに出力する。
実行結果
ご覧の通り。
配列に配列をぶち込むことも可能。
さて、どんな用途があるのかのう……。
Findオブジェクトの設定をリセットするメソッド
Findオブジェクトの設定をリセットするメソッド
このとき
のリスト1で、Document内の黄色蛍光マーカ位置をRange
配列として返すメソッドを作った。
その中で、Find
オブジェクトを初期設定する部分、すなわち、
Call Selection.Find.ClearFormatting Call Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "" .Replacement.Text = "" .Forward = True .Wrap = wdFindContinue .Format = False .Highlight = True .MatchCase = False .MatchWholeWord = False .MatchByte = False .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With
この部分が異様にタテ長で気に入らなかった。
Find
オブジェクトを利用するたびに、こんなタテ長のブロックをいちいち書く(コピペする)となると非常にめんどくさい。
そこで、
- 一旦
Find
オブジェクトを初期化する - 必要なプロパティのみ再設定する
というやり方を考えた。
Findオブジェクトを初期化するメソッド
上掲のリスト1では、「蛍光マーカ」の有無を表すプロパティ(Highlight
)のみTrue
にしていた。蛍光マーカの有無だけを調べたかったのでそうしている。つまり、それ以外は初期設定だとみなせるということ。したがって、Highlight
プロパティをFalse
にしておけば、まさに初期化状態、ということだ。
リスト2 標準モジュール
Public Sub resetFindObject(ByRef targetFind As Find) With targetFind .ClearFormatting .Replacement.ClearFormatting .Text = "" .Replacement.Text = "" .Forward = True .Wrap = wdFindContinue .Format = False .Highlight = False .MatchCase = False .MatchWholeWord = False .MatchByte = False .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With End Sub
Find
オブジェクトを返すFunctionにすることも考えたが、返り値のFind
オブジェクトをSelection.Find
にセットするというのがよくわからなかったので、ByRef
指定で直接Find
オブジェクトを書き換える形にした。
このメソッドに一旦Selection.Find
オブジェクトを渡してFind
オブジェクトを書き換えておき、その後で、設定したいプロパティだけ設定すればよい。
前回のコードの修正
というわけで、前回のコードを修正してみる。
修正前
Public Function getHighLightedRange( _ ByVal targetDocument As Document) As Range() Application.ScreenUpdating = False Dim targetRange As Range Set targetRange = Selection.Range Call targetDocument.Range(0, 0).Select Call Selection.Find.ClearFormatting Call Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "" .Replacement.Text = "" .Forward = True .Wrap = wdFindContinue .Format = False .Highlight = True .MatchCase = False .MatchWholeWord = False .MatchByte = False .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Call Selection.Find.Execute Dim ar() As Range Dim i As Long i = 0 Do While Selection.Find.Found ReDim Preserve ar(i) Set ar(i) = Selection.Range i = i + 1 Call Selection.Collapse(Direction:=wdCollapseEnd) Call Selection.Find.Execute Loop Call targetRange.Select getHighLightedRange = ar Application.ScreenUpdating = True End Function
で、お次が修正後。
修正前
Public Function getHighLightedRange( _ ByVal targetDocument As Document) As Range() Application.ScreenUpdating = False Dim targetRange As Range Set targetRange = Selection.Range Call targetDocument.Range(0, 0).Select Call resetFindObject(Selection.Find) '……(1)' Selection.Find.Highlight = True '……(2)' Call Selection.Find.Execute Dim ar() As Range Dim i As Long i = 0 Do While Selection.Find.Found ReDim Preserve ar(i) Set ar(i) = Selection.Range i = i + 1 Call Selection.Collapse(Direction:=wdCollapseEnd) Call Selection.Find.Execute Loop Call targetRange.Select getHighLightedRange = ar Application.ScreenUpdating = True End Function
(1)の
Call resetFindObject(Selection.Find)
でFind
オブジェクトを初期化し、
(2)の
Selection.Find.Highlight = True
でHighlight
プロパティのみ設定。
あのくっそ長かったFind
オブジェクト準備用コードがすっげー短くなった。
おわりに
これでより一層Find
オブジェクトが使いやすくなったなあ。
Document.ShapeオブジェクトのNameプロパティとは何なのか(Word)
Document.ShapeオブジェクトのNameプロパティとは何なのか
ちょっと意味がわからない現象に気づいたので、記しておく。
二つのテキストボックス
WordのDocumentに二つテキストボックスを設置してみる。
こんな感じ。
んで、次のようなコードを実行してみる。
リスト1 標準モジュール
Public Sub test() Dim shp As Word.Shape For Each shp In ActiveDocument.Shapes Debug.Print shp.Name Next End Sub
ActiveDocument
にあるShapes
コレクション(今回の場合だとテキストボックス二つのこと。)の要素であるShape
オブジェクト(つまりは、二つのテキストボックスそれぞれ)のName
プロパティの値をイミディエイト・ウインドウに出力するだけの簡単なコード。
さて、このコードの実行結果はどうなるか。
こうなるんである。
なぜか、どちらも「テキスト ボックス 2
」。なんでやねん。
インデックスで指定してみる
イミディエイト・ウインドウに
ActiveDocument.Shapes(1).Select
と打ち込んで[Enter]してみる。すると、
こうなる。納得の結果。
次に、同じようにイミディエイト・ウインドウに
ActiveDocument.Shapes(2).Select
と打ち込んで[Enter]してみる。
こうなる。これまた至極当然の結果。
では、
ActiveDocument.Shapes("テキスト ボックス 2").Select
とイミディエイト・ウインドウに打ち込んで[Enter]するとどうなるか。
こうなるのである。
???
今度は、イミディエイト・ウインドウに
ActiveDocument.Shapes("テキスト ボックス 1").Select
と入力して[Enter]。
これはさすがに実行時エラーになると思った。しかし、
なぜかエラーが出ずに、さっきと全く同じ結果になった。
おわりに
二つ目のテキストボックスは、一つ目のテキストボックスをコピーしたものなので、同じ名前になってしまったのだろうか? つまり、
- 一つ目のテキストボックスを挿入する。名前は「テキスト ボックス 1」。
- 一つ目のテキストボックスをコピーして二つ目のテキストボックスが生まれる。
- 二つ目のテキストボックスに名前「テキスト ボックス 2」が付与される。
- しかし、両者は同じものなので、一つ目のテキストボックスの名前も見かけ上「テキスト ボックス 2」になってしまう。
- ただし、元の名前「テキスト ボックス 1」は完全に消滅したわけではない。
こんなわけのわからないことになっているのだろうか???
わからんなあ。
IE操作のためのクラスを作った
クラスにしてしまったのでぶちまけておく
職場のクソWebアプリの攻略の過程で色んなメソッドを作ったので、無駄にクラス化しておいた。
思いつきを行き当たりばったりで形にしただけなので、ツッコミどころはたくさんあると思う。
複数インスタンスを作るシチュエーションも思い浮かばないので、もしかしたらAttribute VB_PredeclaredId
をTrue
にしてもいいかもしれない。
いちいち手入れするのもめんどくさいので、ソースコードをそのままぶちまけておく。
どうせ一般ウケは狙ってないし。(←負け惜しみw)
WebAppクラス
リスト1 クラスモジュール
オブジェクト名を「WebApp
」としています。
Option Explicit Private Declare Function GetTickCount Lib "kernel32" () As Long Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Private GotIE_ As InternetExplorer Private CurrentDocument_ As HTMLDocument Public Property Get GotIE() As InternetExplorer Set GotIE = GotIE_ End Property Public Property Get CurrentDocument() As HTMLDocument On Error Resume Next Set CurrentDocument = GotIE_.Document If Err.Number > 0 Then Set CurrentDocument = Nothing On Error GoTo 0 End Property Public Property Get PageHTMLSource() As String Dim ret As String On Error Resume Next ret = Me.getHTMLSource On Error GoTo 0 PageHTMLSource = ret End Property Private Sub Class_Initialize() Set GotIE_ = New InternetExplorer End Sub Private Sub Class_Terminate() Set GotIE_ = Nothing Set CurrentDocument_ = Nothing End Sub Public Sub init(ByVal targetURL As String) '擬似コンストラクタ' With GotIE_ .Visible = True Call .Navigate(targetURL) Do While .Busy Or .ReadyState <> READYSTATE_COMPLETE DoEvents Loop Call waitFor(2000) Set CurrentDocument_ = .Document End With End Sub Public Function getIEByTitle( _ ByVal titleKeyWord As String) As InternetExplorer 'タイトルにキーワードを含むページを表示中のIEを取得するメソッド' Dim shellApp As New Shell Dim shellWin As Object For Each shellWin In shellApp.Windows If shellWin.Name = "Internet Explorer" Then On Error Resume Next If InStr(1, shellWin.Document.Title, titleKeyWord) > 0 Then Set GotIE_ = Nothing Set GotIE_ = shellWin Exit For End If On Error GoTo 0 End If Next If GotIE_ Is Nothing Then Exit Function Set getIEByTitle = GotIE_ Set shellApp = Nothing Set shellWin = Nothing End Function Public Function getIEByURL( _ ByVal targetURLKey As String) As InternetExplorer 'URLのキーワードが一致するページを表示中のIEを取得するメソッド' Dim shellApp As New Shell Dim shellWin As Object For Each shellWin In shellApp.Windows If shellWin.Name = "Internet Explorer" Then On Error Resume Next If InStr(1, shellWin.Document.URL, targetURLKey) > 0 Then Set GotIE_ = shellWin Exit For End If On Error GoTo 0 End If Next If GotIE_ Is Nothing Then Exit Function Set getIEByURL = GotIE_ Set shellApp = Nothing Set shellWin = Nothing End Function Public Function isTargetPage( _ ByVal pageTitleKeyWord As String) As Boolean 'ページタイトルにキーワードを含んでいるか判定するメソッド' isTargetPage = True On Error Resume Next If InStr(1, Me.CurrentDocument.Title, pageTitleKeyWord) > 0 Then _ Exit Function On Error GoTo 0 isTargetPage = False End Function Public Function getElementByTagAndKeyWord( _ ByVal targetTagName As String, _ ByVal targetKeyWord As String) As Object 'タグ名とキーワードを要素内の文字列に持つ要素を取得するメソッド' Dim ret As Object Set ret = Nothing With Me.CurrentDocument Dim targetElement As Object For Each targetElement In .getElementsByTagName(targetTagName) If InStr(1, targetElement.outerHTML, targetKeyWord) > 0 Then Set ret = targetElement Exit For End If Next End With Set getElementByTagAndKeyWord = ret End Function Public Function getHTMLSource() As String '表示中のHTMLドキュメントのソースを取得するメソッド' Dim ret As String ret = "" On Error Resume Next ret = GotIE_.Document.all(0).outerHTML On Error GoTo 0 getHTMLSource = ret End Function Public Sub createSourceHTMLFile(ByVal fileFullName As String) '表示中のページのHTMLソースをファイルとして出力するメソッド' Dim n As Long n = FreeFile(0) Open fileFullName For Output As n Print #n, Me.PageHTMLSource & vbCrLf Close #n End Sub Public Sub Quit() 'IEを終了するメソッド' GotIE_.Quit End Sub Private Sub waitFor(ByVal milliSeconds As Long) '待ち時間設定用メソッド' '内部処理専用' Dim startTime As Long startTime = GetTickCount Do While (GetTickCount - startTime) < milliSeconds DoEvents Call Sleep(1) Loop End Sub
各メソッドの機能は、コード中のコメントをどうぞ。(なげやり)
使ってみる
WebApp
クラスを、次のコードで利用してみる。
リスト2 標準モジュール
Public Sub disposable03() Dim webApp_ As New WebApp With webApp_ '最初のページに移動する' Call .init("http://akashi-keirin.hatenablog.com/entry/2018/12/16/001606") '最初のページのHTMLソースを出力する' Call .createSourceHTMLFile(ThisWorkbook.Path & "\「マーカ部分を……」_src.html") '「検索」ボックスに「ち~んw」を入力する' Dim targetTextBox As HTMLInputElement On Error Resume Next Do Err.Clear Set targetTextBox = .getElementByTagAndKeyWord("input", "name=""q""") targetTextBox.Value = "ち~んw" Loop Until Err.Number = 0 '検索実行ボタンをクリックする' Dim targetButton As HTMLInputButtonElement Set targetButton = .getElementByTagAndKeyWord("input", "value=""検索""") targetButton.Click Call WindowsAPI.waitFor(5000) 'ページが取得できているかどうか試行錯誤する' Dim n As Long n = 1 Do DoEvents Call WindowsAPI.waitFor(1000) If n > 2 Then _ Call .getIEByTitle("ち~んw") Debug.Print "Wait " & n & " 回目:" & .CurrentDocument.Title n = n + 1 If n > 5 Then .Quit: Exit Do Loop Until .isTargetPage("ち~んw") End With '移動後のページのHTMLソースを出力する' Call webApp_.createSourceHTMLFile( _ ThisWorkbook.Path & "\「ち~んw」の検索結果_src.html") 'メッセージを表示してすべてを終わらせる' Debug.Print "終了" webApp_.Quit Set webApp_ = Nothing Set targetTextBox = Nothing Set targetButton = Nothing End Sub
それぞれのセクションで何をやっているのかは、コード中のコメントをどうぞ。(なげやり)
おわりに
ひとまず、これだけの機能があれば、うちの職場のクソWebアプリには圧勝できる。