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

總覽

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

咱們所作的第一個Model-View-ViewModel(MVVM)程序須要爲一個餐館實現一個跨平臺的小費計算器。android

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

App線框圖

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

  • MvvmCorss應用程序的整體結構。
  • 構建一個MvvmCross程序所須要的基本代碼。
  • 如何在Xamarin.iOS、Xamarin.Android、Windows 通用應用程序中使用MvvmCross以及數據綁定

本文只關注與MvvmCross,Xamarin相關的問題不會詳細的說明。github


1、核心庫(PCL部分)

一個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

1.建立一個新的Portable Class Library

首先在VS中新建一個PCL項目。名稱爲TipCalc.Core,解決方案名稱爲TipCalc(小費計算器):設計模式

新建項目

在新建PCL項目時,VS會詢問PCL的目標平臺,請按照下面圖片設置:
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

關於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都會有以上步驟。

接下來咱們來看看每一個平臺該作些什麼。

2、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


3、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


4、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

相關文章
相關標籤/搜索