上一篇文章已经建立了基本的实体类,并且搞定了多语言的问题,以后在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(二)的更多相关文章

  1. 使用MVVM-Sidekick开发Universal App(一)

    终于要迈进Universal的大坑了,还有点小激动呢. CurrencyExchanger 掌中汇率是我前几年发布在Windows Phone商店中的一个应用,当时是WP7版本,下载链接:http:/ ...

  2. Windows Phone 8.1 开发技术概览 (Universal APP)

    前一阵真的比较懒 WP8.1 已经出来这么长时间了现在才更新BLOG让大家久等了,今天我先为大家介绍下 WP 8.1的开发框架,什么是微软所推崇的 Universal APP,以及我们要开发 Univ ...

  3. 使用 Portable Class Library(可移植类库)开发 Universal Windows App

    今天在这里跟大家聊聊关于 Windows Universal 应用夸平台的问题,首先Universal Windows App的定义相信大家已经有所了解了(如果你是一个刚刚接触 Universal A ...

  4. SharePoint 2013 APP 开发示例 (二)获取用户信息

    SharePoint 2013 APP 开发示例 (二)获取用户信息 这个示例里,我们将演示如何获取用户信息: 1. 打开 Visual Studio 2012. 2. 创建一个新的  SharePo ...

  5. 跨平台移动APP开发进阶(二)HTML5+、mui开发移动app教程

    前端开发APP,从HBuilder开始~ 序 通过 HTML5 开发移动App 时,会发现HTML5 很多能力不具备.为弥补HTML5 能力的不足,在W3C 中国的指导下成立了www.HTML5Plu ...

  6. 带你从零学ReactNative开发跨平台App开发(二)

    ReactNative跨平台开发系列教程: 带你从零学ReactNative开发跨平台App开发(一) 带你从零学ReactNative开发跨平台App开发(二) 带你从零学ReactNative开发 ...

  7. 带你从零学ReactNative开发跨平台App开发[react native SqlLite 终极运用](十二)

    ReactNative跨平台开发系列教程: 带你从零学ReactNative开发跨平台App开发(一) 带你从零学ReactNative开发跨平台App开发(二) 带你从零学ReactNative开发 ...

  8. 0421--"数字口袋精灵app"二次开发(Blackbriar团队开发)

    "数字口袋精灵app"二次开发 目录: 一.项目github总仓库推送 二.开发成员 三.分工与合作 四.各模块成果 五.心得墙 六.团队成员贡献分 内容: 一.项目github总 ...

  9. 打通移动App开发的任督二脉、实现移动互联创业的中国梦

    年初的两会上,第一次听到克强总理讲到“互联网+”的计划,当时就让我为之感到无比振奋.我个人的理解是:“互联网+”的本质就是要对传统行业供需双方的重构,通过移动互联技术来推动各个行业上的全民创新,促使中 ...

随机推荐

  1. JavaScript开发原生App模式能否突出重围?

    移动应用制作的第三方服务市场已经被瓜分得差不多了,对于刚起步的中小企业来说,这些公司的 IT 部门人员比较熟悉的是 Appcan ,但随着互联网公司对 App 开发的需求持续升温,也有不少后来的闯入者 ...

  2. css实现左栏固定右栏自适应,高度自适应的布局

    收集css中的基础知识,所以这并不是什么新鲜的技术,只是作为备忘~本文的内容如题所示,是一个简单的布局,用于左右两栏布局的页面,左侧是固定宽度,右侧占据剩余的宽度.在垂直方向,始终以高度最大的一栏为基 ...

  3. JavaScript思维导图—函数基础

    JavaScript思维导图-来自@王子墨http://julying.com/blog/the-features-of-javascript-language-summary-maps/

  4. Chrome扩展程序的二次开发:把它改得更适合自己使用

    我当然知道未经作者允许修改别人程序是不道德的了,但作为学习研究之用还是无可厚非,这里仅供交流. 一切都是需求驱动的 话说某天我在网上猎奇的时候无意间发现这么一款神奇的谷歌浏览器插件:Extension ...

  5. mongodb数据类型

    随着web2.0的时代到来,关系型数据库在越来越多的场景下暴漏出许多问题,为了解决这类问题,NoSql数据库应用而生,今天就来说说当下比较主流的NoSql数据库mongodb.   1. 基本数据类型 ...

  6. Atitit. Atiposter 发帖机 新特性 poster new feature v11  .docx

    Atitit. Atiposter 发帖机 新特性 poster new feature v11  .docx 1.1.  版本历史1 2. 1. 未来版本规划2 2.1. V12版本规划2 2.2. ...

  7. CSS 性能

    http://boagworld.com/dev/why-you-should-care-about-css-page-performance/ http://css-tricks.com/effic ...

  8. hibernate(七)组件映射与多对一映射

    一.组件映射 用注解配置组件映射: Husband为我们映射的类,wife是这个类的一部分(属性不能与husband中属性重名,不要写Entity注解,不要有主键) Husband类:(在getWif ...

  9. Razor基本语法

    前言: Razor引擎的核心是识别@符号及尖括号:    1.<...></...>    2.<.../>    [之所以说是"尖括号"而非& ...

  10. JavaScript的学习--JavaScript设计模式的总结

    这篇博客只是自己对设计模式的理解的备忘~ 看完了<JavaScript设计模式>这本书,一直没有写博客记录一下,最近抽出时间来重读了一下,就顺便记录一下~ 如果你只是想粗略了解一下Java ...