1

2 つのコンボ ボックスを含む Windows フォームがあります。各コンボ ボックスのSelectedValueプロパティは、単純な DTO のプロパティにバインドされたデータです。各コンボ ボックスのオプションは、モデル オブジェクトのリストから取得されます。DTO を更新するには、フォーム上のコントロールのみが必要です。DTO のプロパティをプログラムで変更したり、対応するコントロールが更新されているのを確認したりする必要はありません。つまり、一方向 (コントロール -> ソース) のデータ バインディングだけで機能します。

ユーザーが最初のコンボ ボックスの値を変更すると、2 番目のコンボ ボックスのオプションが完全に変更されます。ただし、このセットアップで 2 つの問題が発生したため、それらが発生する理由や解決方法がわかりません。

  1. 最初のコンボ ボックスが変更されるたびに、NRE が生成され、データ バインディング フレームワークによって取り込まれます (Visual Studio IDE のイミディエイト ウィンドウに表示されます)。2 番目のコンボ ボックスまたはその他の関連のないデータ バインド コントロール (コンボ ボックスなど) を変更しても、NRE は生成されません。
  2. また、最初のコンボ ボックスが変更されるたびに、上記の 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
4

1 に答える 1

3

DataBinding が正しく動作しない場合、デバッグが困難になる可能性があります。ここには 2 つの問題があり、診断が難しくなっています。期待していなかった最初のことは、通貨マネージャーがバインドされたオブジェクトを更新する前にSelectedValueChanged イベントが発生することです。通常は問題ありませんが、イベント ハンドラーには副作用があります。次に期待していなかったのは、バインドされたオブジェクトの1 つのプロパティを変更すると、のすべてのプロパティのバインドも更新されるということです。

コンボ B を更新すると、_DataObject.ValueA はまだ Nothing です。これにより、_DataObject.ValueB が更新されます。そのため、通貨マネージャーはコンボ Aを再度更新し、プロパティ ValueA の Nothing の値と一致させようとします。これは、NullReferenceException も生成したものです。

考えられる修正の 1 つは、SelectedValueChanged イベント ハンドラーで副作用を遅らせ、通貨マネージャーがバインドされたオブジェクトを更新するまで延期することです。Control.BeginInvoke() を使用してきれいに実行できるよりも、UI スレッドが再びアイドル状態になったときにターゲットが実行されます。これで問題が解決しました:

Private Sub ComboA_SelectedValueChanged(ByVal sender As Object, ByVal e As EventArgs) Handles cboA.SelectedValueChanged
    If _IsInitialized Then Me.BeginInvoke(New MethodInvoker(AddressOf LoadB))
End Sub

Private Sub LoadB()
    LoadComboB(cboB, cboA.SelectedValue.ToString())
    cboB.SelectedIndex = 0
End Sub

おそらく、コンボボックスを更新しようとする代わりに _DataObject を更新する、よりクリーンな修正があります。しかし、辞書を使用してそれを少し難しくしたので、私はそれを追求しませんでした.

于 2013-03-22T02:19:25.783 に答える