原文: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個平臺的運行效果: