見出し画像

VBA応用1:関数/定数を自作:Function

1.概要

 以前の記事でExcel機能の関数を紹介しましたが、ほしい機能がないことも多数あります。

 そこで今回の記事では自分専用の関数を自作します。

2.基礎知識

2-1.用語

 VBA使用時に頻繁に出てくる用語の意味は下記の通りです。

【用語】
●VBA(Visual Basic for Applications):プログラミング言語
●VBE(Visual Basic Editor):コードを記載するエディタ
●マクロ:実行機能

2-2.Functionプロシージャ

 VBAで記載する処理をプロシージャと呼び、主に3種類あります。

【プロシージャ一覧】
Subプロシージャ:戻り値を持たない処理であり「マクロダイアログ」から呼び出して実行可能です。
Private Subプロシージャ:戻り値を持たない処理であり「マクロダイアログ」から呼び出しできません(裏側だけで処理させる用)。
Functionプロシージャ:戻り値を持つ処理であり通常のExcel内でも使用が可能です。

 Functionプロシージャを使用すれば自作関数も作成可能です。

[Functionプロシージャ]
Function KIYO_FUNC(入力値) As 戻り値の型
    KIYO_FUNC = n * 2 + 1 '関数名=処理したい内容と記載する
End Function

 ポイントは「Functionプロシージャ内で関数を定義することで、津城ではVBAでしか使用できない関数をExcel内でも使用可能」です。例として検索文字が対象とする文字列の後ろから何番目にあるかを確認する"InStrRev"も使用可能になります。

[Functionプロシージャ]
Function Instr(Path, word) As Long
    Instr = Instrrev(Path, word)
End Function

2-3.モジュール作成

 「Alt+F11」よりVBEを開いて標準モジュールを追加します。自作関数であることが分かるようにプロパティウィンドウ(左下の枠)から名前を変更しておくと便利です。

 またExcelのVBA内で頻繁に使用する定数があるなら別モジュール化してもよいと思います。ただし変数の重複管理が必要になります。

2-4.変数の適用範囲

 VBAでは変数の宣言箇所・方法により適用範囲(使用できるエリア)が異なり、大きく分けて3つあります(詳細は下記記事参照)。

【変数の適用範囲】
1.プロシージャ内だけで使用可(Sub()―End Subの間のみ)
2.モジュール内だけで使用可(同じモジュール内はどこでも使用可)
3.全モジュールで使用可

 MyConstモジュールで必要な定数を管理する場合は「3.全モジュールで使用可」に該当し、この場合の変数定義方法は下記の通りです。

[Module]
Public <変数名> As <型式>

Sub プロシージャ名()
 <変数名> = 値
End Sub

【参考用トライアル ※参考用のため記事では使用しません】
 サンプルデータは「Qiita:都道府県コード JSON版とJS版」を編集して、下記の通りPythonのコードで作成しました。replace()処理を入れているのは"をエスケープシーケンス処理するためです。

[IN ※Python]
import json
prefectures = {
"prefectures": [{ "code": 1, "name": "北海道"},
    { "code": 13, "name": "東京都"},
    { "code": 14, "name": "神奈川県"},
    { "code": 23, "name": "愛知県"},
    { "code": 26, "name": "京都府"},
    { "code": 27, "name": "大阪府"},
    { "code": 40, "name": "福岡県"},
    { "code": 47, "name": "沖縄県"}]
}

print(type(prefectures), type(json.dumps(prefectures, ensure_ascii=False))) #型式確認:json.dumpsで文字列に変換
prefectures_json = json.dumps(prefectures, ensure_ascii=False) #ensure_ascii->日本語の文字化け防止
prefectures_jsonesc = prefectures_json.replace('"', '""')

print(prefectures_json)
print(prefectures_jsonesc)

[OUT]
<class 'dict'> <class 'str'>
{"prefectures": [{"code": 1, "name": "北海道"}, {"code": 13, "name": "東京都"}, {"code": 14, "name": "神奈川県"}, {"code": 23, "name": "愛知県"}, {"code": 26, "name": "京都府"}, {"code": 27, "name": "大阪府"}, {"code": 40, "name": "福岡県"}, {"code": 47, "name": "沖縄県"}]}
{""prefectures"": [{""code"": 1, ""name"": ""北海道""}, {""code"": 13, ""name"": ""東京都""}, {""code"": 14, ""name"": ""神奈川県""}, {""code"": 23, ""name"": ""愛知県""}, {""code"": 26, ""name"": ""京都府""}, {""code"": 27, ""name"": ""大阪府""}, {""code"": 40, ""name"": ""福岡県""}, {""code"": 47, ""name"": ""沖縄県""}]}

 MyConstモジュールで変数を定義して、同モジュール内および別モジュールで実行した結果、両方とも変数を読み込むことが出来ました。
