DataTableと連携したComboBoxの値更新について

タグの編集
投稿者 タック  (社会人) 投稿日時 2019/3/15 12:18:27
Windows Formに配置したComboBoxのDataSourceにDataTableを指定して、DataTableの値をComboBoxに表示するところまではできました。

ComboBoxのDropDownStyleをDropDownに設定し値を変更した時に、連動して自動的にDataTableの値も更新されると期待したのですがそうはならないようです。
(DataGridViewのように自動的に更新してくれればよかったのですが)

ComboBoxの値を変更した際に、連動するDataTableの値も変更したいのですがどのように行うべきなのか教えていただけますでしょうか。

ComboBoxのTextChangedイベントなどにDataTableを更新するような処理を書くのでしょうか?

サンプルなどございましたら教えていただけますでしょうか。

環境
Windows10, Visual Studio2016, VB.NET
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2019/3/15 14:16:37
ComboBox に対するデータバインドには、大きく分けて 2 種類あります。

「ドロップダウンリストに表示する一覧」のためのバインドと、
「一覧の中のどのアイテムを選択しているか」のためのバインドです。


説明のため、新規プロジェクトにて以下を作ってみてください。

(1) プロジェクトに DataSet クラスを "MyDataSet" という名で追加し、それを開いて
 DataSet デザイナーでテーブルを追加。テーブル名は "MyDataTable" にしておく。

(2) そのテーブルに 列を 2 つ追加する。列名は "DataColumn1"、"DataColumn2" のままとし、
 DataColumn1 列を右クリックして「主キーの設定」を選択して 🔑 マークをつけておく。

(3) プロジェクトをビルドする。ツールボックスに (1) の型付 DataSet が追加される。

(4) ツールボックスに現れた MyDataSet をフォームに貼る。名前は "MyDataSet1" のまま。

(5) 続けて、ComboBox、BindingSource、BindingNavigator もフォームに貼る。

(6) BindingSource1 を選択し、その DataSource プロパティから
 [他のデータ ソース] - [Form1 一覧インスタンス] - [MyDataSet1] を選択。

(7) さらに続けて、BindingSource1 の DataMember プロパティに MyDataTable を指定する。

(8) BindingNavigator1 を選択し、BindingSource プロパティに BindingSource1 を指定。

(9) ComboBox1 を選択し、DataSource プロパティに BindingSource1 を指定。

(10) さらに続けて、ComboBox1 の DisplayMember プロパティに DataColumn2 列を、
 ValueMember プロパティには、主キー設定した DataColumn1 列を指定する。

(11) Form1 の Load イベントに下記を記述して実行。

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Me.MyDataSet1.MyDataTable.AddMyDataTableRow("11""aaa")
    Me.MyDataSet1.MyDataTable.AddMyDataTableRow("12""bbb")
    Me.MyDataSet1.MyDataTable.AddMyDataTableRow("13""ccc")
    Me.MyDataSet1.MyDataTable.AddMyDataTableRow("14""ddd")
End Sub


(12) ComboBox1 のドロップダウンリストに、aaa, bbb, ccc, ddd が表示されることと、
 BindingNavigator1 の現在行が、その選択内容と連動していることを確認する。



> ComboBoxのDropDownStyleをDropDownに設定し値を変更した時に、
> 連動して自動的にDataTableの値も更新されると期待したのですがそうはならないようです。

ここでいう「値を変更した」というのは、どのような操作のことでしょうか。

たとえば下記のようにすれば、上記サンプルの "ccc" が "NewText" に書き換わるはずです。
ComboBox1 と MyDataSet1 の両方の内容が連動して編集されるはず。
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Me.MyDataSet1.MyDataTable(2).DataColumn2 = "NewText"
End Sub


上記では DataTable 側を書き換えていますが、もちろん ComboBox 側から書き換えることもできます。
下記では「現在選択されているアイテム」の文字列を書き換えています。
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    Dim rowView = TryCast(ComboBox1.SelectedItem, DataRowView)
    If rowView IsNot Nothing Then
        Dim row = DirectCast(rowView.Row, MyDataSet.MyDataTableRow)
        rowView.BeginEdit()
        row.DataColumn2 = "内容の変更"
        rowView.EndEdit()
    End If
End Sub


型付DataSet である必要はありません。実行時バインドでも構いません。
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
    If ComboBox1.SelectedIndex <> -1 Then
        ComboBox1.SelectedItem("DataColumn2") = "レイトバインドで変更"
        ComboBox1.SelectedItem.EndEdit()
    End If
End Sub
Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
    If BindingSource1.Position <> -1 Then
        BindingSource1.Current("DataColumn2") = "BindingSource側から編集"
        BindingSource1.EndEdit()
    End If
End Sub
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2019/3/15 14:22:44
> Windows10, Visual Studio2016, VB.NET

