终于要迈进Universal的大坑了,还有点小激动呢。

CurrencyExchanger 掌中汇率是我前几年发布在Windows Phone商店中的一个应用,当时是WP7版本,下载链接:http://www.windowsphone.com/zh-cn/store/app/%E6%8E%8C%E4%B8%AD%E6%B1%87%E7%8E%87free/84e93a20-cefb-460f-b0d9-a57689b33c10

已经很久没有升级了,最近想学习一下Universal开发,就拿这个练练手吧。之前一直没有系统的写过文章,现在从头把开发中的一些过程记录一下,也是对自己的一个促进。因为是边做边写,肯定会有错误,请大家不吝赐教。

一、新建项目

我使用了MVVM-Sidekick框架,这是一个简单但功能强大的MVVM框架,由微软的@韦恩卑鄙 开发,我一直用这个框架开发WP8的程序,前不久作者升级支持了Universal App。

新建项目前需要先安装MVVM-Sidekick的VS扩展插件,在VS2013update2的工具-扩展与更新菜单中搜索mvvm-sidekick就可以找到这个扩展,下载安装即可。安装后会添加项目模板和代码段,比较方便。

github:https://github.com/waynebaby/mvvM-Sidekick

vs插件:http://visualstudiogallery.msdn.microsoft.com/ef9b45cb-8f54-481a-b248-d5ad359ec407

现在可以新建项目了,选择通用应用程序,MVVM-Sidekick Universal App项目模板,输入CurrencyExchanger,等待VS建立项目。这个地方有个需要注意,项目名称不能太长,我第一次输入了一个比较长的名字,结果VS提示名称太长,建立失败了。

二、项目结构

现在可以看到VS2013为我们生成了三个项目,

CurrencyExchanger.Windows

CurrencyExchanger.WindowsPhone

CurrencyExchanger.Shared

可以看到我们熟悉的App.xaml文件被放到了Shared项目中,打开看一下,

#if WINDOWS_PHONE_APP
private TransitionCollection transitions;
#endif

原来有好多条件编译啊,通过这种方式来区分Win8.1和WP8.1,有点坑啊。

在OnLaunched方法中,有这么一行:

//Init MVVM-Sidekick Navigations:
Startups.StartupFunctions.RunAllConfig();

然后我们找到对应的文件看一下,

public static void RunAllConfig()
{ typeof(StartupFunctions)
.GetRuntimeMethods()
.Where(m => m.Name.StartsWith("Config") && m.IsStatic)
.Select(
m => m.Invoke(null, Enumerable.Empty<object>().ToArray()))
.ToArray(); }

这个方法对View和ViewModel进行了配置,以后新加View的话,MVVM-Sidekick会自动添加所需的ViewModel,并在这个类中进行注册,方便使用。

ViewModel文件夹中放着所需的VM,这个文件夹也是在Shared项目中,说明我们可以只用共享的VM去作为不同平台的View的DataContext,实现了共享代码的目的。

然后看MainPage_Model.cs这个vm,这个类继承了ViewModelBase<MainPage_Model>,ViewModelBase是MVVM-Sidekick的重要的一个类,所有的vm都要继承这个类。里面有一个属性Title,可以看到还带着一大坨代码,这一大坨代码是怎么出来的呢,MVVM-Sidekick提供了代码段来帮助生成,所以这就是安装VS扩展的好处。

通过输入 propvm ,按Tab,就会自动生成一个属性,可以方便的绑定到View上了。

然后我们看CurrencyExchanger.WindowsPhone项目中的MainPage.xaml,里面有这么一行:

<Page.Resources>
<!-- TODO: Delete this line if the key AppName is declared in App.xaml --> <vm:MainPage_Model x:Key="DesignVM"/>
</Page.Resources>

定义了一个资源,把VM引入进来。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"  DataContext="{StaticResource DesignVM}">
<TextBlock TextWrapping="Wrap" x:Name="pageTitle" Grid.Column="1" Text="{Binding Title}" Style="{StaticResource HeaderTextBlockStyle}"/>
</Grid>

把这个VM作为Grid的DataContext,这样就可以进行绑定了,可以看到有一个TextBlock的Text属性绑定到了VM的Title字段。

大体的项目结构就是这样,下面我们就开始升级。说是升级,其实就是重新开发啊5555

三、建立所需的Model

货币转换这个app功能就是从雅虎财经获取不同的货币代码直接的汇率,因此首先来建立相应的Model。

