VBAの勉強を始めてみた

色々試しています。

VBAでIEを操る(起動済みIEをシェルとして取得)

前回は、IEを起動させると同時にIEをオブジェクトとして取得しました。
しかし、CreateObjectで起動したIEは何も表示していないIEです。Webページを表示するには、下記のように navigate メソッドでURLを指定します。 

  • ie.navigate "URLをここに記載"

 

ではでは、起動済みの、IEをオブジェクトとして取得するにはどうすればいいでしょうか?

結論からいうと、前回の CreateObject("InternetExplorer.Application") を、CreateObject("Shell.Application") に変えて取得します。

以下、さらっと読んでもらい、とりあえず雰囲気をわかってもらえればOKです。(私もエラそうに語れるほど、詳しくはない・・・・・・)

起動済みのIEを取得するには、起動しているシェルをコレクションとして取得し、さらにシェルのコレクションの中からIEを取り出す必要があります。

シェルとはなぁに?

ってことですが、人間様は普段、OSの核(カーネル)に直接触れることができないので、外皮(シェル)の上から間接的に触れて(アクセスして)います。

f:id:kouten0430:20180811132918j:plain

その、間接的な役割である、シェル(shell32.dll)の機能を使っているプログラム

例えば、ファイルエクスプローラー、コントロールパネル、Internet Explorerなどの中から現在起動中のものを、コレクションとして取得します。

んで、コレクションの中からIEを探し出して取得するために、For Each ~Next でまわして評価するのです。

Nameプロパティで、IEか否か評価するなら次のようにします。

Sub test()
    Dim ie As InternetExplorer
    Dim sh As Object
    Dim win As Object
    
    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

End Sub

 

ついでに、ローカルウィンドウで確認したところ、ファイルエクスプローラーやコントロールパネルのNameプロパティの値は、"エクスプローラー"でした。

 

しかし、上記のコードだとIEが複数起動している場合、どのIEが取得されるか分からないので、目的の(操作したいWebページを表示中の)IEを取得するなら、Titleプロパティ(要するにHTMLのTitleタグで書かれている部分)で判定します。

Titleプロパティで評価するなら次のようにします。

Sub test()
    Dim ie As InternetExplorer
    Dim sh As Object
    Dim win As Object
    Dim DocumentTitle As String
    
    Set sh = CreateObject("Shell.Application")
    
    For Each win In sh.Windows
        DocumentTitle = ""
        On Error Resume Next
        DocumentTitle = win.document.title
        On Error GoTo 0
        If DocumentTitle = "VBAの勉強を始めてみた" Then
            Set ie = win
            Exit For
        End If
    Next
End Sub

 

ファイルエクスプローラーやコントロールパネルは document.title プロパティをもっておらず、実行時エラーとなるため On Error Resume Next ~ On Error GoTo 0 で挟み、無視するようにしています。(無視されると、変数 DocumentTitle の中身は空白になります)

上記のコードで、当ブログのトップページを表示中のIEを取得します。

この状態で、ちょっとした操作をしてみましょう。

Sub test()
    Dim ie As InternetExplorer
    Dim sh As Object
    Dim win As Object
    Dim DocumentTitle As String
    
    Set sh = CreateObject("Shell.Application")
    
    For Each win In sh.Windows
        DocumentTitle = ""
        On Error Resume Next
        DocumentTitle = win.document.title
        On Error GoTo 0
        If DocumentTitle = "VBAの勉強を始めてみた" Then
            Set ie = win
            Exit For
        End If
    Next
    
    ie.document.getElementsByTagName("TITLE")(0).innerText = "タイトルを変更してみた"

End Sub

 

上記のコードを実行すると、下のようにタイトルが変更されます。

f:id:kouten0430:20180811133912j:plain

 

f:id:kouten0430:20180811134130j:plain

 

(ローカルのIE上で変更されているだけなので、サーバー上のデータにはもちろん影響ありません)

 

この辺については次回以降に。

 

補足:タイトルの変更だけなら document.titleプロパティ の値の変更だけでもできる

上記では汎用性がある、getElementsBy・・・・・・の形で変更していますが、タイトルだけなら、単純に、ie.document.title = "タイトルを変更してみた" でも変更できます。

 

補足:起動済みのIEを取得して操作するほうが、オートメーションエラーにならない?

