使用MVVM-Sidekick开发Universal App(二)
上一篇文章已经建立了基本的实体类,并且搞定了多语言的问题,以后在app里用字符串的时候就可以从资源文件中取了。现在继续进行。
一、添加一个页面
CurrencyExchanger首页是一个货币兑换的列表,这个列表比较复杂,我们先不管,先从简单的页面做起。首先要有一个添加货币的页面,显示所有可添加的货币列表。在WP项目中右键添加一个页面,命名为AddCurrencyPage.xaml。
然后MVVM-Sidekick自动添加了一些东西:除了这个页面之外,在WP项目的ViewModels目录中添加了一个AddCurrencyPage_Model.cs文件,在StartUps目录中添加了一个AddCurrencyPage.cs文件。打开看一下:
public static void ConfigAddCurrencyPage()
{
ViewModelLocator<AddCurrencyPage_Model>
.Instance
.Register(context =>
new AddCurrencyPage_Model())
.GetViewMapper()
.MapToDefault<AddCurrencyPage>(); }
看到了吧,所有的View和ViewModel都要进行一下配置才能用。第一次用MVVM-Sidekick的时候我没装vs扩展,直接引用的类库,自己手动建立VM,不知道要进行配置,结果死活绑定不上。问作者才知道有这么个东西,所以一定要装vs扩展插件才可以享受到这个便利。
二、实现第一个Binding列表
在AddCurrencyPage_Model.cs文件中,通过使用propvm代码段的方式添加以下两个属性:
public string AppName
{
get { return _AppNameLocator(this).Value; }
set { _AppNameLocator(this).SetValueAndTryNotify(value); }
}
#region Property string AppName Setup
protected Property<string> _AppName = new Property<string> { LocatorFunc = _AppNameLocator };
static Func<BindableBase, ValueContainer<string>> _AppNameLocator = RegisterContainerLocator<string>("AppName", model => model.Initialize("AppName", ref model._AppName, ref _AppNameLocator, _AppNameDefaultValueFactory));
static Func<string> _AppNameDefaultValueFactory = () => { return AppResources.AppName; };
#endregion public string PageName
{
get { return _PageNameLocator(this).Value; }
set { _PageNameLocator(this).SetValueAndTryNotify(value); }
}
#region Property string PageName Setup
protected Property<string> _PageName = new Property<string> { LocatorFunc = _PageNameLocator };
static Func<BindableBase, ValueContainer<string>> _PageNameLocator = RegisterContainerLocator<string>("PageName", model => model.Initialize("PageName", ref model._PageName, ref _PageNameLocator, _PageNameDefaultValueFactory));
static Func<string> _PageNameDefaultValueFactory = () => { return AppResources.AddCurrency_PageName; };
#endregion
注意,默认值可以直接返回资源中的本地化字符串:return AppResources.AddCurrency_PageName;
AddCurrency_PageName这个资源是事先在Strings\en-US\Resources.resw资源文件中定义好的,以后就不再详述了。
把这两个属性绑定到页面标题区域就可以了,这样可以根据用户选择的语言显示本地化的语言。
<StackPanel Grid.Row="0" Margin="19,0,0,0">
<TextBlock Text="{Binding AppName}" Style="{ThemeResource TitleTextBlockStyle}" Margin="0,12,0,0"/>
<TextBlock Text="{Binding PageName}" Margin="0,-6.5,0,26.5" Style="{ThemeResource HeaderTextBlockStyle}" CharacterSpacing="{ThemeResource PivotHeaderItemCharacterSpacing}"/>
</StackPanel>
然后需要有一个显示货币的列表:
/// <summary>
/// 货币列表
/// </summary>
public ObservableCollection<CurrencyItem> CurrencyItemList
{
get { return _CurrencyItemListLocator(this).Value; }
set { _CurrencyItemListLocator(this).SetValueAndTryNotify(value); }
}
#region Property ObservableCollection<CurrencyItem> CurrencyItemList Setup
protected Property<ObservableCollection<CurrencyItem>> _CurrencyItemList = new Property<ObservableCollection<CurrencyItem>> { LocatorFunc = _CurrencyItemListLocator };
static Func<BindableBase, ValueContainer<ObservableCollection<CurrencyItem>>> _CurrencyItemListLocator = RegisterContainerLocator<ObservableCollection<CurrencyItem>>("CurrencyItemList", model => model.Initialize("CurrencyItemList", ref model._CurrencyItemList, ref _CurrencyItemListLocator, _CurrencyItemListDefaultValueFactory));
static Func<ObservableCollection<CurrencyItem>> _CurrencyItemListDefaultValueFactory = () => { return new ObservableCollection<CurrencyItem>(); };
#endregion
此处需要注意,建议把默认返回值改为new出来的一个列表,不然有时候忘了初始化就使用会报错。
给列表赋值的方法中哪呢?往下找到被注释掉的一大坨代码,Life Time Event Handling这个region里:
///// <summary>
///// This will be invoked by view when the view fires Load event and this viewmodel instance is already in view's ViewModel property
///// </summary>
///// <param name="view">View that firing Load event</param>
///// <returns>Task awaiter</returns>
//protected override Task OnBindedViewLoad(MVVMSidekick.Views.IView view)
//{
// return base.OnBindedViewLoad(view);
//}
嗯貌似就是这个了,页面载入完成时触发的事件,可以在这里面对列表赋值,把注释去掉,代码加一行:
/// <summary>
/// This will be invoked by view when the view fires Load event and this viewmodel instance is already in view's ViewModel property
/// </summary>
/// <param name="view">View that firing Load event</param>
/// <returns>Task awaiter</returns>
protected override Task OnBindedViewLoad(MVVMSidekick.Views.IView view)
{
CurrencyItemList = new ObservableCollection<CurrencyItem>(Context.Instance.AllCurrencyItemList);
return base.OnBindedViewLoad(view);
}
这样货币列表就有数据了。
现在中页面中放一个ListView控件来显示数据,把ItemsSource属性绑定到货币列表CurrencyItemList上:
<Grid Grid.Row="1" x:Name="ContentRoot" Margin="19,9.5,19,0">
<ListView x:Name="list" ItemsSource="{Binding CurrencyItemList}" />
</Grid>
好了怎么来看我们的成果呢?需要从MainPage导航到AddCurrencyPage对吧,对了可以用一个appbar来导航。
三、WP8.1的appbar与导航
WP8以前用的appbar是无法支持绑定的,用着不太方便,WP8.1与Win8.1进行了统一,可以支持Command绑定了。顺便说一句,MVVM-Sidekick为WP8的appbar也提供了一种绑定Command的方法,但此处不是重点,略过不表(韦恩卑鄙会大怒的哈哈哈好不容易写出来了我给略过了)
现在看看Universal App中怎么添加appbar。WP8.1中已经把appbar改为CommandBar了,具体使用方法可见msdn:http://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/hh781232.aspx
我们先来实现导航的Command,可以用propcmd的代码段快速生成一个Command,在MainPage_Model.cs文件中添加以下代码:
/// <summary>
/// 导航到添加货币页面
/// </summary>
public CommandModel<ReactiveCommand, String> CommandNavToAddCurrency
{
get { return _CommandNavToAddCurrencyLocator(this).Value; }
set { _CommandNavToAddCurrencyLocator(this).SetValueAndTryNotify(value); }
}
#region Property CommandModel<ReactiveCommand, String> CommandNavToAddCurrency Setup
protected Property<CommandModel<ReactiveCommand, String>> _CommandNavToAddCurrency = new Property<CommandModel<ReactiveCommand, String>> { LocatorFunc = _CommandNavToAddCurrencyLocator };
static Func<BindableBase, ValueContainer<CommandModel<ReactiveCommand, String>>> _CommandNavToAddCurrencyLocator = RegisterContainerLocator<CommandModel<ReactiveCommand, String>>("CommandNavToAddCurrency", model => model.Initialize("CommandNavToAddCurrency", ref model._CommandNavToAddCurrency, ref _CommandNavToAddCurrencyLocator, _CommandNavToAddCurrencyDefaultValueFactory));
static Func<BindableBase, CommandModel<ReactiveCommand, String>> _CommandNavToAddCurrencyDefaultValueFactory =
model =>
{
//var resource = "NavToAddCurrency"; // Command resource
var resource = AppResources.AppBarButton_Add;
var commandId = "NavToAddCurrency";
var vm = CastToCurrentType(model);
var cmd = new ReactiveCommand(canExecute: true) { ViewModel = model }; //New Command Core
cmd
.DoExecuteUITask(
vm,
async e =>
{
//Todo: Add NavToAddCurrency logic here, or
//await MVVMSidekick.Utilities.TaskExHelper.Yield();
await vm.StageManager.DefaultStage.Show(new AddCurrencyPage_Model());
}
)
.DoNotifyDefaultEventRouter(vm, commandId)
.Subscribe()
.DisposeWith(vm);
var cmdmdl = cmd.CreateCommandModel(resource);
cmdmdl.ListenToIsUIBusy(model: vm, canExecuteWhenBusy: false);
return cmdmdl;
};
#endregion
这一大坨代码里起主要作用的是:
await vm.StageManager.DefaultStage.Show(new AddCurrencyPage_Model());
使用StageManager不但可以实现页面间的导航,还可以实现页面某区域的导航,非常方便。
这里来简单解释一下,resource是这个command携带的一个资源,可以在里面放字符串或任何东西,用于UI的绑定,比如按钮显示的文本。我在这里把默认的字符串改为了AppResources.AppBarButton_Add也是为了实现多语言。vm是当前的ViewModel的实例,可以引用VM中的属性或方法。每个VM都有一个IsUIBusy属性来标识当前UI是否处于Busy状态,默认的DoExecuteUIBusyTask方法是向页面通知这个方法比较费时,页面可以显示一个转圈圈。但据我测试如果在导航的时候使用DoExecuteUIBusyTask,返回的时候会导致原页面还处于Busy状态,导致command无法继续执行,因此把DoExecuteUIBusyTask改为DoExecuteUITask就可以了。
cmdmdl.ListenToIsUIBusy(model: vm, canExecuteWhenBusy: false);这句是让command监听页面的IsUIBusy状态,如果Busy的时候就不能执行。一般会设置为false,也可以根据需要设置为true,就是不管页面是否处于Busy状态都可以继续执行这个command。
下一步把这个command绑定到按钮上。
在MainPage.xaml中添加一下代码,其实只要把注释部分取消就可以了:
<Page.DataContext>
<Binding RelativeSource="{RelativeSource Mode=Self}" Path="ViewModel"/>
</Page.DataContext>
<mvvm:MVVMPage.ViewModel>
<Binding Source="{StaticResource DesignVM}" />
</mvvm:MVVMPage.ViewModel>
<mvvm:MVVMPage.BottomAppBar>
<CommandBar d:DataContext="{StaticResource DesignVM }">
<AppBarButton Icon="AllApps" Label="{Binding CommandNavToAddCurrency.Resource}" Command="{Binding CommandNavToAddCurrency}" />
</CommandBar>
</mvvm:MVVMPage.BottomAppBar>
看到了吧,在Label的属性我绑定到了CommandNavToAddCurrency.resource,而resource是根据用户语言显示的,所以用户会看到一个本地化的文本。resource里不但可以放文本,还可以放icon图片的名称,实现动态改变按钮图标的功能。具体实现以后再讲。
现在来测试一下,运行程序,点击appbar,可以显示到添加货币的页面了。
四、使用Blend设计模板
没有列表项模板的话只是展示了一堆实体列表,现在需要把model用模板展现出来。这时候就要请Blend出马了。在WP项目上右键选择使用Blend打开。打开AddCurrencyPage.xaml文件。如果提示vm:AddCurrencyPage_Model不存在之类的错误的话,把项目重新编译一遍再重新打开一般就可以解决了。
使用Blend的时候一定要使用设计时视图,可以方便的查看当前显示的是什么样子,而不用蒙着改。这也是提高工作效率的重点。现在为VM添加设计时支持:
打开AddCurrencyPage_Model.cs文件,默认是没有构造函数的,添加以下代码:
public AddCurrencyPage_Model()
{
if (IsInDesignMode)
{
Context.Instance.Init();
CurrencyItemList = new ObservableCollection<CurrencyItem>(Context.Instance.AllCurrencyItemList);
//AppName = "CurrencyExchanger";
//PageName = "add currency";
}
}
IsInDesignMode是标识当前是设计模式,这样Blend会找到里面的代码执行,具体到这个页面就是初始化货币列表,这样就可以中设计模板的时候看到实际数据了。在Blend的listview控件上右键-编辑其他模板-编辑生成的项-创建空白项,输入项模板名称ItemDataTemplate,Blend会自动添加一个项模板进入模板编辑模式,然后就可以方便的进行绑定了,比如绑定一个TextBlock的Text属性,先选中,在右侧属性栏Text属性右侧的小方块点一下,选择创建数据绑定,就可以看到这个窗口:
选择Code属性,就绑定好了。大概做成这个样子:
具体代码如下:
<DataTemplate x:Key="ItemDataTemplate">
<Grid Margin="0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Left" Height="63" VerticalAlignment="Top" Width="90"/>
<Grid Height="63" Grid.Column="1" Margin="12,0,0,0">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" Text="{Binding Code}" Style="{StaticResource ListViewItemTextBlockStyle}" Margin="0"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Description}" Margin="0" Grid.Row="1" Style="{StaticResource ListViewItemSubheaderTextBlockStyle}" VerticalAlignment="Bottom"/>
</Grid>
</Grid>
</DataTemplate>
列表的XAML代码变成了:
<ListView x:Name="list" ItemsSource="{Binding CurrencyItemList}" ItemTemplate="{StaticResource ItemDataTemplate}" />
这里图片还没绑定,因为我还没把图片导进来呢……
五、显示图片
把整理好的国旗图片放到Assets目录下的Flag目录中,在货币列表的model中已经有了国旗图片名称,再转换一下得到实际地址就可以了。实际上你也可以中货币列表初始化的时候直接给他一个完整的地址,就不用再转换了。这里只是演示一下怎么用converter。你已经会了?嗯那不多说了直接上代码吧。
因为这个Converter是可以共享的,因此放到Shared项目中。在Utilities目录中添加一个Converters.cs文件,添加一个类:
public class FlagConverter : IValueConverter
{ object IValueConverter.Convert(object value, Type targetType, object parameter, string language)
{
if (value != null)
{
//也可以设置ImageSource 但不如直接返回一个字符串简单
//return new BitmapImage(new Uri("/Assets/Flag/" + value.ToString() + ".png", UriKind.Relative));
return "/Assets/Flag/" + value.ToString() + ".png";
}
else
{
//return new BitmapImage(new Uri("/Assets/Images/Flag/flag_white.png", UriKind.Relative));
return "/Assets/Flag/flag_white.png";
}
} object IValueConverter.ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
} }
然后在页面中定义这个Converter,不过因为这个转换器属于比较常用的,我们也可以直接放到App.xaml里,这样其他页面也可以直接用了。
<Application
x:Class="CurrencyExchanger.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CurrencyExchanger"
xmlns:utils="using:CurrencyExchanger.Utilities">
<Application.Resources>
<utils:FlagConverter x:Key="FlagConverter" />
</Application.Resources>
</Application>
现在可以在添加货币的页面中这样用了,列表模板中图像这个地方改为:
<Image HorizontalAlignment="Left" Height="63" VerticalAlignment="Top" Width="90" Source="{Binding Image,Converter={StaticResource FlagConverter}}"/>
运行一下试试,图片出来了:
六、进军Universal!
WP项目中显示出了所有的货币列表,但Windows项目还没搞呢。既然是Universal App,一定要多用Shared项目,能否把不同平台的View都绑定到一个ViewModel呢?试试看。
在Windows项目中右键添加一个页面,命名为AddCurrencyPage.xaml,和WP项目中的添加货币页面名称相同。然后看到Windows项目中也增加了类似的ViewModel文件和vm配置文件。现在把Windows项目中的StartUps\AddCurrencyPage.cs、ViewModels\AddCurrencyPage_Model.cs删除,把WP项目中的这两个文件转移到Shared项目中。
怀着激动的心情运行一下WP项目,嗯很好,跟以前一样。这样就可以在Windows项目和WP项目中使用同一个ViewModel了。
七、Windows项目添加appbar
同样把MainPage.xmal中的appbar注释部分取消,绑定到Command。
<Page.DataContext>
<Binding RelativeSource="{RelativeSource Mode=Self}" Path="ViewModel"/>
</Page.DataContext>
<mvvm:MVVMPage.ViewModel>
<Binding Source="{StaticResource DesignVM}" />
</mvvm:MVVMPage.ViewModel>
<mvvm:MVVMPage.BottomAppBar>
<CommandBar>
<AppBarButton Icon="Add" Label="{Binding CommandNavToAddCurrency.Resource}" Command="{Binding CommandNavToAddCurrency}"/>
</CommandBar>
</mvvm:MVVMPage.BottomAppBar>
七、使用GridView显示货币列表
Windows Store App布局跟WP不太一样,以横向滚动为主,这里使用GridView来展示货币列表页面。用Blend在AddCurrencyPage.xaml中拖一个GridView进来,将其ItemsSource绑定到VM的CurrencyItemList。
然后用Blend设计GridView的项模板。代码如下:
<DataTemplate x:Key="ItemDataTemplate">
<Grid Margin="0" Width="400" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Left" Height="63" VerticalAlignment="Top" Width="90" Source="{Binding Image, Converter={StaticResource FlagConverter}}"/>
<Grid Height="63" Grid.Column="1" Margin="12,0,0,0">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" Text="{Binding Code}" Margin="0"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Description}" Margin="0" Grid.Row="1" VerticalAlignment="Bottom"/>
</Grid>
</Grid>
</DataTemplate>
因为是横向滚动的,最好给Grid设置一个固定的宽度。其他的基本不变。
运行一下Windows项目,?报错了?
“System.Exception”类型的异常在 CurrencyExchanger.Windows.exe 中发生,但未在用户代码中进行处理
WinRT 信息: 未找到 ResourceMap。
其他信息: 未找到 ResourceMap。
原来是找不到资源了。光设置了WP项目的多语言资源,忘了Windows项目了。在Windows项目上右键添加翻译语言,把缺的语言添加上,保持和WP项目一致。为了提升共享代码程度,把String文件夹也整个移到Shared目录里,甚至可以把Assets目录里的Flags图片都移动过去。
两个项目分别运行一遍,完全OK。现在可以感受到Universal App的好处了,两个平台,只用了一个ViewModel,只实现不同的UI即可。
使用MVVM-Sidekick开发Universal App(二)的更多相关文章
- 使用MVVM-Sidekick开发Universal App(一)
终于要迈进Universal的大坑了,还有点小激动呢. CurrencyExchanger 掌中汇率是我前几年发布在Windows Phone商店中的一个应用,当时是WP7版本,下载链接:http:/ ...
- Windows Phone 8.1 开发技术概览 (Universal APP)
前一阵真的比较懒 WP8.1 已经出来这么长时间了现在才更新BLOG让大家久等了,今天我先为大家介绍下 WP 8.1的开发框架,什么是微软所推崇的 Universal APP,以及我们要开发 Univ ...
- 使用 Portable Class Library(可移植类库)开发 Universal Windows App
今天在这里跟大家聊聊关于 Windows Universal 应用夸平台的问题,首先Universal Windows App的定义相信大家已经有所了解了(如果你是一个刚刚接触 Universal A ...
- SharePoint 2013 APP 开发示例 (二)获取用户信息
SharePoint 2013 APP 开发示例 (二)获取用户信息 这个示例里,我们将演示如何获取用户信息: 1. 打开 Visual Studio 2012. 2. 创建一个新的 SharePo ...
- 跨平台移动APP开发进阶(二)HTML5+、mui开发移动app教程
前端开发APP,从HBuilder开始~ 序 通过 HTML5 开发移动App 时,会发现HTML5 很多能力不具备.为弥补HTML5 能力的不足,在W3C 中国的指导下成立了www.HTML5Plu ...
- 带你从零学ReactNative开发跨平台App开发(二)
ReactNative跨平台开发系列教程: 带你从零学ReactNative开发跨平台App开发(一) 带你从零学ReactNative开发跨平台App开发(二) 带你从零学ReactNative开发 ...
- 带你从零学ReactNative开发跨平台App开发[react native SqlLite 终极运用](十二)
ReactNative跨平台开发系列教程: 带你从零学ReactNative开发跨平台App开发(一) 带你从零学ReactNative开发跨平台App开发(二) 带你从零学ReactNative开发 ...
- 0421--"数字口袋精灵app"二次开发(Blackbriar团队开发)
"数字口袋精灵app"二次开发 目录: 一.项目github总仓库推送 二.开发成员 三.分工与合作 四.各模块成果 五.心得墙 六.团队成员贡献分 内容: 一.项目github总 ...
- 打通移动App开发的任督二脉、实现移动互联创业的中国梦
年初的两会上,第一次听到克强总理讲到“互联网+”的计划,当时就让我为之感到无比振奋.我个人的理解是:“互联网+”的本质就是要对传统行业供需双方的重构,通过移动互联技术来推动各个行业上的全民创新,促使中 ...
随机推荐
- Spring AOP简述
使用面想对象(Object-Oriented Programming,OOP)包含一些弊端,当需要为多个不具有继承关系的对象引入公共行为时,例如日志,安全检测等.我们只有在每个对象中引入公共行为,这样 ...
- 译文---C#堆VS栈(Part Two)
前言 在本系列的第一篇文章<C#堆栈对比(Part One)>中,介绍了堆栈的基本功能和值类型以及引用类型在程序运行时的表现,同时也包含了指针作用的讲解. 本文为文章的第二部分,主要讲解参 ...
- 一个简单的、面向对象的javascript基础框架
如果以后公司再能让我独立做一套新的完整系统,那么我肯定会为这个系统再写一个前端框架,那么我到底该如何写这个框架呢? 在我以前的博客里我给大家展示了一个我自己写的框架,由于当时时间很紧张,做之前几乎没有 ...
- [每日电路图] 8、三轴加速度计LIS3DH电路图及功耗等指标
看TI的官网资料:http://www.st.com/web/en/catalog/sense_power/FM89/SC444/PF250725 一.初次接触关注的信息: 1.1.概述中的关键信息 ...
- atitit。企业的价值观 员工第一 vs 客户第一.docx
atitit.企业的价值观 员工第一 vs 客户第一.docx 1. 客户第一的说法是错误的,员工优先是正确的,理念与价值观1 1.1. 任何一个组织,应该组织成员优先级要比外部成员高才对1 1.2. ...
- js获取当前时间显示在页面上
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- Eclipse中java向数据库中添加数据,更新数据,删除数据
前面详细写过如何连接数据库的具体操作,下面介绍向数据库中添加数据. 注意事项:如果参考下面代码,需要 改包名,数据库名,数据库账号,密码,和数据表(数据表里面的信息) package com.ning ...
- WPF入门教程系列十——布局之Border与ViewBox(五)
九. Border Border 是一个装饰的控件,此控件绘制边框及背景,在 Border 中只能有一个子控件,若要显示多个子控件,需要将一个附加的 Panel 控件放置在父 Border 中.然后可 ...
- How Google TestsSoftware - Part One
This is the firstin a series of posts on this topic.The one question I get morethan any other is &qu ...
- 贪心算法-最小生成树Kruskal算法和Prim算法
Kruskal算法: 不断地选择未被选中的边中权重最轻且不会形成环的一条. 简单的理解: 不停地循环,每一次都寻找两个顶点,这两个顶点不在同一个真子集里,且边上的权值最小. 把找到的这两个顶点联合起来 ...