Hi Folks

I have a DataGridView with some DataGridViewTextBoxColumn columns in it.

The DGV itself is bound to a BindingList(Of Parameter)

The Parameter class has public property members (multiple) that are objects of class ParameterValue, which itself has two Decimal public property members.

I am trying to have the DGV bind to the two decimal members. I have tried custom PropertyDescriptor approach outlined here but it did not seem to do anything at all and is too complex for me to debug if there is a simpler method.

I found a second approach here that works via the DGV CellFormatting event. This approach displayed the values in the cell correctly (yay!) but did not update the related object members when the data was edited or added in the DGV.

I attempted to modify the event handler but must have mucked it up somehow as while it does change the data, it also changes it for every other similarly bound cell also!!

What have I done wrong, how can I adjust it do act as I require?
Or is there a better method? Or even a fix for the PropertyDescriptor approach?

The relevant classes:

vb Code:
  1. Public Class ParameterValue
  2.     Public Property Value As Decimal
  3. End Class
  4.  
  5. Public Class Parameter
  6.     '... Other intrinsic-type members - these bind OK
  7.     Public Property UpperLimit As ParameterValue = New Parameter Value 'How to bind the members of these?
  8.     Public Property DefaultValue As ParameterValue = New Parameter Value
  9.     Public Property LowerLimit As ParameterValue = New Parameter Value
  10. End class
  11.  
  12. Public Class Profile
  13.     ' ... Other members
  14.     Public Property Parameters As BindingList(Of Parameter) = New BindingList(Of Parameter)
  15. End Class

In the form:
vb Code:
  1. Public Class Form1
  2.    
  3.     'A dummy ParameterValue object
  4.     Dim TestValue As parameterValue = New ParameterValue With { _
  5.         .Value = 10, _
  6.     }
  7.  
  8.    'Some dummy objects with populated ParameterValue objects on the BindingList
  9.     Dim TestParameters As BindingList(Of Parameters) = New BindingList(Of Parameters) From { _
  10.         New Parameter With {.UpperLimit = testValue, DefaultValue = TestValue, .LowerLimit = TestValue}, _
  11.         New Parameter With {.UpperLimit = testValue, DefaultValue = TestValue, .LowerLimit = TestValue}, _
  12.         New Parameter With {.UpperLimit = testValue, DefaultValue = TestValue, .LowerLimit = TestValue} _
  13.     }
  14.  
  15.     'A dummy Profile object with a populated BindingList
  16.     Dim TestProfile As Profile = New Profile With {.Parameters = TestParameters}
  17.  
  18.     Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
  19.          'Set the basic data binding up
  20.          DataGridView1.AutoGenerateColumns = False
  21.          DataGridView1.Columns("UpperLimit").DataPropertyName = "UpperLimit.Value"
  22.          DataGridView1.Columns("DefaultValue").DataPropertyName = "DefaultValue.Value"
  23.          DataGridView1.Columns("LowerLimit").DataPropertyName = "LowerLimit.Value"
  24.          DataGridView1.DataSource = TestProfile .Parameters
  25.     End Sub
  26.  
  27.     Private Sub DataGridView1_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles DataGridView1.CellFormatting
  28.         '"Fixes" the DataPropertyNames that use the dot operator before updating the value in the object and the DGV cell.
  29.         Dim Grid As DataGridView = DirectCast(sender, DataGridView)
  30.         Dim Row As DataGridViewRow = Grid.Rows(e.RowIndex)
  31.         Dim Col As DataGridViewColumn = Grid.Columns(e.ColumnIndex)
  32.         If Row.DataBoundItem IsNot Nothing AndAlso Col.DataPropertyName.Contains(".") Then 'Check if the DataPropertyName member of the current cell contains dot operators (".")
  33.             Dim PropertyStrings As String() = Col.DataPropertyName.Split("."c) 'Split the member string at the dot operators
  34.             Dim PropInfo As Reflection.PropertyInfo = Row.DataBoundItem.[GetType]().GetProperty(PropertyStrings(0)) 'Get the property that matches the first of the split strings from the type of the object bound to the current cell
  35.             Dim ParentVal As Object = Nothing
  36.             Dim Val As Object = PropInfo.GetValue(Row.DataBoundItem, Nothing) 'Get the value from the property
  37.             For i As Integer = 1 To PropertyStrings.Length - 1 'Get the property then the value of any subsequent split strings
  38.                 ParentVal = Val 'Save the preceding value as the new parent object
  39.                 PropInfo = Val.[GetType]().GetProperty(PropertyStrings(i)) 'Get the child as the new current property
  40.                 Val = PropInfo.GetValue(Val, Nothing) ' Get the value of the new current property
  41.             Next
  42.             If (e.Value IsNot Nothing) Then
  43.                 If ((e.Value <> "") And (Convert.ChangeType(e.Value, PropInfo.PropertyType) <> Val)) Then 'Update the value in the object
  44.                     PropInfo.SetValue(ParentVal, Convert.ChangeType(e.Value, PropInfo.PropertyType), Nothing)
  45.                     Val = PropInfo.GetValue(ParentVal, Nothing) 'Get the value back from the object (in case the getter or setter alters the value)
  46.                 End If
  47.             End If
  48.             e.Value = Val 'Save the final value as the value to use in the cell
  49.         End If
  50.     End Sub
  51. End Class