Windows 版には(そして Mac 版にも)
「Visual Studio 2016」という製品はありませんよ。

来月 2 日にリリースされる予定の「Visual Studio 2019」を除けば、
最新は「Visual Studio 2017」(の Version 15.9.9)ですし、
その前は「Visual Studio 2015」(最新は Update 3)です。

https://docs.microsoft.com/ja-jp/visualstudio/releasenotes/vs2017-relnotes
投稿者 タック  (社会人) 投稿日時 2019/3/15 14:57:00
魔界の仮面弁士様

初心者の私の理解できるように詳しい説明をしていただき、誠にありがとうございます。

まず、お詫びから
>「Visual Studio 2016」という製品はありませんよ。」
ご指摘の通りで「Visual Studio 2017」の誤りでした。
Office 2016 と頭の中で一緒になっていました。

ComboBoxの値の変更について
>ここでいう「値を変更した」というのは、どのような操作のことでしょうか。

おっしゃる通りで、これではどのような操作かわかりませんね。

新規プロジェクトで(12)まで作成し、きちんと動作することを確認いたしました。

ここで、デバッグ実行した際のForm上のComboBoxの初期値が「aaa」となり、画面上で例えば「xxx」と修正ができますが、この修正をした「xxx」という値をDataTableに更新したい、という意味なのです。

DataGridViewであれば、セルを画面で更新すればそれに連動してDataTableも更新してくれますがComboBoxはそうではないようなので。

画面で変更したテキスト内容をDataTableに適用する方法を教えていただけますでしょうか。

よろしくお願いいたします。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2019/3/15 15:37:33
> ここで、デバッグ実行した際のForm上のComboBoxの初期値が「aaa」となり、画面上で例えば「xxx」と修正ができますが、この修正をした「xxx」という値をDataTableに更新したい、という意味なのです。

DropDownList ではなく DropDown スタイルの時の話でしたよね。

編集直後にドロップダウンすると、どのアイテムも選択されていない状態になっていると思います。

これはテキスト部をユーザーが書き換えた瞬間、
 SelectedIndex は -1
 SelectedItem は Nothing (SelectedValue も同様)
という「未選択」の状態に変更されていることを意味しています。

編集対象のレコードが無いため、編集結果が DataTable に伝わることはありません)


ゆえに例えば、バインド元(先の私の例なら BindingSource)から元のレコード位置を調べ、
そこに、ComboBox の編集結果を反映させるようなコードが必要です。

プログラムから編集する方法は既に示していますので、後はそれを組み込むだけですよね。

先の例ですと、DataSource に BindingSource を割り当てていますので、たとえばこんな感じにできるでしょう。
If ComboBox1.SelectedIndex = -1 AndAlso BindingSource1.Position <> -1 Then
    Dim row = DirectCast(BindingSource1.Current, DataRowView).Row
    row("DataColumn2") = ComboBox1.Text
    BindingSource1.EndEdit()
End If



この処理をどこに組み込むのは、どの時点で確定させたいかに応じて開発者側で決めてください。
たとえば入力値の確定という観点からは、ComboBox1 の Validated イベントあたりが
良いのではないでしょうか。即時性が求められるなら TextUpdate イベントも使えます。

その他、Enter キー押下で確定させたいとか、確定前に Esc を押した時には、
編集中の内容を取り消したいといった要件が出てくるかもしれませんが、
そういった調整は、必要に応じて適宜組み込んでみてください。
投稿者 タック  (社会人) 投稿日時 2019/3/15 16:01:14
魔界の仮面弁士様

ComboBoxのValidated イベントに教えていただいたコードを貼り付け、まさに私のやりたかったことが実現できました。

>編集直後にドロップダウンすると、どのアイテムも選択されていない状態になっていると思います。

>これはテキスト部をユーザーが書き換えた瞬間、
> SelectedIndex は -1
> SelectedItem は Nothing (SelectedValue も同様)
>という「未選択」の状態に変更されていることを意味しています。

>編集対象のレコードが無いため、編集結果が DataTable に伝わることはありません)

このあたりのことが分からず、デバッグしてもSelectedIndex は -1になってしまうし、「未選択」の状態になってしまうため、書き換えた場所(DataTableの何行目を編集したのか)を把握しようにも指定の仕方が分かりませんでした。

これで今開発中の内容が次の処理に進めます。

本当にありがとうございました。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2019/3/15 16:25:56
下記のように書くこともできます。

Dim bc = Me.BindingContext(Me.ComboBox1.DataSource)
If ComboBox1.SelectedIndex = -1 AndAlso bc.Position <> -1 Then
    Dim row = DirectCast(bc.Current, DataRowView).Row
    row("DataColumn2") = ComboBox1.Text
    bc.EndCurrentEdit()
End If


この方法だと、BindingSource を使っていないケース(DataTable を直接バインドしている場合など)にも対応できます。