今回のように、シェルから起動済みIEを取得したIEで操作したほうが、オートメーションエラーに悩まされずに済むようです。
というのも、CreateObject したIEでWebページを操作しようとしたところ、何回か「オートメーションエラー」が発生して困ったため(発生するページ、しないページ、さまざまですが)いろいろ調べてみたところ、原因としては、CreateObject した時の何も表示していないIEのセキュリティレベルと、navigateメソッドでの移動後でセキュリティレベルが変わってしまうことがあるためのようです。(というのをどこかのサイトで見た)

 

本記事は、以下の書籍

book.impress.co.jp

 及び、他の様々なサイト様の情報をパクって参考にしています。ご不明な点は書籍または他のサイト様を合わせて参考にして下さい。

VBAでIEを操る(IEをオブジェクトとして取得)

IEInternet Explorer)の後継としてEdgeが登場していますが、IEもまだまだ現役なハズ。私は個人で Chrome を主に使っていますが、会社では IE を使っています(使わされています)。

なので、IEを使った社内システムにおいて、入力作業などを半自動化、あわよくば全自動化したいと思っている今日この頃なのであります。

と、いうことで、今回はVBAIEを操る前の「IEをオブジェクトとして取得する」というところまでをやってみたいと思います。

ゆっくり進行ですが、よろしくお願いします。

IEをオブジェクトとして取得し、VBAで操作する」というと、裏技的でアウトローな響きがありますが、そんなことはなく、Excelであれ、Wordであれ、オブジェクトとして参照し、VBAで操作するという考え方は一緒です。操作対象がExcelからIEになっただけと考えましょう。

IEをうんぬんする前に、VBAとオブジェクトの関係を、私なりに図示してみました。

f:id:kouten0430:20180804125637j:plain

VBAは「ExcelVBA」と呼ばれることが多く、混同してしまいがちですが、VBAVBAExcelExcelなのです。VBAExcelのオブジェクトを参照し、操り人形しているだけです。
外部ライブラリ群に関しては、プログラムの補佐というイメージですが、補佐機能を発動するためにVBAが操っている、というニュアンスの違いだけで他のオブジェクトと同類です。(違っていたらゴメンなさい)

f:id:kouten0430:20180804125922j:plain

さてさて、

傀儡師(プログラム)の指先と、人形(オブジェクト)を、糸(参照)で繋ぐ作業をコードで記述すると Set myApp = CreateObject("XXX") のようになりますが、XXXの部分に図中のアプリケーションを当てはめると以下の通りです。

  • Set myApp = CreateObject("Excel.Application")
  • Set myApp = CreateObject("Word.Application")
  • Set myApp = CreateObject("Access.Application")
  • Set myApp = CreateObject("PowerPoint.Application")
  • Set myApp = CreateObject("InternetExplorer.Application")

 

あれ?いつも、VBAExcelを操作しているけど、Set myApp = CreateObject("Excel.Application")なんて、記述したことないよ。という声が聞こえてきそうですが・・・・・・

確かに、ExcelVBAでは、Excelに付属しているVBAなので、Excelに関してわざわざ CreateObject しなくてもよく、WordVBA、AccessVBA、PowerPointVBAなども同様です。
親切な反面、VBAとオブジェクトの関係をわかりづらくしているような気もしますね(@_@;)

 

さて、今回の本題であるIEの制御ですが、IEVBAは付属していないので、Excel、Word、AccessPowerPointのいずれかに付属しているVBAで、CreateObjectしてやります。

あわせて、参照設定をしておけばインテリセンス(自動補完システム)が働くので便利です。
参照設定を行う際は、以下の項目にチェックを入れます。

 

ここまでを踏まえて、IEをオブジェクトとして取得するコードを記述してみましょう。

Sub test()
    Dim myApp As Object
    Set myApp = CreateObject("InternetExplorer.Application")
    myApp.Visible = True
End Sub


上記のコードを実行すると、何も表示していない、空っぽのIEが立ち上がったはずです。
myApp.Visible = True で画面表示させています。これをしなければ、非表示でバックグラウンドで立ち上がった状態になります。

このコードを実行後、VBEでオブジェクトブラウザーを開くと、IEオブジェクトのメンバがずらりと並んでいることを確認することができます。
(事前に参照設定してあることが前提ですが)

しかし、これだけでは、IEをオブジェクトとして取得しただけなので、プロパティやメソッドの種類や使用方法を知らなければ何にもできません。
その辺については次回以降に。

ひっそりとExcel関数のおさらい(表の縦・横を入れ替える)

今回は、表の縦・横を簡単に入れ替える方法を紹介します。

 目次

 

形式を選択して貼り付け(行列を入れ替える)

これが一番手っ取り早く、簡単です。
表をコピーし、貼り付けたい位置で、形式を選択して貼り付け →「行列を入れ替える」にチェック → OK
これだけです。

f:id:kouten0430:20180729150644j:plain

f:id:kouten0430:20180729150659j:plain


ただし、コピー元・コピー先は参照関係にはなっていないので、コピー元を変更したからといってコピー先が自動的に変更されるということはありません。むしろ、そのほうが都合いい!って場合には、この「形式を選択して貼り付け」が向いてます。

 

TRANSPOSE関数を使用する

=TRANSPOSE(配列)

 ※配列とは、元となる表を指します。表のセル範囲などで指定します。(これを配列引数という)

TRANSPOSE関数を使用して任意の範囲に、元となる表から縦・横を逆にしたクローンを生成します。

注意する点は、TRANSPOSE関数を適用する任意の範囲は、元となる表の行数・列数を逆にした範囲になるということです。

下の画像の元の表(B2:G8)は、7行・6列なので、クローンを生成する任意の範囲は、6行・7列にします。マウス等で選択中、左上に「6R×7C」のように表示されるので、わざわざ目で追って数える必要はありません。※Rは行、Cは列を表します

f:id:kouten0430:20180729151303j:plain

 

任意の範囲を選択できたら、数式バーに =TRANSPOSE(B2:G8) と入力し、Ctrl + Shift + Enter で確定します。

この、Ctrl + Shift + Enter で確定するという行為は、任意の範囲が一つの数式を共有することを意味します。このように確定された数式を、配列数式またはCSE数式( Ctrl + Shift + Enter で確定するから)と呼び、数式が共有される範囲を配列範囲と呼びます。

数式バーには、配列数式であることを示すために、数式が中カッコで囲まれます。{=TRANSPOSE(B2:G8)}

 

これで、下の画像のように任意の範囲に縦・横 逆のクローンが生成されました。

f:id:kouten0430:20180729152315j:plain

 

TRANSPOSE関数で生成されたクローンは、元となる表を常に参照しているため、元の表に変更があればクローン側にも変更が反映されます。(ただし、書式は参照されないため、罫線や、文字の大きさなどは自分で設定する必要があります)

ひっそりとExcel関数のおさらい(乱数を発生させる)

今回は、エクセルで乱数を発生させる関数についてです。

 目次

 

RAND関数(0以上、1未満の乱数を生成する)

=RAND()
引数はありません。0~0.999999999999999の乱数を生成します。シート再計算(要するにシートに変化があった時)のたびに乱数が生成されます。

 

 

RANDBETWEEN関数(指定した範囲の整数の乱数を生成する)

=RANDBETWEEN(最小値,最大値)
最小値以上、最大値以下の整数の乱数を生成します。これも、シート再計算のたびに乱数が生成されます。

 

 

VBAで、RANDBETWEEN関数を使う

VBAで整数の乱数を生成するには、少しだけ工夫が必要です。
ズバっと、整数の乱数を生成するための関数はないので、Rnd関数(0以上、1未満の乱数を生成する) を応用して、例えば


Int((最大値 - 最小値 + 1) * Rnd + 最小値)


のようにする必要があります。

ん?なんのこっちゃ?

という人の為に解読を試みると・・・・・・ 

f:id:kouten0430:20180728152641j:plain

 

f:id:kouten0430:20180728152730j:plain

 

の、ような感じになります。

うーん・・・・・・なんかメンドくさいし、分かりにくいなぁと思う方は

VBAでも、WorksheetFunctionオブジェクトを介してExcel関数(の一部)を利用することができるので、シンプルに


WorksheetFunction.RandBetween(1, 9)


のように記述することもできます。
Randomizeによる、乱数系列の初期化もしなくてもいいようなので、楽チンです。

ぜひ、ご活用を。

ひっそりとExcel関数のおさらい(アラビア数字とローマ数字の相互変換)

アラビア数字をローマ数字に、ローマ数字をアラビア数字に変換する関数です。

 

ROMAN関数(アラビア数字をローマ数字に変換する)

=ROMAN(数値)
引数にアラビア数字の数値を指定します。

