New book: Developer's Guide to Collections in Microsoft .NET

We’re pleased to announce that the new Microsoft Press book Developer's Guide to Collections in Microsoft® .NET (ISBN 978-0-7356-5927-8, 646 pages), is now available for purchase!

Written by Calvin (Lee) Janes, a data collection expert, this book is unique because a deep look inside .NET collections: what types of collections are available in the .NET Framework, which ones are most suitable for which types of tasks, working with collection events and interfaces, and managing issues with GUI data binding, threading, data querying, and storage. This book not only provides the most thorough explanation of collection classes in .NET, but also provides task-oriented guidance, exercises, and extensive code samples so you can solve common problems and improve application performance. All code appears in both C# and Microsoft Visual Basic® .

To get a solid feel for the depth of coverage in this book, enjoy Chapter 10, “Using Collections with Windows Form Controls.”

Chapter 10

Using Collections with Window Forms

After completing this chapter, you will be able to

· Perform simple bindings with the UI.

· Create two-way bindings with the UI.

· Understand the sample code.

1) Simple Binding

Windows Forms can bind to collections that implement IList and IEnumerable by setting the DataSource property of the ListBox, ComboBox, or DataGridView classes to the collection; the following shows an example.

C#

ListBox lb = new ListBox();
lb.DataSource = new int [] { 1,2,3,4,5 };

Visual Basic

Dim lb As ListBox = New ListBox()
lb.DataSource = New Integer() {1, 2, 3, 4, 5}

To control which property these controls should display, you set the DisplayMember property of the ComboBox or ListBox control, as follows.

C#

ComboBox cb = new ComboBox();
cb.DataSource = new Company[]
{
new Company()
{
Id = 1,
Name = "Alpine Ski House ",
Website = "https://www.alpineskihouse.com/"
},
new Company()
{
Id = 2,
Name = "Tailspin Toys",
Website = "https://www.tailspintoys.com/"
}
};
cb.DisplayMember = "Name";

Visual Basic

Dim cb As ComboBox = New ComboBox()
cb.DataSource = New Company() _
{ _
New Company() With _
{ _
.Id = 1, _
.Name = "Alpine Ski House ", _
.Website = "https://www.alpineskihouse.com/" _
}, _
New Company() With _
{ _
.Id = 2, _
.Name = "Tailspin Toys", _
.Website = "https://www.tailspintoys.com/" _
} _
}
cb.DisplayMember = "Name"

You can also control which property the controls use for the value of selected item(s) for ComboBox or ListBox controls by using the ValueMember property, as follows.

C#

cb.ValueMember = "Id";

Visual Basic

cb.ValueMember = "Id"

When a user selects an item or items, the control’s SelectedValue property then contains the Id property of the selected item(s).

2) Two-Way Data Binding

You may have noticed that if you update the collection that you assigned to the DataSource property of a control, the control doesn’t reflect the change. That happens when the data source isn’t a BindingSource or doesn’t implement IBindingList. To support two-way binding, you must set DataSource to an object that implements IBindingList or use the BindingSource.

Note You must perform all Remove, Clear, and Add method calls through BindingSource if you specify a data source that doesn’t implement IBindingList. Otherwise, the user interface (UI) will not see your changes.

a) Implementing the IBindingList Interface

IBindlingList provides both simple and complex support for binding to data sources. The interface allows you to notify bound items of list changes and provides support for simple searching and sorting of the underlying collection. You should look at IBindingListView if you need to do complex sorting or add filtering. Microsoft provides BindingList(T) in the .NET Framework so that you do not have to implement an IBindingList from scratch.

Note The sample code in Chapter 10 contains a full implementation of the IBindingList interface called WinFormsBindingList(T) . The implementation of each interface implemented by WinFormsBindingList(T) is broken out into separate files to make it easier to follow. You’ll find the implementation of IBindingList in the language-specific files WinFormsBindingList.BindingList.cs and WinFormsBindingList.BindingList.vb. You can review those files for the full implementation of the properties and methods discussed later in this section. Chapters 6 and 8 provide information on how to implement the other interfaces. WinFormsBindingList(T) is the ArrayEx(T) class modified to support the IBindingList interface.

i) Adding List Manipulation Support

Bound items can determine whether a collection can be modified by using the AllowEdit, AllowNew, and AllowRemove properties, and by using the AddNew method.

(1) AllowEdit This property returns a value that states whether items in the list can be updated. The WinFormsBindingList returns true if the items contained in the collection support INotifyPropertyChanged. The DataGridView doesn’t allow editing of the items if the AllowEdit property is false. So the WinFormsBindingList will not allow users to modify the items if the items do not provide property change notifications.
(2) AllowNew This property returns a value that states whether items can be added through the AddNew method. The property should return false if the IList.IsFixedSize or IList.IsReadOnly property is set to true.
(3) AllowRemove This property returns a value that states whether items can be removed from the collection. The property should return false if the IList.IsFixedSize or IList.IsReadOnly property is set to true.
(4) AddNew This method adds a new item to the list. It throws an exception if the AllowNew property is false. The following code shows the implementation contained in WinFormsBindingList(T).

C#

public object AddNew()
{
if (!AllowNew)
{
throw new InvalidOperationException();
}
T retval = Activator.CreateInstance<T>();
Add(retval);
m_newIndex = Count – 1;
return retval;
}

Visual Basic

Public Function AddNew() As Object Implements IBindingList.AddNew
If (Not AllowNew) Then
Throw New InvalidOperationException()
End If
Dim retval As T = Activator.CreateInstance(Of T)()
Add(retval)
m_newIndex = Count - 1
Return retval
End Function

The field m_newIndex holds the index of the item being added. This is needed so that the bound item, such as the DataGridView, can cancel a newly added item. This is accomplished through the ICancelAddNew interface. The ICancelAddNew interface implementation used in WinFormsBinding(T) is as follows.

C#

public void CancelNew(int itemIndex)
{
if (m_newIndex.HasValue && m_newIndex.Value == itemIndex)
{
RemoveAt(itemIndex);
}
}
public void EndNew(int itemIndex)
{
if (m_newIndex.HasValue && m_newIndex.Value == itemIndex)
{
m_newIndex = null;
}
}

Visual Basic

Public Sub CancelNew(ByVal itemIndex As Integer) Implements ICancelAddNew.CancelNew
If (m_newIndex.HasValue And m_newIndex.Value = itemIndex) Then
RemoveAt(itemIndex)
End If
End Sub
Public Sub EndNew(ByVal itemIndex As Integer) Implements ICancelAddNew.EndNew
If (m_newIndex.HasValue And m_newIndex.Value = itemIndex) Then
m_newIndex = Nothing
End If
End Sub

If a user starts a new row in the DataGridView and then clicks elsewhere, the DataGridView calls ICancelAddNew.CancelNew; otherwise it calls ICancelAddNew.EndNew after the user enters data in the new row. The CancelNew implementation removes the newly added item from the collection. The EndNew implementation sets m_newIndex to null to denote that the item has been committed.

