【Excel】VBA-Dictionaryを使用する際の注意点

# はじめに
Excelでwebブラウザの操作を自動化する最小機能のみ実装したフレームワークの「[TinySeleniumVBA](https://github.com/uezo/TinySeleniumVBA/blob/main/README.ja.md)」があります。
これは今のところActiveXを使用しているため、Windows専用になっています。ActiveXの部分を取り除き代替部分を[VBA-tools](https://github.com/VBA-tools)ライブラリー群を使えば、Mac上のExcelで動作するかもと取り組みました。

TinySeleniumVBAではDictionaryオブジェクトを使用する上でActiveXを使用しています。
ただ記述は`New Dictionary`となっているため、移植する手間はありません。
※参照設定ダイアログで「Microsoft Scripting Runtime」にチェックを付けて使用する。

# VBA-Dictionary
MacではActiveXが使用できません。しかし、今回紹介する「VBA-Dictionary」でDictionaryクラスを使用することで、WindowsでもMacでもDictionaryオブジェクトを同様に使うことができるようになります。
導入時に参照設定ダイアログで「Microsoft Scripting Runtime」にチェック付けてもらう必要もなくなります。

## インストール
最新版を下記サイトからダウンロードし、解凍後にVBAプロジェクトに「Dictionary.cls」をインポートします。
 
https://github.com/VBA-tools/VBA-Dictionary

## サンプルコード
VBA-Dictionaryの[サンプルコード](https://github.com/VBA-tools/VBA-Dictionary)そのままです。

```vb
Dim Dict As New Dictionary
Dict.CompareMode = CompareMethod.TextCompare

Dict("A") ' -> Empty
Dict("A") = 123
Dict("A") ' -> = Dict.Item("A") = 123
Dict.Exists "A" ' -> True

Dict.Add "A", 456
' -> Throws 457: This key is already associated with an element of this collection

' Both Set and Let work
Set Dict("B") = New Dictionary
Dict("B").Add "Inner", "Value"
Dict("B")("Inner") ' -> "Value"

UBound(Dict.Keys) ' -> 1
UBound(Dict.Items) ' -> 1

' Rename key
Dict.Key("B") = "C"
Dict.Exists "B" ' -> False
Dict("C")("Inner") ' -> "Value"

' Trying to remove non-existant key throws 32811
Dict.Remove "B"
' -> Throws 32811: Application-defined or object-defined error

' Trying to change CompareMode when there are items in the Dictionary throws 5
Dict.CompareMode = CompareMethod.BinaryCompare
' -> Throws 5: Invalid procedure call or argument

Dict.Remove "A"
Dict.RemoveAll

Dict.Exists "A" ' -> False
Dict("C") ' -> Empty
```
## 注意点
TinySeleniumVBAをVBA-Dictionaryに置き換えた際に、実行すると下記のエラーが出ました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/e7a9dc21-090d-50d0-4b96-e7ad35f010df.png)

デバッグしていくと`For Each`でエラーになります。

```vb
For Each paramKey In parameters
    If VarType(parameters(paramKey)) = vbString Then
        path = Replace(path, "$" + paramKey, parameters(paramKey))
    End If
Next
```

VBA-Dictionaryって、`For Each`が使えないのかと、`For`に書き換えて動かしていました。
```vb
For i = 0 To parameters.Count - 1
    paramKey = parameters.Keys(i)
    If VarType(parameters(paramKey)) = vbString Then
        path = Replace(path, "$" + paramKey, parameters(paramKey))
    End If
Next
```

Issueもあるんだけど、最終的によく分からない。

https://github.com/VBA-tools/VBA-Dictionary/issues/17

VBA-Dictionaryを調べていたら下記サイトがあって、サンプルコードを見ると`For Each`を使用していました。

https://qiita.com/ookikawai/items/01e3d5a9d9a2a0ba39a0

よく見ると、コレクションのところに`.Keys`が付いていました。
そこで、`.Keys`を付けたところ、エラーにもならず正しい動作をすることが分かりました。

```vb
For Each paramKey In parameters.Keys
    If VarType(parameters(paramKey)) = vbString Then
        path = Replace(path, "$" + paramKey, parameters(paramKey))
    End If
Next
```

Windows上でVBA-Dictionaryクラスを一旦解除して、元の「Microsoft Scripting Runtime」にチェックを付けた状態にしても、エラーにもならず正しい動作をすることが分かりました。
よって、TinySeleniumVBAのissueを書いたので、次のバージョンアップ時には修正されるかも知れません。

https://github.com/uezo/TinySeleniumVBA/issues/69

# 最後に
WindowsとMacの両方で動かすなら、`.Keys`を省略しない方がいいですよ。