(一度定義したプロシージャをF5で実行しないと空文字が出力されました)

[MyConstモジュール]
Public Prefectures As String

Sub パブリック変数定義()
    Prefectures = "{""prefectures"": [{""code"": 1, ""name"": ""北海道""}, {""code"": 13, ""name"": ""東京都""}, {""code"": 14, ""name"": ""神奈川県""}, {""code"": 23, ""name"": ""愛知県""}, {""code"": 26, ""name"": ""京都府""}, {""code"": 27, ""name"": ""大阪府""}, {""code"": 40, ""name"": ""福岡県""}, {""code"": 47, ""name"": ""沖縄県""}]}"
End Sub

Sub B()
    Debug.Print Prefectures
End Sub

[OUT]
{"prefectures": [{"code": 1, "name": "北海道"}, {"code": 13, "name": "東京都"}, {"code": 14, "name": "神奈川県"}, {"code": 23, "name": "愛知県"}, {"code": 26, "name": "京都府"}, {"code": 27, "name": "大阪府"}, {"code": 40, "name": "福岡県"}, {"code": 47, "name": "沖縄県"}]}
[MyFuncモジュール]
Sub B()
    Debug.Print Prefectures
End Sub

[OUT ※モジュール内で定義していない変数を出力できた]
{"prefectures": [{"code": 1, "name": "北海道"}, {"code": 13, "name": "東京都"}, {"code": 14, "name": "神奈川県"}, {"code": 23, "name": "愛知県"}, {"code": 26, "name": "京都府"}, {"code": 27, "name": "大阪府"}, {"code": 40, "name": "福岡県"}, {"code": 47, "name": "沖縄県"}]}

2-5.自作定数:シンプルなFunction

 Function内で入力した数値をそのまま出力とする場合は自作定数も作成可能です。モジュール内でのコード・動作は下記の通りです。

[MyConstモジュール ※先ほどのPublic変数とFucntion変数名が重複してエラーとなるためPublicは削除]

Function Prefectures() As String
    Prefectures = "{""prefectures"": [{""code"": 1, ""name"": ""北海道""}, {""code"": 13, ""name"": ""東京都""}, {""code"": 14, ""name"": ""神奈川県""}, {""code"": 23, ""name"": ""愛知県""}, {""code"": 26, ""name"": ""京都府""}, {""code"": 27, ""name"": ""大阪府""}, {""code"": 40, ""name"": ""福岡県""}, {""code"": 47, ""name"": ""沖縄県""}]}"
End Function
[MyFuncモジュール]
Sub B()
 Debug.Print Prefectures
End Sub

[OUT]
{"prefectures": [{"code": 1, "name": "北海道"}, {"code": 13, "name": "東京都"}, {"code": 14, "name": "神奈川県"}, {"code": 23, "name": "愛知県"}, {"code": 26, "name": "京都府"}, {"code": 27, "name": "大阪府"}, {"code": 40, "name": "福岡県"}, {"code": 47, "name": "沖縄県"}]}

 Fucntionで定義したので実際にExcel内で呼び出すことが可能になります。

3.まとめ

3-1.モジュール構成

 モジュールの構成は①MyConst(定数用)、②MyFunc(関数用)です。

3-2.全コード

 全コードを記載したモジュールを(自分用で)添付します。インポートすれば同じ関数が使用可能となります。

4.自作関数1:基礎データ取得

4-1.行列の最終データ位置取得

 新規にデータを追加したり最新の情報を取得するために行列端の位置情報を取得する関数を作成しました。まずは行を紹介します。

[IN]
Function GetEndRow(Column As Variant, Nextcell)
    If Nextcell Then
        addrow = 1 '一番下にあるセルの一つ下の位置
    Else
        addrow = 0 '一番下にあるセルと同じ位置
    End If
    r = ActiveSheet.Rows.Count '使用しているシートの最大行数(1048576)を取得 ※Excel2010
    
'Columnは列の数値または文字列の両方入力出来るようにする。
    If IsNumeric(Column) Then
        GetEndRow = Cells(r, Column).End(xlUp).Row + addrow '最終行から上に移動したときのセルの値
    Else
        Num_Column = Range(Column & "1").Column
        GetEndRow = Cells(r, Num_Column).End(xlUp).Row + addrow '最終行から上に移動したときのセルの値
    End If

