Introduction
In a recent project I required Data Access Classes to have the ability to implement the observer pattern, subscribing to a set of events that are triggered after new ListViewItems
had been added to and removed from a ListView
. Essentially the ListView
was to behave as the Subject and the Data Access class as the Observer, this would inturn facilitate the persistance of the ListViewItem contents to a SQL Server Database, maintaining a record of the current contents (ListViewItems
) of the ListView
. Please NOTE: This article assumes a knowledge of events and delegates. If your are not familiar with these concepts please take a look at this excellent article by Chris Sells.
The Problem
The ListView Class does not inherently support an event that fires when ListViewItem
objects are added to it's Items collection, which is implemented as a Collection property exposing an object of type ListViewItemCollection. The first problem that required solving was raising an event when Items were added to the ListView
and to solve this I chose to extended the ListViewItemCollection
class which provides an interface that exposes the Add(), AddRange(), Remove(), and RemoveAt() methods that manage the adding and removing of ListViewItems
to and from the ListViewItemCollection
. I was faced with a choice at this point, do I simply extend the ListViewItemCollection
class and assign it to be equal to my ListView
's Items
property and subscribe with the new events defined therein? The answer in this case was NO. Originally I did implement that way however logically I always want to access the ListView
properties via the ListView
itself and therefore the overhead of managing another discreet object of the type of my new extended ListViewItemCollection
was unnessary and not as elegant as I would have liked. In preference I chose to extend both the ListView
and the ListViewItemCollection
and to encapsulate and implement the extended ListViewItemCollection
inside the extended ListView as an inner class, an instance of which would be used to explicitly hide the Items property member of the ListView
base class with a new
declaration. This also allowed for a more logical subscribinging for notification with the ListView
as opposed to the ListViewItemCollection
and thus negated the need to manage code like this:
C# Code Listing 1.0
ListViewItemCollectionNewEvent lvic =
new ListViewItemCollectionNewEvent(ListViewItem1);
lvic = ListView1.Items;
lvic.ItemAdded += new ListViewItemDelegate(LviAdded);
private void LviAdded(ListViewItem)
{
}
VB.Net Code Listing 1.0
Private WithEvents lvic As New ListViewItemCollectionNewEvent(ListViewItem1)
lvic = ListView1.Items
Private Sub LviAdded(ListViewItem) Handles lvic.ItemAdded
End Sub
As a consequence of implementing the extended ListViewItemCollection
as an inner class none of the code above is required and registration with events is made directly with the extended ListView
which is demonstrated in the remainder of the article.
Extending ListView (The Subject)
Now for implementing the events that allow us to implement the Observer pattern. First off we need to declare delegate types which will define the signiture of our events.
C# Code Listing 2.0
public class ListViewNewEventCs : ListView
{
public delegate void ListViewItemDelegate(ListViewItem item);
public delegate void ListViewItemRangeDelegate(ListViewItem[] item);
public delegate void ListViewRemoveDelegate(ListViewItem item);
public delegate void ListViewRemoveAtDelegate(int index,
ListViewItem item);
public event ListViewItemDelegate ItemAdded;
public event ListViewItemRangeDelegate ItemRangeAdded;
public event ListViewRemoveDelegate ItemRemoved;
public event ListViewRemoveAtDelegate ItemRemovedAt;
public new ListViewItemCollectionNewEvent Items;
public ListViewNewEventCs():base()
{
Items = new ListViewItemCollectionNewEvent(this);
}
private void AddedItem(ListViewItem lvi)
{
this.ItemAdded(lvi);
}
private void AddedItemRange(ListViewItem[] lvi)
{
this.ItemRangeAdded(lvi);
}
private void RemovedItem(ListViewItem lvi)
{
this.ItemRemoved(lvi);
}
private void RemovedItem(int index, ListViewItem item)
{
this.ItemRemovedAt(index, item);
}
}
VB.NET Code Listing 2.0
Public Class ListViewNewEventVb
Inherits System.Windows.Forms.ListView
Public Delegate Sub ListViewItemDelegate(ByVal item As ListViewItem)
Public Delegate Sub ListViewItemRangeDelegate( _
ByVal item As ListViewItem())
Public Delegate Sub ListViewRemoveDelegate(ByVal item As ListViewItem)
Public Delegate Sub ListViewRemoveAtDelegate( _
ByVal index As Integer, _
ByVal item As ListViewItem)
Public Event ItemAdded As ListViewItemDelegate
Public Event ItemRangeAdded As ListViewItemRangeDelegate
Public Event ItemRemoved As ListViewRemoveDelegate
Public Event ItemRemovedAt As ListViewRemoveAtDelegate
Public Shadows Items As ListViewItemCollectionNewEvent
Public Sub New()
MyBase.New() Items = New ListViewItemCollectionNewEvent(Me)
End Sub
Private Sub AddedItem(ByVal lvi As ListViewItem)
RaiseEvent ItemAdded(lvi)
End Sub
Private Sub AddedItemRange(ByVal lvi As ListViewItem())
RaiseEvent ItemRangeAdded(lvi)
End Sub
Private Overloads Sub RemovedItem(ByVal lvi As ListViewItem)
RaiseEvent ItemRemoved(lvi)
End Sub Private Overloads Sub RemovedItem(ByVal index As Integer, _
ByVal item As ListViewItem)
RaiseEvent ItemRemovedAt(index, item)
End Sub
End Class
And thats it were done with our ListView
.
Exhibit 1.0: Extended ListView and ListViewItemCollection Event Model
Extending ListViewItemCollection
The extended ListViewItemCollection
class is included as an inner class of an extended ListView
class. The first thing to accomplish here is to hide the methods of the derived ListViewItemCollection
, namely Add()
, AddRange()
, Remove()
and RemoveAt()
as none of them are declared as virtual
(C#) or Overridable
(VB.Net). Inside the new implementations of these methods we can call the base classes implementations as per normal and then make a method call to the derived ListViewItemCollection
's parent which is the extended ListView
. The methods called in the extended ListView
then raise their events and notify all Observers that are subscribed listeners.
C# Code Listing 3.0
public class ListViewItemCollectionNewEvent :
System.Windows.Forms.ListView.ListViewItemCollection
{
private ListView parent;
public ListViewItemCollectionNewEvent(
System.Windows.Forms.ListView owner): base(owner)
{
parent = owner;
}
public new void Add(ListViewItem Lvi)
{
base.Add(Lvi);
((ListViewNewEventCs)parent).AddedItem(Lvi);
}
public new void AddRange(ListViewItem[] Lvi)
{
base.AddRange(Lvi);
((ListViewNewEventCs)parent).AddedItemRange(Lvi);
}
public new void Remove(ListViewItem Lvi)
{
base.Remove(Lvi);
((ListViewNewEventCs)parent).RemovedItem(Lvi);
}
public new void RemoveAt(int index)
{
System.Windows.Forms.ListViewItem lvi = this[index];
base.RemoveAt(index);
((ListViewNewEventCs)parent).RemovedItem(index, lvi);
}
}
VB.NET Code Listing 3.0
Public Class ListViewItemCollectionNewEvent
Inherits System.Windows.Forms.ListView.ListViewItemCollection
Dim parent As ListView
Sub New(ByVal owner As System.Windows.Forms.ListView)
MyBase.New(owner)
parent = owner
End Sub
Public Shadows Sub Add(ByVal Lvi As ListViewItem)
MyBase.Add(Lvi)
CType(parent, ListViewNewEventVb).AddedItem(Lvi)
End Sub
Public Shadows Sub AddRange(ByVal Lvi As ListViewItem())
MyBase.AddRange(Lvi)
CType(parent, ListViewNewEventVb).AddedItemRange(Lvi)
End Sub
Public Shadows Sub Remove(ByVal Lvi As ListViewItem)
MyBase.Remove(Lvi)
CType(parent, ListViewNewEventVb).RemovedItem(Lvi)
End Sub
Public Shadows Sub RemoveAt(ByVal index As Integer)
Dim lvi As ListViewItem = Me.Item(index)
MyBase.RemoveAt(index)
CType(parent, ListViewNewEventVb).RemovedItem(index, lvi)
End Sub
End Class
As you can see from the code in Listing 2.0, our inner class knows its parent via the assignment to the parent member which takes place in the constructor. The ListViewItemCollection
class contstructors signiture requires the argument owner to identify it's parent ListView
. The parent member is used again in the new implementations of Add()
, AddRange()
, Remove()
and RemoveAt()
to call the appropriate method in the container class which is the extended ListView
.
The Observer (A Potential Data Access Class)
The whole purpose of this exercise has been to build a framwork that will allow objects to register their interest in being notified when an item is added to our extended ListView
. These interested parties (objects) will be Observers of the subject (the extended ListView
). In the scenario that prompted this article it was a matter of persisting to or removing from a DataBase when the Subject changed, however it may be that you simply want to store the state of the ListView
to an xml file that will be used to populate the ListView
between user sessions of your application. So what is involved in preparing a class to act as an observer of our new ListView
? First we need to establish an object reference to the ListView
(Subject). Note that whilst my implmentation was indeed a Data Access Class, the example here includes no Data Access code and simply pops up MessagBox
windows where that code might be, it does however still illustrate the point. By the way this code is supplied in the downloads.
C# Code Listing 4.0
public class RegisterWithTreeView
{
private ExtendListViewEvents.cs.ListViewNewEventCs listViewNewEventCs1;
public RegisterWithTreeView(ListView lv)
{
this.listViewNewEventCs1 =
(ExtendListViewEvents.cs.ListViewNewEventCs)lv;
this.listViewNewEventCs1.ItemAdded +=
new ListViewItemDelegate(this.ItemAdded);
this.listViewNewEventCs1.ItemRangeAdded +=
new ListViewItemRangeDelegate(this.ItemRangeAdded);
this.listViewNewEventCs1.ItemRemoved +=
new ListViewRemoveDelegate(this.ItemRemoved);
this.listViewNewEventCs1.ItemRemovedAt +=
new ListViewRemoveAtDelegate(this.ItemRemovedAt);
}
public void ItemAdded(System.Windows.Forms.ListViewItem lvi)
{
MessageBox.Show("Item Added Event caught in Data Access Class for " +
"the Item - " + lvi.SubItems[0].Text);
}
public void ItemRangeAdded(System.Windows.Forms.ListViewItem[] lvi)
{
foreach(System.Windows.Forms.ListViewItem item in lvi)
{
MessageBox.Show("Item Range Added Event caught " +
"in Data Access Class for the Item - " + item.SubItems[0].Text);
}
}
public void ItemRemoved(System.Windows.Forms.ListViewItem lvi)
{
MessageBox.Show("Item Removed Event caught " +
"in Data Access Class for the Item - " +
lvi.SubItems[0].Text);
}
public void ItemRemovedAt(int index,
System.Windows.Forms.ListViewItem lvi)
{
MessageBox.Show("Item RemovedAt Event caught " +
in Data Access Class for the Item - " +
lvi.SubItems[0].Text);
}
}
VB.Net Code Listing 4.0
Public Class RegisterWithTreeView
Private WithEvents ListViewNewEventVb1 As _
New NewListView.ListViewNewEventVb()
Public Sub New(ByVal lv As ListView)
Me.ListViewNewEventVb1 = CType(lv, NewListView.ListViewNewEventVb)
End Sub
Private Sub ItemAdded(ByVal lvi As ListViewItem) _
Handles ListViewNewEventVb1.ItemAdded
MessageBox.Show("Item Added Event caught " & _
"in Data Access Class for the Item - " & _
lvi.SubItems(0).Text)
End Sub
Private Sub ItemRangeAdded(ByVal lvi As ListViewItem()) _
Handles ListViewNewEventVb1.ItemRangeAdded
Dim item As ListViewItem
For Each item In lvi
MessageBox.Show("Item Range Added Event caught " & _
"in Data Access Class for the Item - " & _
item.SubItems(0).Text)
Next
End Sub
Private Sub ItemRemoved(ByVal lvi As ListViewItem) _
Handles ListViewNewEventVb1.ItemRemoved
MessageBox.Show("Item Removed Event caught " & _
"in Data Access Class for the Item - " & _
lvi.SubItems(0).Text)
End Sub
Private Sub ItemRemovedAt(ByVal index As Integer, _
ByVal lvi As ListViewItem) _
Handles ListViewNewEventVb1.ItemRemovedAt
MessageBox.Show("Item RemovedAt Event caught " & _
"in Data Access Class for the Item - " & _
lvi.SubItems(0).Text)
End Sub
End Class
Exhibit 2.0
Finally
This articles purpose was solely to illustrate how to implement an observer pattern by extending the event model of the ListView
class and as previously stated it assumes an understanding of delegates and events it is does not attempt to explain either of these concepts. I have also mentioned that my original need for undertaking the exercise was related to having Data Access classes act as observers using the TreeView
as their subject and I have not included any Data Access Code in the example code in an attempt not to cloud the potential use of the pattern in classes that use no Data aware code.