2 つのコンボ ボックスを含む Windows フォームがあります。各コンボ ボックスのSelectedValue
プロパティは、単純な DTO のプロパティにバインドされたデータです。各コンボ ボックスのオプションは、モデル オブジェクトのリストから取得されます。DTO を更新するには、フォーム上のコントロールのみが必要です。DTO のプロパティをプログラムで変更したり、対応するコントロールが更新されているのを確認したりする必要はありません。つまり、一方向 (コントロール -> ソース) のデータ バインディングだけで機能します。
ユーザーが最初のコンボ ボックスの値を変更すると、2 番目のコンボ ボックスのオプションが完全に変更されます。ただし、このセットアップで 2 つの問題が発生したため、それらが発生する理由や解決方法がわかりません。
- 最初のコンボ ボックスが変更されるたびに、NRE が生成され、データ バインディング フレームワークによって取り込まれます (Visual Studio IDE のイミディエイト ウィンドウに表示されます)。2 番目のコンボ ボックスまたはその他の関連のないデータ バインド コントロール (コンボ ボックスなど) を変更しても、NRE は生成されません。
- また、最初のコンボ ボックスが変更されるたびに、上記の NRE を生成した後、2 番目のコンボ ボックスは正常にロードされますが、最初のコンボ ボックスの選択されたインデックスは -1 にリセットされます。これは、データ バインディングの「プッシュ」イベントが発生してコントロールが更新され、何らかの理由で、最初のコンボ ボックスをサポートする DTO のプロパティの値が NULL / Nothing にリセットされるためだと思われます。
なぜこれらのことが起こるのか誰にも分かりますか?上記の 2 つの問題を示す問題をモックアップしました。また、最初の 2 つのいずれとも関係のない 3 つ目のコンボ ボックスを追加しました。これは、別のコンボ ボックスに依存しないコンボ ボックスが正常に動作することを示すサニティ チェックと同じです。
このコードは問題を再現します - Visual Basic Windows Forms プロジェクト (3.5 Framework) の既定の Form1 クラスのコードとして貼り付けます。
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Windows.Forms
Public Class Form1
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.cboA = New System.Windows.Forms.ComboBox()
Me.cboB = New System.Windows.Forms.ComboBox()
Me.cboC = New System.Windows.Forms.ComboBox()
Me.SuspendLayout()
'
'cboA
'
Me.cboA.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList
Me.cboA.FormattingEnabled = True
Me.cboA.Location = New System.Drawing.Point(120, 25)
Me.cboA.Name = "cboA"
Me.cboA.Size = New System.Drawing.Size(121, 21)
Me.cboA.TabIndex = 0
'
'cboB
'
Me.cboB.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList
Me.cboB.FormattingEnabled = True
Me.cboB.Location = New System.Drawing.Point(120, 77)
Me.cboB.Name = "cboB"
Me.cboB.Size = New System.Drawing.Size(121, 21)
Me.cboB.TabIndex = 1
'
'cboC
'
Me.cboC.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList
Me.cboC.FormattingEnabled = True
Me.cboC.Location = New System.Drawing.Point(120, 132)
Me.cboC.Name = "cboC"
Me.cboC.Size = New System.Drawing.Size(121, 21)
Me.cboC.TabIndex = 2
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(284, 262)
Me.Controls.Add(Me.cboC)
Me.Controls.Add(Me.cboB)
Me.Controls.Add(Me.cboA)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
Friend WithEvents cboA As System.Windows.Forms.ComboBox
Friend WithEvents cboB As System.Windows.Forms.ComboBox
Friend WithEvents cboC As System.Windows.Forms.ComboBox
Private _DataObject As MyDataObject
Private _IsInitialized As Boolean = False
Public Sub New()
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_DataObject = New MyDataObject()
BindControls()
End Sub
Private Sub BindControls()
LoadComboA(cboA)
Dim cmbABinding As New Binding("SelectedValue", _DataObject, "ValueA", True, DataSourceUpdateMode.OnPropertyChanged)
cboA.DataBindings.Add(cmbABinding)
Dim cmbBBinding As New Binding("SelectedValue", _DataObject, "ValueB", True, DataSourceUpdateMode.OnPropertyChanged)
cboB.DataBindings.Add(cmbBBinding)
LoadComboC(cboC)
Dim cmbCBinding As New Binding("SelectedValue", _DataObject, "ValueC", True, DataSourceUpdateMode.OnPropertyChanged)
cboC.DataBindings.Add(cmbCBinding)
End Sub
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
_IsInitialized = True
cboA.SelectedIndex = 0
cboC.SelectedIndex = 0
End Sub
Private Sub ComboA_SelectedValueChanged(ByVal sender As Object, ByVal e As EventArgs) Handles cboA.SelectedValueChanged
If _IsInitialized Then
LoadComboB(cboB, cboA.SelectedValue.ToString())
cboB.SelectedIndex = 0
End If
End Sub
Private Sub LoadComboA(ByVal cmbBox As ComboBox)
Dim someData As New Dictionary(Of String, String)()
someData.Add("Value1", "Text 1")
someData.Add("Value2", "Text 2")
someData.Add("Value3", "Text 3")
cmbBox.DataSource = someData.ToList()
cmbBox.DisplayMember = "Value"
cmbBox.ValueMember = "Key"
End Sub
Private Sub LoadComboB(ByVal cmbBox As ComboBox, ByVal selector As String)
Dim someSubData As New Dictionary(Of String, String)()
Select Case selector
Case "Value1"
someSubData.Add("SubValue1", "Value1 - Sub Text 1")
someSubData.Add("SubValue2", "Value1 - Sub Text 2")
someSubData.Add("SubValue3", "Value1 - Sub Text 3")
Case "Value2"
someSubData.Add("SubValue4", "Value2 - Sub Text 4")
someSubData.Add("SubValue5", "Value2 - Sub Text 5")
someSubData.Add("SubValue6", "Value2 - Sub Text 6")
Case "Value3"
someSubData.Add("SubValue7", "Value3 - Sub Text 7")
someSubData.Add("SubValue8", "Value3 - Sub Text 8")
someSubData.Add("SubValue9", "Value3 - Sub Text 9")
End Select
cmbBox.DataSource = someSubData.ToList()
cmbBox.DisplayMember = "Value"
cmbBox.ValueMember = "Key"
End Sub
Private Sub LoadComboC(ByVal cmbBox As ComboBox)
Dim someData As New Dictionary(Of String, String)()
someData.Add("Value100", "Text 100")
someData.Add("Value101", "Text 101")
cmbBox.DataSource = someData.ToList()
cmbBox.DisplayMember = "Value"
cmbBox.ValueMember = "Key"
End Sub
End Class
Public Class MyDataObject ' DTO class
Private _ValueA As String
Public Property ValueA() As String
Get
Return _ValueA
End Get
Set(ByVal value As String)
_ValueA = value
End Set
End Property
Private _ValueB As String
Public Property ValueB() As String
Get
Return _ValueB
End Get
Set(ByVal value As String)
_ValueB = value
End Set
End Property
Private _ValueC As String
Public Property ValueC() As String
Get
Return _ValueC
End Get
Set(ByVal value As String)
_ValueC = value
End Set
End Property
End Class