Assuming that you use (or develop) a component that has a strongly typed Items collection, such as an ObservableCollection<ComponentItem> where ComponentItem class is also provided by the library, you (or your customers) may need a way to sync other types of collections too, automatically. For example, the items source may be an ObservableCollection<CustomItem> or even a simple IEnumerable received from the business layer or from another component, optionally with a dynamic approach.
With the built-in components of the .NET Framework (and related platforms, such as Silverlight™ and WinRT) Microsoft has proposed to handle this type of syncing by having an easy to use ItemsSource property that would accept any object as the data source (backed up by some *MemberPath properties to identify specific feature properties – e.g. DisplayMemberPath of a ComboBox would indicate the property to use for displaying a value, while ValueMemberPath would indicate the actual value to consider for that displayed string), and actually sync everything internally using reflection.
But unfortunately this is neither always an option nor a good solution in certain cases since the component has been designed without intention to support external data binding originally, or supports special item types only in order to ensure optimal built-in performance by avoiding reflection; in such cases the developer is required to populate a strongly typed Items collection instead.
At DlhSoft, we ran into this exact situation when we designed the GanttChartDataGrid control from Gantt Chart Light Library product. This new control needed to provide higher performance, even by trading off the built-in data binding support, since the older product component (GanttChartTaskListView from Gantt Chart Library) has previously had issues when large collections of data needed to be managed (because it always enforced data binding).
Eventually, however, customers started to like this newer product more also because of other aspects – it was the first to support Silverlight™ and it provided more flexible options and higher performance for some other internal features, such as allowing circular dependencies if auto-syncing was not required, and using partially heuristic algorithms for leveling resources.
This eventually leaded us to back to a custom collection data binding request which we needed to address without losing the existing benefits. By design we allowed developers to set up GanttChartItem objects only (or inheriting from that type) to the GanttChartDataGrid.Items collection and that improved performance in default case, but customers often have already had the data in a different – but compatible – format, and didn’t require the best performance if their data set was not very large.
For example, our GanttChartItem object requires Start and Finish property values of type DateTime, but some customers may have a CustomTaskItem defined with Start and End properties instead or a compatible custom Date type (DlhSoft component library also provides a Date type that is compatible with DateTime but ensures time of day is always TimeSpan.Zero for its values).
In this case, customers needed to actually sync their CustomTaskItem collection to our Items collection of GanttChartItem objects, and back. For a few properties this could be handled fairly easy, but if there more and multiple levels of sub-items involved too, such as if there was a need to also sync CustomTaskItem.Dependencies to GanttChartItem.Predecessors sub-collection, it required a lot of time and attention.
However, provided that the Items collection property has a public setter, one would be able to write a custom Converter class to sync everything from the data source layer to the component item collection type and back. The expected XAML to bind the Items collection this way would the be almost as simple as using a Binding on an ItemsSource property:
To continue our side bar story, to address the external data binding requirement for GanttChartDataGrid control without affecting the built-in design of the strongly typed Items collection, we headed to offer a TaskItemsConverter class that could be easily used, like this:
StartMember=”StartDate” FinishMember=”FinishDate” […]/>
(Note that if your Items collection doesn’t have a public setter this won’t work just like that, but you could still use of a workaround bridge that would clear and re-add items to the output collection in custom code whenever a middle layer collection changes; the middle collection may be hosted by a custom attached dependency property to support standard binding operations, and then you could bind it to the actual data context using the custom Converter instead.)
ComponentItemsConverter class may be designed in two ways (and could be developed either by the component’s author or by the application developer that uses that component):
- With *Member fields to indicate the custom item properties to refer to for specific features using reflection, in order to support any data source item type (with different properties, even partially missing);
- Directly targeting the data source item type, if it is known at design time, to avoid reflection and improve performance.
In the reminder of this article we will focus for the first approach, since it is useful in many scenarios, and will provide a possible way to design and develop the custom converter. In this context, from a software designer perspective, ComponentItemsConverter object should be able to:
- Specify which custom item properties are to be mapped to different features of the component item type, allowing partial mapping when a feature is optional;
- Also address multiple levels of sub-items, if necessary;
- two-way syncing – full syncing from source to target and from target to source;
- one-way syncing – from source to target only;
- initial loading only – from source to target;
- initial loading and one way to source afterwards – from target to source.
The ComponentItemsConverter class could then be written following these requirements:
- Initially browse the data source context collection, and for each data source item create a target component item instance; browse the mapped member properties of the data source items and set the appropriate component item properties to those values (optionally further converted);
- When target component items change, update the source items accordingly;
- When the target component item collection changes, create or remove source items in the data source collection accordingly;
- When source component items change, update the target items accordingly;
- When the source component item collection changes, create or remove target items in the component item collection accordingly;
- For each sub-items collection (and recursively), handle a sub-conversion the same as for the main collection, either using a separate Converter instantiated for this purpose, or directly in the main converter;
- When possible, cache objects and conversion instances to optimize performance.
The source code of ComponentItemsConverter class is available for download from our OneDrive account.
The full C# source code for our TaskItemsConverter class that support converting custom task item collections to an ObservableCollection<GanttChartItem> accepted by GanttChartDataGrid.Items property is available for download from the link at the bottom of this DlhSoft Knowledge Base article.
If needed, you can extend or improve our default implementation by adding more supported properties injecting custom code to support them or removing reflection if your data item type is known at design time.
Of course, one could generalize our design one more time to support a variable number of synced features (such as if it is not known what kind of properties are to be synced, i.e. if the converter is not for a specific component’s Items collection), or to support extensions without updating the source code and use virtual methods in our class instead, but we let this as your responsibility. (Don’t hesitate to publish a comment pointing to your source code if you do so and would like to publicly share your output, tough – we and other developers will appreciate it; thank you in advance.)
This article is a DlhSoft contribution to the software development community and you may use all the source code referred by this article for your own needs for free, but we would appreciate indicating its source, when possible. Thank you for your interest!