XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2018/04/09

Xamarin 新手入門 Part 1 Xamarin.Android 專案開發教學

重要提醒:由於 Visual Studio 2017 的安裝過程有持續更新,因此,若想要知道最新的 Visual Studio 2017 for Xamarin 的安裝過程,可以參考 2018 Q2 最新文章 Visual Studio 2017 for Xamarin 開發環境之安裝與設定說明 (2018 Q2 版本)



èXamarin 新手入門 Part 1 Xamarin.Android 專案開發教學
Xamarin 新手入門 Part 2 Xamarin.iOS 專案開發教學
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.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式


由於 Xamairn 開發環境不斷地進行成長與演變,所以,特別針對關於 Xamarin 的新手開發者,我這裡特別準備了一系列的新手開發練習文章,這些文章將是採用 2018 年最新的 Visual Studio 2017 與 Xamarin Toolkit 工具集所撰寫而成的;讓對於 Xamarin 有興趣的同好,可以跟著這一系列的文章,透過逐步練習,了解到 Xamarin 的好處。這一系列文章將會說明如何使用 Xamarin Android + Android XML 視覺宣告定義 來開發出原生的 Android App、使用 Xamarin iOS + Xcode Storyboard 設計頁面視覺並開發出原生的 iOS App、使用 Visual Studio 2017 提供的 Cross Platform 專案樣板並搭配 XAML 宣告式標記語言來設計頁面內容,在應用程式中絕大部分會用到的頁面畫面與商業邏輯,都將會在一個 .NET Standard Class Library 標準類別庫進行設計,如此可以將大部分的程式碼進行共用的 Xamarin.Forms 的開發方式、最後,會進行說明,如何搭配 Prism 這個開發框架,讓您的 Xamarin.Forms 專案開發上更加如虎添翼。
不論您選擇使用 Xamarin.Android 或者 Xamarin.iOS 原生開發方式進行專案設計,或者使用 Xamarin.Forms 架構來進行跨平台專案開發,對於身為 .NET / C# 的開發者而言,Xamarin 將會是您的首選,因為,在這個開發專案過程中,您不再需要面對不同的程式語言 Java / Objective-C / Swift 等等,我們可以使用 C# 這個程式語言與我們孰悉的 .NET Framework 開發環境,就可以完成您所有應用程式上會用到的商業邏輯設計。
首先,第一篇文章,將是針對 Xamarin.Android 的原生開發的練習,不過,在此之前,各位需要先行準備好您的 Visual Studio 2017 + Xamarin 開發環境;關於要如何準備您們的 Xamarin 開發環境,可以參考 第一次安裝 Visual Studio 2017 到新的作業系統上 及 Visual Studio 2017 / Xamarin 企業級行動化開發平台環境建置問題排除指引 或者 可以參考 Xamarin / Xamarin.Forms 行動跨平台 Mobile Cross-Platform 開發學習指引問答集 FAQ;若您還有 Xamarin 相關的問題,可以到 Xamarin.Forms @ Taiwan 與 Xamarin 同好一同進行討論。
您也可以先行觀看這部只有 5 分鐘的 Getting Started with Xamarin Android Deploying to Emulators 教學影片,任您可以有更清晰的了解整個過程。同樣的,您也可以觀看 Getting Started with Xamarin Android Modifying the UI 這部教學影片,了解如何設計 Android 應用程式個頁面 UI (User Interface 使用者介面)的過程。
您也可以查看微軟官方的 Xamarin 介紹文章 使用者入門系列,來了解更多關於 Xamarin Android 原生專案的開發技術與技巧。

建立一個 Xamarin Android 專案

  • 首先,我們打開 Visual Studio 2017 (任何一種版本 Visual Studio Community 2017 / Visual Studio Professional 2017 / Visual Studio Enterprise 2017皆可)
  • 點選 Visual Studio 2017 功能表的 [檔案] > [新增] > [專案]
  • 當出現 [新增專案] 對話窗,請依序選擇 [已安裝] > [Visual C#] > [Android] > [單一檢視應用程式 (Android)]
  • 請在最下方 [名稱] 文字輸入盒欄位,輸入您想要的專案名稱
  • 最後,請點選 [確定] 按鈕
Visual Studio 2017 Xamarin Android new project
  • 一旦 Visual Studio 的方案 ( Solution ) , 專案 ( Project ) 建立完成之後,您可以從 方案總管 ( Solution Explorer ) 中,看到整個 Xamarin Android 的專案結構。
方案總管
  • 其中, Assets 目錄下,可以存放一些該專案會用到的檔案,例如,SQLite 資料庫檔案、音樂或者影片檔案等等。
  • 在 Resources 目錄下,可以存放您的應用程式頁面會用到資源,例如,在整個應用程式中會用到的不同縮放比率下的圖片檔案資源;其中,這個專案預設會有一個頁面,這個頁面所要呈現的內容,將會定義在 layout 資料夾下的 Main.axml 內,我們先來查看一下這個檔案內容;這個檔案是個 XML 檔案,我們可以滑鼠雙擊 Main.xaml ,以便打開這個檔案。
  • 稍微等候一下,您將會看到如下圖的畫面,這裡就是我們要設計頁面呈現內容的地方,在左方,有工具箱視窗,這裡有各種 Android 原生的控制項 (Control / Widget),您可以使用拖拉的方式,選擇您需要的控制項,拖拉到頁面上。也就是說,所有頁面可以使用與可以看到的視覺控制項與版面配置 Layout,都與 Android SDK 上所提到的相同,因此,使用這樣的開發方式,建議若您有 Android SDK 開發經驗,則是更好,否則,還是要來了解一下 Android SDK 之相關內容;您也可以從這份 使用者介面 文件上進行深入研究。
    在設計應用程式中的每個頁面畫面內容,您可以透過拖拉工具箱的方式,從工具箱中找到您需要的控制項,拖拉到設計畫面上,若想要進行某個控制項的相關細節與特性進行設定,您可以先點選這個控制項,接著從 Visual Studio 屬性視窗中,找您想要修改的屬性,就可以進行這個控制項的特性變更,這與之前大家在於使用 Windows Forms 開發框架下的做法相同;另外就是,每個頁面所可以看到的內容,都會定義在一個 XML 檔案內,您也可以直接修改這個 XML 檔案,達到一樣的效果。
    在這中間視窗的上方,您可以看到有3個下拉選單,您可以透過這些下拉選單,改變預覽的樣貌,在下拉選單的右方,有些按鈕,可以改變這個頁面的呈現方式。
Main.xaml
  • 在中間的視窗,我們從該視窗的下方,看到兩個標籤頁次 : Designer / Source,分別可以呈現該頁面的視覺預覽畫面和這個頁面的 XML 宣告內容,所以,我們可以點選 Source 標籤頁次,您就會看到這個頁面的 XML 宣告內容。更多關於這方面的資訊,可以查看 User Interface & Navigation 網頁介紹的內容。
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/myButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
</LinearLayout>
  • 您可以使用滑鼠雙擊 Android 專案中的 Properties 節點,接著切換到 Android 資訊清單頁面標籤,在這個頁面中,您可以找到 最低 Android 版本 這個下拉選單欄位,您可以從這個下拉選單,選擇您的 Android 應用程式可以支援最低之 Android API 版本。
Xamarin Android Project Properties
  • 現在我們需要來執行這個 Xamarin Android 專案,在這裡請先確認這個專案是否已經有連結上適當的模擬器或者實體手機作為執行與測試的目標,接著,您可以按下 F5 或者工具列上的綠色三角形按鈕。
Visual Studio Toolbar

準備進行 Xamarin Android 專案的建置、執行、與除錯

  • 若您的開發環境在安裝、設定上都正確無誤,此時,您可以在 Visual Studio for Android Emulator 上,看到如左下圖的執行結果,現在,您可以點選模擬器上的按鈕五次,此時,模擬器的畫面就是出現如右下圖的結果。因此,我們可以看的出來,這個 Xamarin Android 專案,確實可以產生出可以在 Android 設備上運行的應用程式,並且,我們可以設計頁面上的各種不同使用者介面與控制項,可以與使用者進行互動,並且將我們的商業邏輯加入到這個應用程式內,如此,這個應用程式將會依照我們設計的需求邏輯來運行。
  • 更多關於要產生您的應用程式與有效地進行專案測試技巧,可以參考 部署及測試
Visual Studio for Android Emulator Visual Studio for Android Emulator

了解這個專案中的程式碼

  • 現在,讓我們按下 Shift + F5 來中止除錯執行,回到 Visual Studio 程式中。
  • 在 方案總管 視窗中,在最下方找到 MainActivity.cs 檔案,使用滑鼠雙擊這個檔案,打開它,這是一個 C# 程式碼,其內容如下所示:
  • 在底下,我們可以看到,我們透過 button 這個變數,就可以取得頁面上的按鈕物件,接著,我們進行這個按鈕物件的點擊事件的綁定,在這裡我們使用的匿名 Anonymous 方法來設計;在這個匿名方法中,我們會將總共點擊這按鈕的次數,顯示在這個按鈕上。而您可以看到,我們完完全全都是使用 C# 程式語言來做到這樣需求設計。
  • 從這個 MainActivity 類別定義程式碼中,看到這個類別是繼承 Activity 類別,而一個 Activity 就是表示該應用程式中整個螢幕畫面。我們在這個 MainActivity 類別中,使用 SetContentView 方法,使用關注點分離的 MVC 設計方法,指定這個頁面要顯示、使用到的 UI 控制項與排列方法在上面所提到的 Main.axml 檔案內;因此,這個頁面將會使用這個 XML 檔案內容的宣告事項,將 Layout / Widget 顯示在螢幕上。最後,我們透過了 FindViewById 這個方法,找到按鈕控制項的物件,並且設定該按鈕的點選事件,綁訂到我們設定為匿名委派方法 (Anonymous Delegate Method) 內,也就是說,當使用者在裝置或手機螢幕上點選了這個按鈕,我們將會把當時總共點選次數的文字,設定到這個按鈕上,如此,我們就可以從這個按鈕上,看到當時使用者總共點選這個按鈕幾次了。
關於更多的 UI 與其互動設計需求,可以參考 控制項 這份文件中的說明
C# CSharp
using Android.App;
using Android.Widget;
using Android.OS;

namespace XAQStart
{
    [Activity(Label = "XAQStart", MainLauncher = true, Icon = "@mipmap/icon")]
    public class MainActivity : Activity
    {
        int count = 1;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button>(Resource.Id.myButton);

            button.Click += delegate { button.Text = string.Format("{0} clicks!", count++); };
        }
    }
}