数値 返り値
1 I
2 II
3 III
4 IV
5 V
6 VI
7 VII
8 VIII
9 IX
10 X

 

ARABIC関数(ローマ数字をアラビア数字に変換する)

=ARABIC(文字列)
引数にローマ数字の文字列を指定します。=ARABIC("IX")のようにダブルクォーテーションで囲んで指定します。

文字列 返り値
I 1
II 2
III 3
IV 4
V 5
VI 6
VII 7
VIII 8
IX 9
X 10

 

 

何に使うか?うーん・・・・・・。相互変換して遊ぶ(←ぇ)

ひっそりとExcel関数のおさらい(時間の切り上げ・切り捨て)

エクセルやVBAで、時間のデータを切り上げ・切り捨てするにはどうすればいいでしょうか?
うーん・・・・・・。Round関数?DateAdd関数?
通常の数値と違い、年・月・日・時・分・秒 をプログラム上でどう扱うか悩む人は少なからずいると思います。(私もその一人です)
と、いっても時間を都合よく切り捨て・切り上げするための専用の関数はないので、何かしら別の関数で代用することになります。

時間データも元をたどれば、シリアル値という数値データであることを利用して、数値を切り上げ・切り捨てすることができる CEILING関数 と  FLOOR関数を使用しましょう。

目次

  

時間を切り上げる(CEILING関数)

=CEILING(数値,基準値)
CEILING関数は第1引数の数値を、第2引数に指定した基準値の倍数のうち一番近いものに切り上げします。
・・・・・・と言葉で説明しても、とっても分かりにくいと思うので、簡単な例で示すと、
=CEILING(数値,5) であれば 

数値 返り値
1 5
2 5
3 5
4 5
5 5
6 10
7 10
8 10
9 10
10 10
11 15
12 15
13 15
14 15
15 15

 

というふうに、 5 きざみで切り上げされたものが、返り値として返ってきます。

 

では、ここから本題です。
この関数を使って、時間を切り上げるにはどうしたらいいでしょう?
時間はシリアル値で、24時間を 1 とし、それよりも細かい 時・分・秒 は小数点以下で表されます。12時間であれば 0.5 、1時間であれば 0.0416666666666667 というふうに。
なので、時間を1時間きざみで切り上げしたいのであれば、 =CEILING(シリアル値,0.0416666666666667) とすればいいことが分かります。
でも、この書き方はメンドウなので、 =CEILING(シリアル値,1/24) または、時間と認識できる文字列で =CEILING(シリアル値,"1:00") としましょう。
※以下、シリアル値は時間と表記します

5分きざみで切り上げする例
=CEILING(時間,5/(24*60))

時間 時間のシリアル値 返り値 しきい値[5/(24*60)のn倍] n
0:11 0.007638888888888880 0:15 0.010416666666666700 3
0:12 0.008333333333333320 0:15 0.010416666666666700 3
0:13 0.009027777777777760 0:15 0.010416666666666700 3
0:14 0.009722222222222200 0:15 0.010416666666666700 3
0:15 0.010416666666666600 0:15 0.010416666666666700 3
0:16 0.011111111111111100 0:20 0.013888888888888900 4
0:17 0.011805555555555500 0:20 0.013888888888888900 4
0:18 0.012500000000000000 0:20 0.013888888888888900 4
0:19 0.013194444444444400 0:20 0.013888888888888900 4
0:20 0.013888888888888800 0:20 0.013888888888888900 4
0:21 0.014583333333333200 0:25 0.017361111111111100 5
0:22 0.015277777777777600 0:25 0.017361111111111100 5
0:23 0.015972222222222000 0:25 0.017361111111111100 5
0:24 0.016666666666666400 0:25 0.017361111111111100 5
0:25 0.017361111111110800 0:25 0.017361111111111100 5
0:26 0.018055555555555200 0:30 0.020833333333333300 6
0:27 0.018749999999999600 0:30 0.020833333333333300 6
0:28 0.019444444444444000 0:30 0.020833333333333300 6
0:29 0.020138888888888400 0:30 0.020833333333333300 6
0:30 0.020833333333332800 0:30 0.020833333333333300 6

 

こんなふうに、時間が、直近のしきい値を超えていなければ、直近のしきい値に切り上げされ、返り値となって返されます。なお、n(倍数)はユーザーが特に意識することはなく、関数が自動的に増減します。

 

時間を切り捨てる(FLOOR関数)

