Click here to Skip to main content
15,868,340 members
Articles / Programming Languages / Visual Basic
Article

An Observer Pattern and an Extended ListView Event Model

Rate me:
Please Sign up or sign in to vote.
4.68/5 (24 votes)
29 Jun 2003CPOL5 min read 150.8K   2.2K   62   19
Implementing an Observer Pattern for an Extended ListView Event Model

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

C#
//Declare an extend ListViewItemCollection 
ListViewItemCollectionNewEvent lvic =
     new ListViewItemCollectionNewEvent(ListViewItem1); 
//Assign the extended ListViewItemCollection object to equal the
//current ListView's Items collection 
lvic = ListView1.Items; 

//set the delegate to handle the event 
lvic.ItemAdded += new ListViewItemDelegate(LviAdded); 
  
private void LviAdded(ListViewItem) 
{ 
     //do something with the added item 
}

VB.Net Code Listing 1.0

VB.NET
'Declare an extend ListViewItemCollection,  perhaps within scope of a form 
Private WithEvents lvic As New ListViewItemCollectionNewEvent(ListViewItem1) 

'Assign the extended ListViewItemCollection object to equal the
'current ListView's Items collection 
lvic = ListView1.Items 

'Add a handler for the extended ListViewItemCollection's ItemAdded Event 
Private Sub LviAdded(ListViewItem) Handles lvic.ItemAdded 
     'do something with the added item
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

C#
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); 

     //Next come the event declarations:

     public event ListViewItemDelegate ItemAdded; 
     public event ListViewItemRangeDelegate ItemRangeAdded; 
     public event ListViewRemoveDelegate ItemRemoved; 
     public event ListViewRemoveAtDelegate ItemRemovedAt; 

     //Now explicitly hide the derived Items propery by declaring it as new:

     public new ListViewItemCollectionNewEvent Items; 

     //The Constructor and the initialisation of the new
     //ListViewItemCollection 

     public ListViewNewEventCs():base() 
     { 
          Items = new ListViewItemCollectionNewEvent(this); 
     } 

     //Next we provide the methods that the extended
     //"ListViewItemCollection" inner 
     //class will call and inside their implementation we 
     //raise our events to notify the Observers. 
     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

VB.NET
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

Image 1

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

C#
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

VB.NET
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

C#
public class RegisterWithTreeView 
{ 
     private ExtendListViewEvents.cs.ListViewNewEventCs listViewNewEventCs1;

     //Next comes a constructor which subscribes with 
     //the extended Listviews  events. 

     public RegisterWithTreeView(ListView lv) 
     {  
         this.listViewNewEventCs1 =
               (ExtendListViewEvents.cs.ListViewNewEventCs)lv;  
         //Register with the ListViews events  
         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); 
     }  

     //And now the delegated methods that are called upon firing the events:
     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

VB.NET
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

Image 2

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer
Australia Australia
Simon Segal resides in Melbourne Australia, is a certified MCAD, MCSD, MCDBA, MCSE, MCST BizTalk Specialist and has been working in the Software Development industry for some 10 years now. His key area of interest are distributed systems / SOA built with Microsoft technologies.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Franc Morales8-Jul-20 16:46
Franc Morales8-Jul-20 16:46 
GeneralMy vote of 5 Pin
JayantaChatterjee28-Jan-15 18:53
professionalJayantaChatterjee28-Jan-15 18:53 
GeneralNice article, but... Pin
Zigenstein28-Mar-09 23:32
Zigenstein28-Mar-09 23:32 
GeneralRe: Nice article, but... Pin
tlhIn`toq17-Sep-09 19:37
tlhIn`toq17-Sep-09 19:37 
GeneralFind me here.... Pin
Simon Segal20-Jun-08 21:41
Simon Segal20-Jun-08 21:41 
QuestionListView- Pin
Stixoffire221-Oct-06 16:59
Stixoffire221-Oct-06 16:59 
GeneralProblems with another property that should raise the event Pin
jmiguel_ven25-Jan-06 5:01
jmiguel_ven25-Jan-06 5:01 
GeneralRe: Problems with another property that should raise the event Pin
Simon Segal25-Jan-06 14:21
Simon Segal25-Jan-06 14:21 
GeneralRe: Problems with another property that should raise the event Pin
Igor Velikorossov17-May-06 15:58
Igor Velikorossov17-May-06 15:58 
GeneralRe: Problems with another property that should raise the event Pin
Simon Segal18-May-06 0:57
Simon Segal18-May-06 0:57 
GeneralContains Pin
gxdata27-Dec-05 17:53
gxdata27-Dec-05 17:53 
Generaldoesn't work with .Net 2.0 Pin
Anonymous25-Jul-05 20:38
Anonymous25-Jul-05 20:38 
GeneralRe: doesn't work with .Net 2.0 Pin
Simon Segal25-Jul-05 23:06
Simon Segal25-Jul-05 23:06 
GeneralRe: doesn't work with .Net 2.0 Pin
Anonymous26-Jul-05 12:57
Anonymous26-Jul-05 12:57 
GeneralRe: doesn't work with .Net 2.0 Pin
Simon Segal26-Jul-05 14:55
Simon Segal26-Jul-05 14:55 
GeneralRe: doesn't work with .Net 2.0 Pin
Anonymous26-Jul-05 19:40
Anonymous26-Jul-05 19:40 
AnswerNOW it DOES work in 2.0 !!!!! Re: doesn't work with .Net 2.0 Pin
GeneralBiSoN8-May-09 2:09
GeneralBiSoN8-May-09 2:09 
GeneralNaming Conventions Pin
Heath Stewart30-Jun-03 4:29
protectorHeath Stewart30-Jun-03 4:29 
GeneralRe: Naming Conventions Pin
Simon Segal30-Jun-03 13:10
Simon Segal30-Jun-03 13:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.