在csdn上承诺要分享一个使用Silverlight+MVVM+MEF实现一个项目,都过去一个月了,我还没开始。。。
最近试着去参考了很多文章和例子,今天就和大家分享一个简单的Sell Through系统,销售管理的那种。
代码下载:http://www.n-pei.com/download/Nicholas.SilverlightSellThrough.zip
使用到的MVVM framework:
http://mvvmlight.codeplex.com/
如果你对MEF没概念,建议看看这里:
开发环境介绍下:
VS2010英文旗舰版。
MS SQL Server2008 。
一个做零售类型的公司,最基本的几个主档有哪些? 首先是产品(product),是的,它是基础。然后围绕它的有商店(Shop),销售人员(sales),价格(Price),代理商(dealer),以及区域(主要有Province和City)。OK,有了这些基本的思想我们就可以看看它们的关系了。
数据模块的创建(Entity Data Model )
首先我们需要创建一个空的ADO.NET Entity Data Model,然后添加上面的Model进去。
如果你还没来得及学习Entity Framework 4我建议你可以从这里开始学点基础。
上面的Sellthrough.edmx创建好以后,接下来我们来添加Model以及它们的关系。
在设计模式下的空白处右键,点击添加可以看到添加Model的页面,我们添加一个Model叫Products。
接下来是为Products这个Model添加属性字段。分析下它主要应该有code,name,launchdate,createuser,createdate。添加到上面的Model后:
接着是创建其它表,都是以Products为中心,具体的创建过程我这里就不详细描述了。
创建好Models后,我们需要创建model之间的对应关系。以省份和城市之间1:N的关系创建为例说明如何创建:
在Entity model的空白处右键,选择’Add->association’,如下图:
选择好第一个entity和第二个entity,默认的是1:N关系,就直接选择OK,创建完毕。
最终创建好的Entity data model如下:
那数据库呢?我们还没有数据库呢。还是得找空白地方,右键,选择’Generate Database from Model…’,如下图:
按照提示一步一步操作,就可以生成Sellthrough.edmx.sql,在选择数据库时注意下,因为我们需要新建一个数据库所以要选择New connection:
输入数据库名称’sellthrough’,提示你是否新增数据库,选择Yes:
完成上面的操作后,数据库脚本就会自动生成了。如下图:
接下来在SQL Server Manage Studio中打开这个脚本,执行下,数据库就OK了。
Domain Service的创建
在创建好数据库后,接下来我们来创建Domain Services。
有点点击这个project,选择新增,找到Domain Service控件,命名为SellThroughService,点击下一步:
选中这些实体后点击OK,Domain Service就创建完成。
到这里服务器端的工作基本上完成了。
接下来我们来看看如何设计客户端的结构。
Nicholas.SilverlightSellThrough.Data 是服务器端数据源的扩展部分,Data.Web工程中的WCF Ria Service会连接到这个工程。同时它还包含一些验证方式的扩展功能等。
例如,Product,我们需要有它字段的验证功能,这些功能就写在Nicholas.SilverlightSellThrough.Data中。
Nicholas.SilverlightSellThrough.Common 主要存放了一些通用的方法和接口等。
例如Model的接口, ISellThroughModel.cs。
ViewModelTypes.cs中以常量方式定义了所有的ViewModel。方便后面的工程队ViewModel部分的操作。
Nicholas.SilverlightSellThrough.Model 是为MVVM模式定义的Model部分。
目前我们先定义AddNewProduct方法,RemoveProduct,SaveChangeAsync来实现产品的增删改操作。
Nicholas.SilverlightSellThrough.ViewModel:MVVM模式中的ViewModel部分定义在这个工程中。
目前我只定义了三个ViewModel:
它们分别实现全部产品的展示,新增产品和修改产品的功能。
最后一个是Nicholas.SilverlightSellThrough:很明显了它就是MVVM中存放View的部分,你可以把所有的View放到View文件夹。
了解了整个架构后,
给大家介绍几个MVVM Light的概念:
1. RelayCommand:
Silverlight 4中有了Command这个概念,使得MVVM的模式更加容易。你只需要在ViewModel中定义好你的RelayCommand,然后在View中通过Command来绑定ViewModel中定义好的RelayCommand。
2.Messager
在MVVM Light Toolkit中有个Messager类,就是靠它来让ViewModel和View来通信的。在这个项目中我们定义了AppMessages类来作为通用的一个通信类。
using System;
using System.IO;
using System.Windows.Media.Imaging;
using GalaSoft.MvvmLight.Messaging;
using Nicholas.SilverlightSellThrough.Data.Web;
namespace Nicholas.SilverlightSellThrough.Common
{
/// <summary>
/// class that defines all messages used in this application
/// </summary>
public static class AppMessages
{
enum MessageTypes
{
RaiseError,
PleaseConfirm,
EditProduct,
SubmitChanges,
StatusUpdate,
CancelChanges,
ExpriedProduct
}
public static class RaiseErrorMessage
{
public static void Send(Exception ex)
{
Messenger.Default.Send < Exception > (ex, MessageTypes.RaiseError);
}
public static void Register( object recipient, Action < Exception > action)
{
Messenger.Default.Register < Exception > (recipient,MessageTypes.RaiseError,action);
}
}
public static class PleaseConfirmMessage
{
public static void Send(DialogMessage dialogMessage)
{
Messenger.Default.Send < DialogMessage > (dialogMessage,MessageTypes.PleaseConfirm);
}
public static void Register( object recipient, Action < DialogMessage > action)
{
Messenger.Default.Register < DialogMessage > (recipient, MessageTypes.PleaseConfirm, action);
}
}
public static class StatusUpdateMessage
{
public static void Send(DialogMessage dialogMessage)
{
Messenger.Default.Send < DialogMessage > (dialogMessage, MessageTypes.StatusUpdate);
}
public static void Register( object recipient, Action < DialogMessage > action)
{
Messenger.Default.Register < DialogMessage > (recipient, MessageTypes.StatusUpdate, action);
}
}
public static class EditProductMessage
{
public static void Send(Products product)
{
Messenger.Default.Send < Products > (product, MessageTypes.EditProduct);
}
public static void Register( object recipient, Action < Products > action)
{
Messenger.Default.Register < Products > (recipient, MessageTypes.EditProduct, action);
}
}
public static class SubmitChangesMessage
{
public static void Send()
{
Messenger.Default.Send < Boolean > ( true , MessageTypes.SubmitChanges);
}
public static void Register( object recipient, Action < Boolean > action)
{
Messenger.Default.Register < Boolean > (recipient,MessageTypes.SubmitChanges,action);
}
}
public static class CancelChangesMessage
{
public static void Send()
{
Messenger.Default.Send < Boolean > ( true , MessageTypes.CancelChanges);
}
public static void Register( object recipient, Action < Boolean > action)
{
Messenger.Default.Register < Boolean > (recipient,MessageTypes.CancelChanges,action);
}
}
public static class ExpriedProduct
{
public static void Send()
{
Messenger.Default.Send < Boolean > ( true , MessageTypes.ExpriedProduct);
}
public static void Register( object recipient, Action < Boolean > action)
{
Messenger.Default.Register < Boolean > (recipient,MessageTypes.ExpriedProduct,action);
}
}
}
}
3. ICleanup接口:
当显示某个View时,先调用Cleanup方法清除数据:
在View中显示所有产品:
首先在数据库中插入几条product数据:
我们的目的就是使用MVVM模式把这些数据显示在DataGrid中。
第一步需要在Model中添加GetAllProductsAsync方法:
public void GetAllProductsAsync()
{
PerformQuery<Products>(Context.GetProductsQuery(),GetAllProductsComplete);
}
第二步是在ViewModel工程中添加AllProductsViewModel:
在public Commands部分用到的就是上面提到的RelayCommand方法,submitCommand,CancelCommand等。它的作用好比是你在一个xaml页面中添加几个
Submit按钮和Cancel按钮,然后写在它后台的事件。
Load products的代码在ViewModel的构造函数中:
注意这里有个selectionchangedCommand,从名字上看得出来它主要是为了让View页面知道当前选中的是哪个product。
第三步也是最后一步就是添加View:
创建一个View,添加一个datagrid,数据绑定如下:
注意中间有个trigger,当selectionChanged事件发生时,执行ViewModel中的SelectionChangedCommand方法。
到目前为止我们还未用到MEF,也轮到它登场了。它让程序的扩展性真的是非常好。
在view的后台代码.cs文件中,首先需要使用MEF把AllProductsViewModel导入到View中:
[Import(ViewModelTypes.AllProductsViewModel)]
public object ViewModel
{
set
{
DataContext = value;
}
}
然后在构造函数中使用CompositionInitializer来把ViewModel加载到页面。
if (!ViewModelBase.IsInDesignModeStatic)
{
// Use MEF to load the view model
CompositionInitializer.SatisfyImports(this);
}
最后一步别忘了,使用cleanup接口把当前的这个ViewModel从Messager管道中移除。
运行下看看效果:
上面有提到使用那个trigger来设定当前选中的product。那么我们可以使用这个trigger来让当鼠标选中某一行数据时下面显示它的详细信息,并且我们可以编辑它,也就是Editproduct部分。
也是添加Model部分的代码,ModelView部分的代码,最后添加一个View。
效果:
修改后保存信息,就可以同步更新到服务器端。
最后是新增页面:
具体的实现方法你可以看我提供的源代码。