ii) Adding Notification Support

IBindingList provides list change notifications to items bound to the collection. Bound items can check to see whether the collection sends notifications through the SupportsChangeNotification property, and can receive those notifications through the ListChanged event. Bound items can then inspect the ListChangedEventArgs to see what has changed in the list. Bound items are also notified of property changes to the items in the collection through the ListChanged event.

The collection must raise the ListChanged event whenever the list is modified. This occurs whenever the Add, Clear, Insert, Remove, or RemoveAt methods are called as well as when the Item set property is called. The easiest way to do this is by creating OnXXX and OnXXXComplete methods like those discussed in the “Using CollectionBase” section in Chapter 5, ”Generic and Support Collections.” Each method except the Add method has an OnXXX and OnXXXComplete method, which uses the OnInsert and OnInsertComplete method, and the RemoveAt method, which uses the OnRemove and OnRemoveComplete method. The OnXXX method is called at the beginning of the operation and the OnXXXComplete is called at the end of the method. The following code shows how the code in ArrayEx(T) is modified to support the OnXXX and OnXXXComplete methods.

C#

public void Add(T item)
{
OnInsert(item, Count);
InnerList.Add(item);
OnInsertComplete(item, Count - 1);
}
public void Clear()
{
var removed = ToArray();
OnClear(removed);
InnerList.Clear();
OnClearComplete(removed);
}
public bool Remove(T item)
{
if (!AllowRemove)
{
throw new NotSupportedException();
}
int index = IndexOf(item);
if (index >= 0)
{
OnRemove(item, index);
InnerList.Remove(item);
OnRemoveComplete(item, index);
}
return index >= 0;
}
public void RemoveAt(int index)
{
if (index < 0 || index >= Count)
{
// Item has already been removed.
return;
}
if (!AllowRemove)
{
throw new NotSupportedException();
}
if (IsSorted)
{
throw new NotSupportedException("You cannot remove by index on a sorted list.");
}
T item = m_data[index];
OnRemove(item, index);
InnerList.RemoveAt(index);
OnRemoveComplete(item, index);
}
public void Insert(int index, T item)
{
OnInsert(item, index);
InnerList.Insert(index, item);
OnInsertComplete(item, index);
}
public T this[int index]
{
set
{
T oldValue = InnerList[index];
OnSet(index, oldValue, value);
InnerList[index] = value;
OnSetComplete(index, oldValue, value);
}
}

Visual Basic

Public Sub Add(ByVal item As T) Implements ICollection(Of T).Add
OnInsert(item, Count)
InnerList.Add(item)
OnInsertComplete(item, Count - 1)
End Sub
Public Sub Clear() Implements ICollection(Of T).Clear, IList.Clear
Dim removed As T() = ToArray()
OnClear(removed)
InnerList.Clear()
OnClearComplete(removed)
End Sub
Public Function Remove(ByVal item As T) As Boolean Implements ICollection(Of T).Remove
If (Not AllowRemove) Then
Throw New NotSupportedException()
End If
Dim index As Integer = IndexOf(item)
If (index >= 0) Then
OnRemove(item, index)
InnerList.Remove(item)
OnRemoveComplete(item, index)
End If
Return index >= 0
End Function
Public Sub RemoveAt(ByVal index As Integer) Implements IList(Of T).RemoveAt, IList.RemoveAt
If (index < 0 Or index >= Count) Then
' Item has already been removed.
Return
End If
If (Not AllowRemove) Then
Throw New NotSupportedException()
End If
If (IsSorted) Then
Throw New NotSupportedException("You cannot remove by index on a sorted list.")
End If
Dim item As T = InnerList(index)
OnRemove(item, index)
InnerList.RemoveAt(index)
OnRemoveComplete(item, index)
End Sub
Public Sub Insert(ByVal index As Integer, ByVal item As T) Implements IList(Of T).Insert
OnInsert(item, index)
InnerList.Insert(index, item)
OnInsertComplete(item, index)
End Sub
Default Public Property Item(ByVal index As Integer) As T Implements IList(Of T).Item
Set(ByVal value As T)
Dim oldValue As T = InnerList(index)
OnSet(index, oldValue, value)
InnerList(index) = value
OnSetComplete(index, oldValue, value)
End Set
End Property

The OnXXX method checks whether the corresponding method can be called, such as when a user calls Insert instead of Add on a sorted list.

C#

void OnSet(int index, T oldValue, T newValue)
{
if (IsSorted)
{
throw new NotSupportedException("You cannot set items in a sorted list");
}
}
void OnClear(T[] itemsRemoved)
{
}
void OnInsert(T item, int index)
{
// You can only add to the end of the list is sorting is on
if (IsSorted && index != Count)
{
throw new NotSupportedException();
}
}
void OnRemove(T item, int index)
{
}

Visual Basic

Sub OnSet(ByVal index As Integer, ByVal oldValue As T, ByVal newValue As T)
If (IsSorted) Then
Throw New NotSupportedException("You cannot set items in a sorted list")
End If
End Sub
Sub OnClear(ByVal itemsRemoved As T())
End Sub
Sub OnInsert(ByVal item As T, ByVal index As Integer)
' You can only add to the end of the list is sorting is on
If (IsSorted And index <> Count) Then
Throw New NotSupportedException()
End If
End Sub
Sub OnRemove(ByVal item As T, ByVal index As Integer)
End Sub

To keep it simple, the WinFormsBindingList(T) class throws an exception when a user tries to insert an item in a sorted list instead of trying to figure out whether the user should insert the item in the sorted or unsorted list. If you decide to implement insertion into a sorted list in your version, you should determine whether the user wants to insert into the sorted list they currently see or the stored, unsorted list the user sees when she makes a call such as Insert(2,value) .

Each one of the OnXXXComplete methods is responsible for notifying the bound item about list changes, registering for property changes, and handling special cases after an add or removal, such as re-indexing or resorting the list.

C#

void OnSetComplete(int index, T oldValue, T newValue)
{
UnRegisterForPropertyChanges(oldValue);
RegisterForPropertyChanges(newValue);
if (SupportsSearching)
{
ReIndex();
}
OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index));
}
void OnClearComplete(T[] itemsRemoved)
{
foreach (var item in itemsRemoved)
{
UnRegisterForPropertyChanges(item);
}
if (SupportsSearching)
{
ReIndex();
}
if (m_originalList != null)
{
m_originalList.Clear();
}
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
void OnInsertComplete(T item, int index)
{
RegisterForPropertyChanges(item);
if (IsSorted)
{
Sort();
}
else
{
if (SupportsSearching)
{
ReIndex();
}
OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, index));
}
if (m_originalList != null)
{
m_originalList.Insert(index, item);
}
}
void OnRemoveComplete(T item, int index)
{
UnRegisterForPropertyChanges(item);
if (SupportsSearching)
{
ReIndex();
}
OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
if (m_originalList != null)
{
m_originalList.Remove(item);
}
}