End Function
[セル内での使用方法]
GetEndRow(<列位置(数値or記号)>, True/False)

 実際に何パターンかで使用した結果は下記の通りです。

[IN_A8 ※列記号で入力]
=GetEndRow("A", FALSE)
[OUT_A8]
9

[IN_B10 ※列を数値で入力]
=GetEndRow(2, TRUE)
[OUT_B10]
12

[IN_C9 ※列を関数で入力:関数が自動的にセル情報を取得]
=GetEndRow(COLUMN(), TRUE)
[OUT_C9]
10

 上記と同じ形で列情報用の関数も下記の通り作成しました。

[IN]
Function GetEndColumn(Row As Variant, Nextcell)
    If Nextcell Then
        addrow = 1 '一番下にあるセルの一つ下の位置
    Else
        addrow = 0 '一番下にあるセルと同じ位置
    End If
    col_max = ActiveSheet.Columns.Count '使用しているシートの最大列数(16384)を取得 ※Excel2010
    
    GetEndColumn = Cells(Row, col_max).End(xlToLeft).Column + addrow '最終行から上に移動したときのセルの値

End Function

5.自作関数2:文字列処理

5-1.JSON解析

 JSONデータからKEY->VALUEに変換する関数を作成します。

[Module]
' JSON文字列からキーkeyを取り出す
Function GetJsonKey(JsonStr, key, enc) As String
    Dim d As Object
    If JsonStr = "" Then
        GetJsonKey = ""
        Exit Function
    End If
    Set d = CreateObject("htmlfile")
    d.Write "<meta http-equiv='X-UA-Compatible' content='IE=8' />"
    ' JavaScriptの関数を定義
    d.Write "<script>" & _
            "document.getjson = function(s, key, enc){" & _
            "var vals = eval('('+s+')');" & _
            "if (enc) { return JSON.stringify(vals[key]);}" & _
            "else { return vals[key] }}" & _
            "</script>"
    ' JavaScriptの関数を呼び出す
    GetJsonKey = d.getjson(JsonStr, key, enc)
End Function
[セル内での使用方法]
GetJsonKey(<JSONデータ>, <KEY>, True/False)

 2-4節で作成したサンプルJSONデータを使用して入出力を確認します。

[IN_A1]
=Prefectures()
[OUT_A1]
{"prefectures": [{"code": 1, "name": "北海道"}, {"code": 13, "name": "東京都"}, {"code": 14, "name": "神奈川県"}, {"code": 23, "name": "愛知県"}, {"code": 26, "name": "京都府"}, {"code": 27, "name": "大阪府"}, {"code": 40, "name": "福岡県"}, {"code": 47, "name": "沖縄県"}]}

[IN_A2]
=GetJsonKey(A1,"prefectures",TRUE)
[OUT_A2]
[{"code":1,"name":"北海道"},{"code":13,"name":"東京都"},{"code":14,"name":"神奈川県"},{"code":23,"name":"愛知県"},{"code":26,"name":"京都府"},{"code":27,"name":"大阪府"},{"code":40,"name":"福岡県"},{"code":47,"name":"沖縄県"}]

5-2.文字列のJSONリストからデータ抽出

 前節で出現した下記の(文字列)配列は直接処理できませんでした。

[サンプル:配列内のJSONデータ]
"[{""code"":1,""name"":""北海道""},{""code"":13,""name"":""東京都""},{""code"":14,""name"":""神奈川県""},{""code"":23,""name"":""愛知県""},{""code"":26,""name"":""京都府""},{""code"":27,""name"":""大阪府""},{""code"":40,""name"":""福岡県""},{""code"":47,""name"":""沖縄県""}]"

 Pythonのリストのように処理できるよう、下記手順でデータ抽出できる関数を作成します。処理でわかる通り”[<JSONデータ>]"専用関数です。

【処理イメージ】
1.リストの[]を削除する。
2."},"で文字列を分割する:最後の値は,がないため別処理が必要
3.文字の終わりに}がない文字は追加して、ある場合はそのまま出力

[Module]
Function GetJsonList(JsonListStr, idx) As String
    tmp = Replace(JsonListStr, "[", "") '[を削除
    tmp = Replace(tmp, "]", "") ']を削除
    myarray = Split(tmp, "},") '}, で文字列を分割する->配列出力
    
    '-1で最後のデータが抽出できるような設定を追加
    If idx = -1 Then
        idx = UBound(myarray) 'UBound(array)で配列の最後のインデックスを抽出
    End If
    
    '最後のデータは}が残るため条件分岐
    If Right(myarray(idx), 1) <> "}" Then
        GetJsonList = myarray(idx) + "}"
    Else
        GetJsonList = myarray(idx)
    End If
    
