原文:https://github.com/MvvmCross/MvvmCross/wiki/Tip-Calc-A-first-apphtml
咱们所作的第一个Model-View-ViewModel(MVVM)程序须要为一个餐馆实现一个跨平台的小费计算器。android
他的线框图大概是下面这个样子:ios
本片文章大概会涉及到如下几个方面:git
- MvvmCorss应用程序的整体结构。
- 构建一个MvvmCross程序所须要的基本代码。
- 如何在Xamarin.iOS、Xamarin.Android、Windows 通用应用程序中使用MvvmCross以及数据绑定。
本文只关注与MvvmCross,Xamarin相关的问题不会详细的说明。github
一个MvvmCross的App一般由如下几个部分组成:shell
- 一个共享的核心(Core)跨平台类库(Protable Class Library,PCL)。包含view model、service、converters等。
- 各 个平台的的UI工程。
通常来讲咱们从这个跨平台的Core库开始写。编程
本例使用Visual Studio 2013,Xamarin 3.11.386。iOS部分使用虚拟机、操做系统为10.10.3 xcode6。windows
首先在VS中新建一个PCL项目。名称为TipCalc.Core,解决方案名称为TipCalc(小费计算器):设计模式
在新建PCL项目时,VS会询问PCL的目标平台,请按照下面图片设置:
xcode
确保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生成的。
在 [工具] - [Nuget 程序包管理器] - [程序包管理器控制台] (Package Manager Console)中输入
Install-Package MvvmCross.HotTuna.MvvmCrossLibraries
这时Nuget会自动帮咱们把MvvmCross引入到咱们的工程中。
而后删掉自动生成的Class1.cs。
固然你也能够在项目右键,使用Nuget程序包管理器引入MvvmCross。不过根据个人经验,Nuget管理器一般会比较卡。推荐用控制台。
在项目中增长文件夹,名为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的业务逻辑已经实现了。
在添加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被修改时会从新计算小费。
在写好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>()); } } }
到此咱们完成了Core工程的所有代码。来看看咱们具体都作了什么:
- 用Profile 259新建了一个PCL项目。
- 用Nuget向项目中添加了MvvmCross的程序集。
- 添加了ICalculate接口和他的实现。
- 添加了TipViewModel。
- 继承自MvxViewModel。
- 向框架请求了ICalculate服务。
- 添加了一些会调用RaisePropertyChanged函数的公共属性。
- 添加了App启动配置。
- 继承自MvxApplication。
- 为ICalculate接口注册了他的具体实现。
- 注册了应用程序的启动界面。
差很少每一个使用MvvmCross的App都会有以上步骤。
接下来咱们来看看每一个平台该作些什么。
Android部分y-z-f已经有写过相关的文章了:
在解决方案中建立一个空的Android项目([Android] -- [Blank App]),命名为TipCalc.UI.Droid。
与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>
咱们须要一个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就好了。
Android的View分为2个部分:Layout与Activity。数据绑定能够直接写在布局文件中。
在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>
Android的数据绑定形如:
local:MvxBind="Text Tip"
这句话至关于WPF中的:
Text = "{Binding Tip}"
前一个属性表示须要绑定控件的哪一个属性,后一个属性表示须要将前面指定的属性绑定到ViewModel的哪一个属性。
MvvmCross提供了大部分控件属性的绑定模式,对于没有默认实现的属性咱们能够自定义绑定。
由于在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并无使用泛型。这里推荐用使用泛型类。
接下来看看如何实现iOS App。
本文与原文有必定差别,原文中是用Xib的方式建立界面,在本文中为了方便直接使用代码建立界面。
MvvmCross不推荐使用Storyboard建立iOS界面,由于Storyboard包含有必定的逻辑成分,如导航的逻辑,何况在iOS编程中Storyboard、Xib、纯代码三种建立界面的方式也一直在争论。个人推荐是利用纯代码建立界面,至于缘由我之后会详细说明。MvvmCross也可使用Storyboard的,如何使用我也会在后续的文章中说明。
在解决方案中建立一个空的iOS项目([iOS]--[Universal]--[Blank App(iOS)]),名称为TipCalc.UI.Touch。
和Android同样,用Package Manager Console安装就行。
操做也和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(); } } }
首先咱们须要将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; } } }
由于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 } } }
由于上面所说的缘由,iOS的绑定须要在ViewController内使用代码进行绑定,虽然比Android和Windows平台复杂,可是总的来讲仍是比较简单的。
this.CreateBinding(TipLabel).To<TipViewModel>(vm => vm.Tip).Apply();
等同于
this.CreateBinding(TipLabel).For(label => label.Text).To("Tip").Apply();
由于大部分控件都有一个默认的绑定属性,因此在大部分状况下能够省略指定属性的步骤。
目标属性指定时也能够直接用字符串,实际上利用表达式树的形式也是转换为字符串的,这样作的目的是为了在重命名时可以自动修改全部的绑定,避免重命名时少修改了一个字符串而致使的错误。
来看看iOS的效果吧:
前面咱们已经完成了Android和iOS部分,接下来咱们来看看MvvmCross如何在Windows通用程序使用。
本例以2013的Win8通用程序做为例子。Win10我还没看。
在VS中新建一个[应用商店]--[空白应用程序(通用应用程序)](Blank App Universal Apps),名称为TipCalc.UI.Win。
一个通用应用程序包括了3个部分:
- Shared库项目,这是一个Windows项目和WindowsPhone项目共用的部分。一般咱们会把这2个平台的能够共用业务逻辑放在这,不过由于MvvmCross已经将业务逻辑移到Core库中,以供多个平台使用,因此在这个Share库里面不会有过多的代码。
- Windows平台UI项目。运行Win8和Win10的设备将会使用这个项目。
- WindowsPhone平台UI项目。运行WindowsPhone的设备将会使用这个项目。
和Android、iOS同样,分别对2个平台的UI项目使用Package Manager Console
Install-Package MvvmCross.HotTuna.MvvmCrossLibraries
而后在分别引用Core库。并删掉各自的MainPage.xaml。
在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返回不一样的对象来达到控制程序不一样运行效果的目的。
修改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();
这部分须要分别对Windows与WindowsPhone项目进行操做,可是操做的过程是如出一辙的,除了WindowsPhone的布局有点不同。这里咱们只说如何在Windows项目操做。
在TipCalc.UI.Win.Windows项目中建立Views文件夹。在其中添加一个基本页(Basic Page),名为TipView.xaml。
建立基本页的过程当中VS会问咱们是否是须要添加一些辅助类,选是,这时VS会自动向项目中添加一些辅助类,不过在本文中咱们用不着他们,不用管他们。
在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); }
与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 } }
首先咱们须要修改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>
首先是布局上的区别,很明显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); }
让咱们看看2个平台的运行效果: