XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2018/04/27

Xamarin 新手入門 Part 6 我該選擇 Xamarin.Forms 或者 Xamarin 原生方式來開發跨平台應用程式專案呢?



對於已經具備擁有 .NET / C# 開發技能的開發者,可以使用 Xamarin.Forms Toolkit 開發工具,便可以立即開發出可以在 Android / iOS 平台上執行的 App;對於要學習如何使用 Xamarin.Forms & XAML 技能,現在已經推出兩本電子書來幫助大家學這這個開發技術。
這兩本電子書內包含了豐富的逐步開發教學內容與相關觀念、各種練習範例,歡迎各位購買。
Xamarin.Forms 電子書
想要購買 Xamarin.Forms 快速上手 電子書,請點選 這裡

想要購買 XAML in Xamarin.Forms 基礎篇 電子書,請點選 這裡


經過一系列的初步認識 Xamarin 原生方式開發與 Xamarin.Forms 方式開發的方式,大部分第一次接觸 Xamarin Toolkit 工具集的人,都會開始存在著許多問題,我究竟要使用原生方式來進行專案開發,還是要採用 Xamarin.Forms 的方式來進行開發呢?在這篇文章中,將會提供這些相關疑問的解釋說明。

問題:我該選擇原生方式開發還是 Xamarin.Forms 的專案開發

這個需要視您的行動專案應用程式需求而定,當我們選擇了 Xamarin.Forms 形式來進行跨平台專案開發的時候,我們僅需要設計一套 UI 標記宣告與一個商業邏輯的C# 程式代碼,如此,我們就可以同時在 Xamarin.iOS & Xamarin.Android 專案中使用剛剛設計出來的 UI與商業邏輯,根據我這裡實際幫客戶進行專案開發的經驗,一個約採用了50個以上頁面的 Xamarin.Forms 專案,具備了 RESTful Web API 呼叫、推播等機制,共用程式碼的部分可以達到 96% 以上,上下的 4% 僅需要分別在 Xamarin.iOS & Xamarin.Android 原生專案內進行設計,這樣可以大幅提升同時開發兩個平台的應用程式開發效率,而且,最重的是,當應用程式的頁面或者商業邏輯發生了問題,我們也僅需要再 Xamarin.Forms 專案內進行修正,這樣,兩個平台的應用程式經過重新建置之後,就都修復掉了這些問題。
若我們採用原生方式進行開發跨平台應用程式專案, Xamarin.iOS & Xamarin.Android , 此時,關於採用 C# 設計出來的商業邏輯部分,可以分別共用於 Xamarin.iOS & Xamarin.Android 原生專案內,不過,關於 UI 的部分,我們就需要分別在 Xamarin.iOS 原生專案中,使用 Xcode 的 Storyboard 的方式進行設計,這部分可以參考 iOS 使用者介面;而在 Xamarin.Android 原生專案中,需要使用 Android UI XML 的指定語法來定義,這部分可以參考 Android 使用者介面。換言之,您需要分別在這兩個 iOS & Android 原生平台下,精通這兩個平台的 SDK 說明文件,了解如何設計個平台的專屬 UI 頁面。不過,在 Xamarin.Forms 下,您僅需要使用標記宣告的 XAML 語法,來定義出該應用程式頁面所需呈現的 UI/UX 內容,無須了解每個平台專屬 UI 設計知識。
不過,這不代表 Xamarin.Forms 可以適合開發出各種跨平台應用的程式,若您的程式需要存取大量或者深入的原聲平台 API、需要使用到每個平台的專屬原生行為、您的應用程式對於客製化 UI 的需求比起要更多的程式碼分享需求更為迫切,則,您是不適合選擇 Xamarin.Forms 技術來進行跨平台專案開發;例如,您想要開發出虛擬實際 VR / 擴增實境 AR 應用的程式、多媒體影音的應用、需要使用地圖介面,但是要提供更加多的視覺設計,類似寶可夢抓寶那樣的情境、手機遊戲等等,這些情境都不適合選擇採用 Xamarin.Forms 的開發技術。
不過,對於想要在企業內部導入 Xamarin 開發技術,並且開發出相關企業內部想要使用的應用程式、手機購物應用程式等等,這些情境是絕對適合採用 Xamarin.Forms 的技術來進行開發,尤其是企業內部使用的行動應用程式,選擇採用 Xamarin.Forms 是您唯一的選擇,而且是最好的選擇。
最後,在進行專案開發之前,請務必進行分析您的應用程式類型,是否真的適合使用 Xamarin.Forms 方式來進行開發,否則,當您頭髮已經剪好了而且頭洗了一半,這個時候您才想要說,我是要我的頭髮燙成捲髮模式,此時,我的良心建議是,使用 Xamarin 猿聲專案模式進行開發吧,最起碼,您的商業邏輯部分,您不需要重新再寫一次,您僅需要針對每個平台的所有頁面 UI,使用原生 SDK 的說明,重新設計一次即可。

問題:我在 Xamarin.Forms 上進行開發,一定要使用 XAML 嗎?

關於這部分的問題,個人強烈建議要使用 XAML 來進行每個頁面的 UI 設計,並且選擇採用 MVVM 設計模式來進行關注點分離的開發;不過,任何的 XAML 的標記或者宣告,都可以使用 C# 程式語言來進行開發,您當然也可以不需要使用到任何 XAML 技術,自行使用 C# 程式語言來進行開發出 Xamarin.Forms 的跨平台應用程式。
對於 MVVM vs Code Behind 開發方式選擇,個人偏向強烈建議使用 MVVM 方式來進行開發,並且選擇一個好的 MVVM Framework 框架,幫助我們簡化 MVVM 的使用;我都是採用 Prism 這個開發框架,來進行 Xamarin.Forms 的專案設計。當然,青菜蘿蔔各有所好,市面上還有許多相當優異的 MVVM Framework 框架您可以選擇的。
關於更多關於一些 Xamarin.Forms 開發上的疑問,可以參考 Xamarin / Xamarin.Forms 行動跨平台 Mobile Cross-Platform 開發學習指引問答集 FAQ

Xamarin.Forms 的教育訓練課程

在下圖中,我列出了當您在進行 Xamarin.Forms 專案開發的過程中,您需要了解與會遇到的設計情境與需求,您可以挑選則您可能需要、預期的項目,開始進行了解與研究,不過,當您真的實際開始進行 Xamarin.Forms 專案開發的時候,將會遇到這張圖上所遇到的許多項目。
Xamarin.Forms
若您想要選擇使用 Xamarin.Forms 的方式來進行跨平台行動專案的開發,這裡提供幾個關於 Xamarin.Forms 的學習資源與教育訓練課程
  • 開始使用 Xamarin.Forms (微軟官方文件)
    這裡是微軟官方的各種關於 Xamarin.Forms 的說明文件,這裡有著您在使用 Xamarin 工具集進行開發的時候,所需要具備的知識與技能、進階開發技巧、各種說明範例應用,都有著相當豐富的內容。各位可以參考 開始使用 Xamarin.Forms
  • Xamarin.Forms 跨平台行動應用程式開發實戰
    本次課程包含超過4個小時的先修錄影課程、 一堂 1 小時的線上課程 與 五天共 35 小時的實體課程,將會帶領大家快速上手 Xamarin.Forms 開發工具,您將會認識 Xamarin 開發環境,了解使用 Xamarin.Forms 開發工具所會遇到問題與解決方法,學會 XAML 與 MVVM 開發和設計模式,實際動手做出可以跨平台執行的應用程式,並且學會整個 Xamarin.Forms 開發過程。 Xamarin.Forms 跨平台行動應用程式開發實戰
  • Xamarin.Forms 跨平台行動開發一日實戰營(派工 App)
    本次課程包含 1.5 小時的線上課程與1 天實體課程,將會提供已寫好的派工與回報的後台 Web API(包含原始碼),並藉此帶領大家從無到有,開發出跨平台(Android / iOS)行動應用 App。參考網址 Xamarin.Forms 跨平台行動開發一日實戰營(派工 App)
  • Xamarin.Forms 跨平台行動開發一日實戰營(表單 App)
    在整個課程中,將會使用已開發好的 Web API,進行 Xamarin.Forms 跨平台行動應用程式的開發。實作出一個工作日誌回報的表單 App,可以新增、查詢、更新、刪除 (CRUD) 工作記錄、申請請假的表單功能、主管「審核請假」頁面以進行部屬請假紀錄的核准,另外,也會說明如何取得顯示公司緊急連絡方式清單,並且直接進行撥打電話功能。參考網址 Xamarin.Forms 跨平台行動開發一日實戰營(表單 App)
    +

進階研讀


2018/04/25

Xamarin 新手入門 Part 5 Xamarin.Forms for Prism 專案開發教學


Xamarin 新手入門 Part 6 我該選擇 Xamarin.Forms 或者 Xamarin 原生方式來開發跨平台應用程式專案呢?

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式

了解更多關於 [Xamarin.Forms 快速入門] 的使用方式

現在,我們可以來觀看我最推薦開發 Xamarin.Forms 應用程式的工具與開發框架,那就是使用 Prism Template Pack 這個 Visual Studio 擴充功能,透過其提供的專案樣板,協助我們可以建立出一個 Prism 可用的開發專案,關於 Prism 的更多資訊,可以參考 Prism on Github
另外,通常我們在進行 Xamarin.Forms 專案開發的時候,會使用 MVVM 設計模式技巧,進行頁面與商業邏輯的設計,同樣的,我們會將頁面要出現的內容,使用 XAML 標記語言進行宣告,而每個頁面的商業處理邏輯程式碼,將會寫在 ViewModel 檢視模型中,在這裡,我們不會將這些商業邏輯使用 Code Behind 的方式寫在每個頁面的後置碼內,會有這樣的考量那是因為,我們不希望把頁面與商業邏輯緊密的綁定在起,這樣對於日後進行專案維護與測試上,會顯得非常不方便,我們要能夠建立頁面與檢視模型之間具有低鬆耦合特性;更多關 MVVM 的資訊,可以參考 第 5 部分 從資料繫結至 MVVM。想要完成這樣的需求,您一定需要精通 資料綁定 Data Binding 這個技術,這樣在開發專案的時候,才能夠得心應手;想要更進一步了解資料綁定的相關內容,可以參考 第 4 部分 資料繫結的基本概念

建立一個 Xamarin Forms for Prism 專案

  • 首先,我們打開 Visual Studio 2017 (任何一種版本 Visual Studio Community 2017 / Visual Studio Professional 2017 / Visual Studio Enterprise 2017皆可)
  • 接著,請安裝 Prism Template Pack 這個擴充功能,
  • 點選 Visual Studio 2017 功能表的 [工具] > [擴充功能和更新]
  • 請在點選 [線上] 標籤頁次,接著在右上方搜尋文字輸入盒中,輸入 Prism
  • 稍後一下,就會看到搜尋結果,請您將 [Prism Template Pack] 這個選項安裝到 Visua Studio 2017 中。