End Function

 前節で処理できなかったリストを処理します。

[IN_A4-6]
=GetJsonList(A3,0)
=GetJsonList(A3,1)
=GetJsonList(A3,-1)

[OUT]
{"code":1,"name":"北海道"}
{"code":13,"name":"東京都"}
{"code":47,"name":"沖縄県"}

6.自作関数3:通信関係

 API接続のためのサンプルとして下記サイトを利用しました。

6-1.HTTPクライアント(GET)

 PythonのRequestsと同じことが出来るような関数を作成します。

 各関数は下記の通りです。注意点としてPOSTメソッドはJSONデータを送付するためヘッダー形式は”application/json; charset=UTF-8”にしました。

[In]※GETメソッド
' HTTPにGETメソッドを送信して結果を得る
Function GetHttp(url) As String
    Dim httpObj As Object, s As String
    ' URLのページを開く
    Set httpObj = CreateObject("MSXML2.XMLHTTP")
    httpObj.Open "GET", url
    httpObj.setRequestHeader "Content-Type", "text/plain"
    httpObj.send
    ' 終了まで待機
    Do While httpObj.readyState <> 4
        DoEvents
    Loop
    ' HTTPのステータスコードが200ならば成功
    If (httpObj.Status = 200) Then
        s = httpObj.responseText
        GetHttp = "" & s
    Else
        GetHttp = ""
        Debug.Print "エラー:" & httpObj.statusCode
    End If
End Function

 使用方法はGetHttp(<APIのエンドポイントURL>)です。

[IN_CELL]
=GetHttp("https://jsonplaceholder.typicode.com/posts/1")

[OUT]
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

6-2.HTTPクライアント(POST)

 上記と同様にPOSTメソッドも作成します。

[In]※POSTメソッド
Function PostHttp(url, json) As String
    '--------------
    'POST実行
    '--------------
    Dim xmlhttp As Object
    Set xmlhttp = CreateObject("msxml2.xmlhttp")
    xmlhttp.Open "POST", url, False
    xmlhttp.setRequestHeader "Content-Type", "application/json; charset=UTF-8" '送信形式をJSONに設定
    xmlhttp.send (json) 'JSONデータをPOST

    '--------------
    '応答取得
    '--------------
    Dim retCd As String
    retCd = xmlhttp.Status    '結果コード取得
    
    If retCd <> 200 Then
        Debug.Print "error:" & retCd
    Else
        Dim retHtml As String
        retHtml = StrConv(xmlhttp.responseBody, vbUnicode, 1041)    '結果HTML取得:戻り値JSON
        PostHttp = retHtml
    End If
End Function

 使用方法はPostHttp(<URL>, <Jsonデータ>)です。

[IN_CELL]
=PostHttp("https://jsonplaceholder.typicode.com/posts/1", "{""id"":1}")

[OUT]
出力が出ないため追って調整
※使用方法はあってます

7.標準モジュールの転用

 作成したモジュールを別のブックや他の人と共有するためにモジュールを保存(エクスポート)/読み込み(インポート)します。

7-1.ファイル保存:エクスポート

 モジュールを右クリックして「ファイルのエクスポート」を選択するとbas拡張子のモジュールファイルを保存できます。

7-2.モジュール使用1:インポート

 新規ブックにモジュールをインポートしてみます。新規ブック再生して「Alt+F11」でVBEを開き”VBAProject (<ファイル名>)”を右クリックして「ファイルのインポート」を選択します。
 ウィンドウが立ち上がるのでエクスポートしたbasファイルを選択するとモジュールが挿入されます。

7-3.モジュール使用2:Excelアドインの利用

 2つ目のモジュール利用方法としてアドインの利用があります。手順としては下記の通りです。注意点として自分で利用する場合は全ブックで使用できますが、他人とは直接アドインを共有できないため手順2~4を各自で実施してもらう必要があります。

【アドイン使用の手順】
1.ほしいマクロを作成(VBAコードを記載)する。
2.Excelブックを拡張子「Excel アドイン (*.xlam)」で保存する。
 ー>自動で指定箇所に保存されます(下記コードブロック参照)
3.「開発」タブー>「アドイン」から保存したファイル名のアドイン選択
4.記載したマクロを使用

[AddInnのファイルパス]
C:\Users\KIYO\AppData\Roaming\Microsoft\AddIns

参考資料・参考記事

あとがき

 全部は書ききれないのでゆっくり更新予定

この記事が気に入ったらサポートをしてみませんか?