【VB.NET、C#】表示名と値が異なるコンボボックス


コンボボックスの表示名と実際の値を分けたい場合、次の2パターンの方法がポピュラーです。

- Items.Addで作成
- DataSourceで作成


動作確認用に簡単な画面を用意しました。
![20211227_001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/8adf5677-83fd-e9bb-fc2b-684c97a1f678.jpeg)

画面仕様はとても簡単です。

- 左のコンボボックス…選択可能
- 右のテキストボックス…コンボボックスで選択されたアイテムの実際の値を表示

コンボボックスが選択されるとこうなります。

![20220104_001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2162017/6e0757bf-7c82-e014-18bb-f5023f865462.jpeg)

#Items.Addで作成する場合
まずは、Items.Addで作成する方法。

【必要なモノ】

- フォームとコンボボックス（当たり前）
- コンボボックス用の独自クラス

フォームとコンボボックスは先ほどの画像の通り。
コンボボックス用の独自クラスは次の様に作成します。

```vb
'【VB.NET】
'コンボボックス用の独自クラス
Public Class MyItems
    'クラス変数
    Private m_Code As String = ""
    Private m_Name As String = ""

    'コンストラクタ
    Public Sub New(ByVal strCode As String, ByVal strName As String)
        m_Code = strCode
        m_Name = strName
    End Sub

    'プロパティ（実際の値）
    Public ReadOnly Property Code() As String
        Get
            Return m_Code
        End Get
    End Property

    'プロパティ（表示する名称）
    Public ReadOnly Property Name() As String
        Get
            Return m_Name
        End Get
    End Property

    'コンボボックス表示用メソッド（ToStringのオーバーライド）
    Public Overrides Function ToString() As String
        Return m_Name
    End Function
End Class

```

```c#
//【C#】
//コンボボックス用の独自クラス
class MyItems
{
    private string m_Code = "";
    private string m_Name = "";

    //コンストラクタ
    public MyItems(string strCode, string strName)
    {
        m_Code = strCode;
        m_Name = strName;
    }

    //プロパティ（実際の値）
    public string Code
    {
        get
        {
            return m_Code;
        }
    }

    //プロパティ（表示する名称）
    public string Name
    {
        get
        {
            return m_Name;
        }
    }

    //コンボボックス表示用メソッド（ToStringのオーバーライド）
    public override string ToString()
    {
        return m_Name;
    }
}
```
クラスを用意したら、次はフォーム画面のコード作成。

```vb
'【VB.NET】
'画面
Public Class Form_ComboBox
    Private Sub Form_ComboBox_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim objItem As MyItems

        '要素を独自クラスにセット①
        objItem = New MyItems("01", "北海道")

        'コンボボックスにセット②
        cmbTest.Items.Add(objItem)

        '要素を独自クラスにセット②
        objItem = New MyItems("02", "青森県")

        'コンボボックスにセット②
        cmbTest.Items.Add(objItem)

        '要素を独自クラスにセット②
        objItem = New MyItems("03", "岩手県")

        'コンボボックスにセット②
        cmbTest.Items.Add(objItem)
    End Sub

    'SelectedIndexChangedイベント
    Private Sub cmbTest_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cmbTest.SelectedIndexChanged
        If cmbTest.SelectedIndex <> -1 Then

            Dim objItem As MyItems

            objItem = DirectCast(cmbTest.SelectedItem, MyItems)

            '選択中のコード値をテキストボックスに表示
            txtCode.Text = objItem.Code

        End If
    End Sub
End Class

```

```c#
//【C#】
//画面
public partial class Form_ComboBox : Form
{
    public Form_ComboBox()
    {
        InitializeComponent();
    }

    private void Form_ComboBox_Load(object sender, EventArgs e)
    {
        MyItems objItem;

        //要素を独自クラスにセット①
        objItem = new MyItems("01", "北海道");

        //コンボボックスにセット①
        cmbTest.Items.Add(objItem);

        //要素を独自クラスにセット②
        objItem = new MyItems("02", "青森県");

        //コンボボックスにセット②
        cmbTest.Items.Add(objItem);

        //要素を独自クラスにセット③
        objItem = new MyItems("03", "岩手県");

        //コンボボックスにセット③
        cmbTest.Items.Add(objItem);
    }

    //SelectedIndexChangedイベント
    private void cmbTest_SelectedIndexChanged(object sender, EventArgs e)
    {
        if (cmbTest.SelectedIndex != -1)
        {
            MyItems item;

            //選択中のアイテムのオブジェクトを取得
            item = (MyItems)cmbTest.SelectedItem;

            //Idプロパティをラベルに表示
            txtCode.Text = item.Code;
        }
    }
}
```

察しの良い方は既に分かると思いますが、コンボボックスに要素を1つずつ追加しています。
こんなこと、実際の現場ではなかなか見ません。
実際はSQLの結果や外部ファイルの値をコンボボックスに設定します。

なので、今回はCSVをインポートしてループ処理でコンボボックスの表示名と値を設定します。


```vb
'【VB.NET】
Imports System.Text     'CSVインポートで利用

Public Class Form_ComboBox
    Private Sub Form_ComboBox_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        'CSVファイル
        Dim strFilePath As String = "D:\temp\Combo\todohuken.csv"

        'CSVインポート
        Using objIoTFP As New Microsoft.VisualBasic.FileIO.TextFieldParser(strFilePath, Encoding.GetEncoding("Shift_JIS"))
            '読み込み時の設定
            With objIoTFP
                '区切り文字をカンマに設定
                .TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited
                .SetDelimiters(",")
                '空白があった場合にTrimしない
                .TrimWhiteSpace = False
            End With

            'コンボボックス用クラス
            Dim objItem As MyItems

            'CSV読み込み実行
            While Not objIoTFP.EndOfData
                'CSVの1行を配列にセット
                Dim arrayRow As String() = objIoTFP.ReadFields()
                '要素を独自クラスにセット
                objItem = New MyItems(arrayRow(0), arrayRow(1))
                'コンボボックスにセット
                cmbTest.Items.Add(objItem)
            End While
        End Using
    End Sub

    'SelectedIndexChangedイベント
    Private Sub cmbTest_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cmbTest.SelectedIndexChanged
        If cmbTest.SelectedIndex <> -1 Then

            Dim objItem As MyItems

            objItem = DirectCast(cmbTest.SelectedItem, MyItems)

            '選択中のコード値をテキストボックスに表示
            txtCode.Text = objItem.Code

        End If
    End Sub
End Class

```

```c#
//【C#】
using Microsoft.VisualBasic.FileIO;     //CSVインポートで利用

public partial class Form_ComboBox : Form
{
    public Form_ComboBox()
    {
        InitializeComponent();
    }

    private void Form_ComboBox_Load(object sender, EventArgs e)
    {
        string strFilePath = "D:/temp/Combo/todohuken.csv";

        //TextFieldParserのインスタンス生成
        var objParser = new TextFieldParser(strFilePath, Encoding.GetEncoding("Shift_JIS"));

        using (objParser)
        {
            //読み込み時の設定
            //区切り文字をカンマ指定
            objParser.TextFieldType = FieldType.Delimited;
            objParser.SetDelimiters(",");

            //空白があった場合にTrimしない
            objParser.TrimWhiteSpace = false;

            //コンボボックス用クラス
            MyItems objItem;

            //CSV読み込み実行
            while (!objParser.EndOfData)
            {
                // CSVの1行を配列にセット
                string[] arrayRow = objParser.ReadFields();
                // 要素を独自クラスにセット
                objItem = new MyItems(arrayRow[0], arrayRow[1]);
                //コンボボックスにセット
                cmbTest.Items.Add(objItem);
            }
            
        }
    }

    private void cmbTest_SelectedIndexChanged(object sender, EventArgs e)
    {
        if (cmbTest.SelectedIndex != -1)
        {
            MyItems item;

            //Load時に追加したオブジェクトの中から選択中のものを取得
            item = (MyItems)cmbTest.SelectedItem;

            //Idプロパティをラベルに表示
            txtCode.Text = item.Code;
        }
    }
}
```




#DataSourceで作成する場合
次にDataSourceで作成する方法。

【必要なモノ】

- フォームとコンボボックス（当たり前）

フォームとコンボボックスは先ほどの画像の通り。
以下の順序でコンボボックスのDataSourceに表示名と実際の値を格納します。

1. フォームロード時にCSVをインポート
2. CSVデータを配列に格納（実際の値、表示名）し、その配列をリストに格納
3. リストを走査してDataTableを作成
4. DataTableをコンボボックスのDataSourceにセット

尚、CSVインポート時にJet Providerで一気にDataTableを作成できますが、「配列からDataTableを作ってDataSourceにセットする」というステップを踏みたいので、敢えてTextFieldParserでのCSVインポートを実施しています。
※各現場で環境や制約が異なると思いますが、配列からDataTableが作成できないという現場は流石に無いと思いますので。


