Prism、MEF、MVVM 设计模式示例应用程序。






2.78/5 (6投票s)
本文简要介绍了使用 Prism、MVVM 设计模式构建复合应用程序。
引言
我在网上查找了使用 Prism 的基本应用程序示例,并获得了一些例子。因此,我想写一个这样的应用程序,供我这样的新用户参考。
背景
设计模式用于为经常出现相似类型的问题提供通用且可重用的解决方案。在众多设计模式中,有 Prism 和 MVVM。
在开始示例项目之前,请允许我简要介绍上面提到的 Prism、MEF 和 MVVM 这些术语。
Prism:Prism 主要为程序员提供指导,以开发更具内聚性且松耦合的应用程序。基于 Prism 概念构建的架构随着时间的推移更加健壮且易于维护。
有关 Prism 概念的详细信息,请访问以下链接:
http://www.intertech.com/Blog/7-things-you-need-to-know-before-starting-a-prism-wpf-project/
MVVM:Model-View-ViewModel 是 MVVM 首字母缩写的术语。此设计模式与 WPF 自然融合,并因此与 Prism 融合,因为它对数据绑定、命令和行为提供了强大的支持。
访问以下链接了解 MVVM 的详细信息:
https://msdn.microsoft.com/en-in/library/hh848246.aspx
MEF:Managed Extensibility framework 是 Microsoft 迈出的进一步一步,旨在构建轻量级、可扩展的应用程序,避免模块之间的硬依赖。
https://msdn.microsoft.com/en-us/library/dd460648(v=vs.110).aspx
解决方案的逻辑分离
在进行编码之前,我建议创建一个高层次的结构并对涉及的项目进行分类。以下是我采用的解决方案:
上面描述的
1. **Framework Extended** 是 WPF 库,用于扩展任何 WPF 控件。或将框架相关项保存在公共位置。
2. **InterfacesAndBaseClasses** 是一个类库,其中包含所有接口和基类(抽象类),可用于导入目的。Solutions - Prism_Sample 和 ViewsAndViewModels 相互不引用,因为前者将通过 MEF 访问后者,因此需要导入/导出的接口和类保存在公共位置。
3. **Prism_Sample** 是解决方案组中的可执行项目。此解决方案是一个 WPF 应用程序。在此解决方案中,我将在 App.Xaml 中实现 Bootstrapper。
4. **ViewsAndViewModels** 是一个 WPF 库。在这里,我放置了所有 Views - xamls 及其 ViewModels 用于绑定。在此解决方案中,我还放置了 IModule 实现。
Using the code
以下是实现代码。
Bootstrapper 类
class BootStrapperClass:MefBootstrapper
{
MEFImplementation _MefImplementation = new MEFImplementation();
protected override void ConfigureAggregateCatalog()
{
_MefImplementation.LoadAssembly();
this.AggregateCatalog = _MefImplementation.AggregateCatalog;
}
protected override System.Windows.DependencyObject CreateShell()
{
return (_MefImplementation.InstanceShell.CreateInstance(_MefImplementation.InstanceViewModel) as System.Windows.Window);
}
protected override void InitializeShell()
{
try
{
base.InitializeShell();
App.Current.MainWindow = this.Shell as System.Windows.Window;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
在上面的摘录中,**MEFImplementation** 是我为实现 MEF 而自行声明的类,请参阅附加的 zip 解决方案了解详情。此 Mef Implemenation 类用于 Bootstrapper 类,以在运行时动态获取 shell.xaml 及其视图模型的对象。Bootstrapper 的实现如上所示。
MEFImplementation 类
1. **Import 属性:**在要使用导出类型的解决方案中使用 Import 属性。
public class MEFImplementation
{
[Import(typeof(IShell))]
public IShell InstanceShell = null;
Import(typeof(BaseViewModelClass))]
public BaseViewModelClass InstanceViewModel = null;
public CompositionContainer Container { get; set; }
public AggregateCatalog AggregateCatalog { get; set; }
public AssemblyCatalog AssemblyCatalog { get; set; }
public MEFImplementation()
{ }
public void LoadAssembly( )
{
AssemblyCatalog = new AssemblyCatalog(ConfigurationManager.AppSettings["ViewsAndViewModels"]);
this.AggregateCatalog = new AggregateCatalog(AssemblyCatalog);
this.Container = new CompositionContainer(this.AggregateCatalog);
Container.ComposeParts(this);
}
public void GetInstanceOfShell()
{
InstanceShell = this.Container.GetExportedValue<IShell>();
}
public void GetInstanceOfViewModel()
{
InstanceViewModel = this.Container.GetExportedValue<BaseViewModelClass>();
}
}
Shell.Xaml
您的 Shell.xaml 是容器 xaml,所有其他 UI(Xaml)将在其提供的相应区域加载。请参阅 ViewsAndViewModels 解决方案中的 Shell.xaml。
要点
1. Region Manager:一个区域可以有多个激活视图,也可以只有一个激活视图。区域中的多个激活视图允许同时显示多个视图。而在单一激活视图中,您必须先停用已激活的视图,然后再激活新视图。Region Manager 的多个视图激活或单一视图激活取决于您在 Shell.xaml 中选择的容器控件。例如:使用 ContentControl 进行单一视图激活,使用 ItemControl 进行多个视图激活。
<ContentControl region:RegionManager.RegionName="{x:Static reg:RegionConstants.ModuleRegion }" Margin="5" BorderThickness="1" BorderBrush="Black" Grid.Column="1" HorizontalAlignment="Left" Width="325" Height="450" />
我使用了 ContentControl 在我的 xaml 中,并遵循了上述要点。
2. CommandBindings:使用 Command bindings 与 ViewModel 进行交互。我实现了派生自 ICommand 的 RelayCommands 用于事件绑定。Xaml 的代码隐藏不应用于编写任何代码。而应仅用于将 DataContext 分配给 ViewModel。
<local:ZComboBox Margin="3" Style="{StaticResource RoundCornerStyle}" Width="150" x:Name="MyTabControl" HorizontalAlignment="Left" DisplayMemberPath="ModuleName"
SelectedValue="{Binding MenuItemSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionChangeInvoke ="{Binding TabSelectionChange, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding TabControlCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ModuleType">
</local:ZComboBox>
您还需要实现 Microsoft 提供的 ICommand 接口,为绑定到 ViewModel 中属性的每个命令委托事件处理程序。为此,我创建了另一个类 **RelayCommands**:
public class RelayCommand:ICommand
{
public RelayCommand(Func<object,bool> validateExecution, Action<object,object> executeCommand)
{
ValidateExecution = validateExecution;
ExecuteCommand = executeCommand;
}
public RelayCommand( Action<object,object> executeCommand)
{
// ValidateExecution = validateExecution;
ExecuteCommand = executeCommand;
}
public Func<object, bool> ValidateExecution { get; set; }
public Action<object,object> ExecuteCommand { get; set; }
bool ICommand.CanExecute(object parameter)
{
if (this.ValidateExecution != null)
{
return this.ValidateExecution(parameter);
}
else return true;
}
event EventHandler ICommand.CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
void ICommand.Execute(object sender)
{
this.ExecuteCommand(sender, null);
}
}
我继承了我的 ViewModels 的基类 BaseViewModelClass(自建类),以便在导入时使我的运行时初始化类型转换安全。
在我的 ViewModels 上,我添加了 Export 属性,其目的是如下所述。
3. Export 属性:当您想将类型导出到程序集外部时,请使用 Export 属性。同时,不要将导出类型的库的引用添加到导入该类型的解决方案中。
我的 Shell 的 ViewModel 看起来像这样:
[Export( typeof(BaseViewModelClass))]
public class ShellLoaderViewModel:BaseViewModelClass
{
# region Propeties
public ICommand TabSelectionChange
{
get;
set;
}
public ICommand ViewActivated { get; set; }
private IRegionManager _RegionManager;
public IRegionManager RegionManager
{
get
{
return this._RegionManager;
}
set
{
this._RegionManager = value;
this.RaisePropertyChange();
}
}
private string menuItemSelected;
public string MenuItemSelected
{
get
{
return this.menuItemSelected;
}
set
{
this.menuItemSelected = value;
this.RaisePropertyChange();
}
}
private ObservableCollection<ViewsAndViewModels.ModulesType> tabControlCollection;
public ObservableCollection<ViewsAndViewModels.ModulesType> TabControlCollection
{
get
{
return this.tabControlCollection;
}
set
{
this.tabControlCollection = value;
this.RaisePropertyChange();
}
}
private IUnityContainer _Container;
#endregion
#region Ctor
public ShellLoaderViewModel( )
{
this.TabControlCollection = new ObservableCollection<ViewsAndViewModels.ModulesType>();
this.TabControlCollection.Add(new ModulesType() { ModuleName = "Home", ModuleType = "HomeModuleImplementation" });
this.TabControlCollection.Add(new ModulesType() { ModuleName = "About", ModuleType = "AboutModule" });
RegisterCommands();
}
#endregion
}
......
稍后,当我的 Shell 加载并且其 ViewModel 通过 App.Xaml 中的 Bootstrapper 类设置好后:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
try
{
base.OnStartup(e);
BootStrapperClass bootStrapper = new BootStrapperClass();
bootStrapper.Run(true);
App.Current.MainWindow.Show();
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
}
}
4. RegionManager 的实例:Shell ViewModel 需要 Shell.xaml 上正在运行的 RegionManager 的当前实例。
this.RegionManager= ServiceLocator.Current.GetInstance<IRegionManager>();
5. Events:我自行创建了一个 Events 类,用于显式发布和移除区域中的模块。
当从 Shell 调用事件时,触发您的模块加载。我在我的 Shell ViewModel 中编写了以下代码:
private void TabSelectionChangeEventHandler(object param, object args)
{
if (this.menuItemSelected == null)
{
return;
}
CommandUtility.CreateNewEventsInstance();
IModule myModule = FactoryClass.CreateInstance(this.MenuItemSelected, this.RegionManager);
if (CommandUtility.MyEvent != null)
{
CommandUtility.MyEvent.onPublish(this, myModule);
}
}
在这里,上面的代码中,**CommandUtility** 是我为在 Shell 中发布新模块而创建的类。
我的 CommandUtility 类看起来像:
public class CommandUtility
{
private ViewsAndViewModels.Events.Events _MyEvent;
public static ViewsAndViewModels.Events.Events MyEvent = null;
public static ViewsAndViewModels.Events.Events GetInstanceofEvents()
{
if (MyEvent == null)
{
MyEvent = new Events.Events();
}
return MyEvent;
}
public static void CreateNewEventsInstance()
{
MyEvent = null;
MyEvent = new Events.Events();
}
}
6. IModules:这是 Prism 提供的接口,所有模块都将实现此接口。下面是一个模块的示例:
[Module (ModuleName="HomeModule")]
public class HomeModuleImplementation:IModule
{
private CompositionContainer _Container = new CompositionContainer();
private IRegionManager _RegionManager;
public HomeModuleImplementation( IRegionManager regionManager)
{
// this._Container = container;
this._RegionManager = regionManager;
Events.Events myEvent = CommandUtility.GetInstanceofEvents();
myEvent.Publish += PublishModule;
}
~HomeModuleImplementation()
{
Events.Events myEvent = CommandUtility.GetInstanceofEvents();
myEvent.Publish -= PublishModule;
}
public void Initialize()
{ var viewsCollections = this._RegionManager.Regions[RegionConstants.ModuleRegion].ActiveViews;
if (viewsCollections != null)
{
foreach (var view in viewsCollections)
{
this._RegionManager.Regions[RegionConstants.ModuleRegion].Remove(view);
}
}
this._RegionManager.Regions["ModuleRegion"].Add(new ViewsAndViewModels.View.HomePage(new HomeViewModel()), "HomeModule");
}
private void PublishModule(object sender, IModule moduleType)
{
if (moduleType != null)
{
IRegion allRegions = this._RegionManager.Regions[RegionConstants.ModuleRegion];
var views = allRegions.ActiveViews;
if (views != null)
{
foreach (var view in views)
{
allRegions.Deactivate(view);
}
}
var viewToActivate = this._RegionManager.Regions[RegionConstants.ModuleRegion].GetView("HomeModule");
this._RegionManager.Regions[RegionConstants.ModuleRegion].Activate(viewToActivate);
}
}}
上面模块类中的 IModule 实现中,您需要手动激活或停用区域中的视图,因为我们选择了区域中的单一激活模块,如上所述。代码中上面高亮的部分首先从区域管理器中停用该区域已激活的所有视图,然后将一个新视图(UI)映射到区域并激活它。
上面提供的摘录可能不够详尽和解释充分,作者请求您通过解决方案来全面了解 PRISM、MEF 和 MVVM。
历史
2/04/2016 --- 初始发布。