Visual Basic

Sub OnSetComplete(ByVal index As Integer, ByVal oldValue As T, ByVal newValue As T)
UnRegisterForPropertyChanges(oldValue)
RegisterForPropertyChanges(newValue)
If (SupportsSearching) Then
ReIndex()
End If
OnListChanged(New ListChangedEventArgs(ListChangedType.ItemChanged, index))
End Sub
Sub OnClearComplete(ByVal itemsRemoved As T())
For Each item As T In itemsRemoved
UnRegisterForPropertyChanges(item)
Next
If (SupportsSearching) Then
ReIndex()
End If
If (m_originalList IsNot Nothing) Then
m_originalList.Clear()
End If
OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, -1))
End Sub
Sub OnInsertComplete(ByVal item As T, ByVal index As Integer)
RegisterForPropertyChanges(item)
If (IsSorted) Then
Sort()
Else
If (SupportsSearching) Then
ReIndex()
End If
OnListChanged(New ListChangedEventArgs(ListChangedType.ItemAdded, index))
End If
If (m_originalList IsNot Nothing) Then
m_originalList.Insert(index, item)
End If
End Sub
Sub OnRemoveComplete(ByVal item As T, ByVal index As Integer)
UnRegisterForPropertyChanges(item)
If (SupportsSearching) Then
ReIndex()
End If
OnListChanged(New ListChangedEventArgs(ListChangedType.ItemDeleted, index))
If (m_originalList IsNot Nothing) Then
m_originalList.Remove(item)
End If
End Sub

WinFormsBindingList(T) gets property change notifications by registering to the INotifyPropertyChanged and INotifyPropertyChanging interfaces, as follows.

C#

void UnRegisterForPropertyChanges(T item)
{
// No need to register for property changes if none of the IBindingList features are supported
if ((!m_supportsPropertyChanged && !m_supportsPropertyChanging) || (!SupportsSorting && !SupportsChangeNotification && !SupportsSearching))
{
return;
}
INotifyPropertyChanged changed = item as INotifyPropertyChanged;
INotifyPropertyChanging changing = item as INotifyPropertyChanging;
if (changing != null && SupportsSearching)
{
changing.PropertyChanging -= new PropertyChangingEventHandler(T_OnPropertyChanging);
}
if (changed != null)
{
changed.PropertyChanged -= new PropertyChangedEventHandler(T_OnPropertyChanged);
}
}
void RegisterForPropertyChanges(T item)
{
// No need to register for property changes if none of the IBindingList features are supported
if ((!m_supportsPropertyChanged && !m_supportsPropertyChanging) || (!SupportsSorting && !SupportsChangeNotification && !SupportsSearching))
{
return;
}
INotifyPropertyChanged changed = item as INotifyPropertyChanged;
INotifyPropertyChanging changing = item as INotifyPropertyChanging;
if (changing != null && SupportsSearching)
{
changing.PropertyChanging += new PropertyChangingEventHandler(T_OnPropertyChanging);
}
if (changed != null)
{
changed.PropertyChanged += new PropertyChangedEventHandler(T_OnPropertyChanged);
}
}

Visual Basic

Sub UnRegisterForPropertyChanges(ByVal item As T)
' No need to register for property changes if none of the IBindingList features are supported
If ((Not m_supportsPropertyChanged And Not m_supportsPropertyChanging) Or (Not SupportsSorting And Not SupportsChangeNotification And Not SupportsSearching)) Then
Return
End If
Dim changed As INotifyPropertyChanged = TryCast(item, INotifyPropertyChanged)
Dim changing As INotifyPropertyChanging = TryCast(item, INotifyPropertyChanging)
If (changing IsNot Nothing And SupportsSearching) Then
RemoveHandler changing.PropertyChanging, New PropertyChangingEventHandler(AddressOf T_OnPropertyChanging)
End If
If (changed IsNot Nothing) Then
RemoveHandler changed.PropertyChanged, New PropertyChangedEventHandler(AddressOf T_OnPropertyChanged)
End If
End Sub
Sub RegisterForPropertyChanges(ByVal item As T)
' No need to register for property changes if none of the IBindingList features are supported
If ((Not m_supportsPropertyChanged And Not m_supportsPropertyChanging) Or (Not SupportsSorting And Not SupportsChangeNotification And Not SupportsSearching)) Then
Return
End If
Dim changed As INotifyPropertyChanged = TryCast(item, INotifyPropertyChanged)
Dim changing As INotifyPropertyChanging = TryCast(item, INotifyPropertyChanging)
If (changing IsNot Nothing And SupportsSearching) Then
AddHandler changing.PropertyChanging, New PropertyChangingEventHandler(AddressOf T_OnPropertyChanging)
End If
If (changed IsNot Nothing) Then
AddHandler changed.PropertyChanged, New PropertyChangedEventHandler(AddressOf T_OnPropertyChanged)
End If
End Sub

Property change notification is required internally for re-indexing and re-sorting the list if the property that changed is being indexed or sorted. Property change notifications are also needed so that bound controls can receive the ListChangeType.ItemChange notification.

Finally, the collection raises the ListChanged event through the OnListChanged method.

C#

void OnListChanged(ListChangedEventArgs e)
{
if (!SupportsChangeNotification)
{
return;
}
if (ListChanged != null)
{
ListChanged(this, e);
}
}

Visual Basic

Sub OnListChanged(ByVal e As ListChangedEventArgs)
If (Not SupportsChangeNotification) Then
Return
End If
RaiseEvent ListChanged(Me, e)
End Sub

Notification can be turned off, so the code checks the SupportsChangeNotification flag before raising the ListChanged event.

iii) Adding Sorting Support

IBindingList supports sorting through the SupportsSorting, SortDirection, SortProperty, and IsSorted properties as well as the ApplySort and RemoveSort methods. Internally, IBindingList maintains the original list so that it can be restored when sorting is removed. To do this, the Add, Remove, and Clear methods must add to both lists until sorting is removed.

(1) SupportsSorting This property tells the bound control whether sorting is implemented.
(2) SortDirection This property holds the current sort direction of the sort on the SortProperty. The property is not valid if IsSorted is false.
(3) SortProperty This property states the currently sorted property. The property is not valid if IsSorted is false.
(4) IsSorted This property states whether the contents are currently sorted. This will be true if ApplySort has been called and RemoveSort has not been called after the last ApplySort. The contents of SortDirection and SortProperty are valid only when IsSorted is true.
(5) ApplySort This method applies the specified sort to the list.

C#

public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
{
if (!SupportsSorting)
{
throw new NotSupportedException();
}
if (!IsSorted)
{
if (m_originalList == null)
{
m_originalList = m_data;
m_data = new List<T>(m_originalList);
}
}
m_sortDirection = direction;
m_sortDescriptor = property;
IsSorted = true;
Sort();
}

