使用 WPF 和 Unity Container 的模型视图演示者 (MVP)






3.87/5 (8投票s)
本文演示了在示例程序中使用 Model-View-Presenter (MVP) 模式。
引言
本文演示了在示例程序中使用 Model-View-Presenter (MVP) 模式。我分析了一些关于 MVP 的文章,发现每篇文章在某种程度上与其他文章不同。这可能是由于 MVP 模式存在变体以及实现方法的多样性。本文不侧重于细节,只解释程序流程。有关更多详细信息,请参考下面的链接。
(Microsoft MSDN 网站有很多关于 MVP 的有用文章。)
注意:该模式的实现基于 Unity Application Block。它用于应用程序的 IoC (Inversion of Control,控制反转) 和依赖注入需求。还有其他实现方法。
基础
Model-View-Presenter (MVP) 是一种设计模式,被认为是 Model-View-Controller (MVC) 的派生。它旨在促进测试驱动开发,并有助于将系统的视觉显示和事件处理行为的职责分离到不同的类中。
View 类管理窗体上的控件,并将事件转发给 Presenter 类。Presenter 包含响应事件的逻辑,并进而操纵 View 的状态。Presenter 类使用 Model (业务实体) 来确定如何响应事件。
当 Model 更新时,View 也必须更新以反映更改。有几种方法可以做到这一点。以下两种方法被广泛使用:
- Passive View
- Supervising Controller (监督式控制器)
在 Passive View (被动视图) 中,Presenter 更新 View 以反映 Model 中的更改。在 Supervising Controller (监督式控制器) 中,View 直接与 Model 交互,执行可以在声明中定义的简单数据绑定,无需 Presenter 干预。附加的示例使用了 Supervising Controller 变体。
先决条件
此示例使用 WPF 和 Unity Application Block (Enterprise Library 4.1) 作为 IoC 容器。
- WPF – 演示技术
- Unity IoC Container – 此容器可用于提供依赖注入。
- 数据绑定技术的基础知识
程序流程
此处使用了一个非常简单的示例用于演示。然而,当使用带有数据绑定的复杂控件时,代码可能会变得非常复杂。
我们示例中的窗体包含一个 textbox
用于显示客户姓名,以及一个 button AddCustomer
用于添加新客户。该项目包含两个文件夹:Common 和 Customer。Common 文件夹包含基类,Customer 文件夹包含 Model
、View
和 Presenter
类。这些代表了 Customer
。
创建和初始化 Unity IoC 容器
ContainerAccessor
类用于创建 Unity 依赖注入容器。我们可以配置 IoC 容器以从配置文件读取类型映射 (接口及其实现类的映射)。作为示例,我在配置文件中的
<unity>
<containers>
<container>
<types>
<type
type="CustomerInfoObserver.ICustomerDataSource, CustomerInfoObserver"
mapTo="CustomerInfoObserver.CustomerDataSource, CustomerInfoObserver"/>
<type
type="CustomerInfoObserver.ILogger, CustomerInfoObserver"
mapTo="CustomerInfoObserver.Logger, CustomerInfoObserver"/>
</types>
</container>
</containers>
</unity>
同样,我们可以注册任何服务到配置文件中,以便在应用程序中横向使用。例如,Microsoft Enterprise Library、数据访问服务等。容器将自动解析/创建定义类型的实例,并注入它们通过构造函数请求的依赖对象。
以下代码片段创建了容器实例,并通过读取配置文件中的 unity 部分来配置它。
_container = new UnityContainer();
//---Runtime Type binding - Read the Type mappings from unity
//container section from configuration file.
UnityConfigurationSection section =
(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(_container);
Unity IoC 和 Model-View-Presenter
所有 View 的基类 (泛型类型) – BaseView
通常,View 会获取 presenter
类的实例并调用方法来委托工作。由于在每个 View 中获取 presenter
的实例是很常见的,我们将它写在一个名为 BaseView
的基类中。BaseView
被定义为泛型类型,并接收两个参数:一个是 view
类型,另一个是 presenter
类型。基类使用 IoC 容器来解析 presenter
实例,并向其注入所需的 services。
public class BaseView<TView, TPresenter> : System.Windows.Window
where TPresenter : BasePresenter<TView>
where TView : class
{
public TPresenter Presenter { get; set; }
public BaseView()
{
ContainerAccessor containerAccessor = new ContainerAccessor();
UnityContainer container = containerAccessor.GetContainer();
if (container == null) throw new InvalidOperationException
("Cannot find UnityContainer");
Presenter = container.Resolve<TPresenter>();
Presenter.View = this as TView;
}
}
基类继承自 System.Windows.Window
,这使得所有从 BaseView
派生的类都可以成为一个窗口。
派生的 View 类 – CustomerView
WPF 窗口 (View) 继承自 BaseView
基类,该基类通过 Unity 完成了将 Presenter
注入 View
的所有工作。在实现强制接口 ICustomerView
的同时,CustomerView
还通过泛型参数指定了要使用的 Presenter。
在 Model-View-Presenter 模式中,presenter
始终使用接口与 view
进行通信。
public partial class CustomerView
: BaseView, ICustomerView
您还需要修改与 CustomerView
类关联的 XAML 代码,如下所示。此修改后的代码移除了对 System.Windows.Window
的继承,并使其继承自 CustomerInfoObserver:BaseView
。请注意 xmlns 命名空间中所需的更改。
<CustomerInfoObserver:BaseView x:Class="CustomerInfoObserver.CustomerView"
x:TypeArguments="CustomerInfoObserver:ICustomerView,
CustomerInfoObserver:CustomerPresenter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomerInfoObserver="clr-namespace:CustomerInfoObserver"
Title="Window1" Height="255" Width="341"
>
Window/View 接收 AddCustomer
按钮的点击事件,并将请求传递给 Presenter
。在这种情况下,我将请求委托给 presenter
。我也可以让 Presenter
类订阅各种 View 事件。
private void btnAddCustomer_Click(object sender, RoutedEventArgs e)
{
Presenter.OnAddCustomer(txtCustomerName.Text);
}
所有 Presenter 的基类 (泛型类型) – BasePresenter
作为每个 presenter
的基类,它通过名为 View 的属性暴露了 view。
派生的 Presenter 类 – CustomerPresenter
此类处理从 CustomerView
(View) 接收的所有请求。在初始化时,presenter
通过调用接口方法 SetCustomerModelAsDataContext()
将 Model 对象传递给 View。
View.SetCustomerModelAsDataContext(_customerModel);
CustomerView
类将此 Model 设置为整个窗口的 DataContext
,因此所有子 UI 元素都将使用相同的 DataContext
进行绑定。数据绑定的语法如下。代码可在 CustomerView
XAML 文件中找到。
Text="{Binding Path=CustomerName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}
在 MVP 中使用数据绑定可以节省大量编码工作,并有助于 Model 和 View 之间的数据同步。
Model 类 – CustomerModel
CustomerModel
类包含业务数据,并通过属性暴露。由于我们使用了数据绑定功能,并将窗口元素 (textbox
- txtCustomerName
) 与 CustomerModel
属性 (CustomerName
) 关联起来,因此对 txtCustomerName textbox
所做的更改将立即反映到 CustomerName
属性中。为了将更改反映回屏幕,CustomerModel
类必须实现 INotifyPropertyChanged
接口。INotifyPropertyChanged
接口将通知 View Model 值已更改,并可以更新 UI。
INotifyPropertyChanged
接口也由 Embassy 中的 BusinessEntity
类实现。
注意:代码包含在附加的 zip 文件中。
历史
- 2008年12月25日:初始版本