本章介绍mvfm的详细使用方法,并提供win8下的demo以供参考。
用VS2012建立工程,选择windows应用商店中的网格应用程序。本次的demo就是将这个vs2012自带的示例改造成mvfm方式运行。
由于这个示例已经有了view和model层(SampleDataSource类),这两块就不需要重写了,我们直接开始构造fuction model。
首先引入Windows.UI.Interactivity类库,这个库是微软提供的一个扩展库,需要从NuGet下载安装。
然后再引入mvfm库,这是mvfm开发模式所依赖的动态库。
构建function model,代码如下:
public class SampleFunctionModel : FunctionModelBase
{
#region Property
IEnumerable<SampleDataGroup> _groups;
public IEnumerable<SampleDataGroup> Groups
{
get
{
if (_groups == null)
_groups = SampleDataSource.GetGroups("AllGroups");
return _groups;
}
}
SampleDataGroup _currentGroup;
public SampleDataGroup CurrentGroup
{
get
{
return _currentGroup;
}
set
{
this.SetProperty(ref _currentGroup, value);
}
}
SampleDataItem _currentItem;
public SampleDataItem CurrentItem
{
get
{
return _currentItem;
}
set
{
this.SetProperty(ref _currentItem, value);
}
}
#endregion
#region Command
public ActionCommand<object> SetCurrentGroupCommand { get; private set; }
public ActionCommand<object> SetCurrentItemCommand { get; private set; }
#endregion
#region Command Function
void SetCurrentGroup(object parameter)
{
var Group = (parameter as ExCommandParameter).Parameter as SampleDataGroup;
if (Group != null)
CurrentGroup = Group;
}
void SetCurrentItem(object parameter)
{
var Item = (parameter as ExCommandParameter).Parameter as SampleDataItem;
if (Item != null)
{
CurrentItem = Item;
}
}
#endregion
#region Public Function
#endregion
#region Private Function
#endregion
#region protected Function
protected override void InitializeData()
{
SetCurrentGroupCommand = new ActionCommand<object>(SetCurrentGroup);
SetCurrentItemCommand = new ActionCommand<object>(SetCurrentItem);
}
#endregion
}
其中Groups是整个工程的数据源,Groups从SampleDataSource中获取,并交由view曾显示。mvfm中function model并不存储数据,只负责业务逻辑处理,function model仅保存临时数据。例如CurrentGroup和CurrentItem,这两个属性分别代表当先选择或显示的SampleDataGroup和SampleDataItem。还需要说明一点的时,有些UI逻辑放在页面里处理,并不放在function model中。像页面跳转这样的常用ui逻辑都放在界面中处理。
SetCurrentGroupCommand和SetCurrentItemCommand分别代表设置当前的SampleDataGroup命令和当前的SampleDataItem命令。将逻辑封装成命令进行交互称作命令模式,mvfm中view与function model的绝大部分交互都通过这种模式进行。
在InitializeData函数中指定了具体执行命令的函数,SetCurrentGroupCommand由SetCurrentGroup函数执行。所有的命令都是ICommand类型,而ActionCommand<T>则是将ICommand封装了,方便使用。
下面看看SetCurrentGroup中德代码。若界面上有参数传过来,都将是ExCommandParameter这种类型,下面是ExCommandParameter类的代码:
public class ExCommandParameter
{
/// <summary>
/// 事件触发源
/// </summary>
public object Sender { get; set; }
/// <summary>
/// 事件参数
/// </summary>
public object EventArgs { get; set; }
/// <summary>
/// 额外参数
/// </summary>
public object Parameter { get; set; }
/// <summary>
/// 额外参数 2
/// </summary>
public object Parameter2 { get; set; }
/// <summary>
/// 额外参数 3
/// </summary>
public object Parameter3 { get; set; }
}
其中Sender为发送着,比如控件之类的,EventArgs则是某些代理中德事件参数,比如这个命令是button的click触发的,则EventArgs是对应的RoutedEventArgs事件。Parameter则是附带额外的参数,最多可以带三个。
各位看到这里可能对function model还有些迷惑,等看完xaml中德处理在回头看就好理解了。
function model写完后在APP中声明 function model的资源:
<x:String x:Key="AppName">MvfmText</x:String>
这样就可以在其他的页面中使用function model中Command了。
首先看GroupedItemsPage页面,删光cs中德代码,仅保留构造函数
public sealed partial class GroupedItemsPage : MvfmText.Common.LayoutAwarePage
{
public GroupedItemsPage()
{
this.InitializeComponent();
}
}
在xaml页中声明数据源
<common:LayoutAwarePage
x:Name="pageRoot"
x:Class="MvfmText.GroupedItemsPage"
DataContext="{StaticResource SampleFunctionModel}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MvfmText"
xmlns:data="using:MvfmText.Data"
xmlns:common="using:MvfmText.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mvfm="using:Mvfm"
xmlns:i="using:Windows.UI.Interactivity"
mc:Ignorable="d">
DataContext就是数据源,数据从SampleFunctionModel中获取。特别注意 xmlns:mvfm="using:Mvfm"和 xmlns:i="using:Windows.UI.Interactivity"这两条语句,xaml与function将同过这两个命名空间进行。
然后声明网格所需要使用的数据资源
<Page.Resources>
<!--
此页所显示的分组项的集合,绑定到完整
项列表的子集,因为无法虚拟化组中的项
-->
<CollectionViewSource
x:Name="groupedItemsViewSource"
Source="{Binding Groups}"
IsSourceGrouped="true"
ItemsPath="TopItems"
d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
</Page.Resources>
其中Groups就是SampleFunctionModel中的Groups属性。
然后再造GridView:
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Grid.RowSpan="2"
Padding="116,137,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ItemClick">
<mvfm:EventArgNameCommandAction EventArgName="ClickedItem" Command="{Binding SetCurrentItemCommand}"/>
<local:NavigateAction NavigateName="MvfmText.ItemDetailPage"/>
<!--<mvfm:NavigateAction NavigateName="MvfmText.ItemDetailPage"/>-->
</i:EventTrigger>
</i:Interaction.Triggers>
</GridView>
GridView自带的属性设置就不说明了,重点介绍 <i:Interaction.Triggers>
<i:EventTrigger EventName="ItemClick">
<mvfm:EventArgNameCommandAction EventArgName="ClickedItem" Command="{Binding SetCurrentItemCommand}"/>
<local:NavigateAction NavigateName="MvfmText.ItemDetailPage"/>
</i:EventTrigger>
</i:Interaction.Triggers>
这几行语句。Triggers,Behavior,Action是mvfm所依赖的核心技术中德3个。Triggers为触发器,Behavior为行为附加,Action则负责执行某些命令。Triggers往往与Action相伴出现,通过控件中的某些行为或event触发执行Action。
这几行语句中EventTrigger指定由GridView中的ItemClick事件触发,触发的Action分别为NavigateAction和EventArgNameCommandAction。NavigateAction执行导航,NavigateName参数指定导航页,此Action触发后界面将直接跳转到ItemDetailPage页面。也就是说若点击了GridView中的某一项,界面就会跳转到ItemDetailPage页。EventArgNameCommandAction中的Command指定所要执行的命令,这里则指定了执行SampleFunctionModel中的SetCurrentItemCommand命令。EventArgName参数指定将EventArg中的哪个属性作为参数传递到Command中。
然后看下HeaderTemplate中的代码:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="1,0,0,6">
<Button
AutomationProperties.Name="Group Title"
Style="{StaticResource TextPrimaryButtonStyle}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" Margin="3,-7,10,10" Style="{StaticResource GroupHeaderTextStyle}" />
<TextBlock Text="{StaticResource ChevronGlyph}" FontFamily="Segoe UI Symbol" Margin="0,-7,0,10" Style="{StaticResource GroupHeaderTextStyle}"/>
</StackPanel>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<mvfm:ExInvokeCommandAction Command="{Binding SetCurrentGroupCommand, Source={StaticResource SampleFunctionModel}}" CommandParameter="{Binding}"/>
<local:NavigateAction NavigateName="MvfmText.GroupDetailPage"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
注意其中的这几行语句
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<mvfm:ExInvokeCommandAction Command="{Binding SetCurrentGroupCommand, Source={StaticResource SampleFunctionModel}}" CommandParameter="{Binding}"/>
<local:NavigateAction NavigateName="MvfmText.GroupDetailPage"/>
</i:EventTrigger>
</i:Interaction.Triggers>
原理与前面是一样的,主要说明下ExInvokeCommandAction, ExInvokeCommandAction中Command指定的是完整的Command路径,这样的好处在于不管Action在何处声明,都可以执行到functiong model中的命令。 其中CommandParameter则是将自身的数据源作为参数传输到了执行对应命令的函数中,此命令对应的是如下函数 void SetCurrentGroup(object parameter)
{
var Group = (parameter as ExCommandParameter).Parameter as SampleDataGroup;
if (Group != null)
CurrentGroup = Group;
}
剩下两个页面就不额外说明了,原理差不多,运行后你会发现执行效果和原来没有任何差别。由于page对应的.cs中不需要写人和代码,这将大大减轻我们的工作量,并且这样的边写方式易于修改,当需求频繁变化是也能轻松应对。
并且我们还有两个原model中提供的两个函数没有用到,GetGroup和GetItem完全没用用武之地了,因为我们可以从页面中直接获取对应的数据,并在另一个页面中使用,这样省去了页面间的参数传递以及部分数据处理逻辑。这样轻松的编程方式
你还有什么理由拒绝呢?以后我会介绍mvfm的高级用法,熟练使用以后编写代码效率将会大大提高。
示例代码在本人空间资源中