2013-03-13 57 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