MvvmCross[翻譯] 使用Xamarin與MvvmCross完成一個跨平台App


總覽

原文:https://github.com/MvvmCross/MvvmCross/wiki/Tip-Calc-A-first-app

我們所做的第一個Model-View-ViewModel(MVVM)程序需要為一個餐館實現一個跨平台的小費計算器。

他的線框圖大概是下面這個樣子:

App線框圖

本片文章大概會涉及到以下幾個方面:

  • 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目標平台

確保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

關於Profile259Profile259包括了大多數.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個被擴展過的屬性SubTotalGenerosityTip
當他們被修改的時候會調用RaisePropertyChanged函數來通知其他的對象他們的屬性被修改過了。
且當SubTotalGenerosity被修改時會重新計算小費。

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已經有寫過相關的文章了:

http://www.cnblogs.com/yaozhenfa/p/4709952.html

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個部分:LayoutActivity數據綁定可以直接寫在布局文件中。

①添加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.完成!

Alt text


三、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的效果吧:

Alt text


四、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類來控制程序的啟動行為。對於WindowsWindowsPhone 我們可以共用一個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本質上是返回一些用來控制程序運行的對象,當我們需要在WindowsWindowsPhone中實現不同的效果,可以用條件編譯等方式讓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

這部分需要分別對WindowsWindowsPhone項目進行操作,但是操作的過程是一模一樣的,除了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

並修改OnNavigationToOnNavigationFrom回調函數,讓其調用基類對應的函數。這樣做的目的是為了讓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屬性來確定ViewViewModel對應關系的,如果你將2個View里面的ViewModel的類型設置為一樣,MvvmCross啟動的時候就會報錯,因為他不知道ViewModel該如何對應這2個View。因為MvvmCross的導航系統是基於ViewModel的,所以ViewViewModel必須是一一對應的。

當然也有方法讓不同的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看起來像這個樣子:
Windows平台界面

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個平台的運行效果:
Windows平台運行效果

Alt text


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM