Really complex databinding & 3rd party grids

Posts   
 
    
omar avatar
omar
User
Posts: 569
Joined: 15-Oct-2004
# Posted on: 05-Nov-2004 11:54:13   

Greetings,

I am refering here to Fran's article about implementing ITypedList for weekly typed collections. (article here http://weblogs.asp.net/fbouma/articles/115837.aspx)

The custom collection seems to work just fin with Ms Grid (I even tested it with 3 level nested collection and it works beatifully where even when a child collections has no rows we would still see the grid displaying names of the columns smile )

The problem seems to be with 3rd party grids. I have tried both Janus and DevExpress and both seem to have the same problem. For a ComplexCollection Customers that has a child complex collection of orders, the grids would display the child collection as a column with the name Instead of displaying the child collection's data type (ComplexDataBinding.MyCollection) as the data in the column. (with Janus I had a better behaviour when I used RetrieveStructure after assigning the DataSource; I could see the expansion button and clicked on it to see the child collection but this seems to show ONE level of child collections while with Ms-Grid binded to the same DataSource I could go 4 level deep of child collections)

I was hoping to see if Frans or anyone else in this forum has succesfully implemented custom ITypedList collections that correctly displayed editable collections (with child collections that could have child collections ...) in these 3rd patry grids

OMAR

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 05-Nov-2004 13:40:58   

I implemented 2 types of ITypedList in the entity collections (one for selfservicing and one for adapter).

RetrieveStructure() or similar methods in other grids, do retrieve the hierarchy correctly with these implementations, but it's a lot of code to get that right. The main problem: what is the type of the inner collection's objects? Sometimes you need a new instance of the object to get the property descriptors to return to the grid.

Did you try to bind an EntityCollection to the janusgrid and did it work then?

Frans Bouma | Lead developer LLBLGen Pro
omar avatar
omar
User
Posts: 569
Joined: 15-Oct-2004
# Posted on: 05-Nov-2004 18:32:22   

Did you try to bind an EntityCollection to the janusgrid and did it work then?

the question here is that can we use (EntityCollection) as a bindable general purpose weakly typed collection (as an alternative to building our own ). I am not talking here about collections of entities but rather collections of user-defined types that need to support complex binding to vanilla grid controls. I guess an example would be really helpfull:

 Public Class MyData
    Implements IComparable

    Private _id As Integer = 0
    Private _firstName As String = Nothing
    Private _lastName As String = Nothing
    Private _myChildren As MyCollection = Nothing
    Private _DontBindThis As String = "OMAR"

    Public Sub New()
    End Sub

    Public Property ID() As Integer
        Get
            Return _id
        End Get
        Set(ByVal Value As Integer)
            _id = Value
        End Set
    End Property

    Public Property FirstName() As String
        Get
            Return _firstName
        End Get
        Set(ByVal Value As String)
            _firstName = Value
        End Set
    End Property

    Public Property LastName() As String
        Get
            Return _lastName
        End Get
        Set(ByVal Value As String)
            _lastName = Value
        End Set
    End Property

    <TypeContained(GetType(MyData))> _
    Public Property MyChildren() As MyCollection
        Get
            Return _myChildren
        End Get
        Set(ByVal Value As MyCollection)
            _myChildren = Value
        End Set
    End Property


    'just for the fun of it...
    <HiddenForDataBinding(True)> _
    Public Property DontBindThis() As String
        Get
            Return _DontBindThis
        End Get
        Set(ByVal Value As String)
            _DontBindThis = Value
        End Set
    End Property

    Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
        Dim aMyData As MyData = CType(obj, MyData)
        If aMyData Is Nothing Then
            Return -1
        End If
        Return Me.ID.CompareTo(aMyData.ID)
    End Function
End Class