Visual Basic

Public Sub ApplySort(ByVal [property] As PropertyDescriptor, ByVal direction As ListSortDirection) Implements IBindingList.ApplySort
If (Not SupportsSorting) Then
Throw New NotSupportedException()
End If
If (Not IsSorted) Then
If (m_originalList Is Nothing) Then
m_originalList = m_data
m_data = New List(Of T)(m_originalList)
End If
End If
m_sortDirection = direction
m_sortDescriptor = [property]
m_isSorted = True
Sort()
End Sub

Before a sort operation, the original list is saved and copied over to m_data. The field m_data is then sorted using the Sort method.

C#

void Sort()
{
if (m_sortDescriptor == null)
{
return;
}
m_data.Sort(
new LambdaComparer<T>
(
(x, y) =>
{
object xValue = m_sortDescriptor.GetValue(x);
object yValue = m_sortDescriptor.GetValue(y);
if (m_sortDirection == ListSortDirection.Descending)
{
return System.Collections.Comparer.Default.Compare(
xValue, yValue) * -1;
}
return System.Collections.Comparer.Default.Compare(xValue, yValue);
}
));
if (SupportsSearching)
{
ReIndex();
}
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}

Visual Basic

Function Compare(ByVal x As T, ByVal y As T) As Integer
Dim xValue As Object = m_sortDescriptor.GetValue(x)
Dim yValue = m_sortDescriptor.GetValue(y)
If (m_sortDirection = ListSortDirection.Descending) Then
Return System.Collections.Comparer.Default.Compare(xValue, yValue) * -1
End If
Return System.Collections.Comparer.Default.Compare(xValue, yValue)
End Function
Sub Sort()
If (m_sortDescriptor Is Nothing) Then
Return
End If
m_data.Sort(AddressOf Compare)
If (SupportsSearching) Then
ReIndex()
End If
OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, -1))
End Sub

In the preceding code, the Sort method uses a lambda expression in the Microsoft Visual C# code and a Compare function in the Microsoft Visual Basic code to do the sorting. The lambda expression sort creates the following custom class that implements IComparer(T) and passes the Compare method call to the lambda expression

C#

class LambdaComparer<T> : IComparer<T>
{
Func<T, T, int> m_compare;
public LambdaComparer(Func<T, T, int> compareFunction)
{
if (compareFunction == null)
{
throw new ArgumentNullException("compareFunction");
}
m_compare = compareFunction;
}
public int Compare(T x, T y)
{
return m_compare(x,y);
}
}

The Sort method then notifies the bound item of the collection change by raising the ListChanged event.

(6) RemoveSort This method removes any applied sorting.

C#

public void RemoveSort()
{
if (!SupportsSorting)
{
throw new NotSupportedException();
}
m_sortDescriptor = null;
IsSorted = false;
if (m_originalList != null)
{
m_data = m_originalList;
m_originalList = null;
}
if (SupportsSearching)
{
ReIndex();
}
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}

Visual Basic

Public Sub RemoveSort() Implements IBindingList.RemoveSort
If (Not SupportsSorting) Then
Throw New NotSupportedException()
End If
m_sortDescriptor = Nothing
m_isSorted = False
If (m_originalList IsNot Nothing) Then
m_data = m_originalList
m_originalList = Nothing
End If
If (SupportsSearching) Then
ReIndex()
End If
OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, -1))
End Sub

The original list is restored and then indexed. The bound control is then notified of the change.

iv) Adding Searching Support

Searching is supported through the SupportsSearching property and the Find, AddIndex, and RemoveIndex methods. The WinFormsBindingList(T) shows how to get started implementing indexing if your application needs it, but the vast majority of applications do not need to support indexing.

(1) SupportsSearching Bound items can call this property to determine whether a collection supports searching. The SupportsSearching property lets you know whether the collection implements the Find method—but not necessarily whether the collection supports indexing. Most collections do not support indexing, but the sample code for this book provides an example indexing implementation to get you started.
(2) Find This method searches for the item that contains a property that matches the specified key.

C#

public int Find(PropertyDescriptor property, object key)
{
if (!SupportsSearching)
{
throw new NotSupportedException();
}
IndexData? data = FindIndexData(property);
// See if the property has been indexed
if (data.HasValue)
{
if (data.Value.Indexes.ContainsKey(key))
{
var indexes = data.Value.Indexes[key];
if (indexes.Count > 0)
{
return indexes[0];
}
}
return -1;
}
// Find the key by iterating over every element
for (int i = 0; i < Count; ++i)
{
T item = m_data[i];
try
{
object value = property.GetValue(item);
if (System.Collections.Comparer.Default.Compare(value, key) == 0)
{
return i;
}
}
catch
{
}
}
return -1;
}

Visual Basic

Public Function Find(ByVal [property] As PropertyDescriptor, ByVal key As Object) As Integer Implements IBindingList.Find
If (Not SupportsSearching) Then
Throw New NotSupportedException()
End If
Dim data As Nullable(Of IndexData) = FindIndexData([property])
' See if the property has been indexed
If (data.HasValue) Then
If (data.Value.Indexes.ContainsKey(key)) Then
Dim indexes = data.Value.Indexes(key)
If (indexes.Count > 0) Then
Return indexes(0)
End If
End If
Return -1
End If
' Find the key by iterating over every element
For i As Integer = 0 To Count - 1
Dim item As T = m_data(i)
Try
Dim value = [property].GetValue(item)
If (System.Collections.Comparer.Default.Compare(value, key) = 0) Then
Return i
End If
Catch
End Try
Next
Return -1
End Function

The Find method first checks to see whether the property has been indexed. If it has, it then uses the index table to find the item; otherwise, it performs a linear search for the item.

(3) AddIndex This method adds a PropertyDescriptor to the list to be indexed. The implementation in WinFormsBindingList(T) is as follows.

C#

public void AddIndex(PropertyDescriptor property)
{
IndexData ?data = FindIndexData(property);
if (!data.HasValue)
{
if (m_indexes == null)
{
m_indexes = new List<WinFormsBindingList<T>.IndexData>();
}
m_indexes.Add
(
new IndexData()
{
Indexes = new Dictionary<object,List<int>>(),
PropertyDescriptor = property
}
);
}
ReIndex();
}
IndexData ?FindIndexData(PropertyDescriptor property)
{
if (m_indexes == null)
{
return null;
}
foreach (var data in m_indexes)
{
if (data.PropertyDescriptor == property)
{
return data;
}
}
return null;
}

Visual Basic