進階研讀

您可以繼續來研讀 Xamarin 新手入門 Part 2 Xamarin.iOS 專案開發教學

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


關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程




2018/04/06

Prism 使用 Unity 在 Xamarin.Forms 中,進行三種注入實作物件練習

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

當我們使用 Prism 來開發 Xamarin.Forms 專案的時候,可以選擇不同 DI 容器 Container 的開發框架,不論您選擇的是哪一個,這些 DI 容器都可以支援 1.建構函式注入 Constructor Injection 2. 屬性注入 Property Injection 3. 方法注入 Method Injection ,這三種相依性注入途徑。在這篇文章中,我們將來練習與研究,我們如何使用 Visual Studio 2017 擴充功能的 Prism Template Pack 這個套件所提供的專案樣板,建立起一個 Xamarin.Forms 的專案,接著,我們在這個專案內的 ViewModel 來練習這三種相依性注入的用法,不過,在這裡,我們選擇的 IoC 套件是 Unity。

準備測試環境

  • 首先,我們先要使用 Prism Template Pack 建立一個 Xamarin.Forms 跨平台行動專案
  • 接著,請打開 App.xaml.cs 檔案,依照底下程式碼替換掉
  • 在新的 App.xaml.cs 檔案中,我們建立了兩個介面 IServiceA, IServiceB ,並且使用這兩個介面時做出這兩個類別 ServiceA, ServiceB
  • 為了要能夠讓 Unity IoC 容器可以自動注入 IServiceA, IServiceB 的實作物件,我們在 App.RegisterTypes 方法內,使用 containerRegistry.Register<IServiceA, ServiceA>(); , containerRegistry.Register<IServiceB, ServiceB>(); 敘述,註冊了這兩個介面與具體實作類別間的關係。
C# CSharp
public partial class App : PrismApplication
{
    /* 
     * The Xamarin Forms XAML Previewer in Visual Studio uses System.Activator.CreateInstance.
     * This imposes a limitation in which the App class must have a default constructor. 
     * App(IPlatformInitializer initializer = null) cannot be handled by the Activator.
     */
    public App() : this(null) { }

    public App(IPlatformInitializer initializer) : base(initializer) { }

    protected override async void OnInitialized()
    {
        InitializeComponent();

        await NavigationService.NavigateAsync("NavigationPage/MainPage");
    }

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterForNavigation<NavigationPage>();
        containerRegistry.RegisterForNavigation<MainPage>();

        containerRegistry.Register<IServiceA, ServiceA>();
        containerRegistry.Register<IServiceB, ServiceB>();
    }
}
public interface IServiceA
{
    string MethodA1();
}

public interface IServiceB
{
    string MethodB1();
}

public class ServiceA : IServiceA
{
    public string MethodA1()
    {
        return "MethodA1() " ;
    }
}

public class ServiceB : IServiceB
{
    public string MethodB1()
    {
        return "MethodB1() ";
    }
}
  • 現在,我們打開 MVVM 中的 View 檢視,也就是 MainPage.xaml 檔案,並且使用底下 XAML 宣告標記,替換到原先檔案內容;在這裡,我們在頁面宣告了三個文字控制項,將分別用來顯示 建構式注入、屬性注入、方法注入後的執行結果。
MainPage
<?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="XFPropInject.Views.MainPage"
             Title="Unity IoC 三種相依性注入用法">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="{Binding ConstructorInjection}" />
        <Label Text="{Binding PropertyInjection}" />
        <Label Text="{Binding MethodInjection}" />
    </StackLayout>

</ContentPage>
  • 最後,請使用底下 C# 程式碼,將 MainPageViewModel 使用底下程式碼來替換掉
  • 在這裡,_ServiceA 的實作具體物件,將會透過 Unity IoC 的建構式注入方式,將其注入到建構函式內,而到底要由 IoC 幫我們產生哪個具體實作型別物件呢?其實,在上面的步驟中,我們在 App.RegisterTypes 方法內,就有宣告這個介面與具體實作型別間的關聯
  • 另外,_ServiceB 這個介面物件,由於在其 C# 屬性 (Property) 上宣告了 [Dependency] 這個屬性 (Attribute),這個屬性可以參考 DependencyAttribute Class 文件,做進一步的深入了解;因為我們在這個 C# 屬性(Property)上宣告了 [Dependency] 這個屬性 (Attribute),所以,當使用到這個屬性的時候,將會透過屬性注入的方式,來取得這個抽象介面的具體實作物件。
  • 最後一個注入具體實作物件的方式,那就是方法注入,在這裡,我們宣告了一個方法 Initialize(IServiceB serviceMethodB),由於我們在這個方法前面,使用了 [InjectionMethod] 屬性 (Aattribute);更多關於這個屬性的說明,可以參考 InjectionMethodAttribute 文件。因為 Initialize 方法標記了 [InjectionMethod] 屬性 (Aattribute),因此,在 IoC 建立該物件之後,也會執行這個方法,也就是會幫我們注入了 IServiceB 的具體實作物件進來。
C# CSharp
public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
{
    public event PropertyChangedEventHandler PropertyChanged;

    private readonly INavigationService _navigationService;
    public string ConstructorInjection { get; set; }
    public string PropertyInjection { get; set; }
    IServiceA _ServiceA;
    public string MethodInjection { get; set; }
    [Dependency]
    public IServiceB _ServiceB { get; set; }
    [Dependency]
    public IPageDialogService _dialogService { get; set; }
    IServiceB _ServiceMethodB;
    public MainPageViewModel(INavigationService navigationService, IServiceA serviceA)
    {
        _navigationService = navigationService;
        _ServiceA = serviceA;
    }

    public void OnNavigatedFrom(NavigationParameters parameters)
    {

    }

    public void OnNavigatingTo(NavigationParameters parameters)
    {
        ConstructorInjection = $"建構式注入 {_ServiceA.MethodA1()} Hash:{_ServiceA.GetHashCode()}";
        PropertyInjection = $"屬性注入 {_ServiceB.MethodB1()} Hash:{_ServiceB.GetHashCode()}";
        MethodInjection = $"方法注入 {_ServiceMethodB.MethodB1()} Hash:{_ServiceMethodB.GetHashCode()}";
        //await _dialogService.DisplayAlertAsync("通知", "完成相依性注入練習", "打完收工");
    }
    public void OnNavigatedTo(NavigationParameters parameters)
    {

    }

    [InjectionMethod]
    public void Initialize(IServiceB serviceMethodB)
    {
        _ServiceMethodB = serviceMethodB;
    }

}

