Windows Formsで配列の要素に対してバインドする方法

# はじめに
Windows Formsで配列の**要素**に対してバインドしたい時が結構あります。
同じコントロールを複数配置した場合も、配列で管理した方が楽な時があります。

## 前提
* Window.Forms
* VB 2017
* .NET Framework 4.6.1

# ソースコード
こんな画面をぱぱっと作ります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/244050/a23e51bb-aca5-7aa9-801b-51215c902c2b.png)

ささっと以下のコード。

``` vb:Form1.vb
Imports System.ComponentModel

Public Class Form1

    Private VM As New ViewModel

    Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        TextBox1.DataBindings.Add(New Binding("Text", VM.Inputs(0), "Value"))
        TextBox2.DataBindings.Add(New Binding("Text", VM.Inputs(1), "Value"))
        TextBox3.DataBindings.Add(New Binding("Text", VM.Inputs(2), "Value"))
    End Sub

    Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        VM.Update()
    End Sub

    Private Class ViewModel

        Public Inputs As t()

        Sub New()
            Inputs = {
                 New t With {.Value = "a"},
                 New t With {.Value = "b"},
                 New t With {.Value = "c"}
            }
        End Sub

        Sub Update()
            Inputs(0).Value = "1"
            Inputs(1).Value = "2"
            Inputs(2).Value = "3"
        End Sub

        Class t
            Implements INotifyPropertyChanged

            Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

            Private _Value As String
            Property Value As String
                Get
                    Return _Value
                End Get
                Set
                    _Value = Value
                    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(Value)))
                End Set
            End Property

        End Class

    End Class

End Class
```

# 試す

起動時
![image.png](https://qiita-image-store.s3.amazonaws.com/0/244050/0329210d-f278-dedb-53b9-f6a8e4544abc.png)

ボタン押下
![image.png](https://qiita-image-store.s3.amazonaws.com/0/244050/85b6c63b-1cc2-6b43-4dfe-cfd4ff64bb89.png)

ばっちり


# なにがおきたか


## 配列の用意
ViewModelクラス内では、`Inputs`の**要素**に対して**クラス**を指定します。そして、`Inputs`の**要素**に対してバインドする為、`Inputs`自身はただのメンバでよいです。

``` vb:バインド先であるViewModel内
    Private Class ViewModel

        Public Inputs As t()

        ' ~~中略~~

    End Class
```

## 配列の「要素」となるプロパティの用意

代わりに、`Inputs`の**要素**に対して指定した**クラス**とその**プロパティ**に対しては、プロパティを用意し、`INotifyPropertyChanged`による`PropertyChanged `イベントを発火(Raise)させるようにします。
はい、要素自身を`INotifyPropertyChanged`を継承した**クラス**にすることで、**個々の要素に対してバインド可能なプロパティを設定することに成功しています**。

``` vb:バインド先であるViewModel内
    Private Class ViewModel

        Public Inputs As t()

        ' ~~中略~~

        Class t
            Implements INotifyPropertyChanged

            Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

            Private _Value As String
            Property Value As String
                Get
                    Return _Value
                End Get
                Set
                    _Value = Value
                    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(Value)))
                End Set
            End Property

        End Class

    End Class
```

## 配列とその要素の初期化

バインド先がnullだと普通にヌルポされちゃいます。ちゃんと配列とその要素を初期化しましょう。ついでに

``` vb:配列の初期化
' ~~前略~~

    Private Class ViewModel

        Public Inputs As t()

        Sub New()
            Inputs = {
                 New t With {.Value = "a"},
                 New t With {.Value = "b"},
                 New t With {.Value = "c"}
            }
        End Sub

        Sub Update()
            Inputs(0).Value = "1"
            Inputs(1).Value = "2"
            Inputs(2).Value = "3"
        End Sub

        ' ~~中略~~

    End Class

```



## バインドの設定
フォームロード時に、各テキストボックスに対して、ViewModelクラスのメンバにある配列`Inputs`の**要素**と結び付けます。もちろん個々の要素に対してバインドするので、配列へのインデックスはきちんと決めてあげましょう。

``` vb:フォームロード時に処理されるバインドの設定
' ~~前略~~

Public Class Form1

    Private VM As New ViewModel

    Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        TextBox1.DataBindings.Add(New Binding("Text", VM.Inputs(0), "Value"))
        TextBox2.DataBindings.Add(New Binding("Text", VM.Inputs(1), "Value"))
        TextBox3.DataBindings.Add(New Binding("Text", VM.Inputs(2), "Value"))
    End Sub

    ' ~~中略~~

End Class
```

この時、指定されたインデックスに当てはまる要素がない場合、エラーが起きます。
あと、変な設定すると正しくバインドされませんでした（3敗）

``` vb:だめな例__バインドの設定
' ~~前略~~
Public Class Form1

    Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        ' エラーになる：存在しない(ようにした)インデックスを指定
        TextBox1.DataBindings.Add(New Binding("Text", VM.Inputs(5000), "Value"))

        ' エラーになる：プロパティ「名」としてのInputs(2)は存在しない。特に「(2)」が存在しない
        TextBox3.DataBindings.Add(New Binding("Text", VM, "Inputs(2).Value"))

        ' エラーにはならないし、初回だけ値は設定されるけど、実はバインドされてない
        TextBox2.DataBindings.Add(New Binding("Text", VM.Inputs(1).Value, "")) 
    End Sub

    ' ~~中略~~

End Class
```