=FLOOR(数値,基準値)
FLOOR関数は第1引数の数値を、第2引数に指定した基準値の倍数のうち一番近いものに切り捨てします。

考え方は、CEILING関数と同じであり、「切り上げ」が「切り捨て」になっているだけなので省略します。

5分きざみで切り捨てする例
=FLOOR(時間,5/(24*60))

時間 時間のシリアル値 返り値 しきい値[5/(24*60)のn倍] n
0:11 0.00763888888888888 0:10 0.00694444444444444 2
0:12 0.00833333333333332 0:10 0.00694444444444444 2
0:13 0.00902777777777776 0:10 0.00694444444444444 2
0:14 0.00972222222222220 0:10 0.00694444444444444 2
0:15 0.01041666666666660 0:10 0.01041666666666670 3
0:16 0.01111111111111110 0:15 0.01041666666666670 3
0:17 0.01180555555555550 0:15 0.01041666666666670 3
0:18 0.01250000000000000 0:15 0.01041666666666670 3
0:19 0.01319444444444440 0:15 0.01041666666666670 3
0:20 0.01388888888888880 0:15 0.01388888888888890 4
0:21 0.01458333333333320 0:20 0.01388888888888890 4
0:22 0.01527777777777760 0:20 0.01388888888888890 4
0:23 0.01597222222222200 0:20 0.01388888888888890 4
0:24 0.01666666666666640 0:20 0.01388888888888890 4
0:25 0.01736111111111080 0:20 0.01736111111111110 5
0:26 0.01805555555555520 0:25 0.01736111111111110 5
0:27 0.01874999999999960 0:25 0.01736111111111110 5
0:28 0.01944444444444400 0:25 0.01736111111111110 5
0:29 0.02013888888888840 0:25 0.01736111111111110 5
0:30 0.02083333333333280 0:25 0.01736111111111110 5

 

この表はCEILING関数の時と同じように見えますが(切り上げが、切り捨てになっただけ)、でも、この表はどこかがおかしいです。お気づきでしょうか?

 

FLOOR関数の不具合?

例えば、0:15 であれば、切り捨てられずに返り値が 0:15 となってほしいのですが、切り捨てられてしまっています。次の 0:20 、 0:25 、 0:30 も同じです。

なぜ、そうなるのか?
しきい値と、シリアル値を見比べると、しきい値のほうが僅かに大きいことが分かります。

なので、FLOOR関数さんは、「まだしきい値を超えてないので、切り捨てだね!」と判断して、一つ手前のしきい値で返り値を返してしまっているのです。
これは、厳密に言うとFLOOR関数の不具合ではなく、浮動小数点の演算誤差によって発生してしまっています。
FLOOR関数に限った話ではありません。

CEILING関数の場合にも、しきい値の誤差はありますが、切り上げ方向のため問題にならなかっただけです。

 

誤差があることはわかった。じゃあ、どうすればいいの?

 

方法はいくつかあるけど、例えば、第1引数が誤差分を確実に上回るようにしたらどうでしょう?

例:FLOOR(時間 + 1秒,5/(24*60))

1秒の箇所は、実際には 1/(24*60*60) や "00:00:01" と記述します。

 

VBAで使う際の注意点

VBAでも、ワークシート関数として CEILING関数 、 FLOOR関数 を使うことができます。ただし、第2引数を文字列 "0:05"とすることはできないようなので、下記のようにする必要があります。

  • 直接数値で指定する:5/(24*60)
  • 日付リテラルで指定する:#0:05#(自動的に #12:05:00 AM# のように変換されるが、意味は同じなので気にしなくてもいい)
  • Date型の変数で指定する:事前に、変数 = "0:05" のように代入して引数に指定
  • 関数で日付シリアル値に変換する:TimeValue("0:05")

セルを縦方向に結合

f:id:kouten0430:20180714203732j:plain

エクセルの標準ボタンには「横方向に結合」がありますが、「縦方向に結合」はありません。
どうしてないのかわかりませんが、個人的にその機能が欲しいので、VBAで作ってみます。

ググればありそうだけど、簡単そうなので、自分でやってみます。

Sub 縦方向に結合()
    Dim i As Integer

    For i = Selection.Column To Selection.Columns(Selection.Columns.Count).Column
        Range(Cells(Selection.Row, i), Cells(Selection.Rows(Selection.Rows.Count).Row, i)).Merge
    Next i

End Sub

 

※コードの使用方法

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