XAML in Xamarin.Forms 基礎篇 電子書

XAML in Xamarin.Forms 基礎篇 電子書
XAML in Xamarin.Forms 基礎篇 電子書

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書

2016/08/04

Xamarin.Forms MVVM 123

有無MVVM的專案開發比較

在這篇筆記中,將會透過三個專案,分別說明:
  1. 當沒有採用 MVVM 設計方法來進行專案開發的時候,如何透過程式碼後置 (Code Behind) 的方式,把相關商業邏輯串接起來。
  2. 當採用 MVVM 設計方法來進行專案開發的時候,如何自行定義與架構出您的 ViewModel,並且,在 View 中如何設定目標物件的綁定模式。
  3. 在這裡,將會說明如何使用 微軟 Microsoft Prism 這個套件提供的 MVVM 功能,快速方便的開發出 MVVM 專案。
這個專案的視覺設計,首先會
  1. 放置一個 Label 控制項,這個控制項會取得下一個 Entry 使用者輸入的值。
  2. 接著放置一個 Entry 控制項,讓使用者輸入任何文字,並且會動態即時更新道上一個 Label 控制項內
  3. 放置一個 Entry 控制項,讓使用者輸入任何文字,並且會更新上一個 Entry 控制項內的值,當然,就會連動更新第一個 Label 控制項的顯示內容。
  4. 放置一個 Button 按鈕,當按下這個按鈕,會在下一個 Label 內顯示內容
  5. 放置一個 Label 控制項,用來顯示當按鈕按下之後的內容。
下圖說明了,當使用者在 Entry 控制項輸入任何內容的時候,會在第一個 Label 控制項內顯示出來。
接著,當使用按下按鈕,也會顯示一段訊息。
MVVM1的結果
下圖表示當在第二個 Entry 控制項內輸入了任何內容,前面兩個 Label & Entry 控制項,都會同步更新同樣的內容。
MVVM1的結果

沒有使用 MVVM

底下說明的內容,已經實作出專案,並且放是在底下 GitHub 內。
使用 Visual Studio 2015,開啟 MVVM1.sln 方案
開啟查看核心PCL MVVM1 專案內的 MainPage.xaml,其 XAML 標記宣告內容如下:
這裡依照前面的視覺定義,使用 StackLayout 版面配置物件項目將這些控制項垂直排列出來。
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MVVM1"
             x:Class="MVVM1.MainPage">

  <StackLayout
    HorizontalOptions="Fill" VerticalOptions="Center">
    <Label x:Name="lbl輸入文字" />
    <Entry x:Name="eny輸入文字1" />
    <Entry x:Name="eny輸入文字2" />
    <Button x:Name="btn按鈕" Text="請按下我" />
    <Label x:Name="lbl按鈕文字" />
  </StackLayout>

</ContentPage>
開啟查看核心PCL MVVM1 專案內的 MainPage.xaml.cs,其code behind 的C#程式碼如下:
在 code beind 內,
  1. 定義了 Entry 的 TextChanged 的事件,當這個事件發生的時候,要進行其他資料的更新。
  2. 定義了 Button 的 Clicked 事件,當使用者按下這個按鈕之後,要更新相關控制項的內容。
這樣的定義與處理,好像非常普通,可是,由於相關商業邏輯都寫在 Code Behind 內,會帶來許多不良問題,因此,才會需要接下來的 MVVM 設計方法來改善這些問題。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace MVVM1
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            eny輸入文字1.TextChanged += (s, e) =>
            {
                lbl輸入文字.Text = eny輸入文字1.Text;
            };
            eny輸入文字2.TextChanged += (s, e) =>
            {
                eny輸入文字1.Text = eny輸入文字2.Text;
            };
            btn按鈕.Clicked += (s, e) =>
            {
                lbl按鈕文字.Text = $"您已經按下按鈕";
            };
        }
    }
}

使用 INotifyPropertyChanged 的 MVVM

底下說明的內容,已經實作出專案,並且放是在底下 GitHub 內。
使用 Visual Studio 2015,開啟 MVVM1.sln 方案
開啟查看核心PCL MVVM2 專案內的 MainPage.xaml,其 XAML 標記宣告內容如下:
  1. 在 ContentPage 的屬性項目 (Property Element)中(ContentPage.BindingContext) ,定義了這個整個頁面與所有控制項的 BindingContext 可以資料綁定的來源,都是來自於 MainPageViewModel 物件內。
  2. 在需要的控制項內,使用 XAML 標記延伸 (Markup Extension) 功能,也就是大括號,標示這個屬性值,是要透過資料細節的方式,從 BindingContext 內的 ViewModel 內取得。
  3. 在這裡,其實已經不再需要 x:Name 這個標記延伸功能,因為,在 ViewModel 內,是無法讀取到任何 View 內的 x:Name 內容(這個標記延伸僅是提供 code behind 程式碼可以抓取到 XAML 內的這個控制項)
  4. 在按鈕的宣告中,其相關事件將會使用 Command 這個屬性來取代,透過資料細節到 ViewModel 內的特定的 ICommand 屬性;也就是,當使用者按下這個按鈕之後,就會執行綁定的 ICommand 方法。
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MVVM2"
             x:Class="MVVM2.MainPage">

  <ContentPage.BindingContext>
    <local:MainPageViewModel />
  </ContentPage.BindingContext>

  <StackLayout
  HorizontalOptions="Fill" VerticalOptions="Center">
    <Label x:Name="lbl輸入文字"
           Text="{Binding EntryText1}"/>
    <!--這裡若改成 OneWay 會有甚麼效果呢?-->
    <Entry x:Name="eny輸入文字1"
           Text="{Binding EntryText1, Mode=TwoWay}"/>
    <Entry x:Name="eny輸入文字2"
           Text="{Binding EntryText2, Mode=TwoWay}"/>
    <Button x:Name="btn按鈕" Text="請按下我"
            Command="{Binding PushMeCommand}"/>
    <Label x:Name="lbl按鈕文字"
           Text="{Binding LabelText1}"/>
  </StackLayout>

</ContentPage>
由於使用了 MVVM 設計方法,所以,也就不會有任何 code behind 在 MainPage.xaml.cs 內。
開啟查看核心PCL MVVM2 專案內的 MainPageViewModel,其 ViewModel 的C#程式碼如下:
  1. 在這個 ViewModel 內,定義了四個屬性 (Property)
  2. 這個 ViewModel 需要實作 INotifyPropertyChanged 葉面,並且也要實作 OnPropertyChanged 方法
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;

namespace MVVM2
{
    public class MainPageViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        #region EntryText1
        private string _EntryText1;

        public string EntryText1
        {
            get
            {
                return _EntryText1;
            }
            set
            {
                if (_EntryText1 != value)
                {
                    _EntryText1 = value;
                    OnPropertyChanged("EntryText1");
                }
            }
        }
        #endregion

        #region EntryText2
        private string _EntryText2;

        public string EntryText2
        {
            get
            {
                return _EntryText2;
            }
            set
            {
                if (_EntryText2 != value)
                {
                    EntryText1 = value;
                    _EntryText2 = value;
                    OnPropertyChanged("EntryText2");
                }
            }
        }
        #endregion

        #region LabelText1
        private string _LabelText1;

        public string LabelText1
        {
            get
            {
                return _LabelText1;
            }
            set
            {
                if (_LabelText1 != value)
                {
                    _LabelText1 = value;
                    OnPropertyChanged("LabelText1");
                }
            }
        }
        #endregion

        #region Button ICommand
        public ICommand PushMeCommand { protected set; get; }
        #endregion

        public MainPageViewModel()
        {
            PushMeCommand = new Command(() =>
            {
                this.LabelText1 = $"您已經按下按鈕";
            });
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

使用 Prism MVVM 套件

底下說明的內容,已經實作出專案,並且放是在底下 GitHub 內。
使用 Visual Studio 2015,開啟 MVVM3.sln 方案
開啟查看核心PCL MVVM3 專案內的 MainPage.xaml,其 XAML 標記宣告內容如下:
  1. 在這個 XAML 宣告中,與 MVVM2 不同的地方,在於 Prism 提供了 ViewModel 自動注入之功能,只要在根項目內,使用 prism:ViewModelLocator.AutowireViewModel="True" 語法,就可以在建立 View 的時候,自動產生相對應的 ViewModel 並且,綁定到 BindingContext 屬性內。
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="MVVM3.Views.MainPage"
             Title="MainPage">

  <StackLayout
  HorizontalOptions="Fill" VerticalOptions="Center">
    <Label x:Name="lbl輸入文字"
           Text="{Binding EntryText1}"/>
    <Entry x:Name="eny輸入文字1"
           Text="{Binding EntryText1, Mode=TwoWay}"/>
    <Entry x:Name="eny輸入文字2"
           Text="{Binding EntryText2, Mode=TwoWay}"/>
    <Button x:Name="btn按鈕" Text="請按下我"
            Command="{Binding PushMeCommand}"/>
    <Label x:Name="lbl按鈕文字"
           Text="{Binding LabelText1}"/>
  </StackLayout>

</ContentPage>
由於使用了 MVVM 設計方法,所以,也就不會有任何 code behind 在 MainPage.xaml.cs 內。
開啟查看核心PCL MVVM3 專案內的 MainPageViewModel,其 ViewModel 的C#程式碼如下:
  1. 由於使用了 Prism 框架來開發,所以,繼承了 BindableBase 這個類別,在這個類別內,已經把 MVVM 需要實作的 ViewModel 要處理的內容,都做好了。
  2. 其他的內容大都與 MVVM2 內的 ViewModel 一樣。
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;

namespace MVVM3.ViewModels
{
    public class MainPageViewModel : BindableBase, INavigationAware
    {
        //private string _title;
        //public string Title
        //{
        //    get { return _title; }
        //    set { SetProperty(ref _title, value); }
        //}

        #region EntryText1
        private string _EntryText1;

        public string EntryText1
        {
            get
            {
                return _EntryText1;
            }
            set
            {
                SetProperty(ref _EntryText1, value);
            }
        }
        #endregion

        #region EntryText2
        private string _EntryText2;

        public string EntryText2
        {
            get
            {
                return _EntryText2;
            }
            set
            {
                SetProperty(ref _EntryText2, value);
                EntryText1 = value;
            }
        }
        #endregion

        #region LabelText1
        private string _LabelText1;

        public string LabelText1
        {
            get
            {
                return _LabelText1;
            }
            set
            {
                SetProperty(ref _LabelText1, value);
            }
        }
        #endregion

        #region Button ICommand
        public DelegateCommand PushMeCommand { protected set; get; }
        #endregion

        public MainPageViewModel()
        {
            PushMeCommand = new DelegateCommand(() =>
            {
                this.LabelText1 = $"您已經按下按鈕";
            });
        }


        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
        }
    }
}

沒有留言:

張貼留言