What I want to have is a collection of MyData called (MyCollection). Note that one of MyData's properties is also of type MyCollection. What I'm hoping for is to display MyCollection in a grid and (MyChildren) would NOT appear as a column but appear as related sub-collection (This example uses guidelines from Frans's article about "weakly typed collections supporting ITypedList" and works just fine with Ms-Grid). summary: How can I implement (MyCollection) using an EntityCollection and then bind it to a DevExpress grid ?

OMAR

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 06-Nov-2004 11:27:21   

That would be pretty hard I think (using EntityCollection as a base class). I'm a little puzzled why the system I wrote about in the article doesn't work in the DevExpress grid, as the devexpress grid SHOULD use ITypedList to retrieve the properties.

I therefore think they do something else: they read instances and reflect the properties from those instances. Which is bad, as it will load the complete database into memory with SelfServicing code... and also unnecessary, as the mechanism to do it is via ITypedlist. However on the other hand... the DataView (used to bind a datatable to a grid) uses also ITypedList to produce the columns so I'm a bit unsure why it doesn't work with the DevExpress grid.

The EntityCollection class internally can produce property descriptors for every property of an entity in the hierarchy hold by the objects inside the entitycollection (virtually or existing). Part of the trick is the type descriptor attributes on the properties in the entities which hold collections. So Customer.Orders has an attribute which describes the type contained in Orders, OrderEntity. This way it entitycollection can determine what properties it has to produce if it is bound to a grid and the Orders collection of a Customer is clicked open, even if it is empty.

Frans Bouma | Lead developer LLBLGen Pro
omar avatar
omar
User
Posts: 569
Joined: 15-Oct-2004
# Posted on: 08-Nov-2004 20:27:18   

I have been fiddling around with DevExpress and Janus grids to get them to behave in similar fashion as Ms-Grid when complex-binding to your example of custom collection. With DevExpress: I managed to get it to work after doing the following:

1- extended the weekly typed collection to a strongly typed one that implemented its own ITEM property returning an object of the type contianed in the collection

2- implemented DevExpress.Data.IXtraList interface. It seems that DevExpress needs this interface for master-detail data if its NOT coming from a data set

the code looks like the follwing:


         Dim dataSource As SmartCollection = FakeData.GetData()

         Dim gridView2 As GridView = New GridView(GridControl1)
        GridControl1.LevelDefaults.Add("MyChildren 1", gridView2)
        gridView2.OptionsView.ShowGroupPanel = False
        gridView2.Columns.Add("ID").VisibleIndex = 0
        gridView2.Columns.Add("FirstName").VisibleIndex = 1
        gridView2.Columns.Add("LastName").VisibleIndex = 2

        Dim gridView3 As GridView = New GridView(GridControl1)
        GridControl1.LevelDefaults.Add("MyChildren 2", gridView3)
        gridView3.OptionsView.ShowGroupPanel = False
        gridView3.Columns.Add("ID").VisibleIndex = 0
        gridView3.Columns.Add("FirstName").VisibleIndex = 1
        gridView3.Columns.Add("LastName").VisibleIndex = 2

        Dim gridView4 As GridView = New GridView(GridControl1)
        GridControl1.LevelDefaults.Add("MyChildren 3", gridView4)
        gridView4.OptionsView.ShowGroupPanel = False
        gridView4.Columns.Add("ID").VisibleIndex = 0
        gridView4.Columns.Add("FirstName").VisibleIndex = 1
        gridView4.Columns.Add("LastName").VisibleIndex = 2

        Dim gridView5 As GridView = New GridView(GridControl1)
        GridControl1.LevelDefaults.Add("MyChildren 4", gridView5)
        gridView5.OptionsView.ShowGroupPanel = False
        gridView5.Columns.Add("ID").VisibleIndex = 0
        gridView5.Columns.Add("FirstName").VisibleIndex = 1
        gridView5.Columns.Add("LastName").VisibleIndex = 2

        GridControl1.DataSource = dataSource
        With DirectCast(GridControl1.MainView, GridView)
            .Columns.Clear()
            .Columns.Add("ID").VisibleIndex = 0
            .Columns.Add("FirstName").VisibleIndex = 1
            .Columns.Add("LastName").VisibleIndex = 2
        End With

it is really a pitty that Ms-Grid would give the same behaviour by just one line of code

 dataGrid1.DataSource = dataSource

you note also that for each detail level's view I had to actually specify my columns. If I don't then an extra column would appear showing the child collection's type. In addition, my derived collection (SmartCollection) extends (MyCollection) as follows:

 Imports DevExpress.Data

Public Class SmartCollection
    Inherits MyCollection
    Implements IRelationList


    Public Sub New(ByVal containedType As Type)
        MyBase.New(containedType)
    End Sub

    Public Sub New(ByVal c As ICollection, ByVal containedType As Type)
        MyBase.New(c, containedType)
    End Sub

    Public Sub New(ByVal capacity As Integer, ByVal containedType As Type)
        MyBase.New(capacity, containedType)
    End Sub

    Default Public Overridable Shadows Property Item(ByVal index As Integer) As MyData
        Get
            Return CType(MyBase.Item(index), MyData)
        End Get
        Set(ByVal Value As MyData)
            MyBase.Item(index) = Value
        End Set
    End Property

    Private m_relationName As String

    Public Property RelationName() As String
        Get
            Return m_relationName
        End Get
        Set(ByVal Value As String)
            m_relationName = Value
        End Set
    End Property

    Public Function GetRelationName(ByVal index As Integer, ByVal relationIndex As Integer) As String Implements DevExpress.Data.IRelationList.GetRelationName
        Return m_relationName
    End Function

    Public Function GetDetailList(ByVal index As Integer, ByVal relationIndex As Integer) As System.Collections.IList Implements DevExpress.Data.IRelationList.GetDetailList
        Return Me.Item(index).MyChildren
    End Function

    Public Function IsMasterRowEmpty(ByVal index As Integer, ByVal relationIndex As Integer) As Boolean Implements DevExpress.Data.IRelationList.IsMasterRowEmpty
        Return False
    End Function

    Public ReadOnly Property RelationCount() As Integer Implements DevExpress.Data.IRelationList.RelationCount
        Get
            Return 1
        End Get
    End Property
End Class

and my fake data is as follows:

     Public Shared Function GetData() As SmartCollection
        Dim collection As SmartCollection = New SmartCollection(GetType(MyData))
        collection.RelationName = "MyChildren 1"

        Dim i As Integer = 0
        While i < 10
            Dim iCollection As SmartCollection = New SmartCollection(GetType(MyData))
            iCollection.RelationName = "MyChildren 2"

            Dim ii As MyData = New MyData
            ii.ID = i
            ii.FirstName = "aaa" + i.ToString()
            ii.LastName = "bbb" + i.ToString()
            ii.MyChildren = iCollection
            collection.Add(ii)
            Dim j As Integer = 0
            While j < 10
                Dim jCollection As SmartCollection = New SmartCollection(GetType(MyData))
                jCollection.RelationName = "MyChildren 3"

                Dim jj As MyData = New MyData
                jj.ID = j
                jj.FirstName = "ccc" + j.ToString()
                jj.LastName = "ddd" + j.ToString()
                jj.MyChildren = jCollection
                iCollection.Add(jj)
                Dim k As Integer = 0
                While k < 10
                    Dim kCollection As SmartCollection = New SmartCollection(GetType(MyData))
                    kCollection.RelationName = "MyChildren 4"

                    Dim kk As MyData = New MyData
                    kk.ID = k
                    kk.FirstName = "eee" + k.ToString()
                    kk.LastName = "fff" + k.ToString()
                    jCollection.Add(kk)
                    k += 1
                End While
                j += 1
            End While
            i += 1
        End While
        Return collection
    End Function

you note also that I had to explicitly define a name for each detail relation. This is important for XtraGrid because it uses a hash table for (relationName, gridView). This way the grid formats the detail view based on the relation name.

It seems like alot of work and I started thinking maybe its just easier to dump evrything in a dataset to make life easier..

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 09-Nov-2004 10:26:27   

omar wrote:

I have been fiddling around with DevExpress and Janus grids to get them to behave in similar fashion as Ms-Grid when complex-binding to your example of custom collection. With DevExpress: I managed to get it to work after doing the following:

1- extended the weekly typed collection to a strongly typed one that implemented its own ITEM property returning an object of the type contianed in the collection

2- implemented DevExpress.Data.IXtraList interface. It seems that DevExpress needs this interface for master-detail data if its NOT coming from a data set

You're kidding, right? frowning Microsoft designed ITypedList, IListSource and other misery interfaces for this job. Ok, poorly documented and often wrongly implemented, but nevertheless, DevExpress is now making life really hard for developers who don't use datasets but custom classes...

it is really a pitty that Ms-Grid would give the same behaviour by just one line of code

 dataGrid1.DataSource = dataSource

you note also that for each detail level's view I had to actually specify my columns. If I don't then an extra column would appear showing the child collection's type.

Normally, the ITypedList is for this: an implementation of that interface has to produce property descriptors for the type contained in the type specified by the caller. So the collection bound to the grid has objects of type T, these objects have collections with objects of type A, and these objects have collections with objects of type C. The grid will always ask the bound collection to produce the property descriptors for any level in the grid, no matter what. So the bound collection for example has to produce the property descriptors for the objects of type C 3 levels deep. This is not that hard with an instance present in that collection, just perform some reflection, but without an instance it is hard. Nevertheless, the ITypedList implementation makes sure this is taken care of and produces the property descriptors to the grid, which uses them to produce columns (or match the column definitions with the retrieved properties).

Because MS did such a lousy job describing these interfaces (there are 2 articles on ITypedList, one by an ORM.NET developer and one by myself, a real shame) it takes hard work to get this right, but it has to be done right, especially in grid controls, otherwise standard-obeying implementations will fail in a databinding scenario with these controls.

you note also that I had to explicitly define a name for each detail relation. This is important for XtraGrid because it uses a hash table for (relationName, gridView). This way the grid formats the detail view based on the relation name.

It seems like alot of work and I started thinking maybe its just easier to dump evrything in a dataset to make life easier..

Hmm. My question to you is: do the EntityCollection objects have the same problem? (I have access to the DevExpress grids, I haven't installed them though, as I don't use DevExpress stuff)

Frans Bouma | Lead developer LLBLGen Pro
jtgooding
User
Posts: 126
Joined: 26-Apr-2004
# Posted on: 09-Nov-2004 19:37:30   

The IXtraList interface mentioned isn't meant to be a work around for ITypedList, it is part of their ORM tool, which in my opinion is totally lacking, I beta tested it through its first iterations and it had several key limitations and no code generation at all, its come further but is still missing what I consider basic functionality.

The IXtraList does have a lot more power than the ITypedList interface but isn't native to other objects, so in most cases its not a good answer, I have considered using it, and may if the current limitation isn't removed as they have promised.

DevExpress has a couple of issues with ITypedList embeded within an ITypedList, this issue is to be addressed in the next release, I will give them a poke and remind them of it, in the interim I have written a couple small helper classes to 'hide' collections within collections, and when I do need master/detail I explicitly define my detail records without using IXtraList interface.

Overall I like the DevExpress controls more than any other set, it's just a minor inconvienence for me at the moment.

John

jtgooding
User
Posts: 126
Joined: 26-Apr-2004
# Posted on: 09-Nov-2004 21:01:08   

Wanted to follow-up one thing on my last post the IXtraList, IXtraSortable, IRelationList, IXtraRowFilter, IXtraListNotify interfaces are all add-ons to the Microsoft interfaces effectively.

For example the IXtraList interface requires your objects to be IEditableObject and IBindingList, the advantage of using them with the Developer Express controls yeilds simpler coding and higher performance if you are using the Developer Express controls.

As an example column sorting an IBindingList set of 10000 rows takes about 600 ticks, using an IXtraList takes 103 ticks, sorting the same IBindingList in Janus takes 1453 ticks. This is due to IXtraList exposing indexes to your data to the grid, implementing this is just a few lines of code to add on to your object.

These interfaces are extensively used by their ORM tool, but aren't exclusive to it, as you can incorporate them into any of your objects and gain the benefits as long as you use Developer Express controls (the catch).

John

jtgooding
User
Posts: 126
Joined: 26-Apr-2004
# Posted on: 09-Nov-2004 21:29:15   

Omar,

I cut out two of my helper functions you might like:

        private void CleanColumns(DevExpress.XtraGrid.Views.Grid.GridView gridView)
        {
            for(int i = gridView.Columns.Count - 1; i >= 0; i--) 
            {
                GridColumn column = gridView.Columns[i];
                if(column.ColumnType.GetInterface("IList") != null)
                    column.Dispose();
            }

        }

        private void CleanGrid(DevExpress.XtraGrid.GridControl grid)
        {
            foreach(DevExpress.XtraGrid.Views.Grid.GridView gridView in grid.Views)
            {
                CleanColumns(gridView);
            }
        }

that should change your setup to: gridControl1.Datasource = dataSource; CleanGrid(gridControl1);

I have some other helpers I've written to use the DevEx property bags to bind/init controls in standard ways to LLBLGen, such as setting text boxes to max length, read only, masks etc from the LLBLGen settings.

John

omar avatar
omar
User
Posts: 569
Joined: 15-Oct-2004
# Posted on: 09-Nov-2004 23:32:12   

Farns, No.. I didn't try XtraGird with EntityCollection yet. I will keep you updated how things will work out as the project progresses. I think Jhon here could also shed a light on his experience with using XtraGrid and EntityCollections

John, Thanks for the post and thank you for the helper functions. Now my grid is displaying the Master-Detail relation with just the two lines simple_smile

 gridControl1.Datasource = dataSource
CleanGrid(gridControl1)

the only drawback is that although the master view does NOT show a column for the child collection, the detail views still show that column!! maybe you could explain what you ment when you said

..I have written a couple small helper classes to 'hide' collections within collections, and when I do need master/detail I explicitly define my detail records without using IXtraList interface.

Are you managing to do this in a generic way or are you doing it in the UI (like handeling the GridView1_MasterRowGetChildList event) ? I also would appreciate any other helper functions you have for DevExpress controls since I am starting my first LLBL project and all the UI will be using DevExpress controls.

I am happy that someone is nagging DevEx about this issue and I do hope they come through in the coming release. BTW, did you use their XtraReport with any LLBL projects. I am interested in hearing any field stories about this report writer specially in relation to LLBL.

again.. thank you the help

OMAR

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 10-Nov-2004 10:57:18   

Great stuff, John, and thanks for the explanation simple_smile

Personally I can understand why DevExpress and other grids didn't implement ITypedList support as good as they should have, as they didn't have to, DataSets worked, so why bother, and Microsoft didn't (and doesn't) do a good job explaining how to do it properly. After all the time I spend on the ITypedList crap, I still don't think I have it implemented properly, but there is NO example, NO documentation and NO help from Microsoft on this. I even mailed MS for documentation, examples ANYTHING, "I'll forward it to the right person", 1 or 2 emails followed, no examples, no docs, nothing. And I'm even an MVP, which seem to get a response faster than others (why is beyond me, but hey, if it helps wink ).

In .NET 2.0 this is completely rewritten, they will introduce proper interfaces now, proper documentation and it should work much more smoothly also for custom classes. When Microsoft re-implements something from the ground up, it's a sign the original wasn't that great (read: broken beyond repair).

What I'll try again, is to check if I have implemented correctly, using a different approach, the type descriptor. I ingored that because it is not documented that well (as in: not documented with documentation that you can use to get started) but there should be a way to make this work properly as the DataView uses the same mechanism (ok, and some weird manager classes under the hood which are COMPLETELY not documented, but seem to be very important in the process...) and which should make the properties which shouldn't show up in an ITypedList scenario also really hidden, for example in webgrids (which completely ignore these apparently)...

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 10-Nov-2004 12:01:55   

Whoa simple_smile I did some fast hacking on ICustomTypeDescriptor on an adapter entity and it really works! simple_smile I can now properly hide the properties which SHOULD be hidden when binding to a webgrid.

I'll move this code into the runtime lib soon. I don't think this will solve the databinding-in-design-time problem though, as that uses obscure methods no-one knows (and Microsoft is refusing to tell me) but which crash the code.

The ICustomTypeDescriptor implementation is a helpful tool I think for using the code in a databinding scenario.

Frans Bouma | Lead developer LLBLGen Pro
omar avatar
omar
User
Posts: 569
Joined: 15-Oct-2004
# Posted on: 10-Nov-2004 13:24:18   

Hi Frans,

can you please explain how this interface could help in a situation like the example I've show above...

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39826
Joined: 17-Aug-2003
# Posted on: 10-Nov-2004 14:01:53   

I'm not sure, but it returns the property descriptors of an object at runtime, so instead of using reflection, the control will consult ICustomTypeDescriptor, which can then return the proper property descriptors for the grid to use, which allows you to hide properties you don't want to see.

Frans Bouma | Lead developer LLBLGen Pro
jtgooding
User
Posts: 126
Joined: 26-Apr-2004
# Posted on: 10-Nov-2004 15:12:33   

Omar,

I meant IRelationList not IXtraList, I use IXtraList as the performance gains are huge over other grids and methods, the code to hide is pretty close to the code that I provided above, I stripped out some stuff that changes behavior to the way we want things to always behave.

I use the Event model for sub-detail data instead of the IRelationList way of doing things to give me greater control over the process, how to do it is in the help file under Master-Detail using events; it's a little more coding but I always get what I want out of it, and it gives me full control over data retrieval, much like adapter vs self servicing.

In your case are you saying the sub-detail rows are still showing the collections within collections?

If this is the case it means that the sub-detail rows are being populated on demand instead of preloading, to resolve this you will need to put my CleanView method inside one of the grids events that trigger when the sub-detail row is expanded for display. This should 'Clean' the grid dynamically on demand, even if you swap views or repopulate data etc.

I forget what the event is, I will try and look it up later after my morning meetings, if you find it post it here for me too, I am going to run a performance comparison between the two eventually.

John