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






4.67/5 (23投票s)
为扩展的 ListView 事件模型实现观察者模式。
引言
在最近的一个项目中,我需要数据访问类能够实现观察者模式,订阅当新的 ListViewItems
被添加到 ListView
或从中移除后触发的一组事件。本质上,ListView
作为被观察者(Subject),数据访问类作为观察者(Observer),这将有助于将 ListViewItem 的内容持久化到 SQL Server 数据库,从而记录 ListView
的当前内容(ListViewItems
)。请注意:本文假定您已了解事件和委托。如果您不熟悉这些概念,请参考 Chris Sells 的这篇优秀文章。
问题
ListView
类本身并不支持在 ListViewItem
对象被添加到其 Items 集合(该集合实现为一个公开 ListViewItemCollection
类型对象的 Collection 属性)时触发的事件。首先需要解决的问题是在向 ListView
添加 Item 时引发事件。为了解决这个问题,我选择扩展 ListViewItemCollection
类,该类提供了一个接口,公开了用于管理 ListViewItems
添加和移除到 ListViewItemCollection
的 Add()、AddRange()、Remove() 和 RemoveAt() 方法。此时我面临一个选择:是直接扩展 ListViewItemCollection
类,并将其赋值给我的 ListView
的 Items
属性,然后订阅其中定义的新事件吗?答案在此情况下是“否”。起初我是那样实现的,但从逻辑上讲,我总是想通过 ListView
本身来访问 ListView
的属性,因此管理一个我的新扩展 ListViewItemCollection
类型的独立对象的开销是不必要的,而且不够优雅。我更倾向于扩展 ListView
和 ListViewItemCollection
,并将扩展的 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
作为它们的目标,并且我在示例代码中未包含任何数据访问代码,以避免混淆该模式在不使用任何数据感知代码的类中的潜在用途。