Prism MVVM 初探

本次中年大叔的鹹魚翻身作戰計畫執行至止也有一個多月了,在昨天也已經初步地完成了《後端》(也稱為伺服器端)的實作,接下來要學習的是《前端》(也稱為用戶端)的實作。在最初的幾天會先學習與 Xamarim.Forms 以及 Prism MVVM 有關的基本知識,接著會學習使用 HttpClient 由 Xamarin.Forms 應用程式(不再透過 Postman)存取 Web API 的 CRUD 動作。

註:
剛剛所提到的:「初步地完成了《後端》的實作」,這並不是指《後端》所要學的只是這些,不要忘了,我們要學的是企業級的商業應用程式,將來要面對的商業邏輯會比目前的複雜好幾倍,目前只是約略地理解商業應用程式有《前端》與《後端》之分,《前端》與《後端》是使用什麼機制來溝通,往後還會有更多的文章會再更深入的學習。

MVVM Pattern

阿源哥哥並不想在這裡深入地解釋何謂 MVVM(Model - View - ViewModel)設計模式,因為在網路上隨便找都能找到篇講得比阿源哥哥好的,本篇的重點會比較著重於如何將 Prism MVVM 應用在我們的專案開發上。

註:
先前在討論《Models 和 Entities》時,有提到 ViewModel 一詞,當時所講的 ViewModel 與目前 MVVM Pattern 中的 ViewModel 並不是相同的,先前的 ViewModel 是指因應在 View 中展示所需的資料模型(這倒比較像是 MVVM 中的 Model), 而現在所講的 ViewModel 是用來將 Model 和 View 串接起來,並包含使用者介面的展示邏輯(比如說判斷按下 View 中的某一按鈕,接著要執行什麼指令或導向某一頁,這倒比較類似 MVC 中所講的 Controller)。

Prism MVVM 命名規則

還記得先前《Xamarin.Forms 初體驗》時,我們使用 Prism Template Pack 所提供的專案範本,建立出 Xamarin.Forms 專案,其專案架構如下圖下示:

由圖示,我們可以看出 View 與 ViewModel 的命名,以及擺放的位置有一定的規則,其中 View 是放置在 Views 資料夾中,而 ViewModel 被放置在 ViewModels 資料夾中。而 ViewModel 的名稱為 View 的名稱 + ViewModel,例如命名為 MainPage 的 View 所對應用 ViewModel 就該命名為 MainPageViewModel。依循正確的命名規則 View 和 ViewModel 才會被自動繫結。

行動應用程式的進入點

一般來講一支行動應用程式會有好幾個 View 所組成,當應用程式啟動時,該先進入哪一個 View 呢?

請以滑鼠左鍵雙擊 App.xaml.cs 開啟該檔案:

開啟的檔案如下:

using Prism.Unity;
using Demae.App.Views;
using Xamarin.Forms;

namespace Demae.App
{
    public partial class App : PrismApplication
    {
        public App(IPlatformInitializer initializer = null) : base(initializer) { }

        protected override void OnInitialized()
        {
            InitializeComponent();

            NavigationService.NavigateAsync("NavigationPage/MainPage?title=Hello%20from%20Xamarin.Forms");
        }

        protected override void RegisterTypes()
        {
            Container.RegisterTypeForNavigation<NavigationPage>();
            Container.RegisterTypeForNavigation<MainPage>();
        }
    }
}

接著會一一說明上述程式碼,命名為 App 繼承 PrismApplocation 的類別,在應用程式啟動時會被初始化,接著比較值得留意的是如下類別初始化時所執行程式:

protected override void OnInitialized()
{
    InitializeComponent();

    NavigationService.NavigateAsync("NavigationPage/MainPage?title=Hello%20from%20Xamarin.Forms");
}

覆寫OnInitialized() 方法,首先初始化元件,接著以 NavigationService.NavigateAsync() 將頁面導覽到以字串傳入的參數所指定的 View。以目前的參數為 "NavigationPage/MainPage?title=Hello%20from%20Xamarin.Forms" 表示應用程式啟動之後,首先進入的是命名為 MainPage 的 View。而在這之前的 NavigationPage 是一個特別虛擬存在的頁面,若是應用程式有必要有類似上一頁、下一頁,一頁一頁導覽的功能,則首頁必需放在 NavigationPage 之後(如果應用程式只有單獨一頁,就不需要在前面再加 NavigationPage 了)。

在 MainPage 之後的 ?title=Hello%20from%20Xamarin.Forms 為傳入 MainPage 的參數,參數名再為 title 內容為 Hello%20from%20Xamarin.Forms

接下來覆寫RegisterTypes() 方法,在方法內註冊應用程式所用到的 View ,往後隨著應用程式頁面的增加,每加入一個 View 就要記得在此註冊才能使用。

protected override void RegisterTypes()
{
    Container.RegisterTypeForNavigation<NavigationPage>();
    Container.RegisterTypeForNavigation<MainPage>();
}

XAML 語言

請以滑鼠左鍵雙擊 MainPage.xaml 開啟該檔案:

對於不曾開發過 WPF 或 Silverlight 的讀者可能會對於如下所示的 XAML(eXtensible Application Markup Language)語言感到莫生,往後還會再找機會深入解說 XAML 語法,目前只需要理解 XAML 可以對應於網頁的 HTML ,是一種標記語言,用來標記視覺化有關的元件。目前值得留意的是 prism:ViewModelLocator.AutowireViewModel="True" 的 ContentPage 屬性,此設定會自動繫結依命名規則命名的相對應 ViewModel 而 <Label Text="{Binding Title}" /> 的文字內容會繫結 ViewModel 內的 Title 屬性:

<?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="Demae.App.Views.MainPage"
             Title="MainPage">
  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
    <Label Text="{Binding Title}" />
  </StackLayout>
</ContentPage>

ViewModel

請以滑鼠左鍵雙擊 MainPageViewModel.cs 開啟該檔案:

在下述的程式碼,我們先留意 Title 這個屬性的值的如何來的(因為這個值會繫結到頁面顯示):

using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;

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

        public MainPageViewModel()
        {

        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
            if (parameters.ContainsKey("title"))
                Title = (string)parameters["title"] + " and Prism";
        }
    }
}

下述式為當頁面導入完成之後,取得 title 參數值加上 " and Prism"  指定為 Title 的屬性值:

public void OnNavigatedTo(NavigationParameters parameters)
{
    if (parameters.ContainsKey("title"))
        Title = (string)parameters["title"] + " and Prism";
}

經過以上的解說,聰明的讀者應該者應該知道底下畫面的執行結果是如何來的吧!

好吧!今天就先學習到這裡,明天再來學習各個頁面間的切換。