Visual Studio 2017 Extension and Update
  • 一旦您安裝完成 Prism Template Pack,現在可以
  • 點選 Visual Studio 2017 功能表的 [檔案] > [新增] > [專案]
  • 當出現 [新增專案] 對話窗,請依序選擇 [已安裝] > [Visual C#] > [Prism] > [Prism Blank App (Xamarin.Forms)]
  • 請在最下方 [名稱] 文字輸入盒欄位,輸入您想要的專案名稱
  • 最後,請點選 [確定] 按鈕
Visual Studio 2017 Xamarin Forms Prism new project
  • 現在,Visual Studio 2017 將會顯示 [PRISM PROJECT WIZARD] 這個對話窗,您可以在這個對話窗中,選擇需要跨平台的目標,在這裡,您將會有三種選擇: ANDROID, iOS, UWP,請依照您的需求,勾選需要建立的專案類型。接著,請在下方 [Container] 下拉選單中,選擇 Unity 這個選項;最後,請點選 [CREATE PROJECT] 這個按鈕,請 Prism Template Pack 所提供的樣板精靈,幫助我們產生出可用於 Prism 框架的 Xamarin.Forms 方案與專案。
PRISM PROJECT WIZARD
  • 此時,Visual Studio 2017 的 Prism Template Pack 將會開始幫您建立起四個專案(假設我們在上個步驟,勾選的所有平台目標),分別是:
    • Xamarin.Android 原生專案
    • Xamarin.iOS 原生專案
    • Windows UWP 原生專案
    • .NET Standard Class Library SCL 共用類別庫專案
  • 現在,我們可以從 Visual Studio 2017 的方案總管中,看到已經產生了四個專案,如下圖所示,其中,當我們使用 Xamarin.Forms 進行專案開發的時候,原則上大多只會在 SCL 專案上進行程式碼與 XAML 的設計,也就是 .NET Standard Class Library 類別庫內進行程式開發。在 SCL 專案中,我們看到建立了兩個資料夾 [ViewModels] & [Views],我們日後將會在這兩個資料夾來建立頁面的 XAML 標記宣告 (在 Views 資料夾)與每個頁面的商業邏輯(在 ViewModels 資料夾)。
Xamarin.Forms Solution and Projects
  • 現在,請在方案總管中的 SCL 共用類別庫專案內,展開 Views 資料夾,找到 MainPage.xaml 這個節點,使用滑鼠雙擊打開這個檔案;您將會看到這個應用程式頁面的 XAML 標記宣告內容,在這個頁面中,我們僅有宣告顯示一個文字在螢幕上,他將會顯示出 Welcome to Xamarin Forms and Prism! 這個文字。
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XFPQStart.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
    </StackLayout>

</ContentPage>
  • 現在,請將這個 MainPage.xaml 內容,修改成為如底下所呈現內容。
  • 在這個頁面 XAML 宣告中,我們並沒有使用 x:Name 這個 XAML 延伸標記功能,因為,我們在這個新手練習中,不會使用 Code Behind 來設計相關商業邏輯,我們將會使用資料綁定 Data Binding 的方法,將頁面檢視 (View) 中的 XAML 與檢視模型 (ViewModel) 進行串接再一起,在底下的 XAML 標記宣告中,我們將會綁訂一個 Text 屬性到 ViewModel 中的一個 C# 字串屬性,另外,我們也需要透過資料綁訂 (Data Binding) 來綁定一個 Command 屬性到 ViewModel 中的一個有實作 ICommand 介面 (Interface) 的 DelegateCommand 物件上。
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XFPQStart.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Button
            HorizontalOptions="Center" VerticalOptions="Center"
            Text="{Binding ButtonText}"
            Command="{Binding ButtonCommand}"/>
    </StackLayout>

</ContentPage>
  • 另外,請展開 ViewModels 資料夾,您將會找到 MainPageViewModel.cs 這個節點,使用滑鼠雙擊打開這個節點,在這個檔案內將會有個 MainPageViewModel 類別,也就是我們採用 MVVM 開發模式中的 ViewModel 檢視模型部分,我們會在這裡設計該頁面會用到的各種商業邏輯程式碼。所以,我們使用底下程式碼將其替換,使用具備如同我們前兩個 Android / iOS 練習專案所呈現的相同商業邏輯。
  • 在這裡,我們建立一個 ButtonText C# Property 屬性,這個屬性需要實作 .NET 提供的 INotifyPropertyChanged INPC 介面,如此,資料綁定的機制才能夠成功運作起來,而我們將會透過 Prism 所提供的 ViewModelBase 這個繼承類別內提供的 SetProperty 方法來做到這樣需求;因此,只要我們在 ViewModel 中變更這個屬性值,頁面中的按鈕標示的文字也會變更。另外,我們也建立一個型別為 DelegateCommand 的 ButtonCommand 的物件,搭配按鈕控制項的 Command 屬性,作為使用者點選這個按鈕之後,所要執行的商業邏輯程式碼。
C# CSharp
public class MainPageViewModel : ViewModelBase
{
    private string _ButtonText = "Button";
    public string ButtonText
    {
        get { return _ButtonText; }
        set { SetProperty(ref _ButtonText, value); }
    }
    public int count { get; set; }
    public DelegateCommand ButtonCommand { get; set; }
    public MainPageViewModel(INavigationService navigationService) 
        : base (navigationService)
    {
        Title = "Main Page";

        ButtonCommand = new DelegateCommand(() =>
        {
            ButtonText = string.Format("{0} clicks!", count++);
        });
    }
}

準備進行 Xamarin.Forms for Prism 之 Android 專案的建置、執行、與除錯

  • 請設定 Android 專案為預設起始專案
  • 確認選擇適當的 Android 模擬器
  • 按下 F5 開始執行
Visual Studio for Android Emulator

準備進行 Xamarin.Forms for Prism 之 iOS 專案的建置、執行、與除錯

  • 請設定 iOS 專案為預設起始專案
  • 確認選擇適當的 iOS 模擬器與SDK版本
  • 按下 F5 開始執行
Visual Studio for iOS Simulator

其他參考文章

很多人認為使用 MVVM 開發造成 Xamarin.Forms 開發上的困難度,這就像您喜歡 ASP.NET Web Forms 的開發方式,不喜歡 ASP.NET MVC 開發方式,您就可以直接使用 Web Forms 的方式來開發專案;您喜歡 MVC 的架構來開發您的網站,您就專心研究出如何寫出好的 MVC 架構專案,所謂,青菜蘿蔔各有所好。您可以參考這篇文章 Xamarin.Forms 用 MVVM 開發的好處? Prism 和 cross-platform 開發(優劣)差異? (我發現寫在 ViewModel 內 程式碼要多好多) , Xamarin.Forms MVVM 123 , 我要使用 Code Behind or MVVM ? 會有更多的說明。
另外,在這篇文章中提到使用 Prism 設計框架來幫助我們快速開發出 Xamarin.Forms 應用程式專案,難道沒有其他更好的選擇嗎?我還是那句話,青菜蘿蔔各有所好,挑選您喜歡的 Framework,坐下來,開始進行 Xamarin.Forms 專案開發,才是對的,嘴砲沒有甚麼實質幫助。關於更多其他 MVVM Framework,可以參考 Xamarin Forms and MVVM frameworks part 1: Xamarin Forms’s specific frameworks 與 Xamarin Forms and MVVM frameworks part 2: something else? 和 Should I Use A Xamarin.Forms MVVM Framework? 討論文章吧

進階研讀


2018/04/23

Xamarin 新手入門 Part 4 Xamarin.Forms Master Detail 專案開發教學


Xamarin 新手入門 Part 6 我該選擇 Xamarin.Forms 或者 Xamarin 原生方式來開發跨平台應用程式專案呢?

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式

了解更多關於 [Xamarin.Forms 快速入門] 的使用方式

在這篇新手教學文章中,我們將要來看看如何使用 Xamarin.Forms 的方式,來開發出一個 Master Detail 的多對一的行動應用程式,也就是說,當我們建立了一個 Xamarin.Forms 方案/專案,首先,會使用 ListView 控制項來顯示集合資料在螢幕上,使用者可以點選集合資料的任一筆紀錄,此時,將會顯示出這筆紀錄的明細頁面;另外,在這個練習中,我們也可以進行新增資料的輸入作業,當輸入這筆新紀錄完成後,就會顯示在 ListView 控制項內。最後,我們也透過這個專案練習,了解到標籤式頁面的設計與使用方法。
Xamarin.Forms ListView Xamarin.Forms ListView Xamarin.Forms ListView Xamarin.Forms ListView
在這個專案練習中,我們將會採用 MVVM 架構方式來開發專案,不過,在這裡,我們也並不是把所有的商業邏輯全部都寫在 ViewModel 中,而是絕大部分與使用者互動的商業邏輯,我們將會使用 Code Behind 的方式,來撰寫這些互動商業邏輯程式碼。
同樣的,在此之前,各位需要先行準備好您的 Visual Studio 2017 + Xamarin 開發環境;關於要如何準備您們的 Xamarin 開發環境,可以參考 第一次安裝 Visual Studio 2017 到新的作業系統上 或者 可以參考 Xamarin / Xamarin.Forms 行動跨平台 Mobile Cross-Platform 開發學習指引問答集 FAQ;若您還有 Xamarin 相關的問題,可以到 Xamarin.Forms @ Taiwan 與 Xamarin 同好一同進行討論。
您也可以查看微軟官方的 Xamarin.Forms 介紹文章 開始使用 Xamarin.Forms,來了解更多關於 Xamarin.Forms 專案的開發技術與技巧。另外,若您對於 XAML 有進一步的興趣或者想要研究,可以參考這篇微軟官方的 eXtensible Application Markup Language (XAML) 介紹文章。

建立一個 Xamarin Forms 專案

  • 首先,我們打開 Visual Studio 2017 (任何一種版本 Visual Studio Community 2017 / Visual Studio Professional 2017 / Visual Studio Enterprise 2017皆可)
  • 點選 Visual Studio 2017 功能表的 [檔案] > [新增] > [專案]
  • 當出現 [新增專案] 對話窗,請依序選擇 [已安裝] > [Visual C#] > [Cross-Platform] > [Mobile App (Xamarin.Forms)]
  • 請在最下方 [名稱] 文字輸入盒欄位,輸入您想要的專案名稱
  • 最後,請點選 [確定] 按鈕
Visual Studio 2017 Xamarin Forms new project
  • 現在,Visual Studio 2017 將會顯示 [New Cross Platform App - XFQStart] 這個對話窗,請在這個對話窗中點選 [Master Detail] 圖示,接著在下方的 Platform 欄位中,選擇您想要跨平台的目標,在這裡,我們將會選擇 Android iOS Windows(UWP) 這三個所有的行動作業系統平台;而在 [Code Sharing Strategy] 程式碼共用策略欄位中,請您選擇 [.NET Standard],說明我們將要使愈 SCL (.NET Standard Class Library) 類別庫的方式來時做出我們頁面與商業邏輯的共用程式碼 (在此之前,我們所使用的共用程式碼策略將會是 PCL Portable Class Library,也就是可攜式類別庫)。
  • 當然,您也可以選擇使用 Shared Project 的 [Code Sharing Strategy] 共用程式碼策略,來完成在不同行動作業系統平台下的共同商業邏輯的程式碼共用設計方式,Shared Project 與 .NET Standard 兩者的作法不盡相同,前者使用類似專案捷徑的方式來設計,這些共用程式碼將會再編譯時期來進行與原生專案的一起編譯,而後者則是一個類別庫的架構,也就是說,您所設計的所有共用程式碼,都會建立在一個類別庫內,只要同時與猿聲專案一同佈署到手機裝置中,就可以執行了。
  • 確認無誤之後,請點選 [OK] 按鈕。
New Cross Platform App
  • 此時,Visual Studio 2017 將會開始幫您建立起四個專案,分別是:
    • Xamarin.Android 原生專案
    • Xamarin.iOS 原生專案
    • Windows UWP 原生專案
    • .NET Standard Class Library SCL 共用類別庫專案
  • 現在,我們可以從 Visual Studio 2017 的方案總管中,看到已經產生了四個專案,如下圖所示,其中,當我們使用 Xamarin.Forms 進行專案開發的時候,原則上大多只會在 SCL 專案上進行程式碼與 XAML 的設計,也就是 .NET Standard Class Library 類別庫內進行程式開發。
  • 在這裡,我們展開了 SCL ( .NET 標準類別庫 ) 專案節點,您可以從下圖看到這樣的成果,在這裡,多了許多資料夾,其中,您會看到 MVVM 開發模式會用到的資料夾 Models - Views - ViewModels,把這三個資料夾的大寫字母組合起來,就是 MVVM 囉。
  • 在 Services 資料夾內的兩個檔案,則是提供集合資料的服務類別。
Xamarin.Forms Solution and Projects
  • 現在,讓我們來看看這個專案是如何實做出來的。
  • 首先,我們來看看要提供 ListView 的集合資料物件的類別,要做些事情。
  • 請開啟 IDataStore.cs 檔案,這個檔案內定義了 IDataStore 介面,我們可以看到了這個頁面宣告了 CRUD 的操作所需要時做出來的方法介面,而這個介面中宣告的方法,都是採用非同步的操作。這個介面也是一個泛型介面,也就是當要實作這個介面的時候,也需要提供這個介面所需要的泛型指定型別。
C# CSharp
public interface IDataStore<T>
{
    Task<bool> AddItemAsync(T item);
    Task<bool> UpdateItemAsync(T item);
    Task<bool> DeleteItemAsync(T item);
    Task<T> GetItemAsync(string id);
    Task<IEnumerable<T>> GetItemsAsync(bool forceRefresh = false);
}
C# CSharp
public class Item
{
    public string Id { get; set; }
    public string Text { get; set; }
    public string Description { get; set; }
}
  • 因此,這個檔案 MockDataStore.cs 中的 MockDataStore 類別,就是實作了 IDataStore 這個介面,並且指定了 Item 為這個介面的泛型型別。
  • MockDataStore 類別的建構式,將會預設產生一些測試用的集合紀錄。
  • 關於 CRUD 操作的方法實作,則分別定義在 AddItemAsync , GetItemAsync, UpdateItemAsync , DeleteItemAsync ;由於這些介面的實作方法在宣告的時候,都是使用非同步的方式來宣告,因此,我們需要使用 Task.FromResult.aspx) 方法來建立已成功完成且具有指定之結果。
C# CSharp
public class MockDataStore : IDataStore<Item>
{
    List<Item> items;

    public MockDataStore()
    {
        items = new List<Item>();
        var mockItems = new List<Item>
        {
            new Item { Id = Guid.NewGuid().ToString(), Text = "First item", Description="This is an item description." },
            new Item { Id = Guid.NewGuid().ToString(), Text = "Second item", Description="This is an item description." },
            new Item { Id = Guid.NewGuid().ToString(), Text = "Third item", Description="This is an item description." },
            new Item { Id = Guid.NewGuid().ToString(), Text = "Fourth item", Description="This is an item description." },
            new Item { Id = Guid.NewGuid().ToString(), Text = "Fifth item", Description="This is an item description." },
            new Item { Id = Guid.NewGuid().ToString(), Text = "Sixth item", Description="This is an item description." },
        };

        foreach (var item in mockItems)
        {
            items.Add(item);
        }
    }

    public async Task<bool> AddItemAsync(Item item)
    {
        items.Add(item);

        return await Task.FromResult(true);
    }

    public async Task<bool> UpdateItemAsync(Item item)
    {
        var _item = items.Where((Item arg) => arg.Id == item.Id).FirstOrDefault();
        items.Remove(_item);
        items.Add(item);

        return await Task.FromResult(true);
    }

    public async Task<bool> DeleteItemAsync(Item item)
    {
        var _item = items.Where((Item arg) => arg.Id == item.Id).FirstOrDefault();
        items.Remove(_item);

        return await Task.FromResult(true);
    }

    public async Task<Item> GetItemAsync(string id)
    {
        return await Task.FromResult(items.FirstOrDefault(s => s.Id == id));
    }

    public async Task<IEnumerable<Item>> GetItemsAsync(bool forceRefresh = false)
    {
        return await Task.FromResult(items);
    }
}
  • 請在方案總管中的 SCL 共用類別庫專案內,找到 MainPage.xaml 這個節點,使用滑鼠雙擊打開這個檔案;您將會看到這個應用程式頁面是宣告採用 TabbedPage 這種頁面,而不是我們經常看到的 ContentPage;因為我們在這裡使用了 TabbedPage 標籤式頁面,所以,我們需要使用 TabbedPage.Children 這個標籤頁面屬性來定義出,這個標籤頁面內擁有的其他頁面,在這裡,我們指定了兩個 NavigationPage 頁面。第一個 NavigationPage 頁面裡面則是嵌入 ItemsPage,第二個 NavigationPage 頁面裡面則是嵌入 AboutPage 頁面。
  • 這個 MainPage 的頁面並沒有任何商業邏輯控制程式碼,要顯示哪個頁面,則是由使用者自行操作手機螢幕。
XAML
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:views="clr-namespace:XFMDQStart.Views"
            x:Class="XFMDQStart.Views.MainPage">
    <TabbedPage.Children>
        <NavigationPage Title="Browse">
            <NavigationPage.Icon>
                <OnPlatform x:TypeArguments="FileImageSource">
                    <On Platform="iOS" Value="tab_feed.png"/>
                </OnPlatform>
            </NavigationPage.Icon>
            <x:Arguments>
                <views:ItemsPage />
            </x:Arguments>
        </NavigationPage>

        <NavigationPage Title="About">
            <NavigationPage.Icon>
                <OnPlatform x:TypeArguments="FileImageSource">
                    <On Platform="iOS" Value="tab_about.png"/>
                </OnPlatform>
            </NavigationPage.Icon>
            <x:Arguments>
                <views:AboutPage />
            </x:Arguments>
        </NavigationPage>
    </TabbedPage.Children>
</TabbedPage>
  • 現在,我們先還看看 AboutPage 這個頁面的相關設計,在底下是 AboutPage 頁面的 XAML 宣告內容。我們可以看到這個頁面透過了 ContentPage.BindingContext 屬性,將 ViewModel,也就是 vm:AboutViewModel (這個 XAML 用法,也就是如同 C# 的 new AboutViewModel() ,即產生一個 AboutViewModel 類別的執行個體) 綁定到這個屬性上,如此,整個頁面的控制項,將可以透過 Data Binding 資料綁定的功能,從 AboutViewModel 類別物件中取得相關內容。
  • 由於我們需要在這個頁面使用 ViewModel 類別,因此,需要建立一個 XAML Namespace 命名空間, xmlns:vm="clr-namespace:XFMDQStart.ViewModels;" ,如此,在這個頁面中,就可以使用 XFMDQStart.ViewModels .NET 命名空間的類別。
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XFMDQStart.Views.AboutPage"
             xmlns:vm="clr-namespace:XFMDQStart.ViewModels;"
             Title="{Binding Title}">
    <ContentPage.BindingContext>
        <vm:AboutViewModel />
    </ContentPage.BindingContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackLayout BackgroundColor="{StaticResource Accent}" VerticalOptions="FillAndExpand" HorizontalOptions="Fill">
            <StackLayout Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="Center">
                <ContentView Padding="0,40,0,40" VerticalOptions="FillAndExpand">
                    <Image Source="xamarin_logo.png" VerticalOptions="Center" HeightRequest="64" />
                </ContentView>
            </StackLayout>
        </StackLayout>
        <ScrollView Grid.Row="1">
            <StackLayout Orientation="Vertical" Padding="16,40,16,40" Spacing="10">
                <Label FontSize="22">
                    <Label.FormattedText>
                        <FormattedString>
                            <FormattedString.Spans>
                                <Span Text="AppName" FontAttributes="Bold" FontSize="22" />
                                <Span Text=" " />
                                <Span Text="1.0" ForegroundColor="{StaticResource LightTextColor}" />
                            </FormattedString.Spans>
                        </FormattedString>
                    </Label.FormattedText>
                </Label>
                <Label>
                    <Label.FormattedText>
                        <FormattedString>
                            <FormattedString.Spans>
                                <Span Text="This app is written in C# and native APIs using the" />
                                <Span Text=" " />
                                <Span Text="Xamarin Platform" FontAttributes="Bold" />
                                <Span Text="." />
                            </FormattedString.Spans>
                        </FormattedString>
                    </Label.FormattedText>
                </Label>
                <Label>
                    <Label.FormattedText>
                        <FormattedString>
                            <FormattedString.Spans>
                                <Span Text="It shares code with its" />
                                <Span Text=" " />
                                <Span Text="iOS, Android, and Windows" FontAttributes="Bold" />
                                <Span Text=" " />
                                <Span Text="versions." />
                            </FormattedString.Spans>
                        </FormattedString>
                    </Label.FormattedText>
                </Label>
                <Button Margin="0,10,0,0" Text="Learn more" Command="{Binding OpenWebCommand}" BackgroundColor="{StaticResource Primary}" TextColor="White" />
            </StackLayout>
        </ScrollView>
    </Grid>
</ContentPage>
  • 關於 AboutPage 頁面用到的 ViewModel 類別,則是使用底下程式碼定義,在這個類別中,僅定義兩個屬性 Title 的字串屬性與 OpenWebCommand 這個 ICommand 的屬性;其中,後者屬性將會在上面 XAML 中,使用語法 Command="{Binding OpenWebCommand}" 來進行命令的綁定,也就是,當使用者點選這個按鈕(按鈕文字為 Learn more),就會執行 Device.OpenUri 靜態方法,使用手機本身預設瀏覽器來開啟指定 URL 的網頁。
C# CSharp
public class AboutViewModel : BaseViewModel
{
    public AboutViewModel()
    {
        Title = "About";

        OpenWebCommand = new Command(() => Device.OpenUri(new Uri("https://xamarin.com/platform")));
    }

    public ICommand OpenWebCommand { get; }
}
  • 接著,我們來看看如何使用 ListView 這個控制項來顯示出集合的物件資料,在這個 ItemsPage.xaml 檔案內,我們並沒有看到使用 BindingContext 屬性來設定 ViewModel 的類別,這樣的需求將會使用 Code Behind 的方式來設定,等下我們來檢視這個頁面的 Code Behind的時候,就會看到。
  • ListView 控制項的使用方式,如同一般控制項一樣,需要設定 ListView 的相關屬性,另外,還需要宣告 ListView.ItemTemplate 這個項目,在這個節點內,我們可以自訂每筆紀錄要顯示出來的樣貌,與美個控制項的大小與位置。
  • ListView 控制項最為重要的屬性為 ItemsSource ,這個屬性將會透過資料綁定 Data Binding 的來指定 ViewModel 類別中的屬性 ( C# Property ),作為 ListView 要顯示的集合資料來源。
  • 由於 ListView 支援手勢下更新的操作,因此,我們可以使用 ListView 的 RefreshCommand 屬性,綁定 ViewModel 內有實作 ICommand 的物件,如此,當使用者想要透過下拉更新的手式操作來進行 ListView 內的所有紀錄更新的時候,就可以直接執行 ViewModel 內的 LoadItemsCommand 物件內的委派方法。
  • 為了讓 ListView 的表現更加有效率與有效的使用記憶體使用量,我們在這裡使用 ListView 的 CachingStrategy="RecycleElement" 屬性設定。
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XFMDQStart.Views.ItemsPage"
              Title="{Binding Title}"
             x:Name="BrowseItemsPage">
    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Add" Clicked="AddItem_Clicked" />
    </ContentPage.ToolbarItems>
    <ContentPage.Content>
        <StackLayout>
            <ListView x:Name="ItemsListView" 
                ItemsSource="{Binding Items}"
                VerticalOptions="FillAndExpand"
                 HasUnevenRows="true"
                 RefreshCommand="{Binding LoadItemsCommand}"
                 IsPullToRefreshEnabled="true"
                 IsRefreshing="{Binding IsBusy, Mode=OneWay}"
                 CachingStrategy="RecycleElement"
                 ItemSelected="OnItemSelected">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <StackLayout Padding="10">
                                <Label Text="{Binding Text}" 
                       LineBreakMode="NoWrap" 
                       Style="{DynamicResource ListItemTextStyle}" 
                       FontSize="16" />
                                <Label Text="{Binding Description}" 
                       LineBreakMode="NoWrap"
                       Style="{DynamicResource ListItemDetailTextStyle}"
                       FontSize="13" />
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>
  • 接下來,我們來看看 ItemsPage 頁面的 Code Behind 程式碼,請打開 ItemsPage.xaml.cs 這個節點,就會看到如下程式碼。
  • 在建構式中, BindingContext = viewModel = new ItemsViewModel(); ,這行程式碼就是用來設定這個頁面要進行資料綁定的來源,是這個頁面的 ViewModel 類別,也就是 ItemsViewModel 。
  • 在 Code Behind 中,這裡設定了三個觸發事件的處理商業邏輯。
  • OnItemSelected 這個事件會當使用者點選 ListView 的任何一筆紀錄的時候,就會觸發與執行這個事件所綁定的方法,在這裡,將會取得使用者點選的項目物件,建立 ItemDetailPage 物件,並且在建構函式內將這個物件傳送過去,接著,透過 Navigation 導航物件,進行頁面切換與導航的工作。
  • AddItem_Clicked 這個事件是在 XAML 中的導航工具列中的一個按鈕所綁定的事件,也就是 ,當這個按鈕被點選下去之後,就會進行導航到 NewItemPage 頁面,開始進行新增一筆記錄的工作。
    await Navigation.PushModalAsync(new NavigationPage(new NewItemPage()));
  • OnAppearing 事件會在這個頁面顯示在螢幕上的時候,便會觸發執行(例如,按下 Home 按鈕,這個應用程式就會變成背景模式,若您重新切換顯示這個 App,而當時的頁面正好有 OnAppearing 事件,此時,OnAppearing 則又會被觸發一次)
C# CSharp
public partial class ItemsPage : ContentPage
{
    ItemsViewModel viewModel;

    public ItemsPage()
    {
        InitializeComponent();

        BindingContext = viewModel = new ItemsViewModel();
    }

    async void OnItemSelected(object sender, SelectedItemChangedEventArgs args)
    {
        var item = args.SelectedItem as Item;
        if (item == null)
            return;

        await Navigation.PushAsync(new ItemDetailPage(new ItemDetailViewModel(item)));

        // Manually deselect item.
        ItemsListView.SelectedItem = null;
    }

    async void AddItem_Clicked(object sender, EventArgs e)
    {
        await Navigation.PushModalAsync(new NavigationPage(new NewItemPage()));
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();

        if (viewModel.Items.Count == 0)
            viewModel.LoadItemsCommand.Execute(null);
    }
}
  • 下面的程式碼就是 ItemsPage 這個頁面的 ViewModel,在這個建構函式中,特別有使用 MessagingCenter.Subscribe 來訂閱一個 AddItem 的事件,這個事件將會於當使用者在新增一筆新紀錄的頁面中,點選了儲存按鈕時候,會使用 MessagingCenter.Send(this, "AddItem", Item); 來觸發這個事件。 MessagingCenter 這個類別在 Xamarin.Forms 專案開發的時候,將會扮演相當重要的腳色,用來支援再不同頁面、不同類別、不同類別庫之間,可以使用事件出發的方式,通知有訂閱這個事件的物件,執行相對應的委派方法。
  • ExecuteLoadItemsCommand 命令委派方法,則是當要進行讀取集合紀錄到 ListView 中,才會被執行的。
C# CSharp
public class ItemsViewModel : BaseViewModel
{
    public ObservableCollection<Item> Items { get; set; }
    public Command LoadItemsCommand { get; set; }

    public ItemsViewModel()
    {
        Title = "Browse";
        Items = new ObservableCollection<Item>();
        LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());

        MessagingCenter.Subscribe<NewItemPage, Item>(this, "AddItem", async (obj, item) =>
        {
            var _item = item as Item;
            Items.Add(_item);
            await DataStore.AddItemAsync(_item);
        });
    }

    async Task ExecuteLoadItemsCommand()
    {
        if (IsBusy)
            return;

        IsBusy = true;

        try
        {
            Items.Clear();
            var items = await DataStore.GetItemsAsync(true);
            foreach (var item in items)
            {
                Items.Add(item);
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
        }
        finally
        {
            IsBusy = false;
        }
    }
}
  • 打開 ItemDetailPage.xaml 頁面宣告,這裡非常簡單的使用 StackLayout 堆疊版面配置的方式,顯示出4個 Label 文字控制項的內容,而這些文字控制項的內容,將會透過資料綁定的方式,從 ViewModel 中取得
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XFMDQStart.Views.ItemDetailPage"
             Title="{Binding Title}">
    <StackLayout Spacing="20" Padding="15">
        <Label Text="Text:" FontSize="Medium" />
        <Label Text="{Binding Item.Text}" FontSize="Small"/>
        <Label Text="Description:" FontSize="Medium" />
        <Label Text="{Binding Item.Description}" FontSize="Small"/>
    </StackLayout>
</ContentPage>
  • 接下來,我們來查看這個頁面的 Code Behind,請打開 ItemDetailPage.xaml.cs 節點;在這個 Code Behind 中,有兩個建構函式,若為沒有參數的建構函式,將會使用 自行產生一個 ItemDetailViewModel 物件,來指定這個頁面要用到的 ViewModel;若是有參數的建構函式,將會直接指定這個串送進來的物件,作為該頁面的 ViewModel。
C# CSharp
public partial class ItemDetailPage : ContentPage
{
    ItemDetailViewModel viewModel;

    public ItemDetailPage(ItemDetailViewModel viewModel)
    {
        InitializeComponent();

        BindingContext = this.viewModel = viewModel;
    }

    public ItemDetailPage()
    {
        InitializeComponent();

        var item = new Item
        {
            Text = "Item 1",
            Description = "This is an item description."
        };

        viewModel = new ItemDetailViewModel(item);
        BindingContext = viewModel;
    }
}
  • 這個頁面的 ViewMOdel 則是相當的簡單,他就是有兩個屬性 Title 與 Item。
C# CSharp
public class ItemDetailViewModel : BaseViewModel
{
    public Item Item { get; set; }
    public ItemDetailViewModel(Item item = null)
    {
        Title = item?.Text;
        Item = item;
    }
}
  • 最後,我們來看看 NewItemPage.xaml 這個頁面,也就是當您點選新增紀錄後,所會顯示的頁面,在這裡,使用了 ContentPage.ToolbarItems 宣告了一個導航工具列的按鈕,也就是 Save 儲存按鈕,當使用者點選了這個儲存按鈕,就會將這筆紀錄透過 MessagingCenter 發送出一個事件,通知有訂閱這個事件的物件,要將這筆紀錄儲存起來。
XAML
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="XFMDQStart.Views.NewItemPage"
             Title="New Item">
    <ContentPage.ToolbarItems>
        <ToolbarItem Text="Save" Clicked="Save_Clicked" />
    </ContentPage.ToolbarItems>
    <ContentPage.Content>
        <StackLayout Spacing="20" Padding="15">
            <Label Text="Text" FontSize="Medium" />
            <Entry Text="{Binding Item.Text}" FontSize="Small" />
            <Label Text="Description" FontSize="Medium" />
            <Editor Text="{Binding Item.Description}" FontSize="Small" Margin="0" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>
  • 打開 NewItemPage.xaml.cs 節點,就會看到這個頁面的 Code Behind 程式碼;在這個 Code Behind 程式碼中,將會在建構函式內,設定這個頁面需要用到的 ViewModel 物件,也就是這個頁面物件本身 BindingContext = this;
  • 另外,也設定當使用者點選了導航工具列按鈕之後,所觸發的按鈕 Clicked 事件程式碼。
C# CSharp
public partial class NewItemPage : ContentPage
{
    public Item Item { get; set; }

    public NewItemPage()
    {
        InitializeComponent();

        Item = new Item
        {
            Text = "Item name",
            Description = "This is an item description."
        };

        BindingContext = this;
    }

    async void Save_Clicked(object sender, EventArgs e)
    {
        MessagingCenter.Send(this, "AddItem", Item);
        await Navigation.PopModalAsync();
    }
}

進階研讀