執行結果

  • 下圖為在 Android 模擬器下執行的結果。
  • 其中我們也觀察到了,透過 屬性注入與方法注入的 IServiceB 介面之具體實作物件,並不是同一個。
Unity IoC for Xamarin.Forms

2018/04/05

Part 3 Xamarin.Forms 的頁面導航之導航堆疊頁面移除,直接返回上上一頁

在前面已經有兩篇文章探討 Xamarin.Forms 頁面導航之相關應用 Xamarin.Forms 的頁面導航Page Navigation 之有無強制回應Modal 對話窗和導航工具列NavigationPage 的體驗 與 Part II Xamarin.Forms 的頁面導航 Page Navigation 之有無強制回應 Modal 對話窗和導航工具列 NavigationPage 的體驗 的兩篇文章,現在,我們繼續來探討頁面導航的應用,那就是我們要如何在進行頁面導航的過程中,需要調整導航堆疊的內容,以便當使用者要返回到上個頁面時候,可以跳到更前面的頁面。

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

在這裡,我們將要模擬一個日常開發專案中,經常會遇到的情境,我們需要做到這樣的頁面導航情境,在這裡我們有三個頁面,專案啟動之後,會先顯示 MainPage,在這個螢幕上,使用者可以在 MainPage 頁面點選按鈕,此時,就會導航到 DetailPage 頁面,而在這個時候,若使用點選了 DetailPage 頁面上的按鈕,系統將會使用 Modal 強制回應模式進行頁面導航到 EditPage;由於我們導航到 EditPage 採用的 Modal 強制回應的模式,因此,在這個頁面上沒有任何的導航工具列,所以,使用者若要回到前一個頁面,緊能夠點選該頁面上的按鈕,不過,當使用者點選這個按鈕之後,將會跳過 DetailPage,而是回到 MainPage 頁面(請不要使用絕對導航模式,重啟新的導航到 MainPage);另外,您將會注意到,在 Android 平台上,EditPage 頁面雖然沒有了左上角的軟體回上頁按鈕,可是,當我們點選了實體回上頁按鈕,此時應用程式卻回到了 DetailPage,這並不是我們所要看到的,因此,我們也要解決此一問題。
NavigationPage -> MainPage -> DetailPage --(使用 Modal)--> EditPage

從導航堆疊中移除頁面節點

首先,我們要來修正 EditPage ViewModel 的按鈕 DelegateCommand 的委派方法,透過 App.Current.MainPage 屬性,取得現在 Xamarin.Forms 正在顯示的頁面物件,接著,就可以透過 INavigation 介面的實作物件 (從 ContentPage.Navigation 屬性來取得),進一步取得 NavigationStack 導航堆疊物件,有了導航堆疊 Navigation Stack,我們就可以透過了 Navigation.RemovePage 方法,把導航堆疊中的某個頁面,從導航堆疊中移除。若您想要在導航堆疊中插入某個新的頁面,也可以透過 InsertPageBefore 方法來做到。
經過這樣的修正之後,您將會看到當您呼叫了 _navigationService.GoBackAsync 方法之後,將會依照您調整過的導航堆疊,返回到導航堆疊中的最後一個頁面。
C# CSharp
GoBackPageCommand = new DelegateCommand(async () =>
{
    var fooPage = App.Current.MainPage;
    var fooNavigationStack = fooPage.Navigation.NavigationStack;
    var fooModalStack = fooPage.Navigation.ModalStack;
    var fooRemovePageNode = fooNavigationStack[fooNavigationStack.Count - 1];
    fooPage.Navigation.RemovePage(fooRemovePageNode);

    await _navigationService.GoBackAsync() ;
});
最後,我們還需要解決另外一個問題,那就是如何在 EditPage 頁面中,關閉實體回上頁按鈕的功能,想要做到這樣的需求,您需要在 EditPage 的 Code Behind 程式碼中,覆寫 Override 這個方法 OnBackButtonPressed,在這個方法我們需要回傳 true 布林值,這樣,就可以在這個頁面,把實體回上頁按鈕關閉起來了。
C# CSharp
protected override bool OnBackButtonPressed()
{
    return true;
}