在CurrencyExchanger.Shared项目中新建一个Models文件夹,添加一个CurrencyItem.cs,内容如下:

 public class CurrencyItem : BindableBase<CurrencyItem>
{
/// <summary>
/// 货币代码
/// </summary>
public string Code
{
get { return _CodeLocator(this).Value; }
set { _CodeLocator(this).SetValueAndTryNotify(value); }
}
#region Property string Code Setup
protected Property<string> _Code = new Property<string> { LocatorFunc = _CodeLocator };
static Func<BindableBase, ValueContainer<string>> _CodeLocator = RegisterContainerLocator<string>("Code", model => model.Initialize("Code", ref model._Code, ref _CodeLocator, _CodeDefaultValueFactory));
static Func<string> _CodeDefaultValueFactory = () => { return default(string); };
#endregion /// <summary>
/// 描述
/// </summary>
public string Description
{
get { return _DescriptionLocator(this).Value; }
set { _DescriptionLocator(this).SetValueAndTryNotify(value); }
}
#region Property string Description Setup
protected Property<string> _Description = new Property<string> { LocatorFunc = _DescriptionLocator };
static Func<BindableBase, ValueContainer<string>> _DescriptionLocator = RegisterContainerLocator<string>("Description", model => model.Initialize("Description", ref model._Description, ref _DescriptionLocator, _DescriptionDefaultValueFactory));
static Func<string> _DescriptionDefaultValueFactory = () => { return default(string); };
#endregion /// <summary>
/// 图片名称
/// </summary>
public string Image
{
get { return _ImageLocator(this).Value; }
set { _ImageLocator(this).SetValueAndTryNotify(value); }
}
#region Property string Image Setup
protected Property<string> _Image = new Property<string> { LocatorFunc = _ImageLocator };
static Func<BindableBase, ValueContainer<string>> _ImageLocator = RegisterContainerLocator<string>("Image", model => model.Initialize("Image", ref model._Image, ref _ImageLocator, _ImageDefaultValueFactory));
static Func<string> _ImageDefaultValueFactory = () => { return default(string); };
#endregion }

这个Model要继承BindableBase<CurrencyItem>,在MVVM-Sidekick中BindableBase是和ViewModelBase一样重要的几个基类,用于实现可绑定的model,但区别是ViewModelBase中还会放一些Command,而BindableBase顾名思义仅用于绑定属性,不建议在里面放Command这些东西。不要看上面这么一大坨,其实就输入了几个单词而已,都是用propvm生成的。主要是三个属性,货币代码,描述,图片名称。图片用于在显示货币的时候显示一个国旗的图片。

与此类似再建立一个货币转换的model,新建CurrencyExchangeItem.cs文件,代码如下:

public class CurrencyExchangeItem : BindableBase<CurrencyExchangeItem>
{
/// <summary>
/// 日期
/// </summary>
public DateTime TradeDate
{
get { return _TradeDateLocator(this).Value; }
set { _TradeDateLocator(this).SetValueAndTryNotify(value); }
}
#region Property DateTime TradeDate Setup
protected Property<DateTime> _TradeDate = new Property<DateTime> { LocatorFunc = _TradeDateLocator };
static Func<BindableBase, ValueContainer<DateTime>> _TradeDateLocator = RegisterContainerLocator<DateTime>("TradeDate", model => model.Initialize("TradeDate", ref model._TradeDate, ref _TradeDateLocator, _TradeDateDefaultValueFactory));
static Func<DateTime> _TradeDateDefaultValueFactory = () => { return default(DateTime); };
#endregion /// <summary>
/// 汇率
/// </summary>
public double Rate
{
get { return _RateLocator(this).Value; }
set { _RateLocator(this).SetValueAndTryNotify(value); }
}
#region Property double Rate Setup
protected Property<double> _Rate = new Property<double> { LocatorFunc = _RateLocator };
static Func<BindableBase, ValueContainer<double>> _RateLocator = RegisterContainerLocator<double>("Rate", model => model.Initialize("Rate", ref model._Rate, ref _RateLocator, _RateDefaultValueFactory));
static Func<double> _RateDefaultValueFactory = () => { return default(double); };
#endregion /// <summary>
/// 逆向汇率
/// </summary>
public double InverseRate
{
get { return _InverseRateLocator(this).Value; }
set { _InverseRateLocator(this).SetValueAndTryNotify(value); }
}
#region Property double InverseRate Setup
protected Property<double> _InverseRate = new Property<double> { LocatorFunc = _InverseRateLocator };
static Func<BindableBase, ValueContainer<double>> _InverseRateLocator = RegisterContainerLocator<double>("InverseRate", model => model.Initialize("InverseRate", ref model._InverseRate, ref _InverseRateLocator, _InverseRateDefaultValueFactory));
static Func<double> _InverseRateDefaultValueFactory = () => { return default(double); };
#endregion /// <summary>
/// 是否为基准货币
/// </summary>
public bool IsStandard
{
get { return _IsStandardLocator(this).Value; }
set { _IsStandardLocator(this).SetValueAndTryNotify(value); }
}
#region Property bool IsStandard Setup
protected Property<bool> _IsStandard = new Property<bool> { LocatorFunc = _IsStandardLocator };
static Func<BindableBase, ValueContainer<bool>> _IsStandardLocator = RegisterContainerLocator<bool>("IsStandard", model => model.Initialize("IsStandard", ref model._IsStandard, ref _IsStandardLocator, _IsStandardDefaultValueFactory));
static Func<bool> _IsStandardDefaultValueFactory = () => { return default(bool); };
#endregion /// <summary>
/// 货币数量
/// </summary>
public double Amount
{
get { return _AmountLocator(this).Value; }
set { _AmountLocator(this).SetValueAndTryNotify(value); }
}
#region Property double Amount Setup
protected Property<double> _Amount = new Property<double> { LocatorFunc = _AmountLocator };
static Func<BindableBase, ValueContainer<double>> _AmountLocator = RegisterContainerLocator<double>("Amount", model => model.Initialize("Amount", ref model._Amount, ref _AmountLocator, _AmountDefaultValueFactory));
static Func<double> _AmountDefaultValueFactory = () => { return default(double); };
#endregion /// <summary>
/// 基准货币
/// </summary>
public CurrencyItem CurrencyBase
{
get { return _CurrencyBaseLocator(this).Value; }
set { _CurrencyBaseLocator(this).SetValueAndTryNotify(value); }
}
#region Property CurrencyItem CurrencyBase Setup
protected Property<CurrencyItem> _CurrencyBase = new Property<CurrencyItem> { LocatorFunc = _CurrencyBaseLocator };
static Func<BindableBase, ValueContainer<CurrencyItem>> _CurrencyBaseLocator = RegisterContainerLocator<CurrencyItem>("CurrencyBase", model => model.Initialize("CurrencyBase", ref model._CurrencyBase, ref _CurrencyBaseLocator, _CurrencyBaseDefaultValueFactory));
static Func<CurrencyItem> _CurrencyBaseDefaultValueFactory = () => { return default(CurrencyItem); };
#endregion /// <summary>
/// 目标货币
/// </summary>
public CurrencyItem CurrencyTarget
{
get { return _CurrencyTargetLocator(this).Value; }
set { _CurrencyTargetLocator(this).SetValueAndTryNotify(value); }
}
#region Property CurrencyItem CurrencyTarget Setup
protected Property<CurrencyItem> _CurrencyTarget = new Property<CurrencyItem> { LocatorFunc = _CurrencyTargetLocator };
static Func<BindableBase, ValueContainer<CurrencyItem>> _CurrencyTargetLocator = RegisterContainerLocator<CurrencyItem>("CurrencyTarget", model => model.Initialize("CurrencyTarget", ref model._CurrencyTarget, ref _CurrencyTargetLocator, _CurrencyTargetDefaultValueFactory));
static Func<CurrencyItem> _CurrencyTargetDefaultValueFactory = () => { return default(CurrencyItem); };
#endregion }

这个model就是用来显示货币汇率转换的,里面有两个货币的信息还有汇率的信息等等。

四、初始化数据

在用户第一次进入app时,应该让用户选择要显示哪些货币的汇率,这样就要给用户提供一个货币列表,这个列表需要我们提前初始化好。

新建一个Context类,放一些常用的东东。在Shared项目中新建Utilities目录,添加一个Context.cs文件,做成单例。

public sealed class Context
{
static readonly Context instance = new Context(); static Context()
{ } private Context()
{
} /// <summary>
/// Gets the instance.
/// </summary>
/// <value>The instance.</value>
public static Context Instance
{
get
{
return instance;
}
}
}

在里面添加一个列表:

public List<CurrencyItem> AllCurrencyItemList { get; set; }

然后一个初始化方法:

public void Init()
{
AllCurrencyItemList = new List<CurrencyItem>()
{
new CurrencyItem{Code = "AED", Description ="阿联酋迪拉姆", Image="flag_united_arab_emirates"},
new CurrencyItem{Code = "ALL", Description = "阿尔巴尼亚列克", Image="flag_albania"},
……
}

找到App.xaml.cs,在OnLaunched方法中调用此方法:

//Init Context
Context.Instance.Init();

添加货币列表是一个很枯燥的工作,当初我是从雅虎财经网页上扒下的货币代码,又从网页素材网站找到国旗的图片,挨个整理好。当然也可以事先整理成xml来读取。

慢着,我的WP7程序就是支持多语言的,此时当然不能把货币描述直接hard code,而应该从资源文件中按照用户当前的语言来显示。

好吧又多了一个问题,多语言。

五、可以叫全球化多语言本地化……反正就是可以让用户选择语言

以前的WP7多语言需要自己搞一大坨代码,到了WP8方便了一点,VS会帮助干很多事。但到了Universal,情况又变了。WP8添加资源文件的时候资源文件格式为resx,同时程序会自动添加一个AppResouces.Designer.cs,通过一个全局的ResourceManager去取得资源文件中的字符串,代码中可以直接调用:

String appName = AppResources.AppName;

是不是很方便?

到了Universal里,自动生成的没有了,添加的资源文件格式变成了resw,需要用这种方式来调用:

var loader = new Windows.ApplicationModel.Resources.ResourceLoader();
var string = loader.GetString('Farewell');

是不是很坑?万一字符串写错了就找不到了。

添加多语言文件倒不麻烦,有多语言工具包,链接:http://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/jj572370.aspx

但是调用显得不太友好。所以我仿照WP8的方式新建了一个AppResources.cs,放到Utilities,里面这样写:

public static class AppResources
{
public static ResourceLoader CurrentResourceLoader
{
get
{
return ResourceLoader.GetForCurrentView();
}
} public static string AppName
{
get
{
return CurrentResourceLoader.GetString("AppName");
}
}
。。。。。。
}

只要保证这里写对,这样以后调用的时候就不怕出错了。

多语言资源文件的添加比较简单,有工具包协助,甚至翻译都可以帮你做好,具体步骤见

http://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/jj572370.aspx

需要注意的是,以前的方式需要我们为每种语言建立一个资源文件,现在有多语言工具包就不需要了,只添加一个默认语言的即可,工具包会自动填充其他的语言。比如CurrencyExchanger默认语言是英语,那么步骤就是:

打开Package.appxmanifest文件,把默认语言改成en-US,然后添加一个Strings文件夹,下面添加en-US文件夹,添加一个Resources.resw资源文件,在这里面编辑所需要的字符串。

右键单击CurrencyExchanger.WindowsPhone,选择添加翻译语言,

这样会自动建立一个MultilingualResources文件夹,里面是一大坨xlf后缀的文件,qps-ploc.xlf这个是伪语言,用于测试的,在其他的几个文件上点右键,选择打开方式,选择多语言编辑器,出来这么一个东东:

看到菜单没有,点翻译,Microsoft Translator直接就帮你翻译好了。当然还需要进一步校对,但已经很智能化了。这样就不需要为每种语言建资源文件了,可以从这些xlf文件里找。需要注意的是,如果你的程序选择了zh-CN的默认语言,就不能再有zh-CN.xlf的多语言资源,否则会提示错误,删掉重复的即可。你也可以在xlf文件上右键发送邮件给朋友,翻译完了再导入进来。

呼呼,先别管翻译的准不准,代码里我们可以这样初始化货币列表了:

AllCurrencyItemList = new List<CurrencyItem>()
{
new CurrencyItem{Code = "AED", Description = AppResources.AED, Image="flag_united_arab_emirates"},
new CurrencyItem{Code = "ALL", Description = AppResources.ALL, Image="flag_albania"},
//new CurrencyItem{Code = "ANG", Description = AppResources.ANG, Image=""},
new CurrencyItem{Code = "ARS", Description = AppResources.ARS, Image="flag_argentina"},
。。。。。
}

因为是从资源文件中读取的货币描述,所以在UI会显示和用户系统匹配的语言。

未完待续。

使用MVVM-Sidekick开发Universal App(一)的更多相关文章

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

    上一篇文章已经建立了基本的实体类,并且搞定了多语言的问题,以后在app里用字符串的时候就可以从资源文件中取了.现在继续进行. 一.添加一个页面 CurrencyExchanger首页是一个货币兑换的列 ...

  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. Cordova+ionic 开发hybird App --- 开发环境搭建

    Cordova 开发hybird App 开发环境搭建 一.一些基础概念: Ant : 简单说来可以这么理解,如果你用记事本写JAVA程序,然后在cmd里输入javac命令编译它,但是有一天你发现每次 ...

  5. Android 基于MVC的MVVM模式开发

    由skay整理  http://blog.csdn.net/sk719887916/article/details/50386144 什么是MVVM 我们一步步来,从MVC开始. MVC 我们都知道, ...

  6. UWP开发:APP之间的数据交互(以微信为例)

    目录 说明 UWP应用唤醒方式 跟微信APP交互数据 APP之间交互数据的前提 说明 我们经常看到,在手机上不需要退到桌面,APP之间就可以相互切换,并且可以传递数据.比如我在使用知乎APP的时候,需 ...

  7. 【转载】用Ionic开发hybrid APP

    使用phonegap开发APP的优劣在此不必细说,快速,简单,跨平台,以及随着iOS,Android本身对webview优化所带来的不错的性能,便是其独有的优势.而且私以为在目前激烈而又变化快速的移动 ...

  8. 分享 Ionic 开发 Hybrid App 中遇到的问题以及后期发布 iOS/Android 的方方面面

    此篇文章主要整理了最近在使用 Ionic 开发 Hybrid App 过程中遇到的一些疑难点以及后期发布生成 iOS 和 Android 版本过程中的种种问题. 文章目录 Ionic 简介和项目需求介 ...

  9. 后移动互联网时代:到底还要不要开发一个App?

    后移动互联网时代,到底是什么样的一个时代? 首先,后移动互联网时代中,产生头部应用的几率变小了,像微信这样巨头式的App很难在产生第二个.其次,后移动互联网时代,物联网发展迅速,所有的智能硬件都需要一 ...

随机推荐

  1. Java多线程14:生产者/消费者模型

    什么是生产者/消费者模型 一种重要的模型,基于等待/通知机制.生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点: ...

  2. dojo/query源码解析

    dojo/query模块是dojo为开发者提供的dom查询接口.该模块的输出对象是一个使用css选择符来查询dom元素并返回NodeList对象的函数.同时,dojo/query模块也是一个插件,开发 ...

  3. Javascript:是你的高阶函数

    在通常的编程语言中,函数的参数只能是基本类型或者对象引用,返回值也只是基本数据类型或对象引用.但在Javascript中函数作为一等公民,既可以当做参数传递,也可以被当做返回值返回.所谓高阶函数就是可 ...

  4. 熟用js中的Date

    js中的Date类型是使用UTC(国际协调时间)自1970年1月1日午夜(零时)开始,经过的毫秒数来保存日期. 1. 创建日期对象  ---> 获得当前日期和时间  var now = new ...

  5. 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!

    <30天自制操作系统>笔记(01)——hello bitzhuwei's OS! 最初的OS代码 ; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序的装载地址 ; 以 ...

  6. IT传统组织结构及新型扁平化组织

    如今互联网企业正凶猛的改变人们衣食住行的方方面面,衣->淘宝,蘑菇街;食->大众点评,口碑;住->去哪,途牛:行->12306, 多次听到互联网的同行介绍他们就是要“快”,快速 ...

  7. jQuery疑惑记录

    不断更新 1.项目中有一句 this.$html = $('<div/>').html(html).attr('sspa-mod-id', this.modName).hide();不知道 ...

  8. Java基础之String、StringBuffer、StringBuilder

    1:String类:字符串(重点) (1)多个字符组成的一个序列,叫字符串. 生活中很多数据的描述都采用的是字符串的.而且我们还会对其进行操作. 所以,java就提供了这样的一个类供我们使用. (2) ...

  9. salesforce 零基础学习(二十二)Test简单使用

    本篇内容只是本人简单的mark开发中常出现的一些疑问,方便后期项目使用时奠定基础,如果对Test零基础童鞋,欢迎查看Test官方的使用介绍: https://help.salesforce.com/a ...

  10. salesforce 零基础开发入门学习(十一)sObject及Schema深入

    sObject在salesforce中占有举足轻重的位置,除了在数据库中数据以外,我们还应该关心一下他的元信息.元信息封装在Schema命名空间内. 作为面向对象语言,我们可以畅想一下如果我们是设计人 ...