```vb
'【VB.NET】
Imports System.Text     'CSVインポートで利用
Public Class Form_ComboBox_DataTable
    Private Sub Form_ComboBox_DataTable_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        'CSVファイル
        Dim strFilePath As String = "D:\temp\Combo\todohuken.csv"

        '配列格納用リスト
        Dim strListDT As List(Of String()) = New List(Of String())
        'データテーブルの用意
        Dim objDT As New DataTable()
        objDT.Columns.Add("Code", GetType(String))      '実際の値
        objDT.Columns.Add("Name", GetType(String))      '表示名

        'CSVインポート
        Using objIoTFP As New Microsoft.VisualBasic.FileIO.TextFieldParser(strFilePath, Encoding.GetEncoding("Shift_JIS"))
            '読み込み時の設定
            With objIoTFP
                '区切り文字をカンマに設定
                .TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited
                .SetDelimiters(",")
                '空白があった場合にTrimしない
                .TrimWhiteSpace = False
            End With

            'CSV読み込み実行
            While Not objIoTFP.EndOfData
                'CSVの1行を配列にセット
                Dim arrayRow As String() = objIoTFP.ReadFields()
                '配列格納用リストにセット
                strListDT.Add(arrayRow)
            End While
        End Using

        'データテーブルの作成
        For Each strArray As String() In strListDT
            'データテーブル用の行オブジェクトを作成
            Dim objDR As DataRow = objDT.NewRow()
            objDR("Code") = strArray(0)    '実際の値（ValueMember）
            objDR("Name") = strArray(1)    '表示名（DisplayMember）

            '配列格納用リストに行オブジェクトを格納
            objDT.Rows.Add(objDR)
        Next

        'データテーブルのコミット
        objDT.AcceptChanges()
        'データテーブルをコンボボックスのデータソースにセット
        cmbTest.DataSource = objDT

        'ValueMemberとDisplayMemberを設定
        cmbTest.ValueMember = "Code"    '実際の値
        cmbTest.DisplayMember = "Name"  '表示名
    End Sub

    'SelectedIndexChangedイベント（）
    Private Sub cmbTest_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cmbTest.SelectedIndexChanged
        If cmbTest.SelectedIndex <> -1 Then
            '選択中のコード値をテキストボックスに表示
            txtCode.Text = cmbTest.SelectedValue.ToString()
        End If
    End Sub
End Class

```

```c#
//【C#】
using Microsoft.VisualBasic.FileIO;     //CSVインポートで利用

namespace CS_ComboBox_DataTable
{
    public partial class Form_ComboBox_DataTable : Form
    {
        public Form_ComboBox_DataTable()
        {
            InitializeComponent();
        }

        private void Form_ComboBox_DataTable_Load(object sender, EventArgs e)
        {
            //CSVファイル
            string strFilePath = "D:/temp/Combo/todohuken.csv";

            //配列格納用リスト
            List<String[]> strListDT = new List<String[]>();
            //データテーブルの用意
            DataTable objDT = new DataTable();
            objDT.Columns.Add("Code", typeof(String));      //実際の値
            objDT.Columns.Add("Name", typeof(String));      //表示名

            //TextFieldParserのインスタンス生成
            var objParser = new TextFieldParser(strFilePath, Encoding.GetEncoding("Shift_JIS"));

            using (objParser)
            {
                // 読み込み時の設定
                // 区切り文字をカンマ指定
                objParser.TextFieldType = FieldType.Delimited;
                objParser.SetDelimiters(",");

                // 空白があった場合にTrimしない
                objParser.TrimWhiteSpace = false;

                // CSV読み込み実行
                while (!objParser.EndOfData)
                {
                    // CSVの1行を配列にセット
                    string[] arrayRow = objParser.ReadFields();
                    //リストにセット
                    strListDT.Add(arrayRow);
                }

            }

            //データテーブルの作成
            foreach(string[] strArray in strListDT)
            {
                //データテーブル用の行オブジェクトを作成
                DataRow objDR = objDT.NewRow();
                objDR["Code"] = strArray[0];    //実際の値（ValueMember）
                objDR["Name"] = strArray[1];    //表示名（DisplayMember）

                //配列格納用リストに行オブジェクトを格納
                objDT.Rows.Add(objDR);
            }

            //データテーブルのコミット
            objDT.AcceptChanges();
            //データテーブルをコンボボックスのデータソースにセット
            cmbTest.DataSource = objDT;

            //ValueMemberとDisplayMemberを設定
            cmbTest.ValueMember = "Code";
            cmbTest.DisplayMember = "Name";
        }

        private void cmbTest_SelectedIndexChanged(object sender, EventArgs e)
        {
            //選択されていればSelectedValueに入っている
            if (cmbTest.SelectedIndex != -1)
            {
                //選択中のコード値をテキストボックスに表示
                txtCode.Text = cmbTest.SelectedValue.ToString();
            }
        }
    }
}

```
CSVの部分をSQLに書き換えても使えます。
データテーブルの作成の処理の前に、リストの中に配列が入っていればOKです。

データテーブルの作成に至る前に、リストの中に配列を入れています。二次元配列か多段階配列にする方法も考えましたが、ソースコードが非常に煩雑になるため、リストにしています。

#参考

https://dobon.net/vb/dotnet/control/tbdisplaymember.html

https://www.curict.com/item/96/96c6104.html

https://stellacreate.com/entry/vbnet-csv-import-TFP