2018/04/01

Part II Xamarin.Forms 的頁面導航 Page Navigation 之有無強制回應 Modal 對話窗和導航工具列 NavigationPage 的體驗

在上一篇 
Xamarin.Forms 的頁面導航Page Navigation 之有無強制回應Modal 對話窗和導航工具列NavigationPage 的體驗
 文章中,當我們使用了 Modal 模式進行導航功能的時候,由於在每個導航過去的頁面,並沒有任何按鈕可以讓我們返回上一頁,因此,當時測試的時候,是在 Android 模擬器下,使用實體回上頁按鈕,使得應用程式可以回上頁;不過,當在 iOS 環境下,並沒有實體回上頁的按鈕,此時就會發生問題。
在這篇文章中,將會延續剛剛提到的文章,繼續來進行探討關於有強制回應 Modal 的導航上的問題,那就是,若我們使用了強制回應 Modal 進行導航到新頁面,不過,該新頁面又有使用了 NavigationPage,這個時候,若我們已經導航過去了,您將會發現,在這個時間點,您是無法使用 _navigationService.GoBackAsync(); 這樣的敘述,讓應用程式回到上一頁,不過,您卻發現到使用了實體回上頁按鈕,卻可以強制回到上頁,對於這樣的問題,我們該如何進行因應呢?
首先,我們需要擴充 
Xamarin.Forms 的頁面導航Page Navigation 之有無強制回應Modal 對話窗和導航工具列NavigationPage 的體驗
 文章中的範例專案程式碼,這個專案程式碼可以 這裡 看到

修正測試專案

在上一篇文章中,若您點選了 [導航到下一頁(有強制回應且有導航頁面)] 倒數第二個按鈕,此時,我們將會使用 _navigationService.NavigateAsync("NavigationPage/Page1Page", null, true); 這樣的敘述,進行頁面導航,不過,我們發現到了,這個時候,我們有在一次使用了 NavigationPage 這個頁面,並且用於此次新的頁面導航中,不過,若我們有再 Page1 頁面中放一個按鈕,當使用按下了這個按鈕,我們就會執行 _navigationService.GoBackAsync(); 這樣的敘述,因此,您將會發現到,按下的這個頁面中的按鈕,卻無法幫助我們回到上一頁,為什麼會有這樣的現象呢?
經過了除錯測試,我們可以確定我們真的有執行了 _navigationService.GoBackAsync(); ,只不過很可惜的,頁面一樣停留在 Page1 中,無法回到主頁面中。
舊的主頁面
現在,我們將這個測試專案修正成為如下截圖內容,其中,我們將原先 [導航到下一頁(有強制回應且有導航頁面)] 這個按鈕,修正名稱成為 [導航到下一頁(有強制回應且有導航頁面)(回上頁按鈕程式碼會失效)],並且另外三個按鈕:[導航到下一頁(有強制回應且有導航頁面)(PopModalAsync)] , [導航到下兩頁(有強制回應且有導航頁面)(逐頁返回)] , [導航到下兩頁(有強制回應且有導航頁面)(全部返回)]
  • 導航到下一頁(有強制回應且有導航頁面)(回上頁按鈕程式碼會失效)
    當按下這個按鈕,將會使用 NavigationPage/Page1Page 這個導航路徑進行頁面導航,也就是 Home -> Page1 (實際上在 Xamarin.Forms 的內部中,紀錄的是 Home -> NavigationPage -> Page1)
  • 導航到下一頁(有強制回應且有導航頁面)(PopModalAsync)
    當按下這個按鈕,將會使用 NavigationPage/Page3Page 這個導航路徑進行頁面導航,也就是 Home -> Page3 (實際上在 Xamarin.Forms 的內部中,紀錄的是 Home -> NavigationPage -> Page3)
  • 導航到下兩頁(有強制回應且有導航頁面)(逐頁返回)
    當按下這個按鈕,將會使用 NavigationPage/Page3Page/Page2Page 這個導航路徑進行頁面導航,也就是 Home -> Page3 -> Page2 (實際上在 Xamarin.Forms 的內部中,紀錄的是 Home -> NavigationPage -> Page1 -> Page2)
  • 導航到下兩頁(有強制回應且有導航頁面)(全部返回)
    當按下這個按鈕,將會使用 NavigationPage/Page3Page/Page4Page 這個導航路徑進行頁面導航,也就是 Home -> Page3 -> Page4 (實際上在 Xamarin.Forms 的內部中,紀錄的是 Home -> NavigationPage -> Page3 -> Page4)