Public Sub AddIndex(ByVal [property] As PropertyDescriptor) Implements IBindingList.AddIndex
Dim data As Nullable(Of IndexData) = FindIndexData([property])
If (Not data.HasValue) Then
If (m_indexes Is Nothing) Then
m_indexes = New List(Of WinFormsBindingList(Of T).IndexData)()
End If
m_indexes.Add( _
New IndexData() With { _
.Indexes = New Dictionary(Of Object, List(Of Integer))(), _
.PropertyDescriptor = [property] _
})
End If
ReIndex()
End Sub
Function FindIndexData(ByVal prop As PropertyDescriptor) As Nullable(Of IndexData)
If (m_indexes Is Nothing) Then
Return Nothing
End If
For Each data As IndexData In m_indexes
If (data.PropertyDescriptor.Name = prop.Name) Then
Return data
End If
Next
Return Nothing
End Function

The FindIndexData method tries to locate the index that belongs to the specified property. If the property isn’t already indexed, it creates a new index, and then calls the ReIndex method to force a re-indexing of all of the data, as follows.

C#

void ReIndex()
{
if (m_indexes == null)
{
return;
}
// Remove the old indexes
foreach (var index in m_indexes)
{
index.Indexes.Clear();
}
if (m_indexes.Count == 0 || !SupportsSearching || !m_canIndex)
{
return;
}
// Iterate over each item and add the index to the collection
for (int i = 0; i < Count; ++i)
{
T item = m_data[i];
foreach (var data in m_indexes)
{
try
{
object value = data.PropertyDescriptor.GetValue(item);
AddIndexData(data, value, i);
}
catch
{
}
}
}
}

Visual Basic

Sub ReIndex()
If (m_indexes Is Nothing) Then
Return
End If
' Remove the old indexes
For Each index As IndexData In m_indexes
index.Indexes.Clear()
Next
If (m_indexes.Count = 0 Or Not SupportsSearching Or Not m_canIndex) Then
Return
End If
' Iterate over each item and add the index to the collection
For i As Integer = 0 To Count - 1
Dim item As T = InnerList(i)
For Each Data As IndexData In m_indexes
Try
Dim value = Data.PropertyDescriptor.GetValue(item)
AddIndexData(Data, value, i)
Catch
End Try
Next
Next
End Sub

ReIndex works by erasing all of the old indexed data and then traversing each item in the collection and indexing that item. As you can tell, this is not an efficient way of indexing the system, but it shows the basic idea behind the AddIndex and RemoveIndex methods.

(4) RemoveIndex This method removes a PropertyDescriptor from the list of indexed properties. The implementation in WinFormsBindingList(T) is as follows.

C#

public void RemoveIndex(PropertyDescriptor property)
{
if (m_indexes == null)
{
return;
}
IndexData? data = FindIndexData(property);
if (data.HasValue)
{
m_indexes.Remove(data.Value);
}
ReIndex();
}

Visual Basic

Public Sub RemoveIndex(ByVal [property] As PropertyDescriptor) Implements IBindingList.RemoveIndex
If (m_indexes Is Nothing) Then
Return
End If
Dim data As Nullable(Of IndexData) = FindIndexData([property])
If (data.HasValue) Then
m_indexes.Remove(data.Value)
End If
ReIndex()
End Sub

b) Implementing the IBindingListView Interface

The IBindingListView interface extends the IBindingList interface by providing advanced sorting and filtering capabilities.

Note The sample code in Chapter 10 contains a full implementation of the IBindingListView interface called WinFormsBindingListView(T) . The implementation of each interface implemented by WinFormsBindingListView(T) is broken out into separate files to make the logic easier to follow. You’ll find the implementation of IBindingListView in the language-specific files WinFormsBindingListView.BindingListView.cs and WinFormsBindingListView.BindingListView.vb. You can review those files for the full implementation of the properties and methods discussed later in this section. Chapters 6 and 8 describe how to implement the other interfaces. WinFormsBindingListView (T) is the WinFormsBindingList(T) class modified to support the IBindingListView interface. See the “Implementing the IBindingList Interface” section earlier in this chapter for information on implementing IBindingList.

i) Adding Filtering Support

Filtering is accomplished by using the SupportsFiltering and Filter properties as well as the RemoveFilter method.

(1) SupportsFiltering This property states whether the collection supports filtering.
(2) Filter Set this property to filter a collection. The filter format for WinFormsBindingListView(T) is a small subset of the expression that you can pass to DataColumn.Expression. The allowed syntax is as follows.

Filter = [NOT] (PropertyName|[PropertyName]) (>|<|<>|<=|>=|=) (Value|’Value’) [(AND|OR) Filter]

Using that syntax, you can write code such as the following

[Name] = ‘Value’

Name <> Value

Name <= Value AND NOT IsDeleted

NOT IsDeleted

Name = ‘Value’ AND IsDeleted = False

Name = ‘Value’ OR Count = 2

Name = ‘Value’ OR Count = 2 OR Height = 36

The following code shows the implementation in WinFormsBindingListView(T). Because this book is a guide for collections rather than parsing, you won’t be learning the implementation in FilterParser. You can investigate lexer/parsers if you want to implement your own parsing.

C#

public string Filter
{
get
{
return m_filter;
}
set
{
if (m_filter == value)
{
return;
}
m_filterRoot = FilterParser.Parse(value);
m_filter = value;
if (!string.IsNullOrEmpty(value))
{
// Something needs to be filtered
if (!m_isFiltering)
{
ApplyFilter();
}
else
{
ReapplyFilter();
}
}
else
{
RemoveFilterInternal();
}
}
}

Visual Basic

Public Property Filter() As String Implements IBindingListView.Filter
Get
Return m_filter
End Get
Set(ByVal value As String)
If (m_filter = value) Then
Return
End If
m_filterRoot = FilterParser.Parse(value)
m_filter = value
If (Not String.IsNullOrEmpty(value)) Then
' Something needs to be filtered
If (Not m_isFiltering) Then
ApplyFilter()
Else
ReapplyFilter()
End If
Else
RemoveFilterInternal()
End If
End Set
End Property

The Filter property parses the specified string and then determines whether it should apply the filter for the first time, reapply it, or remove the current filter. If the filter is null or empty, the code calls the RemoveFilterInternal method to remove the filter.

C#

void RemoveFilterInternal()
{
if (!m_isFiltering)
{
return;
}
if (IsSorted)
{
m_data = new List<T>(m_originalList);
Sort();
}
else
{
m_data = m_originalList;
m_originalList = null;
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
m_isFiltering = false;
}

Visual Basic

Sub RemoveFilterInternal()
If (Not m_isFiltering) Then
Return
End If
If (IsSorted) Then
m_data = New List(Of T)(m_originalList)
Sort()
Else
m_data = m_originalList
m_originalList = Nothing
OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, -1))
End If
m_isFiltering = False
End Sub

The RemoveFilterInternal function restores the original list if the list is currently not being sorted, or it copies the original list and then calls Sort if the list is currently being sorted.

The Filter property calls ApplyFilter if no filter has been applied yet, as follows.

C#

void ApplyFilter()
{
if (m_isFiltering)
{
return;
}
if (m_originalList == null)
{
m_originalList = m_data;
m_data = new List<T>();
}
m_isFiltering = true;
ReapplyFilter();
}

Visual Basic

Sub ApplyFilter()
If (m_isFiltering) Then
Return
End If
If (m_originalList Is Nothing) Then
m_originalList = m_data
m_data = New List(Of T)()
End If
m_isFiltering = True
ReapplyFilter()
End Sub

ApplyFilter saves the original list and creates an empty list if the list is currently not being sorted. The empty list is filled in by the ReapplyFilter method.

The ReapplyFilter method traverses the original list and checks the IsFiltered method to see whether the item has been filtered.

C#

bool IsFiltered(T item)
{
if (m_filterRoot == null)
{
return false;
}
return m_filterRoot.Eval(item);
}
void ReapplyFilter()
{
if (!IsFiltering)
{
return;
}
m_data.Clear();
foreach (T item in m_originalList)
{
if (IsFiltered(item))
{
m_data.Add(item);
}
}
if (IsSorted)
{
Sort();
}
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}

Visual Basic

Function IsFiltered(ByVal item As T) As Boolean
If (m_filterRoot Is Nothing) Then
Return False
End If
Return m_filterRoot.Eval(item)
End Function
Sub ReapplyFilter()
If (Not m_isFiltering) Then
Return
End If
m_data.Clear()
For Each item As T In m_originalList
If (IsFiltered(item)) Then
m_data.Add(item)
End If
Next
If (IsSorted) Then
Sort()
End If
OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, -1))
End Sub

(3) RemoveFilter This methodremoves the current filter. This is the equivalent of writing Filter = String.Empty.

C#

public void RemoveFilter()
{
if (!SupportsAdvancedSorting)
{
throw new NotSupportedException();
}
Filter = String.Empty;
}

Visual Basic

Public Sub RemoveFilter() Implements IBindingListView.RemoveFilter
If (Not SupportsAdvancedSorting) Then
Throw New NotSupportedException()
End If
Filter = String.Empty
End Sub

ii) Adding Advanced Sorting

You can support advanced sorting through the SupportsAdvancedSorting and SortDescriptions properties as well as the ApplySort method. The ApplySort and RemoveSort methods as well as SortDirection and SortProperty in the IBindingList implementation need to be modified to support both simple and advanced sorting.

(1) SupportsAdvancedSorting This property states whether the list supports advanced sorting.
(2) SortDescriptions This property contains the list of currently sorted descriptors.
(3) Modifications to IBindingList ApplySort must be modified to add the specified sort properties to m_sortDescriptors.

C#

public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
{
if (!SupportsSorting)
{
throw new NotSupportedException();
}
SaveUnsortedList(false);
m_sortDescriptors.Clear();
m_sortDescriptors.Add(new ListSortDescription(property,direction));
IsSorted = true;
Sort();
}

Visual Basic

Public Sub ApplySort(ByVal sorts As ListSortDescriptionCollection) Implements IBindingListView.ApplySort
If (Not SupportsAdvancedSorting) Then
Throw New NotSupportedException()
End If
SaveUnsortedList(True)
m_sortDescriptors.Clear()
For Each sort As ListSortDescription In sorts
m_sortDescriptors.Add(sort)
Next
m_isSorted = True
Sort()
End Sub

RemoveSort restores the original list if filtering is not enabled, and notifies the bound item of the list change.

C#

public void RemoveSort()
{
if (!SupportsSorting)
{
throw new NotSupportedException();
}
m_sortDescriptors.Clear();
IsSorted = false;
if (m_originalList != null)
{
m_data = m_originalList;
m_originalList = null;
}
if (IsFiltering)
{
ReapplyFilter();
}
if (SupportsSearching)
{
ReIndex();
}
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}

Visual Basic

Public Sub RemoveSort() Implements IBindingList.RemoveSort
If (Not SupportsSorting) Then
Throw New NotSupportedException()
End If
m_sortDescriptors.Clear()
m_isSorted = False
If (m_originalList IsNot Nothing) Then
m_data = m_originalList
m_originalList = Nothing
End If
If (IsFiltering) Then
ReapplyFilter()
End If
If (SupportsSearching) Then
ReIndex()
End If
OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, -1))
End Sub

SortDirection and SortPropery return the first item in m_sortDescriptors if m_sortDescriptors contains only one item .

C#

public ListSortDirection SortDirection
{
get
{
if (SupportsSorting)
{
if (m_sortDescriptors.Count == 1)
{
return m_sortDescriptors[0].SortDirection;
}
return ListSortDirection.Ascending;
}
throw new NotSupportedException();
}
}
public PropertyDescriptor SortProperty
{
get
{
if (SupportsSorting)
{
if (m_sortDescriptors.Count == 1)
{
return m_sortDescriptors[0].PropertyDescriptor;
}
return null;
}
throw new NotSupportedException();
}
}

Visual Basic

Public ReadOnly Property SortDirection() As ListSortDirection Implements IBindingList.SortDirection
Get
If (SupportsSorting) Then
If (m_sortDescriptors.Count = 1) Then
Return m_sortDescriptors(0).SortDirection
End If
Return ListSortDirection.Ascending
End If
Throw New NotSupportedException()
End Get
End Property
Public ReadOnly Property SortProperty() As PropertyDescriptor Implements IBindingList.SortProperty
Get
If (SupportsSorting) Then
If (m_sortDescriptors.Count = 1) Then
Return m_sortDescriptors(0).PropertyDescriptor
End If
Return Nothing
End If
Throw New NotSupportedException()
End Get
End Property

The Sort method must also be modified to traverse through each item in m_sortDescriptors.

C#

void Sort()
{
if (m_sortDescriptors.Count <= 0)
{
return;
}
m_data.Sort(
new LambdaComparer<T>
(
(x, y) =>
{
for (int i = 0; i < m_sortDescriptors.Count; ++i)
{
var sd = m_sortDescriptors[i];
object xValue = sd.PropertyDescriptor.GetValue(x);
object yValue = sd.PropertyDescriptor.GetValue(y);
int result = 0;
if (sd.SortDirection == ListSortDirection.Descending)
{
result = System.Collections.Comparer.Default.Compare(
xValue, yValue) * -1;
}
else
{
result = System.Collections.Comparer.Default.Compare(
xValue, yValue);
}
if (result != 0 || i == m_sortDescriptors.Count - 1)
{
return result;
}
}
System.Diagnostics.Debug.Assert(false);
return 0;
}
));
if (SupportsSearching)
{
ReIndex();
}
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}

Visual Basic

