.Net 2.0 Complex Business Object & DataGridViews



  • I don't normally deal with the UI/Presentation Layer stuff (other than reporting), so I haven't really come across this issue before.  I'm trying to bind a complex business object to a DataGridView, and I'm not sure whether this is possible or whether I need to create my own DataGridViewColumn class.  I'm hoping someone can point me in the right direction or offer suggestions on how to do this.

    Suppose you have a "Bar" object defined as such:

    Class Bar
    {
    private barID int;
    private name string;
    private description string;

    ... snip ...

    (getters/setters)

    }

    And a "Foo" object defined as such that contains a single "Bar" object:

    Class Foo
    {
    private FooID int;
    private name string;
    private bar Bar;

    ... snip ...

    (getters/setters)

    }

    I've created a BindingList(of Foo) and want to bind the collection to a DataGridView with the following columns.

    myFoo.Name    myFoo.Bar.Name    myFoo.Bar.Description

    It doesn't seem that I can bind columns directly to child object properties and I know that for TextBoxes, ComboBoxes, etc you can do this by manually binding in code.  Can this be done with the DataGridView and if so, does anybody know of any relevant resources?  I'd also be interested in other approaches.

    Any guidance/insight would be greatly appreciated.




  • Seems to me like you'd want to expose the child's properties as properties on the parent.  I've only done object databinding in asp.net, but if the DataGridView works anything like the asp.net versions, you should get access to properties by default.  Here's what I was thinking would work for you, although you might also need to implement setters to get it going.  Hope this ends up helping if there isn't a way to get at the child properties:

    public class Child
    {
        public string name;
        public int age;
    }
    public class Parent
    {
        private Child child;
        private string name;
    
    public string ParentName
    {
        get
        {
            return (name);
        }
    }
    public string ChildName
    {
        get
        {
            return (child.name);
        }
    }
    public int ChildAge
    {
        get
        {
            return (child.age);
        }
    }
    

    }



  • Oscar,

    Thanks for your input.  That's the approach I've taken, I just wanted to make sure I wasn't missing anything obvious.  Maybe it's just me, but I just don't like Parent.ChildAge == Parent.Child.Age.  Seems hokey to me. 




  • That's what I thought, too.  The only other improvement I can think of is to write an interface that implements the fields you want, but I'll be watching this thread to see if anyone else has a cleaner looking solution.



  • I didn't get a chance to read this yet, but the title made me think it was something that would be of interest to you:

    Behind the Scenes: Improvements to Windows Forms Data Binding in the .NET Framework 2.0, Part 1



  • I've read similiar articles which I've used to implement sorting and searching in my custom collections, but not much help in my quest though.  I did find some of the extras like null handling useful though so I thank you for providing the link. 

    I've started working on creating a custom DataGridViewColumn and DataGridViewCell that overrides the GetValue method of the cell and uses reflection to return the child properties value.  In my application, I won't have to worry about the runtime performance hit, since I'm expecting no more than a few hundred rows max.  I didn't have too much time to work on it today (too busy designing database schemas for inventory transactions).  I have it working by hardcoding the child property name in the Custom cell class.  I just need to figure out how to pass the child property name from the Column to the Cell and persist the prop value in the designer.  I found few articles that says thats the tricky part when deriving from DataGridViewColumn and DataGridViewCell.

    Once I get a little further along, I'll post my skeleton so we all can rip it apart. 



  • Here's the down and dirty way to do it with Reflection.  Sorry for the VB, it's my primary language. You just need to set the column type to ExtDataGridViewTextBoxColumn and the ChildPropertyName in the Data section.

    Custom DataGridViewCell Class:

    Imports System.ComponentModel
    Public Class ExtDataGridViewTextBoxCell
    Inherits System.Windows.Forms.DataGridViewTextBoxCell

    Private _childPropertyName As String

    Public Sub New()
    MyBase.New()
    Me._childPropertyName = ""
    End Sub

    Public Property ChildPropertyName() As String
    Get
    Return Me._childPropertyName
    End Get
    Set(ByVal value As String)
    Me._childPropertyName = value
    End Set
    End Property

    ' Overrides the Clone Method to add custom members
    Public Overrides Function Clone() As Object
    Dim myCell As ExtDataGridViewTextBoxCell = CType(MyBase.Clone, ExtDataGridViewTextBoxCell)
    myCell._childPropertyName = Me._childPropertyName
    Return myCell
    End Function

    ' Override the GetValue Method and us reflection to retrieve the
    ' child prop value
    Protected Overrides Function GetValue(ByVal rowIndex As Integer) As Object
    Return GetChildValue(MyBase.GetValue(rowIndex))
    End Function

    ' Get the child object property value,
    ' If the method fails, return an empty string
    Private Function GetChildValue(ByVal value As Object) As Object
    Try
    Dim myType As Type = value.GetType
    Dim myProps As PropertyDescriptorCollection = System.ComponentModel.TypeDescriptor.GetProperties(myType)
    Dim returnVal As Object = myProps(Me._childPropertyName).GetValue(value)

    Return returnVal
    Catch ex As Exception
    Return String.Empty
    End Try
    End Function
    End Class


    Custom DataGridViewColumn Class:
    Imports System.ComponentModel
    Public Class ExtDataGridViewTextBoxColumn
    Inherits System.Windows.Forms.DataGridViewColumn

    Private _childPropertyName As String

    Public Sub New()
    MyBase.New(New ExtDataGridViewTextBoxCell)
    End Sub

    <Category("Data")> _
    Public Property ChildPropertyName() As String
    Get
    Return Me._childPropertyName
    End Get
    Set(ByVal value As String)
    Me._childPropertyName = value

    ' Pass the new Child Property Name down to the Custom Cell
    Dim myTemplate As ExtDataGridViewTextBoxCell = CType(MyBase.CellTemplate, ExtDataGridViewTextBoxCell)
    myTemplate.ChildPropertyName = Me._childPropertyName
    MyBase.CellTemplate = myTemplate
    End Set
    End Property

    ' Overrides the Clone Method to add custom members.
    Public Overrides Function Clone() As Object
    Dim myCol As ExtDataGridViewTextBoxColumn = CType(MyBase.Clone, ExtDataGridViewTextBoxColumn)
    myCol._childPropertyName = Me._childPropertyName
    Return myCol
    End Function
    End Class


    There are definitely some issues with this.  Since I'm not overriding the SetValue method, editing the cell will probably blow up.  For what I need, I'm only concerned with read-only access so I'll probably just override the read-only property to always be true.  It probably wouldn't be too difficult to extend it to handle variable number of levels either.  If anyone has suggestions to improve it, I'd be interested hearing them. 



  • Had to finally join to the Daily WTF just to post my solution to this problem.

    I'm working on a school project at the moment and just finishing up on a course in .NET .

    I'm mostly focused on Java and just learning .NET and C# so go easy on me :)



    I came across this same problem and before reading about your solution on the matter I just came up with this:

        void dgvProducts_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) {
          if (e.ColumnIndex == dgvProducts.Columns["Productname"].Index) {
            Product product = e.Value as Product;
            e.Value = Product.ProductName;
          }
        }
    

    What it does is it handles the CellFormatting event of a DataGridViewCell, and based on that gets the child object value.

    Now I looked at the performance impact this will cause, since this event is raised everytime a cell is drawn or on a mouseover. There could be another (less costly) event to bind to but I just came up with this .. Then again, even though the event is raised needlessly, it doesn't seem to cause a big impact (tried DateTime.Now.Ticks before and after and didn't get anything but 0).

    This might be a Real WTF Solution, but hey, it works for me. Also this has the same problem of being limited to a readonly datagridview.


Log in to reply