MvvmCross[翻译] 使用Xamarin与MvvmCross完成一个跨平台App
总览
原文:https://github.com/MvvmCross/MvvmCross/wiki/Tip-Calc-A-first-app
我们所做的第一个Model-View-ViewModel(MVVM)程序需要为一个餐馆实现一个跨平台的小费计算器。
他的线框图大概是下面这个样子:
本片文章大概会涉及到以下几个方面:
- MvvmCorss应用程序的总体结构。
- 构建一个MvvmCross程序所需要的基本代码。
- 如何在Xamarin.iOS、Xamarin.Android、Windows 通用应用程序中使用MvvmCross以及数据绑定。
本文只关注与MvvmCross,Xamarin相关的问题不会详细的说明。
一、核心库(PCL部分)
一个MvvmCross的App通常由以下几个部分组成:
- 一个共享的核心(Core)跨平台类库(Protable Class Library,PCL)。包含view model、service、converters等。
- 各 个平台的的UI工程。
一般来说我们从这个跨平台的Core库开始写。
本例使用Visual Studio 2013,Xamarin 3.11.386。iOS部分使用虚拟机、操作系统为10.10.3 xcode6。
1.创建一个新的Portable Class Library
首先在VS中新建一个PCL项目。名称为TipCalc.Core,解决方案名称为TipCalc(小费计算器):
在新建PCL项目时,VS会询问PCL的目标平台,请按照下面图片设置:
确保PCL的Profile为Profile259,实在不想选可以通过编辑PCL工程的csproj文件将
<TargetFrameworkProfile>Profile***</TargetFrameworkProfile>
中的值改为Profile259。
如果你的VS报错请参考 http://danrigby.com/2014/04/10/windowsphone81-pcl-xamarin-fix/
以及yzf的博客: http://www.cnblogs.com/yaozhenfa/p/4709952.html
关于Profile259,Profile259包括了大多数.net程序集,也可以通过Nuget获取第三方的库,通常跨平台的类库都是使用Profile259生成的。
2.安装MvvmCross
在 [工具] - [Nuget 程序包管理器] - [程序包管理器控制台] (Package Manager Console)中输入
Install-Package MvvmCross.HotTuna.MvvmCrossLibraries
这时Nuget会自动帮我们把MvvmCross引入到我们的工程中。
然后删掉自动生成的Class1.cs。
当然你也可以在项目右键,使用Nuget程序包管理器引入MvvmCross。不过根据我的经验,Nuget管理器通常会比较卡。推荐用控制台。
3.增加计算小费的Service
在项目中增加文件夹,名为Services。在小型App中我们可以把App的业务逻辑放在这里。
在文件夹中增加一个接口。这个接口用来抽象计算小费的逻辑。
namespace TipCalc.Core.Services
{
public interface ICalculation
{
double TipAmount(double subTotal, int generosity);
}
}
然后我们来实现它
namespace TipCalc.Core.Services
{
public class Calculation : ICalculation
{
public double TipAmount(double subTotal, int generosity)
{
return subTotal * ((double)generosity)/100.0;
}
}
}
到此为止我们App的业务逻辑已经实现了。
4.增加ViewModel
在添加ViewModel前我们先梳理下我们这个页面需要完成的任务
- 用途:
- 使用Calculation服务计算小费。- 需要用户输入:
- 账单的总价(SubTotal)。
- 我们想要留下小费的百分比 (Generosity)。- 输出:
- 我们需要留下多少小费。
在MvvmCross中,所有的ViewModel都需要继承自MvxViewModel。
在项目中建立ViewModels文件夹,专门用来放ViewModel。在其中建立新的ViewModel.
using Cirrious.MvvmCross.ViewModels;
using TipCalc.Core.Services;
namespace TipCalc.Core.ViewModels
{
public class TipViewModel : MvxViewModel
{
private readonly ICalculation _calculation;
private int _generosity;
private double _subTotal;
private double _tip;
/// <summary>
/// 构造函数注入ICalculation
/// </summary>
/// <param name="calculation"></param>
public TipViewModel(ICalculation calculation)
{
_calculation = calculation;
}
/// <summary>
/// 总消费
/// </summary>
public double SubTotal
{
get { return _subTotal; }
set
{
_subTotal = value;
RaisePropertyChanged(() => SubTotal);
Recalcuate();
}
}
/// <summary>
/// 消费比例(百分比)
/// </summary>
public int Generosity
{
get { return _generosity; }
set
{
_generosity = value;
RaisePropertyChanged(() => Generosity);
Recalcuate();
}
}
/// <summary>
/// 小费
/// </summary>
public double Tip
{
get { return _tip; }
set
{
_tip = value;
RaisePropertyChanged(() => Tip);
}
}
/// <summary>
/// ViewModel初始化时执行
/// </summary>
public override void Start()
{
_subTotal = 100;
_generosity = 10;
Recalcuate();
base.Start();
}
/// <summary>
/// 调用ICalculation给我们的接口计算小费
/// </summary>
private void Recalcuate()
{
Tip = _calculation.TipAmount(SubTotal, Generosity);
}
}
}
如果你之前在WPF、Sliverlight等平台中接触过MVVM设计模式应该对以上的代码并不陌生。
这个ViewModel中有3个被扩展过的属性SubTotal、Generosity、Tip。
当他们被修改的时候会调用RaisePropertyChanged函数来通知其他的对象他们的属性被修改过了。
且当SubTotal和Generosity被修改时会重新计算小费。
5.增加启动配置代码(App)
在写好Calculation Service和TipViewModel之后,我们现在来添加App的启动配置代码,对于App类来说:
- 他通常会待在在PCL项目的根目录下。
- 他继承自MvxApplication。
- 一般来说他的名字就叫App。
- 他的主要功能是:
- 为IoC容器注册接口以及相应的实现。以后我会专门写一篇关于MvvmCross的IoC容器的文章来介绍。
- 设置App启动后第一个界面对应的的ViewModel。
- 为整个App提供ViewModel的定位器(Locator)。定位器作用是通过ViewModel的Type以及以下参数来生成对应的ViewModel。通常情况下我们用默认的就行了。
对于我们的小费计算器来说,App的代码很简单:
using Cirrious.CrossCore;
using Cirrious.MvvmCross.ViewModels;
using TipCalc.Core.Services;
using TipCalc.Core.ViewModels;
namespace TipCalc.Core
{
public class App : MvxApplication
{
public App()
{
//向IoC注册计算小费的服务
Mvx.RegisterType<ICalculation, Calculation>();
//设置App的启动界面
Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<TipViewModel>());
}
}
}
6.完成!
到此我们完成了Core工程的全部代码。来看看我们具体都做了什么:
- 用Profile 259新建了一个PCL项目。
- 用Nuget向项目中添加了MvvmCross的程序集。
- 添加了ICalculate接口和他的实现。
- 添加了TipViewModel。
- 继承自MvxViewModel。
- 向框架请求了ICalculate服务。
- 添加了一些会调用RaisePropertyChanged函数的公共属性。- 添加了App启动配置。
- 继承自MvxApplication。
- 为ICalculate接口注册了他的具体实现。
- 注册了应用程序的启动界面。
差不多每个使用MvvmCross的App都会有以上步骤。
接下来我们来看看每个平台该做些什么。
二、Android部分
Android部分y-z-f已经有写过相关的文章了:
1.创建Android项目
在解决方案中创建一个空的Android项目([Android] -- [Blank App]),命名为TipCalc.UI.Droid。
2.安装MvvmCross并引用Core库
与Core一样使用Package Manager Console安装MvvmCross。记得切换Package Manager Console的对应项目。
Install-Package MvvmCross.HotTuna.MvvmCrossLibraries
因为需要在Android的axml文件中使用MvvmCross的相关命令,需要添加一个名为MvxBind.Xml文件到Resources/Values文件夹中。内容如下
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MvxBinding">
<attr name="MvxBind" format="string"/>
<attr name="MvxLang" format="string"/>
</declare-styleable>
<declare-styleable name="MvxListView">
<attr name="MvxItemTemplate" format="string"/>
<attr name="MvxDropDownItemTemplate" format="string"/>
</declare-styleable>
<item type="id" name="MvxBindingTagUnique"/>
<declare-styleable name="MvxImageView">
<attr name="MvxSource" format="string"/>
</declare-styleable>
</resources>
3.添加Setup代码
我们需要一个Setup类来控制Android App的启动行为,在Android根目录下添加一个Setup类,内容如下:
using Android.Content;
using Cirrious.MvvmCross.Droid.Platform;
using Cirrious.MvvmCross.ViewModels;
namespace TipCalc.UI.Droid
{
public class Setup : MvxAndroidSetup
{
public Setup(Context applicationContext)
: base(applicationContext)
{
}
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
}
}
本文中使用默认的启动行为,所以简简单单的继承MvxAndroidSetup就行了。
4.添加View
Android的View分为2个部分:Layout与Activity。数据绑定可以直接写在布局文件中。
①添加Android布局文件(AXML)
在Resource\layout中添加View_Tip.axml文件,内容如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res/TipCalc.UI.Droid"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="总消费" />
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxBind="Text SubTotal" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="小费比例" />
<SeekBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:max="40"
local:MvxBind="Progress Generosity" />
<View
android:layout_width="fill_parent"
android:layout_height="1dp"
android:background="#ffff00" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="小费" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxBind="Text Tip" />
</LinearLayout>
Note:Android的数据绑定
Android的数据绑定形如:
local:MvxBind="Text Tip"
这句话相当于WPF中的:
Text = "{Binding Tip}"
前一个属性表示需要绑定控件的哪个属性,后一个属性表示需要将前面指定的属性绑定到ViewModel的哪个属性。
MvvmCross提供了大部分控件属性的绑定模式,对于没有默认实现的属性我们可以自定义绑定。
②添加Activity代码
因为在Android中AXML文件需要经由Activity(Fragment)来呈现,而且也可以顺便在Activity写一些AXML文件里面无法写的后台代码,所以MVVM中的View在Android指的是Activity。
本处于原文有一定差异,原文中MvvmCross官方将Activity文件命名为xxxView,容易和Android中的View混淆,所以在这里我将其命名为xxxViewActivity。
在项目中新建Views文件夹,并添加一个TipViewActivity类,内容如下:
using Android.App;
using Cirrious.MvvmCross.Droid.Views;
using TipCalc.Core.ViewModels;
namespace TipCalc.UI.Droid.Views
{
[Activity(Label = "Tip", MainLauncher = true)]
public class TipViewActivity
: MvxActivity<TipViewModel>
{
protected override void OnViewModelSet()
{
base.OnViewModelSet();
SetContentView(Resource.Layout.View_Tip);
}
}
}
此处与原文有差异,原文是利用new关键字覆盖了父类中的ViewModel并没有使用泛型。这里推荐用使用泛型类。
5.完成!
三、iOS部分
接下来看看如何实现iOS App。
本文与原文有一定差异,原文中是用Xib的方式创建界面,在本文中为了方便直接使用代码创建界面。
MvvmCross不推荐使用Storyboard创建iOS界面,因为Storyboard包含有一定的逻辑成分,如导航的逻辑,况且在iOS编程中Storyboard、Xib、纯代码三种创建界面的方式也一直在争论。我的推荐是利用纯代码创建界面,至于原因我以后会详细说明。MvvmCross也可以使用Storyboard的,如何使用我也会在后续的文章中说明。
1.创建iOS项目
在解决方案中创建一个空的iOS项目([iOS]--[Universal]--[Blank App(iOS)]),名称为TipCalc.UI.Touch。
2.安装MvvmCross并引用Core库
和Android一样,用Package Manager Console安装就行。
3.添加Setup代码
操作也和Android一样,不过构造函数有些许不同。
using Cirrious.MvvmCross.Touch.Platform;
using Cirrious.MvvmCross.Touch.Views.Presenters;
using Cirrious.MvvmCross.ViewModels;
namespace TioCalc.UI.Touch
{
public class Setup : MvxTouchSetup
{
public Setup(IMvxApplicationDelegate applicationDelegate, IMvxTouchViewPresenter presenter)
: base(applicationDelegate, presenter)
{
}
protected override IMvxApplication CreateApp()
{
return new TipCalc.Core.App();
}
}
}
4.在AppDelegate中启用Setup
首先我们需要将AppDelegate的基类改为MvxApplicationDelegate。
public partial class AppDelegate : MvxApplicationDelegate
修改FinishedLaunching函数,这个函数是在App启动初始化完成后被调用的。
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
//使用默认的呈现器
var presenter = new MvxTouchViewPresenter(this, window);
var setup = new Setup(this, presenter);
setup.Initialize();
//从IoC中获取启动界面
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
window.MakeKeyAndVisible();
return true;
}
修改后的AppDelegate.cs文件如下:
using Cirrious.CrossCore;
using Cirrious.MvvmCross.Touch.Platform;
using Cirrious.MvvmCross.Touch.Views.Presenters;
using Cirrious.MvvmCross.ViewModels;
using Foundation;
using UIKit;
namespace TioCalc.UI.Touch
{
[Register("AppDelegate")]
public class AppDelegate : MvxApplicationDelegate
{
private UIWindow window;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
//使用默认的呈现器
var presenter = new MvxTouchViewPresenter(this, window);
var setup = new Setup(this, presenter);
setup.Initialize();
//从IoC中获取启动界面
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
window.MakeKeyAndVisible();
return true;
}
}
}
5.添加View
因为iOS原本为MVC模式,UIView只是纯粹的界面,并不能添加逻辑代码,所以对于MvvmCross来说,iOS的View应该是ViewController。Xib与Storyboard的描述文件虽然是Xml但是可读性很差,苹果也不推荐修改Xml,所以数据绑定等代码需要写在ViewController里面。
原文中官方对ViewController的命名是直接命名成View的,但是我觉得会和UIView混淆,所以对MVVM中View在iOS中的命名写成xxxxViewController。
在项目中新建Views文件夹,并在其中添加一个类,名称为TipCalcViewController:
using Cirrious.MvvmCross.Binding.BindingContext;
using Cirrious.MvvmCross.Touch.Views;
using CoreGraphics;
using TipCalc.Core.ViewModels;
using UIKit;
namespace TioCalc.UI.Touch.Views
{
public class TipViewController : MvxViewController<TipViewModel>
{
/// <summary>
/// 当View被加载完成后调用,此时View还没有被显示出来,详情请查看iOS ViewController的生命周期
/// </summary>
public override void ViewDidLoad()
{
base.ViewDidLoad();
#region 创建6个控件
var SubTotalTextField = new UITextField();
var GenerositySlider = new UISlider();
var TipLabel = new UILabel();
var subTotalInfoLabel = new UILabel {Text = "总消费:"};
var generosityInfoLabel = new UILabel {Text = "比例:"};
var tipInfoLabel = new UILabel {Text = "小费:"};
#endregion
#region 将6个控件加入到View中,并固定位置
View.AddSubview(subTotalInfoLabel);
subTotalInfoLabel.Frame = new CGRect(60, 100, 200, 30);
View.AddSubview(SubTotalTextField);
SubTotalTextField.Frame = new CGRect(60, 140, 200, 30);
SubTotalTextField.KeyboardType = UIKeyboardType.NumberPad;
SubTotalTextField.BorderStyle = UITextBorderStyle.RoundedRect;
View.AddSubview(generosityInfoLabel);
generosityInfoLabel.Frame = new CGRect(60, 180, 200, 30);
View.AddSubview(GenerositySlider);
GenerositySlider.Frame = new CGRect(60, 220, 200, 30);
GenerositySlider.MaxValue = 100;
GenerositySlider.MinValue = 0;
View.AddSubview(tipInfoLabel);
tipInfoLabel.Frame = new CGRect(60, 260, 200, 30);
View.AddSubview(TipLabel);
TipLabel.Frame = new CGRect(60, 300, 200, 30);
#endregion
#region 数据绑定
this.CreateBinding(SubTotalTextField).To<TipViewModel>(vm => vm.SubTotal).Apply();
this.CreateBinding(GenerositySlider).To<TipViewModel>(vm => vm.Generosity).Apply();
this.CreateBinding(TipLabel).To<TipViewModel>(vm => vm.Tip).Apply();
#endregion
}
}
}
Note : 关于iOS的绑定
因为上面所说的原因,iOS的绑定需要在ViewController内使用代码进行绑定,虽然比Android和Windows平台复杂,但是总的来说还是比较简单的。
this.CreateBinding(TipLabel).To<TipViewModel>(vm => vm.Tip).Apply();
等同于
this.CreateBinding(TipLabel).For(label => label.Text).To("Tip").Apply();
因为大部分控件都有一个默认的绑定属性,所以在大部分情况下可以省略指定属性的步骤。
目标属性指定时也可以直接用字符串,实际上利用表达式树的形式也是转换为字符串的,这样做的目的是为了在重命名时能够自动修改所有的绑定,避免重命名时少修改了一个字符串而导致的错误。
6.完成!
来看看iOS的效果吧:
四、Windows 8 通用应用程序部分
前面我们已经完成了Android和iOS部分,接下来我们来看看MvvmCross如何在Windows通用程序使用。
本例以2013的Win8通用程序作为例子。Win10我还没看。
1.创建Windows通用应用程序
在VS中新建一个[应用商店]--[空白应用程序(通用应用程序)](Blank App Universal Apps),名称为TipCalc.UI.Win。
一个通用应用程序包括了3个部分:
- Shared库项目,这是一个Windows项目和WindowsPhone项目共用的部分。通常我们会把这2个平台的可以共用业务逻辑放在这,不过因为MvvmCross已经将业务逻辑移到Core库中,以供多个平台使用,所以在这个Share库里面不会有过多的代码。
- Windows平台UI项目。运行Win8和Win10的设备将会使用这个项目。
- WindowsPhone平台UI项目。运行WindowsPhone的设备将会使用这个项目。
2.安装MvvmCross与引用Core库
和Android、iOS一样,分别对2个平台的UI项目使用Package Manager Console
Install-Package MvvmCross.HotTuna.MvvmCrossLibraries
然后在分别引用Core库。并删掉各自的MainPage.xaml。
3.增加Setup代码
在Shared项目的根目录中新建一个Setup类,正如我们前面说的一样,每一个平台都需要对应的Setup类来控制程序的启动行为。对于Windows和WindowsPhone 我们可以共用一个Setup类。
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.WindowsCommon.Platform;
using Cirrious.MvvmCross.WindowsCommon.Views;
namespace TipCalc.UI.Win
{
public class Setup : MvxWindowsSetup
{
public Setup(Frame rootFrame)
: base(rootFrame)
{
}
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
}
}
Setup本质上是返回一些用来控制程序运行的对象,当我们需要在Windows和WindowsPhone中实现不同的效果,可以用条件编译等方式让Setup返回不同的对象来达到控制程序不同运行效果的目的。
4.在App.xaml.cs启用Setup
修改App.xaml.cs文件的OnLaunch回调函数:
先删掉这几行:
if (!rootFrame.Navigate(typeof(MainPage), e.Arguments))
{
throw new Exception("Failed to create initial page");
}
然后在这几行的位置输入下面这段代码:
var setup = new Setup(rootFrame);
setup.Initialize();
var start = Mvx.Resolve<IMvxAppStart>();
start.Start();
5.增加View
这部分需要分别对Windows与WindowsPhone项目进行操作,但是操作的过程是一模一样的,除了WindowsPhone的布局有点不一样。这里我们只说如何在Windows项目操作。
①在TipCalc.UI.Win.Windows项目中新建Page
在TipCalc.UI.Win.Windows项目中创建Views文件夹。在其中添加一个基本页(Basic Page),名为TipView.xaml。
创建基本页的过程中VS会问我们是不是需要添加一些辅助类,选是,这时VS会自动向项目中添加一些辅助类,不过在本文中我们用不着他们,不用管他们。
②将TipView修改为MvvmCross基本页
在TipView.cs文件中将TipView的基类改为MvxWindowsPage:
public sealed partial class TipView : Page
修改为:
public sealed partial class TipView : MvxWindowsPage
并修改OnNavigationTo和OnNavigationFrom回调函数,让其调用基类对应的函数。这样做的目的是为了让MvvmCross知道页面的状态,并执行相应操作,如利用IoC填充ViewModel属性。
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
navigationHelper.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
navigationHelper.OnNavigatedFrom(e);
}
③设置ViewModel
与iOS和Android不同的是我们需要用new关键字手动设置ViewModel。
这里与原文有点差异,原文3个平台都是用这个方法设置ViewModel的,但是因为可以利用泛型来设置ViewModel,所以我们没有采用这种方法。通用应用程序与iOS、Android不同的是,他的View的基类在Xaml中定义了一次,而Xaml不能使用泛型(也可能是我不知道怎么设置),所以只能采用这种方式。
为什么一定要设置好View的ViewModel的类型,而不是使用一个公用的基类呢?因为MvvmCross是通过反射View的ViewModel属性来确定View和ViewModel的对应关系的,如果你将2个View里面的ViewModel的类型设置为一样,MvvmCross启动的时候就会报错,因为他不知道ViewModel该如何对应这2个View。因为MvvmCross的导航系统是基于ViewModel的,所以View和ViewModel必须是一一对应的。
当然也有方法让不同的View对应相同的ViewModel,因为涉及到导航系统,所以在这里不展开讲,以后会有专门的文章来介绍的。
修改TipView.cs增加如下代码:
public new TipViewModel ViewModel
{
get { return (TipViewModel)base.ViewModel; }
set { base.ViewModel = value; }
}
这样后我们的TipView.cs文件大概是这样子的:(我删掉了注释)
using Windows.UI.Xaml.Navigation;
using Cirrious.MvvmCross.WindowsCommon.Views;
using TipCalc.Core.ViewModels;
using TipCalc.UI.Win.Common;
namespace TipCalc.UI.Win.Views
{
public sealed partial class TipView : MvxWindowsPage
{
private NavigationHelper navigationHelper;
private ObservableDictionary defaultViewModel = new ObservableDictionary();
public new TipViewModel ViewModel
{
get { return (TipViewModel)base.ViewModel; }
set { base.ViewModel = value; }
}
public ObservableDictionary DefaultViewModel
{
get { return this.defaultViewModel; }
}
public NavigationHelper NavigationHelper
{
get { return this.navigationHelper; }
}
public TipView()
{
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += navigationHelper_LoadState;
this.navigationHelper.SaveState += navigationHelper_SaveState;
}
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
}
private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
}
#region NavigationHelper 注册
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
navigationHelper.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
navigationHelper.OnNavigatedFrom(e);
}
#endregion
}
}
④修改Xaml布局文件
首先我们需要修改Page的基类为MvxWindowsPage,将:
<Page ..***被魔法少女隐藏起来的Page属性***...>
<!-- 被触手拖走的Page内容 -->
</Page>
修改为:
<views:MvxWindowsPage
xmlns:views="using:Cirrious.MvvmCross.WindowsCommon.Views"
..***被魔法少女隐藏起来的其他Page属性***...
>
<!-- 被触手拖走的Page内容 -->
</views:MvxWindowsPage>
手动拖入控件,或者自己写Xaml,让Page看起来像这个样子:
Xaml代码如下:
<views:MvxWindowsPage
x:Class="TipCalc.UI.Win.Views.TipView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="using:Cirrious.MvvmCross.WindowsCommon.Views"
>
<Page.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="40"/>
<Setter Property="Margin" Value="20"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="FontSize" Value="40"/>
<Setter Property="Margin" Value="20"/>
</Style>
</Page.Resources>
<StackPanel Margin="100" Width="400">
<TextBlock Text="总消费" />
<TextBox Text="{Binding SubTotal,Mode = TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="小费的比例" />
<Slider Value="{Binding Generosity,Mode=TwoWay}" SmallChange="1" LargeChange="10" Minimum="0" Maximum="100"/>
<TextBlock Text="小费" />
<TextBlock Text="{Binding Tip}"/>
</StackPanel>
</views:MvxWindowsPage>
⑥WindowsPhone项目与Windows的区别
首先是布局上的区别,很明显FontSize = 40在WP上有点大,还有StackPaanel的Margin也有点大,改到比较合适的值就行了。
然后是WindowsPhone的导航行为和其他平台有一定的差异,系统会缓存View来复用,所以我们需要在逻辑上创建一个新View时重置他的ViewModel。
我也没怎么研究过WindowsPhone,所以不了解这个复用机制。如果我说的有问题请在评论中指出。
在修改TipView.cs时需要将OnNavigationTo函数修改成下面这样:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.NavigationMode == NavigationMode.New)
{
ViewModel = null;
}
base.OnNavigatedTo(e);
this.navigationHelper.OnNavigatedTo(e);
}
6.完成
让我们看看2个平台的运行效果:
MvvmCross[翻译] 使用Xamarin与MvvmCross完成一个跨平台App的更多相关文章
- (翻译)Xamarin.Essentials: 移动应用的跨平台 API
原文地址:https://blog.xamarin.com/xamarin-essentials-cross-platform-apis-mobile-apps/ 当使用 Xamarin 开发 IOS ...
- 用Xamarin和Visual Studio编写iOS App
一说开发 iOS app,你立马就会想到苹果的开发语言 Objective C/Swift 和 Xcode.但是,这并不是唯一的选择,我们完全可以使用别的语言和框架. 一种主流的替换方案是 Xamar ...
- 使用MvvmCross框架实现Xamarin.Forms的汉堡菜单布局
注:本文是英文写的,偷懒自动翻译过来了,原文地址:Implementing MasterDetail layout in Xamarin.Forms by MvvmCross 欢迎大家关注我的公众号: ...
- Xamarin.iOS + MvvmCross: UIPickerView data binding, SelectedItemChanged event
UI initialization: _pickerView = new UIPickerView(); _pickerView.ShowSelectionIndicator = true; _pic ...
- 《Programming WPF》翻译 第9章 2.选择一个基类
原文:<Programming WPF>翻译 第9章 2.选择一个基类 WPF提供了很多类,当创建一个自定义元素时,你可以从这些类中派生.图9-1显示了一组可能作为类--可能是合适的基类, ...
- xamarin其实也是一个鸡肋
刚刚体验了一把VS 2015,后来因为部分sdk和工具下载太慢,还是最终决定卸载.顺道了解了下xamarin studio.个人感觉Xamarin完全也是一个鸡肋,没必要过多的时间在xamarin上面 ...
- (翻译)Xamarin.Essentials 最新预览版的更多跨平台 API
原文地址:https://blog.xamarin.com/cross-platform-apis-xamarin-essentials-latest-preview/ 在 Microsoft Bui ...
- 编写第一个Flutter App(翻译)
博客搬迁至http://blog.wangjiegulu.com RSS订阅:http://blog.wangjiegulu.com/feed.xml 以下代码 Github 地址:https://g ...
- Xamarin+Prism小试牛刀:定制跨平台Outlook邮箱应用(后续)
在[Xamarin+Prism小试牛刀:定制跨平台Outlook邮箱应用]里面提到了Microsoft 身份认证,其实这也是一大块需要注意的地方,特作为后续补充这些知识点.上章是使用了Microsof ...
随机推荐
- Error creating bean with name 'sessionFactory' defined in class path resource [applicationContext.xml]: Invocation of init method failed; neste
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFacto ...
- ORA-65096: invalid common user or role name
CREATE USER xx IDENTIFIED BY yy DEFAULT TABLESPACE USERS TEMPORARY TABLESPACE TEMP PROFILE "DEF ...
- 《C语言程序设计现代方法》第2章 编程题
7 编写一个程序,要求用户输入一个美金数量,然后显示出如何使用最少的20美元.10美元.5美元和1美元来付款. 提示:将付款金额除以20,确定20美元的数量,然后从付款金额中减去20美元的总金额.对其 ...
- Bzoj 3450: Tyvj1952 Easy 期望/概率,动态规划
3450: Tyvj1952 Easy Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 431 Solved: 325[Submit][Status] ...
- 广州Uber优步司机奖励政策(2月1日~2月7日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- java的主函数中各个词的作用
主函数 public static void main(String[] args){} public: main主方法是由jvm(虚拟机)来调用,jvm实际也是一程序,为了保证jvm能在任何情况下调 ...
- 多目标遗传算法 ------ NSGA-II (部分源码解析) 拥挤距离计算 crowddist.c
/* Crowding distance computation routines */ # include <stdio.h> # include <stdlib.h> # ...
- 2016-5-19模拟测试 bzoj3652 bzoj3653 bzoj3654
T1 description 给定正整数\(n\),定义\(f(x) = \max{y \ \mathrm{xor}\ x}(y<n)\) \(x\)在\([0,n)\)随机取值,求\(f(x) ...
- Winform 换皮肤
winform换肤流程如下: (1)程序入口,添加以下代码: //换肤 private void Skinjsj() { DevExpress.UserSkins.BonusSkins.Registe ...
- <input type="radio" >与<input type="checkbox">值得获取
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"% ...