Function Compare(ByVal x As T, ByVal y As T) As Integer
For i As Integer = 0 To m_sortDescriptors.Count - 1
Dim sd = m_sortDescriptors(i)
Dim xValue As Object = sd.PropertyDescriptor.GetValue(x)
Dim yValue = sd.PropertyDescriptor.GetValue(y)
Dim result As Integer = 0
If (sd.SortDirection = ListSortDirection.Descending) Then
result = System.Collections.Comparer.Default.Compare(xValue, yValue) * -1
Else
result = System.Collections.Comparer.Default.Compare(xValue, yValue)
End If
If (result <> 0 Or i = m_sortDescriptors.Count - 1) Then
Return result
End If
Next
System.Diagnostics.Debug.Assert(False)
Return 0
End Function
Sub Sort()
If (m_sortDescriptors.Count <= 0) Then
Return
End If
m_data.Sort(AddressOf Compare)
If (SupportsSearching) Then
ReIndex()
End If
OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, -1))
End Sub

AU: I changed the H4 “void ApplySort(ListSortDescirptionCollection sorts) ” to just “ApplySort” as we did with long names earlier in the chapter. Change OK?

(4) ApplySort This method sorts on all of the items in sorts.

C#

public void ApplySort(ListSortDescriptionCollection sorts)
{
if (!SupportsAdvancedSorting)
{
throw new NotSupportedException();
}
SaveUnsortedList(true);
m_sortDescriptors.Clear();
foreach (ListSortDescription sort in sorts)
{
m_sortDescriptors.Add(sort);
}
IsSorted = true;
Sort();
}

Visual Basic

Public Sub ApplySort(ByVal [property] As PropertyDescriptor, ByVal direction As ListSortDirection) Implements IBindingList.ApplySort
If (Not SupportsSorting) Then
Throw New NotSupportedException()
End If
SaveUnsortedList(False)
m_sortDescriptors.Clear()
m_sortDescriptors.Add(New ListSortDescription([property], direction))
m_isSorted = True
Sort()
End Sub

The SaveUnsortedList method saves the original list if the list is not currently being sorted.

C#

void SaveUnsortedList(bool advance)
{
if (!IsSorted)
{
if (m_originalList == null)
{
m_originalList = m_data;
m_data = new List<T>(m_originalList);
}
}
}

Visual Basic

Sub SaveUnsortedList(ByVal advance As Boolean)
If (Not IsSorted) Then
If (m_originalList Is Nothing) Then
m_originalList = m_data
m_data = New List(Of T)(m_originalList)
End If
End If
End Sub

c) Using the BindingList(T) Class

BindingList(T) provides a generic implementation of IBindingList that simplifies the creation of a custom implementation of the IBindingList interface. BindingList(T) implements IBindingList and provides generic support for bounded collections. To override the default implementation of BindingList(T), look for the method or property with the word Core appended to it. For example, to implement RemoveSort, you need to override the RemoveSortCore method. See the section “Implementing the IBindingList Interface” earlier in this chapter for more information on each method and property.

d) Using the BindingSource Class

The BindingSource class provides search and sort capability to a data source that implements IBindingList. If the data source you plan to use doesn’t implement IBindingList, and you want bound items to see your changes, you need to call the appropriate methods in the BindingSource class rather than on the data source.

Warning According to the Microsoft documentation at https://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource(v=VS.100).aspx\#Y8235, the DataSource property value of BindingSource should be changed on the UI thread to ensure that the UI reflects the changes.

To use BindingSource, create an instance of the BindingSource class and assign the DataSource property to your collection.

C#

List<int> items = new List<int>();
// Populate items
BindingSource source = new BindingSource();
source.DataSource = items;

Visual Basic

Dim items As List(Of Integer) = New List(Of Integer)()
' Populate items
Dim source As BindingSource = New BindingSource()
source.DataSource = items

Then set the UI to the BindingSource as follows.

C#

dataGridView1.DataSource = source;

Visual Basic

dataGridView1.DataSource = source

You need to call the Add, Clear, Insert, Remove, and RemoveAt methods in BindingSource instead of the data source if your data source doesn’t implement IBindingList, as follows.

C#

source.Add(1);
source.RemoveAt(0);

Visual Basic

source.Add(1)
source.RemoveAt(0)

3) Understanding the Sample Code

The sample project Driver, located in the Samples\Chapter 10 folder, contains examples of using the binding interfaces and objects for collections with a ComboBox, DataGrid, and ListBox.

a) Binding with the ComboBox Control

The ComboBoxBinding form demonstrates how to use the binding classes with a combo box, which is created using the ComboBox class in Windows Forms. An object that implements IBindingList can bind to a combo box by using the following code.

C#

comboBox1.DataSource = m_datasource;
comboBox1.DisplayMember = "Name";
comboBox1.ValueMember = "Id";

Visual Basic

comboBox1.DataSource = m_datasource
comboBox1.DisplayMember = "Name"
comboBox1.ValueMember = "Id"

The Update panel shows the currently selected item in the combo box. You can update the selected item by clicking on the Update Itembutton. The combo box is automatically updated to reflect the updated item. Because each item implements INotifyPropertyChanged, bound controls receive notifications of property changes when items are updated with the following code.

C#

m_showing.Name = NameTextBox.Text;
m_showing.Website = WebsiteTextBox.Text;

Visual Basic

m_showing.Name = NameTextBox.Text
m_showing.Website = WebsiteTextBox.Text

You can use the Add panel to add new items to the combo box. You add items to the combo boxby clicking the Add Item button. The combo boxis automatically updated to reflect the newly added item. The code for adding an item is as follows.

C#

Company company = new Company();
company.Id = int.Parse(AddIdTextBox.Text);
company.Name = AddNameTextBox.Text;
company.Website = AddWebsiteTextBox.Text;
m_datasource.Add(company);

Visual Basic

Dim company As Company = New Company()
company.Id = Integer.Parse(AddIdTextBox.Text)
company.Name = AddNameTextBox.Text
company.Website = AddWebsiteTextBox.Text
m_datasource.Add(company)

You can remove the selected item in the combo boxby clicking the Remove Item button. The following shows the code for removing an item.

C#

if (comboBox1.SelectedIndex >= 0)
{
m_datasource.RemoveAt(comboBox1.SelectedIndex);
}

Visual Basic

If (comboBox1.SelectedIndex >= 0) Then
m_datasource.RemoveAt(comboBox1.SelectedIndex)
End If

b) Binding with the ListBox Control

The ListBoxBinding form demonstrates how to use the binding classes with a list box, which is created using the ListBox class in Windows Forms. An object that implements IBindingList can bind to a list box by using the following code.

C#

listBox1.DataSource = m_datasource;
listBox1.DisplayMember = "Name";

Visual Basic

listBox1.DataSource = m_datasource
listBox1.DisplayMember = "Name"

The Update panel shows the currently selected item in the list box. You update the selected item by clicking the Update Item button. The list boxis automatically updated to reflect the updated item. Because each item implements INotifyPropertyChanged, bound controls receive notifications of property changes when items are updated with the following code.

C#

m_showing.Name = NameTextBox.Text;
m_showing.Website = WebsiteTextBox.Text;

Visual Basic

m_showing.Name = NameTextBox.Text
m_showing.Website = WebsiteTextBox.Text

