2013-03-13 42 views
1

我有一个Windows窗体有两个组合框。每个组合框的SelectedValue属性都绑定到简单DTO上的属性。每个组合框的选项都是从模型对象列表中绘制的。我只需要窗体上的控件来更新DTO;我不需要以编程方式修改任何DTO的属性,并查看正在更新的相应控件 - 即,我只需要单向(控件 - >源)数据绑定即可工作。为什么绑定到一组Winforms级联组合框会导致根组合框未正确设置其值?

当用户更改第一个组合框的值时,第二个组合框的选项将完全更改。但是,我遇到了在此设置两个问题,他们为什么会发生或如何解决这些问题我想不通:

  1. 每当第一个组合框被改变,一个NRE产生并通过数据绑定框架吞噬(我可以看到它在Visual Studio IDE的立即窗口中抛出),这让我意识到某些东西设置不正确。更改第二个组合框或任何其他不相关的数据绑定控件(组合框或其他)不会生成NRE。
  2. 此外,无论何时第一个组合框更改,在生成上述NRE后,第二个组合框加载成功,但第一个组合框的选定索引重置为-1。我怀疑这是因为数据绑定的“推”事件触发更新控件,并出于某种原因,我的DTO属性支持第一个组合框的值重置为NULL/Nothing。

有没有人有任何想法为什么发生这些事情?我嘲笑了我的问题,它展示了上述两个问题。我还添加了第三个组合框,与前两者无关,只是作为一个理智检查来显示组合框没有任何依赖另一个组合框工作正常。

此代码复制问题 - 粘贴为Visual Basic Windows窗体项目(3.5框架)的默认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 
+0

这个问题试图描述一组相关组合框(绑定到对象的数据)如何导致至少一个组合框的基本功能失败。级联/依赖组合框非常普遍。数据绑定也是一种常见的做法。我认为这种情况并不是非常狭隘。请重新打开这个问题。 – 2013-03-16 05:07:16

+0

我同意,重新打开它。 – OneFineDay 2013-03-16 06:01:29

回答

1

DataBinding在运行异常时很难调试。有两件事情在这里出错,足以使它难以诊断。首先,您不指望的是在货币经理更新绑定对象之前,SelectedValueChanged事件触发之前。这通常不是问题,但您的事件处理程序有副作用。接下来你不指望的是,更改一个绑定对象的属性导致绑定的所有其他属性也被更新。

还有就是蹭,_DataObject.ValueA仍然没什么,当你更新组合B.哪个更新_DataObject.ValueB。因此,货币经理再次更新组合A ,试图使其与属性ValueA中的值的值相匹配。这也是产生NullReferenceException的原因。

一种可能的解决方法是延迟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而不是尝试更新组合框。但是你用一本字典让我有点困难,我没有去追求它。

+0

+1谢谢你的答案和推理。我的问题只是一个事件的顺序问题,而且你的快速解决方案工作得很好。 – 2013-03-26 14:23:01