65.9K
CodeProject 正在变化。 阅读更多。
Home

观察者模式和扩展的 ListView 事件模型

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (23投票s)

2003年6月26日

CPOL

5分钟阅读

viewsIcon

154094

downloadIcon

2252

为扩展的 ListView 事件模型实现观察者模式。

引言

在最近的一个项目中,我需要数据访问类能够实现观察者模式,订阅当新的 ListViewItems 被添加到 ListView 或从中移除后触发的一组事件。本质上,ListView 作为被观察者(Subject),数据访问类作为观察者(Observer),这将有助于将 ListViewItem 的内容持久化到 SQL Server 数据库,从而记录 ListView 的当前内容(ListViewItems)。请注意:本文假定您已了解事件和委托。如果您不熟悉这些概念,请参考 Chris Sells 的这篇优秀文章

问题

ListView 类本身并不支持在 ListViewItem 对象被添加到其 Items 集合(该集合实现为一个公开 ListViewItemCollection 类型对象的 Collection 属性)时触发的事件。首先需要解决的问题是在向 ListView 添加 Item 时引发事件。为了解决这个问题,我选择扩展 ListViewItemCollection 类,该类提供了一个接口,公开了用于管理 ListViewItems 添加和移除到 ListViewItemCollectionAdd()AddRange()Remove()RemoveAt() 方法。此时我面临一个选择:是直接扩展 ListViewItemCollection 类,并将其赋值给我的 ListViewItems 属性,然后订阅其中定义的新事件吗?答案在此情况下是“否”。起初我是那样实现的,但从逻辑上讲,我总是想通过 ListView 本身来访问 ListView 的属性,因此管理一个我的新扩展 ListViewItemCollection 类型的独立对象的开销是不必要的,而且不够优雅。我更倾向于扩展 ListViewListViewItemCollection,并将扩展的 ListViewItemCollection 封装在扩展的 ListView 中作为内部类,并使用该类的实例来显式地隐藏 ListView 基类的 Items 属性成员,通过 new 声明。这也使得与 ListView 的订阅通知比与 ListViewItemCollection 的订阅更具逻辑性,从而避免了管理类似这样的代码:

C# 代码列表 1.0

//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 代码列表 1.0

'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

由于将扩展的 ListViewItemCollection 实现为内部类,因此上述所有代码都是不必要的,并且可以直接与扩展的 ListView 进行事件注册,这一点将在文章的其余部分进行演示。

扩展 ListView (被观察者)

现在来实现允许我们实现观察者模式的事件。首先,我们需要声明委托类型,这些委托将定义我们事件的签名。

C# 代码列表 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); 

     //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 代码列表 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 

这样我们就完成了 ListView 的工作。

图示 1.0:扩展的 ListView 和 ListViewItemCollection 事件模型

扩展 ListViewItemCollection

扩展的 ListViewItemCollection 类作为扩展的 ListView 类的内部类包含在内。这里首先要完成的是**隐藏**派生的 ListViewItemCollection 类的方法,即 Add()AddRange()Remove()RemoveAt(),因为它们没有一个被声明为 virtual (C#) 或 Overridable (VB.Net)。在这些方法的新的实现中,我们可以像平常一样调用基类的方法,然后调用派生的 ListViewItemCollection 的父类(即扩展的 ListView)的方法。扩展的 ListView 中被调用的方法然后会引发其事件,并通知所有已订阅的**观察者**。

C# 代码列表 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 代码列表 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 

从列表 2.0 中的代码可以看出,我们的内部类通过在构造函数中进行的父类赋值来知道其父类。ListViewItemCollection 类的构造函数签名需要 **owner** 参数来标识其父 ListView。在 Add()AddRange()Remove()RemoveAt() 的新实现中,再次使用 **parent** 成员来调用容器类(即扩展的 ListView)中的相应方法。

观察者(潜在的数据访问类)

本次练习的全部目的在于构建一个框架,允许对象注册它们对被通知的兴趣,当有项目添加到我们扩展的 ListView 时。这些感兴趣的方(对象)将成为我们新的(扩展的)ListView(被观察者)的观察者。在促成本文的场景中,当被观察者发生变化时,需要将其持久化到数据库或从中移除,但您可能只想将 ListView 的状态存储到 XML 文件中,以便在用户会话之间用于填充 ListView。那么,准备一个类来充当我们新的 ListView 的观察者需要什么呢?首先,我们需要建立一个指向 ListView(被观察者)的对象引用。请注意,虽然我的实现确实是一个数据访问类,但这里的示例不包含任何数据访问代码,只是在可能出现数据访问代码的地方弹出 MessagBox 窗口,但它仍然说明了要点。顺便说一下,这段代码包含在下载文件中。

C# 代码列表 4.0

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 代码列表 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 

图示 2.0

最后

本文的目的仅仅在于说明如何通过扩展 ListView 类的事件模型来实现观察者模式。如前所述,本文假定读者已理解委托和事件,并不试图解释这些概念。我还提到,我最初需要进行此练习是由于让数据访问类充当观察者,并将 TreeView 作为它们的目标,并且我在示例代码中未包含任何数据访问代码,以避免混淆该模式在不使用任何数据感知代码的类中的潜在用途。

© . All rights reserved.