You can use the Add panel to add new items to the list box. You add items to the list box by clicking the Add Item button. The list box is automatically updated to reflect the newly added item. The code for adding an item is as follows.

C#

Company company = new Company();
company.Id = int.Parse(AddIdTextBox.Text);
company.Name = AddNameTextBox.Text;
company.Website = AddWebsiteTextBox.Text;
m_datasource.Add(company);

Visual Basic

Dim company As Company = New Company()
company.Id = Integer.Parse(AddIdTextBox.Text)
company.Name = AddNameTextBox.Text
company.Website = AddWebsiteTextBox.Text
m_datasource.Add(company)

You can remove the selected item in the list box by clicking the Remove Item button. The following shows the code for removing an item.

C#

if (comboBox1.SelectedIndex >= 0)
{
m_datasource.RemoveAt(listBox1.SelectedIndex);
}

Visual Basic

If (comboBox1.SelectedIndex >= 0) Then
m_datasource.RemoveAt(listBox1.SelectedIndex)
End If

c) Binding with the DataGridView Control and IBindingList

The DataGridViewBinding form demonstrates how to use the binding classes with a data grid, which is created using the DatGridView class in Windows Forms . An object that implements IBindingList can bind to a DataGridView by using the following code.

C#

m_datasource = DL.GetDataSource();
dataGridView1.DataSource = m_datasource;

Visual Basic

m_datasource = DL.GetDataSource()
dataGridView1.DataSource = m_datasource

Items can be added and removed from the data grid by using the same code that is in the ComboBoxBinding and ListBoxBinding forms. In fact, both of the forms use the same data source instance in the sample code as the DataGridViewBinding, so updating the data source in the ComboBoxBinding or ListBoxBinding form also updates the DataGridViewBinding form.

Items can be searched by entering a search string in the search box, selecting a property to search on, and pressing the Search button. A message box will appear stating the row the item was found in. You can also test indexing by selecting the properties you want to index on and then searching on the indexed property. The code for searching is as follows.

C#

if (string.IsNullOrEmpty(SearchTextBox.Text) || SearchPropertyComboBox.SelectedIndex < 0)
{
return;
}
var pd = SearchPropertyComboBox.SelectedItem as PropertyDescriptor;
int found = -1;
try
{
found = m_datasource.Find(pd, pd.Converter.ConvertFromString(SearchTextBox.Text));
if (found >= 0)
{
MessageBox.Show(string.Format("Found '{0}' at index {1}", SearchTextBox.Text, found));
}
else
{
MessageBox.Show(string.Format("Didn't find '{0}'", SearchTextBox.Text));
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

Visual Basic

If (String.IsNullOrEmpty(SearchTextBox.Text) Or SearchPropertyComboBox.SelectedIndex < 0) Then
Return
End If
Dim pd = TryCast(SearchPropertyComboBox.SelectedItem, PropertyDescriptor)
Dim found As Integer = -1
Try
found = m_datasource.Find(pd, pd.Converter.ConvertFromString(SearchTextBox.Text))
If (found >= 0) Then
MessageBox.Show(String.Format("Found '{0}' at index {1}", SearchTextBox.Text, found))
Else
MessageBox.Show(String.Format("Didn't find '{0}'", SearchTextBox.Text))
End If
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try

d) Binding with the DataGridView Control and IBindingListView

The DataGridViewAdvanceBinding form is the DataGridViewBinding form with additional UI elements for the IBindingListView interface. The IBindingListView interface allows users to filter the results and sort on multiple columns. The following code shows how to bind IBindingListView object to the data grid control.

C#

m_datasource = DL.GetDataSourceView();
m_binding = New BindingSource();
m_binding.DataSource = m_datasource;
dataGridView1.DataSource = m_binding;

Visual Basic

m_datasource = DL.GetDataSourceView()
m_binding = New BindingSource()
m_binding.DataSource = m_datasource
dataGridView1.DataSource = m_binding

Users can filter items by entering the filter string into the Filter text box and clicking the Filter button. The following code shows how to filter the IBindingListView object.

C#

m_datasource.Filter = FilterTextBox.Text;

Visual Basic

m_datasource.Filter = FilterTextBox.Text

The IBindingListView collection can be sorted on multiple properties by using the Sort property on the BindingSource. To do this in the UI, enter the sort text into the Sort text box, and click the Sortbutton. The code for doing this is as follows.

C#

m_binding.Sort = SortTextBox.Text;

Visual Basic

m_binding.Sort = SortTextBox.Text

e) Binding with the BindingSource Object

The BindingSourceBinding form demonstrates how to perform two-way binding on a collection that doesn’t implement IBindingList. An object that doesn’t implement IBindingList can bind to a list box by using the following code.

C#

m_source = new BindingSource();
m_source.DataSource = new List<Company>(DL.GetData());
listBox1.DataSource = m_source;
listBox1.DisplayMember = "Name";

Visual Basic

m_source = New BindingSource()
m_source.DataSource = New List(Of Company)(DL.GetData())
listBox1.DataSource = m_source
listBox1.DisplayMember = "Name"

The Update panel shows the currently selected item in the list box. You can update the selected item by clicking the Update Itembutton. The list box is automatically updated to reflect the updated item. Because each item implements INotifyPropertyChanged, items are updated with the following code.

C#

m_showing.Name = NameTextBox.Text;
m_showing.Website = WebsiteTextBox.Text;

Visual Basic

m_showing.Name = NameTextBox.Text
m_showing.Website = WebsiteTextBox.Text

You can use the Add panel to add new items to the list box. You add items to the list box by clicking the Add Item button. The list box updates automatically to reflect the newly added item. The item needs to be added using the BindingSource instead of the List(T) for the list box to see the changes, because List(T) doesn’t implement IBindingList.

C#

Company company = new Company();
company.Id = int.Parse(AddIdTextBox.Text);
company.Name = AddNameTextBox.Text;
company.Website = AddWebsiteTextBox.Text;
m_source.Add(company);

Visual Basic

Dim company As Company = New Company()
company.Id = Integer.Parse(AddIdTextBox.Text)
company.Name = AddNameTextBox.Text
company.Website = AddWebsiteTextBox.Text
m_source.Add(company)

You can remove the selected item in the list box by clicking the Remove Item button. The following shows the code for removing an item. The item needs to be removed using the BindingSource instead of the List(T) for the list box to see the changes, because List(T) doesn’t implement IBindingList.

C#

if (comboBox1.SelectedIndex >= 0)
{
m_source.RemoveAt(listBox1.SelectedIndex);
}

Visual Basic

If (comboBox1.SelectedIndex >= 0) Then
m_source.RemoveAt(listBox1.SelectedIndex)
End If

4) Summary

In this chapter, you saw how to bind collections to controls used in Windows Forms. You saw how to have two-way bound controls by using the IBindingList interface. You also saw that you can use the IBindingList interface to sort and search, and use the IBindingLIstView interface to do advanced filtering and sorting.