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