Tip | We won't implement the searching or sorting capabilities of this interface in this book. If these features are important to your business collections, you can enhance this code to provide that support for your framework. We'll hard-code the methods for these features of the interface to indicate that the features aren't supported. |
Add a new class named BindableCollectionBase to the project. Since this will act as our base collection class, it needs to inherit from System.Collections.CollectionBase , and of course it needs to implement the IBindingList interface as well. This means that we'll declare the class with the following code:
using System; using System.Collections; using System.ComponentModel; namespace CSLA.Core { /// <summary> /// This is a base class that exposes an implementation /// of IBindableList that does nothing other than /// create a nonserialized version of the listchanged /// event. /// </summary> [Serializable] public abstract class BindableCollectionBase : CollectionBase, IBindingList { } }
When building a framework, it's always wise to think ahead to what future developers might want to do with it. Wherever possible, we should build in flexibility so that business developers can override or alter the behavior of our framework in meaningful ways. For instance, we can allow business developers to have their actual business collection objects control whether data binding can add, remove, or edit items in the collection. While we won't support those concepts by default, we'll add code in our framework so that business developers can support the features if they desire .
To do this, we'll expose a set of protected variables that can optionally be altered by our business classes. Add the highlighted lines to the BindableCollectionBase class:
[Serializable] public abstract class BindableCollectionBase : CollectionBase, IBindingList { protected bool AllowNew = false; protected bool AllowEdit = false; protected bool AllowRemove = false;
Since these values default to false , we're indicating that by default, these features aren't supported by business collections. Because they're protected in scope, a business developer can set their values to true in order to enable the features for a specific business collection object. This is ideal, because the business collection class will need to implement extra code to support the addition of new objects and may also want to do extra processing to properly support the editing or removal of child objects.
Next, we'll declare the ListChanged event to be NonSerialized :
[field: NonSerialized] public event ListChangedEventHandler ListChanged;
As with the BindableBase class, this event declaration creates an event that is safe for use with serialization. Once again, we also need to provide a way for our business collections to raise this event, so we create a protected method:
virtual protected void OnListChanged(ListChangedEventArgs e) { if (ListChanged != null) ListChanged(this, e); }
This method can be called to raise the ListChanged event by any code that inherits from our base class. Much of the remaining code in the class, then, provides a very simple implementation of the IBindingList interface:
void IBindingList.AddIndex(PropertyDescriptor property) { } object IBindingList.AddNew() { if(AllowNew) return OnAddNew(); else throw new InvalidOperationException("Adding items not allowed"); } void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction) { } int IBindingList.Find(PropertyDescriptor property, object key) { return 0; } void IBindingList.RemoveIndex(PropertyDescriptor property) { } void IBindingList.RemoveSort() { } bool IBindingList.AllowEdit { get { return AllowEdit; } } bool IBindingList.AllowNew { get { return AllowNew; } } bool IBindingList.AllowRemove { get { return AllowRemove; } } bool IBindingList.IsSorted { get { return false; } } ListSortDirection IBindingList.SortDirection { get { return ListSortDirection.Ascending; } } PropertyDescriptor IBindingList.SortProperty { get { return null; } } bool IBindingList.SupportsChangeNotification { get { return true; } } bool IBindingList.SupportsSearching { get { return false; } } bool IBindingList.SupportsSorting { get { return false; } } virtual protected object OnAddNew() { return null; } } }
Since we're not supporting searching or sorting, this code provides no real functionality ”it merely returns simple values or performs no operation. Even so, an important element of the code here is the implementation of the OnAddNew() method:
virtual protected object OnAddNew() { return null; }
This function is called if data binding tries to add a new object to the collection. It's up to our business implementation to override this OnAddNew() method with a version that actually does return a new, valid object.
Because the AllowNew variable defaults to false , our default behavior is to not support adding new items to the collection. This is enforced in our implementation of IBindingList.AddNew .
Note | If the business developer changes the AllowNew value to true in his business collection class, then he must provide an implementation of the OnAddNew() method. |
Finally, the BindableCollectionBase class needs to override some methods from CollectionBase itself. When an item is inserted, removed, or changed, or the collection is cleared, a method is run that we can override. In each of these cases, we know that the content of the collection has changed, so we want to raise the ListChanged event:
override protected void OnInsertComplete(int index, object value) { OnListChanged( new ListChangedEventArgs(ListChangedType.ItemAdded, index)); } override protected void OnClearComplete() { OnListChanged( new ListChangedEventArgs(ListChangedType.Reset, 0)); } override protected void OnRemoveComplete(int index, object value) { OnListChanged( new ListChangedEventArgs(ListChangedType.ItemDeleted, index)); } override protected void OnSetComplete(int index, object oldValue, object newValue) { OnListChanged( new ListChangedEventArgs(ListChangedType.ItemChanged, index)); } } }
Now, anytime the collection's contents change, our event will be raised automatically.