Results 1 to 3 of 3

Thread: [RESOLVED] How to bind DGV to complex type properties

  1. #1

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2007
    Location
    cobwebbed to PC
    Posts
    311

    Resolved [RESOLVED] How to bind DGV to complex type properties

    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
    Thanks

  2. #2

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2007
    Location
    cobwebbed to PC
    Posts
    311

    Re: How to bind DGV to complex type properties

    OK, so I reverted to the PropertyDescriptor approach after reading this Which showed that the previous example had a bit missing.

    the columns now have data in them again!

    But changing the data in one cell still results in it being changed seemingly random other cells that are of the same type...

    So atm I have done away with the event handler from above and added the below classes, and applied and attribute of <TypeDescriptionProvider(GetType(ParameterDescriptionProvider))> to the Parameter class

    vb Code:
    1. Public Class ParameterPropertyDescriptor
    2.     Inherits PropertyDescriptor
    3.  
    4.     Private _SubPD As PropertyDescriptor
    5.     Private _ParentPD As PropertyDescriptor
    6.  
    7.     Public Sub New(parentPD As PropertyDescriptor, subPD As PropertyDescriptor, pdName As String)
    8.         MyBase.New(pdName, Nothing)
    9.         _SubPD = subPD
    10.         _ParentPD = parentPD
    11.     End Sub
    12.  
    13.     Public Overrides Function CanResetValue(component As Object) As Boolean
    14.         Return False
    15.     End Function
    16.  
    17.     Public Overrides ReadOnly Property ComponentType As System.Type
    18.         Get
    19.             Return _ParentPD.ComponentType
    20.         End Get
    21.     End Property
    22.  
    23.     Public Overrides Function GetValue(component As Object) As Object
    24.         Return _SubPD.GetValue(_ParentPD.GetValue(component))
    25.     End Function
    26.  
    27.     Public Overrides ReadOnly Property IsReadOnly As Boolean
    28.         Get
    29.             Return False
    30.         End Get
    31.     End Property
    32.  
    33.     Public Overrides ReadOnly Property PropertyType As System.Type
    34.         Get
    35.             Return _SubPD.PropertyType
    36.         End Get
    37.     End Property
    38.  
    39.     Public Overrides Sub ResetValue(component As Object)
    40.         ' TODO: Code for this sub?
    41.     End Sub
    42.  
    43.     Public Overrides Sub SetValue(component As Object, value As Object)
    44.         _SubPD.SetValue(_ParentPD.GetValue(component), value)
    45.         OnValueChanged(component, EventArgs.Empty)
    46.     End Sub
    47.  
    48.     Public Overrides Function ShouldSerializeValue(component As Object) As Boolean
    49.         Return True
    50.     End Function
    51. End Class
    52.  
    53. Public Class ParameterTypeDescriptor
    54.     Inherits CustomTypeDescriptor
    55.  
    56.     Public Sub New(parent As ICustomTypeDescriptor)
    57.         MyBase.New(parent)
    58.     End Sub
    59.  
    60.     Public Overrides Function GetProperties() As System.ComponentModel.PropertyDescriptorCollection
    61.         Dim Cols As PropertyDescriptorCollection = MyBase.GetProperties()
    62.  
    63.         Dim UpperLimitPD As PropertyDescriptor = Cols("UpperLimit")
    64.         Dim UpperLimitChild As PropertyDescriptorCollection = UpperLimitPD.GetChildProperties()
    65.         Dim DefaultValuePD As PropertyDescriptor = Cols("DefaultValue")
    66.         Dim DefaultValueChild As PropertyDescriptorCollection = DefaultValuePD.GetChildProperties()
    67.         Dim LowerLimitPD As PropertyDescriptor = Cols("LowerLimit")
    68.         Dim LowerLimitChild As PropertyDescriptorCollection = LowerLimitPD.GetChildProperties()
    69.  
    70.         Dim array(Cols.Count + 2) As PropertyDescriptor
    71.         Cols.CopyTo(array, 0)
    72.         array(Cols.Count) = New ParameterPropertyDescriptor(UpperLimitPD, UpperLimitChild("Value"), "UpperLimit.Value")
    73.         array(Cols.Count + 1) = New ParameterPropertyDescriptor(DefaultValuePD, DefaultValueChild("Value"), "DefaultValue.Value")
    74.         array(Cols.Count + 2) = New ParameterPropertyDescriptor(LowerLimitPD, LowerLimitChild("Value"), "LowerLimit.Value")
    75.  
    76.         Dim NewCols As PropertyDescriptorCollection = New PropertyDescriptorCollection(array)
    77.         Return NewCols
    78.     End Function
    79.  
    80.     Public Overrides Function GetProperties(attributes() As Attribute) As System.ComponentModel.PropertyDescriptorCollection
    81.         Return Me.GetProperties()
    82.     End Function
    83. End Class
    84.  
    85. Public Class ParameterDescriptionProvider
    86.     Inherits TypeDescriptionProvider
    87.  
    88.     Private td As ICustomTypeDescriptor
    89.  
    90.     Public Sub New()
    91.         Me.New(TypeDescriptor.GetProvider(GetType(Parameter)))
    92.     End Sub
    93.  
    94.     Public Sub New(parent As TypeDescriptionProvider)
    95.         MyBase.New(parent)
    96.     End Sub
    97.  
    98.     Public Overrides Function GetTypeDescriptor(objectType As Type, instance As Object) As ICustomTypeDescriptor
    99.         If td Is Nothing Then
    100.             td = MyBase.GetTypeDescriptor(objectType, instance)
    101.             td = New ParameterTypeDescriptor(td)
    102.         End If
    103.         Return td
    104.     End Function
    105. End Class
    Thanks

  3. #3

    Thread Starter
    Hyperactive Member
    Join Date
    Apr 2007
    Location
    cobwebbed to PC
    Posts
    311

    Re: How to bind DGV to complex type properties

    Got it.

    When I set up my objects that I was testing with I created a single TestValue object of type ParameterValue.
    then I set all of the ParameterValue type members in each Parameter equal to this object.

    Thinking of course that I was being clever and saving typing. But of course I actually set the ParameterValue members to a Reference to the TestValue object!

    This meant that when the binding updated a members of TestValue it was only one member shared between all the ParameterValue references and so updated everywhere else that it had been used as well... D'oh!!! Such a waste of most of the day :\

    EDIT: Note that the descriptor approach is better than handling the CellFormatting event because the former automatically handles types, whereas the latter runs into type conflicts if the DGV column's bound data are not all of the same type and you wish to have data changes in the grid reflected by changes in the bound data

    W99
    Last edited by wolf99; Dec 7th, 2015 at 04:31 AM.
    Thanks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width