Hi,
I'm looking to bind an ultragrid to a dynamic datasource. The source of my data is found in a list of "prices" objects, which have Properties holding the numbers I want displayed.
It would work perfectly to create an object MyDataRow which would encapsulate some of the "prices" objects, and expose one property for each column.
That way I could use a BindingList of MyDataRow objects, and each column would be nicely bound to the UltraGrid, with a proper INotifyPropertyChanged implementation to insure the grid refreshes when my underying "prices" change.
However, one of my constraints is that I need to dynamically add columns at run time. That seems to be a problem.
Indeed, since on this architecture a column is necessarily a Property of the MyDataRow object, I cannot dynamically modify the class to add Properties, and then fill all the existing instances...
How can I get around this problem, and properly have BOTH Databinding to object properties AND dynamic column creation in an ultragrid?
I'm not entirely sure I understand what you are trying to do.How do you intend to make your prices object vary it's own properties? Even without considering the grid or data binding, there is no way to do this. So how do you intend to define your data structure and add properties to it?
So I don't see any way to do this using a custom-defined object like a "Prices" object that you create yourself.
It sounds to me like what you need is to use some other data source which has the ability to dynamically add and remove columns. A DataTable/DataSet has this ability, as does the UltraDataSource component.
Actually.... what I said above is not really true. There is a way to have an object that dynmically adds and removes bound columns to itself. Obviously there is, since Datatable, DataSet, and UltraDataSource do it. They way they do this is by implementing IBindingList and ITypedList. But it doesn't make a lot of sense for you to re-invent the wheel here when there are perfectly good objects already in place that will do this for you.
My advice would be to use an UltraDataSource, since it seems like you have a pretty simple set of data here without any hierarchy and without dynamically-changing relationships.
Hi Mike,
My prices objects will not update their properties themselves, they are living inside a big dictionary, and are updated by other classes browing this dictionary and modifying them independantly.
Therefore the Data grid is a read-only display of those prices, but it needs to manage databinding, so that when a price moves (by the action of an outside class), the grid displays instantly the new values.
To be clear, here is an example of what it may look like:
A "Price" class has a "Value" property of type "double", and I have a List<Price> living somewhere, holding all my prices.
Then I have a "MyDataRow" object, which has several properties of type "Price". like "MidPrice", "HiPrice", and "LoPrice" for example. MyDataRow also needs to implement INotifyPropertyChanged, to support databinding.
As I fill the MyDataRow objects, I take Price objects from my List of Prices.
I then place the MyDataRow objects in a BindingList<MyDataRow>, and use it as the datasource for the ultragrid.
This will display a column for each Property of the MyDataRow object: a MidPrice Column, a HiPrice column, and a LoPrice column. And if MyDataRow correctly propagates the NotifyPropertyChanged events caught from its Price objects, they will be correctly refreshed on the grid.
The problem with this very standard solution, is that I cannot add a new column at run time (let's say "AveragePrice"), and fill it with new prices objects, that will be displayed in the grid. A new column requires a new property of the MyDataRow object, which I obviously can't create in the class at run time, let alone populate its value for all the existing instances of MyDataRow already in the grid.
The alternative, as you mention, is to use a DataTable, or an UltraDataSource, but my understanding is that those sources only allow me to place simple type objects (double, string, etc..) in them, and therefore I can't have my "Price" objects live in them. And even if I choose to have columns of type "system.object", all they will do to display my "Price", is call the "ToString()" method, but they will not display the "Value" property, nor will they manage databinding to display a new value when the prices change.
In sum, it looks like BindingSource will allow a great databinding with my underlying objects but won't be flexible enough to add columns on the fly at runtime;
whereas the DataTable/UltraDataSource would be flexible and would easily add columns, but won't allow proper databinding with my Price objects.
Is my dilemma clearer in those terms?
Do you see a way out of my dilemma?
Guillaume Bignon said:my understanding is that those sources only allow me to place simple type objects (double, string, etc..) in them, and therefore I can't have my "Price" objects live in them.
That is not correct. Both the DataTable and the UltraDataSource can have columns of any DataType. There is no such limitation to simple types.
Guillaume Bignon said:And even if I choose to have columns of type "system.object", all they will do to display my "Price", is call the "ToString()" method, but they will not display the "Value" property
The DataType of the column need not be object, it can be Price. In such a case, the grid will call ToString on the Price object in order to display it. But this is not a big problem. All you have to do is override the ToString method on the Price class so that it returns the Value.
Or... if that is not acceptable, you could handle this in the grid using a DataFilter to translate the Price object into a string yourself.
Guillaume Bignon said:nor will they manage databinding to display a new value when the prices change.
This is a bit tricky. I'm not sure what will happen if you update a property of an object which is the Value of a cell in the grid. I don't know if the grid will get a notification in such a case. It might work, as long as you implement INotifyPropertyChanged correctly. Or, it might now work, since the actual Value of the cell is not changing, only a property on that value.
But if if does not work automatically, it would still be pretty easy to handle. You just have to manually notify the grid that the data has changed and it needs to update it's display. You do this by calling:
grid.Rows.Refresh(ReloadData);
Thank you for your feedback. I tried to place the Price objects in the datatable, and indeed they live there perfectly fine. I overrode the ToString method, and the value is displayed correctly.
However, as you suspected, the triggered PropertyChanged event doesn't provoke the refresh of the UltraGrid. As you anticipated, the UltraGrid thus needs to receive a direct call to Rows.Refresh, but then I'm losing all the benefits of databinding, since I'm no longer doing inversion of control, right? That means that any process which comes and modifies the underlying data, needs to know that this data is displayed, and on which grid it is displayed, to directly come and tell that grid that it needs to refresh.
This price is too high to pay for my design. Was that the backup plan you had in mind?
Thanks.
Alright, thank you very much Mike. I'm not yet sure what will fit my needs best, but I think my options are pretty clear by now.
Thank you.
Well, you don't lose all the benefits of DataBinding. In point of fact, DataBinding itself isn't really the most efficient way to do things. The main benefit of it is that it's the easiest way to set up a control to display data from a data source. It requires the least amount of code, but it's never been the best of most efficient way to do it.
You still gain all the usual benefit for every other field in the data (assuming that you have other fields that are of simpler types). And you still gain the initial loading of the grid without having to write code.
Anyway, I don't see any better alternative here. The problem is that the BindingManager and the INotifyPropertyChanged interface notify the data source when the value of a field in the DataSource has changed. And in this case, the value in the field is not changing - but rather a property on that value is changing.
You could, of course, implement INotifyPropertyChange on the Price object and hook that notification on your custom DataRow, and then have the DataRow implement INotifyPropertyChanged and trigger that notification any time any property on the Price object changes. I'm still not sure this will work, but it might be worth a shot.
If that does not work, then you could create your own DataSource and implement IBindingList yourself, rather than using BindingList<T>. That way you have complete control of the IBindingList notifications and you can make sure that the ListChaned notification is called when a property on a Price object changes.