舊的主頁面
另外,我們也追加了兩個新的頁面 Page3, Page4,這兩個頁面與 Page1 , Page2 的差別在於當在這些 ContentPage 頁面中,按下了 Back 按鈕之,前者使用了 PopModalAsync 方法來回上頁,而後者使用了 GoBackAsync 方法來回上頁。

檢視測試結果 導航到下一頁(有強制回應且有導航頁面)(回上頁按鈕程式碼會失效)

當我們導航到了 Page1 (Home -> Page1),並且按下頁面中間的 Back 按鈕,我們發現到這個時候,我們是無法正常回到了首頁;這是因為我們在導航路徑中使用了 NavigationPage 在導航路徑中。
Page1

檢視測試結果 導航到下一頁(有強制回應且有導航頁面)(PopModalAsync)

當我們導航到了 Page1 (Home -> Page3),並且按下頁面中間的 Back 按鈕,現在我們可以正常的回到首頁了,這是因為我們在按下這個按鈕的 DelegateCommand 委派方法中,使用了底下的程式碼,在這裡,我們透過了靜態屬性 App.Current 取得了整個系統的 App 類別的物件,接著,透過 MainPage 屬性,取得正在顯示的頁面物件,如此,我們就可以呼叫 ContentPage 頁面的 Navigation 屬性內的 PopModalAsync 方法,讓系統可以關閉強制回應的頁面。所以,在這樣的設計模式下,不論使用者使用實體退回按鈕或者頁面中的 Back 按鈕,都可以正常回上頁。
會有這樣的結果,這是因為 ContentPage 頁面的 Navigation 屬性是屬於 Xamarin.Forms.INavigation 介面實作,在 Xamarin.Forms.INavigation 介面中,宣告了兩個屬性 ModalStack 與 NavigationStack 。因為我們現在使用了強制回應的導航模式,因此,將會進入到 ModalStack 堆疊內,可能是 Prism 導航服務上有些狀況,無法正常取消強制回應的顯示模式,關於更多關於這方面的內容,可以查看 Prism PageNavigationService 原始碼;因此,我們需要使用 Xamarin.Forms.INavigation.PopModalAsync/) 來關閉強制回應的導航模式。
C# CSharp
GoBackCommand = new DelegateCommand(async () =>
{
    var fooPage = App.Current.MainPage;
    var fooNavigationStack = fooPage.Navigation.NavigationStack;
    var fooModalStack = fooPage.Navigation.ModalStack;
    await fooPage.Navigation.PopModalAsync();
});
Page3

檢視測試結果 導航到下兩頁(有強制回應且有導航頁面)(逐頁返回)

現在,讓我們來測試強制回應的深度導航,當我們導航到了 Page1 (Home -> Page3 -> Page2),我們可以按下在 Page2 頁面中間的 Back 按鈕,此時,該 Back 按鈕會使用 Prism 導航服務來回上頁;現在在 Page3 中,點選頁面中間的 Back 按鈕,該 Back 按鈕會使用關閉強制回應導航模式,並且回到了首頁了。
Page3 > Page4

檢視測試結果 導航到下兩頁(有強制回應且有導航頁面)(全部返回)

現在,讓我們來測試強制回應的深度導航,當我們導航到了 Page1 (Home -> Page3 -> Page4),我們可以按下在 Page4 頁面中間的 Back 按鈕,此時,該 Back 按鈕會使用 關閉強制回應導航模式,也就是會強制關閉這一連串強制回應的深度導航了,並且直接回到了首頁了,因此,您再也看不到 Page3 的顯示內容了。
Page3 > Page4

回顧上篇文章




+