ExcelVBAでTreeviewを使う

## 環境
- Excel for Microsoft365(64bit)

## 準備
標準ではツールボックスに表示されていないので、以下の手順でツールボックスに追加します。
1. ツールボックスを右クリックし、「その他のコントロール」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945532/28051bed-bb09-5452-6eb0-c007c85c2cd0.png)
1. リストから「Microsoft TreeView Control,version 6.0」にチェックを入れる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945532/62da2621-fa36-4e82-0727-5c0e33d57792.png)
1. ツールボックスに追加されたTreeviewをフォームに配置する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945532/e7ba0dc7-fefc-c775-7eb1-e583a024685a.png)

## 基本的な使い方

### ノードの追加
ノードの追加はNodesプロパティからAddメソッドを呼び出します。
Addの引数は以下の通り。
- Key
ノードを識別するためのパラメータで一意の値を設定します。
また、Relativeで親子関係を設定する際は、親ノードのKeyを子ノードのRelativeに指定します。
- Text
ノードの表示名を指定します。
- Image
ノードに対してアイコンを指定する際に使用します。
- Relative
子ノードを追加する際に、親ノードのKeyを指定することで親子関係を設定します。
- Relationship
子ノードを追加する際に、tvwChild(=4)を設定してノード種類を指定します。

### Treeviewコントロールの見た目
コントロールの見た目を調整するための代表的なプロパティを紹介します。

- Checkboxesプロパティ
チェックボックスの有無を指定することができます。
各ノードのCheckedプロパティからチェックのON/OFF状態を取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945532/61625ef8-b0b4-e58e-8005-32873f0fdaf1.png)

- LineStyleプロパティ
ノード間をつなぐラインの見た目を「0-tvwTreeLines」「1-tvwRootLines」の２種類から設定します。 
TreeLinesの注意点として、デフォルトではノードが畳まれており、ノード展開ボタンも表示されないため子ノードがあるか見た目ではわかりません。
対策として、ノードのExpandedプロパティにtrueを設定してあらかじめ展開しておくなどするとよいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945532/4d5ad5b9-4d31-60dc-93a0-bd6de66a19e7.png)

### イベント処理
Treeviewコントロールでは主にNodeCheckとNodeClickの２つのイベントを用いることになります。

- NodeCheckイベント
CheckBoxesプロパティを有効にしている時に、ノードのチェック状態が変化した際に呼ばれます。
チェック状態が変化したノードを引数で受け取れるので、チェック状態に応じた処理を実装します。
使いどころとしては以下のような機能の有効/無効の設定画面とかでしょうか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945532/fdfa4ec3-d57d-37cd-dd6a-b8ff087fa996.png)
子ノードも含めてまとめてチェック状態の切り替えを行う場合もこのイベントに記述します。
~~~vb
Private Sub TreeView1_NodeCheck(ByVal Node As MSComctlLib.Node)

    '子ノードを取得
    Dim childNode As Node
    Set childNode = Node.Child
    
    While Not childNode Is Nothing
        '親ノードのチェック状態を子に反映
        childNode.Checked = Node.Checked
        
        '次の子ノードに変更
        Set childNode = childNode.Next
    Wend
    
End Sub

~~~
使っていて気付いたのが、このイベントは各ノードのチェックボックスを直接変更した時だけ発生します。
ノードのチェック状態を変更する(Node.Checkedプロパティを操作する)実装の場合は発生しませんので、連動させるような場合は気を付ける必要があります。

- NodeClickイベント
ノードをクリックした際に呼ばれます。
クリックしたノードを引数で受け取れるので、ノードに応じた処理を実装します。
使いどころとしては設定まわりの機能でしょうか。
選択したノードにあわせてプロパティの内容を入れ替えるというような使い方です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3945532/19436fce-c35d-37d8-0d6e-1ec11abb496e.png)

## 注意点
TreeviewコントロールをMultiPageコントロール上に配置してページを切り替えるとチェック状態が外れるバグ(？)があります。
対策としては、チェック状態を別途配列に保持しておき、ページ切り替えのタイミングで各ノードに反映します。
~~~vb:NodeCheckイベント
Dim NodeChecked(1 To 6) As Boolean

Private Sub TreeView1_NodeCheck(ByVal Node As MSComctlLib.Node)

    '親ノードのチェック状態をバックアップ
    NodeChecked(Node.Index) = Node.Checked

    '子ノードを取得
    Dim childNode As Node
    Set childNode = Node.Child
    
    While Not childNode Is Nothing
        '親ノードのチェック状態を子に反映
        childNode.Checked = Node.Checked
        
        '子ノードのチェック状態をバックアップ
        NodeChecked(childNode.Index) = childNode.Checked
        
        '次の子ノードに変更
        Set childNode = childNode.Next
    Wend
    
End Sub
~~~
~~~vb:ページ切り替えイベント
Private Sub MultiPage1_Change()

    'Treeviewコントロールを配置しているページに切り替わった時のみ実行
    If MultiPage1.SelectedItem.Index = 0 Then
    
        'バックアップしていたチェック状態を各ノードに反映する
        For idx = 1 To UBound(NodeChecked)
            TreeView1.Nodes(idx).Checked = NodeChecked(idx)
        Next idx
    End If

End Sub
~~~
Node.Indexは1から始まるので配列も同様に定義します。
上記コードでは直値を使っていますが、実コードではノードの登録後にReDimで配列サイズの再定義をするのがよいと思います。