上一篇文章已经创建了基本的实体类,而且搞定了多语言的问题,之后在app里用字符串的时候就能够从资源文件中取了。如今继续进行。windows
1、添加一个页面设计模式
CurrencyExchanger首页是一个货币兑换的列表,这个列表比较复杂,咱们先无论,先从简单的页面作起。首先要有一个添加货币的页面,显示全部可添加的货币列表。在WP项目中右键添加一个页面,命名为AddCurrencyPage.xaml。app
而后MVVM-Sidekick自动添加了一些东西:除了这个页面以外,在WP项目的ViewModels目录中添加了一个AddCurrencyPage_Model.cs文件,在StartUps目录中添加了一个AddCurrencyPage.cs文件。打开看一下:async
public static void ConfigAddCurrencyPage() { ViewModelLocator<AddCurrencyPage_Model> .Instance .Register(context => new AddCurrencyPage_Model()) .GetViewMapper() .MapToDefault<AddCurrencyPage>(); }
看到了吧,全部的View和ViewModel都要进行一下配置才能用。第一次用MVVM-Sidekick的时候我没装vs扩展,直接引用的类库,本身手动创建VM,不知道要进行配置,结果死活绑定不上。问做者才知道有这么个东西,因此必定要装vs扩展插件才能够享受到这个便利。mvvm
2、实现第一个Binding列表ide
在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资源文件中定义好的,之后就再也不详述了。测试
把这两个属性绑定到页面标题区域就能够了,这样能够根据用户选择的语言显示本地化的语言。this
<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来导航。
3、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,能够显示到添加货币的页面了。
4、使用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}" />
这里图片还没绑定,由于我还没把图片导进来呢……
5、显示图片
把整理好的国旗图片放到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}}"/>
运行一下试试,图片出来了:
6、进军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了。
7、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>
7、使用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便可。