- Home
- General
- General Chat
Really complex databinding & 3rd party grids
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 )
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
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?
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
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.
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..
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? 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)
Joined: 26-Apr-2004
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
Joined: 26-Apr-2004
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
Joined: 26-Apr-2004
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
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
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
Great stuff, John, and thanks for the explanation
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 ).
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)...
Whoa I did some fast hacking on ICustomTypeDescriptor on an adapter entity and it really works!
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.
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.
Joined: 26-Apr-2004
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