XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2016/06/30

Xamarin.Forms Hello World

這是這本書的第一個範例程式,也是大部分電腦程式語言或者開發工具第一個會介紹要做出的程式,那就是:您好 Hello。這個專案會讓您透過文字輸入盒,讓您輸入您的名字,接著會有一個按鈕,當您按下了這個按鈕,螢幕會顯示出這段文字 您的名字 您好,這段文字。
在這個實作範例展示過程,除了告訴您,當要使用 C# 配合 Xamarin.Forms Toolkit 來進行行動裝置跨平台應用程式開發,會經歷過哪些步驟,也會向您說明您應該、必須要知道的一些事情。

開始建立新的專案

  1. 首先,開啟您的 Visual Studio 2015 (在此,假設您已經安裝好 Visual Studio 2015 與 Xamarin Toolkit工具)
  2. 接著透過 Visual Studio 2015 功能表,選擇這些項目 檔案 > 新增 > 專案 準備新增一個專案。
  3. 接著,Visual Studio 2015 會顯示 新增專案 對話窗,請在這個對話窗上,進行選擇 Visual C# >Cross-Platform > Blank Xaml App (Xamarin.Forms Portable)
  4. 接著,在最下方的 名稱 文字輸入盒處,輸入 HelloBlankXamlApp 這個名稱,最後使用滑鼠右擊右下方的 確定 按鈕。
    HelloBlankXamlApp
  5. 當專案建立到一半,會看到 Xamarin Mac Agent Instructions 對話窗出現,這個對話窗是要提醒您進行與 Mac 電腦連線設定,這是因為,您無法在 Windows 作業系統進行 iOS 相關應用程式的建立與設計工作,而是需要透過 Mac 電腦安裝的 XCode 來協助您完成這些 iOS 應用程式的建立工作。
  6. 接著會看到 新的通用Windows專案 對話視窗,此時,您只需要按下 確定 按鈕即可,此時,專案精靈會繼續完成相關平台的專案建立工作。

建立的相關專案檢視

當新專案確定建立完成後,您可以從 方案總管 視窗中,看到專案精靈已經為您建立了總共6個專案,這別是:
  • HelloBlankXamlApp.Droid
    這個專案為 Android 應用程式使用的專案
  • HelloBlankXamlApp.iOS
    這個專案為 iOS 應用程式使用的專案
  • HelloBlankXamlApp.UWP
    這個專案為 Windows Universal Platform 應用程式使用的專案
  • HelloBlankXamlApp.Windows
    這個專案為 Windows 8.1 for WinRT 應用程式使用的專案
  • HelloBlankXamlApp.WinPhone
    這個專案為 Windows Phone 8.1 應用程式使用的專案
  • HelloBlankXamlApp
    這個專案是最為重要的核心,也是一個 PCL (Portable Class Library),所有的 Xamarin.Forms 的 UI 定義與商業邏輯程式碼,都會定義在這個專案內。
原則上,當在進行 Xamarin.Forms 跨平台裝置應用程式專案開發的時候,絕大部分的時間都會專注在 PCL 這個核心專案上,您需要在這個專案內定義各個手機頁面,透過 View Model 來進行商業邏輯程式碼的撰寫;當您需要建立個行動裝置平台要用的安裝檔案、需要使用個平台專屬的部分功能的時候,才會用到各個行動裝置的專案。

檢視 Android 專案

首先,先來檢視 Android 專案,看看在 Android 專案中,整個 Xamarin.Forms 的系統如何串接與運作的。
在方案總管中,展開 HelloBlankXamlApp.Droid 專案結構,如下圖所示,您可以看到這個專案就與 Xamarin.Android 類型的專案一樣,您可以在 Resource目錄下定義各種 Android 平台中會用到的各種資源。
Xamarin.Forms 的應用程式開發,其觀念非常的簡單,那就是,每個行動裝置平台,都會擁有一個專案,例如,在這個範例中,Android平台,會有一個 HelloBlankXamlApp.Droid,而 iOS平台,則會有一個HelloBlankXamlApp.iOS。每個行動裝置平台的專案都會參考到核心PCL專案,也就是HelloBlankXamlApp,您會在該專案內,透過 XAML 或者C#程式語言來定義各種不同的手機頁面。
通常,我們會將每個頁面要出現哪些內容,使用 XAML 宣告式語言來描述,我們稱作 View,接著,會定義另外一個類別,將這個頁面的商業邏輯行為與頁面互動的動作或者要讀取外部的資料,皆會在這個類別中定義出來。之後,就會透過 MVVM (Model, View, View Model)這個技術框架,將 View 與 View Model 整合在一起,若在頁面中需要顯示各項 View Model 內的資料,需要透過資料繫結的方式,將 View 的某個視覺元件的某個屬性與 View Model 內的某個屬性進行綁定,如此,當View Model 內的資料變動的時候,View 內的視覺元件屬性值,也會隨之變動;當然,您也可以做到當 View 內視覺元件的屬性值有變動的時候,可以讓 View Model 內已經綁定的屬性值也隨之變動。
這樣的開發方式,就是MVVM ,在使用 MVVM框架架構下開發的時候,View Model 不需要知道他是與哪個 View 會結合再一起,我們只需要在 View 中,描述需要進行資料綁定的路徑;這樣在執行時期,系統就會自動依據資料綁定的定義,進行資料更新;另外,當我們需要定某個 UI 元件互動事件的時候,以往是需要透過 Code Behind 的方式,這些事件定義在該 View,也就是該 Xaml 類型檔案的 Code Behind .cs 檔案內,由於採用了 MVVM 方式開發,現在就不需要這樣做了,我們可以將所有這樣的需求全部轉移到 View Model 內。更多關於 MVVM 的應用與觀念,會在其他範例中進行深入介紹。
讓我們回到 HelloBlankXamlApp.Droid 專案,在這個專案內,您會看到這個專案內只有一個 Activity類別檔案,其實,這就是 Xamarin.Forms 的 Android 應用程式地進入點,打開該檔案來檢視一下這個 Activity 做了哪些事情。
namespace HelloBlankXamlApp.Droid
{
    // 定義這個 Android 應用程式主要行為與應用程式圖示
    [Activity(Label = "HelloBlankXamlApp", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);

            // 從這裡開始,所有的手機畫面與互動,皆會透過 核心PCL (`HelloBlankXamlApp.Droid`) 來運行,
            // 也就是,都會使用 Xamarin.Forms 的框架來執行
            global::Xamarin.Forms.Forms.Init(this, bundle);
            // 載入定義在 核心PCL 內的主要進入點物件,將生命週期交給 Xamarin.Forms
            LoadApplication(new App());
        }
    }
}
在這個 MainActivity.cs 類別定義中,首先看到底下關於這個類別的宣告與相關屬性使用。
// 定義這個 Android 應用程式主要行為與應用程式圖示
    [Activity(Label = "HelloBlankXamlApp", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity

Android Activity 的屬性(Attribute)

在這個 Activity 中,使用了 ActivityAttribute 這個客製化屬性來描述予宣告 Activity 在應用程式中要如何建置其視覺介面,另外, ActivityAttribute 這個客製化屬性也會更新 AndroidMainfest.xml 這個檔案內容,例如,當我們想要讓這個 Android 應用程式可以存取 GPS,拍照鏡頭等使用能力,透過這樣的宣告,使得不再需要去修改 AndroidMainfest.xml 這個檔案,而直接在這裡定義即可。
在這個範例中,ActivityAttribute使用到了 LabelIconTheme, 'MainLauncher', 'ConfigurationChanges' 這些個屬性。
  • Label 屬性
    用於宣告這個 Activity 的名稱
  • Icon 屬性
    用於宣告這個 Activity 表示用的圖示稱(這個名稱用能夠存在於 Resources 內)
  • Theme 屬性
    用於宣告這個 Activity 所要參考的布景配置資源名稱
  • MainLauncher 屬性
    用於宣告這個 Activity 是否為當應用程式啟動之後,第一個要顯示的畫面,在這裡,一定要定義為 true
  • ConfigurationChanges 屬性
    用於宣告這個 Activity 當 Configuration 行為有變動的時候,這個 Activity 會自己處理這些相關事件;在這個範例中,定義了,當螢幕的尺寸有所異動、螢幕有轉向的時候,這個 Activity 會來處理這些事件,也就是說,可以在 Xamarin.Forms 內處理這些異動事件。

Android Activity 的建構式

這建構式非常的簡單,當執行這個建構式之後,就會把整個程式的執行與運作權利,轉交給 Xamarin.Forms 來處理,也就是說,接下來要顯示哪個畫面、每個畫面要顯示那些 UI、這些 UI 若接收到不同的手勢操作又該由哪些相對應的事件來處理等等需求,都可以直接在 核心PCL 並且使用 Xamarin.Forms 所提供的各樣功能來完成;您也可以視為,當您進行 Xamarin.Forms 程式開發的時候,原則上,您不再需要來用到這個專案 (HelloBlankXamlApp.Droid)。
雖然這個建構式裡面只有幾行程式碼,特別看到這個建構式的有下列程式碼:
            // 從這裡開始,所有的手機畫面與互動,皆會透過 核心PCL (`HelloBlankXamlApp.Droid`) 來運行,
            // 也就是,都會使用 Xamarin.Forms 的框架來執行
            global::Xamarin.Forms.Forms.Init(this, bundle);
            // 載入定義在 核心PCL 內的主要進入點物件,將生命週期交給 Xamarin.Forms
            LoadApplication(new App());
global::Xamarin.Forms.Forms.Init(this, bundle); 是進行 Xamarin.Forms 初始化工作。
LoadApplication(new App()); 則是使用 核心PCL 裡面定義的一個 Xamarin.Forms 進入點類別 (App),由這個專案載入這個物件,接著,就將執行控制權交給 Xamarin.Forms 來執行。
其中, golbal:: 這個是 C# 的 全域命名空間(global namespace)別名,讓您可以能夠存取全域命名空間(global namespace) 中的成員會十分有用,尤其是該成員可能會被其他同名的實體隱藏的時候。

Android Activity 的屬性(Attribute) 進一步了解

更多關於 Android Activity 的屬性定義,請參考官方網站https://developer.xamarin.com/api/type/Android.App.ActivityAttribute/

核心PCL

接下來就來研究 Xamarin.Forms 的核心PCL專案,並且了解上一段落提到的 Xamarin.Forms 進入點類別App 做了哪些事情,以及要如何透過 Xamarin.Forms 提供的 XAML 宣告語言來定義出這個專案需要的相關介面與功能。
打開 HelloBlankXamlApp 核心 PCL 專案,其相關成員如下圖所示:
從上圖,可以觀察到,這個專案擁有一個 App.Xaml 與 MainPage.Xaml 這兩個檔案,前者是各個行動裝置平台專案的 Xamarin.Forms 的進入點類別,後者是第一個要看到的畫面 XAML 定義檔案。
下列為 App.xaml 檔案內容。
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="HelloBlankXamlApp.App">
  <Application.Resources>

    <!-- Application resource dictionary -->

  </Application.Resources>
</Application>
下列為 App.c# Code Behind 檔案內容。 這個 App 類別的 Code Behind .cs 檔案,定義了一個HelloBlankXamlApp.MainPage 物件,並且將它設定給 App 類別繼承的 Application 類別中的 MainPage 屬性,這表示當這個應用程式一啟動之後,第一個要顯示的畫面,就是使用 MainPage 這個類別中所定義的相關內容。
在這個遊專案樣板所產生的 App 類別中,有著 OnStartOnSleepOnResume 這三個方法,這三個方法將會用於處理 Xamarin.Forms 的應用程式整體生命週期相關事件。您可以依照您的應用程式需求,自行在這三個方法中加入更多的控制與處理功能,例如,當應用程式切換到背景模式的時候,需要把相關應用程式執行的狀態值,先儲存起來,而在回到前景模式的時候,可以把這些狀態值讀取出來,並且回覆到相關視覺控制項上。
  • OnStart
    當應用程式啟動的時候,會呼叫這個方法。
  • OnSleep
    每次當應用程式進入到背景情境的時候,都會呼叫這個方法。
  • OnResume
    當應用程式從背景情境,切換到前景情境下,要繼續執行的時候,會呼叫這個方法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Xamarin.Forms;

namespace HelloBlankXamlApp
{
    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();

            MainPage = new HelloBlankXamlApp.MainPage();
        }

        protected override void OnStart()
        {
            // Handle when your app starts
        }

        protected override void OnSleep()
        {
            // Handle when your app sleeps
        }

        protected override void OnResume()
        {
            // Handle when your app resumes
        }
    }
}
想要知道這三個方法,在不同的情況下的呼叫順序為和,可以加入底下程式碼,並且執行與操作不同情境,查看 Visual Studio 的輸出頁面的相關訊息輸出。
protected override void OnStart()
{
    Debug.WriteLine ("OnStart");
}
protected override void OnSleep()
{
    Debug.WriteLine ("OnSleep");
}
protected override void OnResume()
{
    Debug.WriteLine ("OnResume");
}
接著,進入到這個應用程式的第一個畫面的設計情境,打開核心PCL專案內的 MainPage.xaml & MainPage.cs 這兩檔案。
在 MainPage.xaml 檔案內,定義了根節點,是個 ContentPage 這個元素(Element),在這個根結點裡面,定義了一個 Label 控制項,用來顯示一段文字,並且該 Label 控制項是會水平與垂直採用置中對齊的方式來排列。
<?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:HelloBlankXamlApp"
             x:Class="HelloBlankXamlApp.MainPage">

  <Label Text="Welcome to Xamarin Forms!"
           VerticalOptions="Center"
           HorizontalOptions="Center" />

</ContentPage>
在 MainPage.cs 這個 Code Behind 檔案內,只有一個建構式方法,並且在這個建構式方法內,呼叫了InitializeComponent() 方法,這個方法會將 MainPage.xaml 檔案內的相關 XAML 宣告定義,使用 C# 程式語言產生相對應的物件,並且進行初始化。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

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

開始在模擬器中執行 Android 專案

請使用滑鼠右擊方案總管的 HelloBlankXamlApp.Droid 專案,在彈出功能表選擇 設定為起始專案 這個選項,接著,按下 F5 按鍵,進行該專案的執行工作。
這個專案將會在 4.7" KiKat (4.4) XHDPI Phone (Android 4.4 - API 19) Visual Studio 模擬器上進行執行。想要知道 如何建立 Visual Stdio for Android的模擬器 ,請參考後面 如何建立 Visual Stdio for Android的模擬器 小節說明。
下圖是實際在 Visual Studio for Android 模擬器上執行結果的螢幕截圖。

檢視 iOS 專案

在檢視完 Android 專案並且順利執行該專案之後,接下來了解 iOS的專案組成方式。
在方案總管中,展開 HelloBlankXamlApp.iOS 專案結構,如下圖所示
在這個 iOS 專案內,有兩個檔案,需要來查看一下,那就是 Main.cs 與 ApDelegate.cs
打開 Main.cs 檔案,其內容會如下所示:
這個檔案為 iOS 應用程式的進入點,也就是 iOS 的程式會從這個地方開始執行;這個 Application 類別中,只有一個 Main 方法,該方法裡面只有一行,那就是呼叫 UIApplication.Main(args, null, "AppDelegate"); 方法,這個方法會產生一個 AppDelegate 物件類別,透過這個物件,開始進行進入到 Xamarin.Forms 的生命週期循環。
using System;
using System.Collections.Generic;
using System.Linq;

using Foundation;
using UIKit;

namespace HelloBlankXamlApp.iOS
{
    public class Application
    {
        // This is the main entry point of the application.
        static void Main(string[] args)
        {
            // if you want to use a different Application Delegate class from "AppDelegate"
            // you can specify it here.
            UIApplication.Main(args, null, "AppDelegate");
        }
    }
}
打開 AppDelegate.cs 檔案,其程式碼如下:
在 AppDelegate 繼承了 類別 Xamarin.Forms.Platform.iOS.FormsApplicationDelegate;在FinishedLaunching 方法內,執行了 `Xamarin.Forms.Forms.Init() 方法,進行 Xamarin.Forms的初始化(當然,在連結的時候,也會將 Xamarin.Forms 的組件(Assembly)連結到最終應用程式執行檔案內),而後,將 核心PCL 的 Xamarin.Forms 進入點類別產生一個物件,並且開始執行 Xamarin.Forms 的相關生命週期程序與功能。
using System;
using System.Collections.Generic;
using System.Linq;

using Foundation;
using UIKit;

namespace HelloBlankXamlApp.iOS
{
    // The UIApplicationDelegate for the application. This class is responsible for launching the 
    // User Interface of the application, as well as listening (and optionally responding) to 
    // application events from iOS.
    [Register("AppDelegate")]
    public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
    {
        //
        // This method is invoked when the application has loaded and is ready to run. In this 
        // method you should instantiate the window, load the UI into it and then make the window
        // visible.
        //
        // You have 17 seconds to return from this method, or iOS will terminate your application.
        //
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            global::Xamarin.Forms.Forms.Init();
            LoadApplication(new App());

            return base.FinishedLaunching(app, options);
        }
    }
}

建立與 Mac 電腦的連線

想要開發 iOS 的應用程式,您需要準備一台 Mac 電腦,裡面要安裝最新版本的 XCode & Xamarin Studio。
接著,在Visual Studio這裡,請在 Visual Studio 功能表中選擇 工具 > iOS > Xamarin Mac Agent 這個選項。
當出現了 Xamarin Mac Agent 對話窗之後,點選中間的 Add Mac... 按鈕,準備開始進行建立與 Mac 連線的資訊。
在出現的 Add Mac 對話窗中,輸入您的 Mac 電腦當時擁有的 IP 位置,輸入完成後,點選下方 Add按鈕。
接著出現了 Connect to Mac 對話窗,請輸入需要登入 Mac 電腦的帳號與密碼後,滑鼠右擊 Login 按鈕。這個時候 Xamarin Mac Agent會開始嘗試與遠端 Mac 電腦連線,一旦帳號與密碼正確且登入成功,就會出現如下圖的畫面。

開始在模擬器中執行 iOS 專案

請使用滑鼠右擊方案總管的 HelloBlankXamlApp.iOS 專案,在彈出功能表選擇 設定為起始專案 這個選項。
此時,Visual Studio 的工具列會變成如下圖,在這裡選擇了 iPhone 6s iOS 9.3 這個模擬器做為執行測試之用。
下圖是實際在 iOS 模擬器上執行的結果。

編輯 XAML 檔案與 IntelliSense

根據微軟官方對於 IntelliSense 的定義
IntelliSense 是一些功能的概括詞彙:列出成員、參數資訊,快速諮詢和自動完成文字。 這些功能有助於深入了解您使用的程式碼,追蹤所輸入的參數,以及幾個按鍵即可加入屬性和方法呼叫。
當我們在使用 Visual Studio 2015 進行 Xamarin.Forms 專案程式開發的時候,這是一個非常好用且實在的功能,相信很多人若突然沒有了這項功能,整體程式開發生產力與效率必定下降很多。
當然,在我們對於 Xamarin.Forms 專案內的 .xaml 檔案進行編輯的時候,一定要搭配 IntelliSense 這項功能,才能夠快速、正確地寫出相關 XAML 的宣告語法。
因此,請將 核心PCL 專案內的 MainPage.xaml 檔案在 Visual Studio IDE 編輯器中打開,在 Label 控制項的上一行,輸入一個 < 字元 ,此時,您會很興奮地看到 Visual Studio 顯示了 IntelliSense 視窗;不過,不要興奮的過早,這個時候,您看到的 IntelliSense 視窗,竟然只有幾個項目可以選擇,原來,Visual Studio 2015 把這個 MainPage.xaml 檔案,視為一般的 XML 檔案了。
沒有IntelliSense的功能
要解決這個問題相當的容易,此時,請先在方案總管切換預設專案為 Android 的專案,先找到 方案總管 >HelloBlankXamlApp.Droid 這個專案節點,使用滑鼠右擊該節點,再彈出功能表中選擇 設定為起始專案 這個選項,再使用滑鼠右擊該節點,選擇 重建 這個選項,將 HelloBlankXamlApp.Droid 這個專案進行編譯與建立出來。一旦完成的重建工作,一樣的在 MainPage.xaml 檔案內,在 Label 控制項的上一行,輸入一個 < 字元 ,此時,您會很興奮地看到 Visual Studio 顯示了 IntelliSense 視窗,並且在顯示了許多 XAML 項目供您選擇。
有IntelliSense的功能

修改 MainPage 成為一個有互動的 App

首先,請先將您整個方案有使用到 Xamarin.Forms 這個 NuGet 套件,都升級到最新版本;需要這樣做的原因是,現在這個 Xamarin.Forms 套件的版本是 2.0.0.6482,升級到最新的版本,可以任您的 Xamarin.Forms 應用程式避免掉之前就版本上存在的問題;所以,請先升級到最新版本,這樣,您就可以使用到更多、更豐富的 XAML 屬性。
請使用滑鼠右擊 方案總管 視窗中的 'HelloBlankXamlApp' 方案,接著點選 管理方案的 NuGet 套件,此時,會出現下圖視窗。請點選 更新 標籤頁次,並且在該頁次的最下方找到 Xamarin.Forms 這個項目。此時,在該視窗的右半部,請確認所有的專案都有選取,並且要更新到最新版本 (可以點選 版本 標題右邊的下拉選單),最後,點選 安裝 按鈕,進行該方案內所有的專案,所有有安裝 Xamarin.Forms 這個套件升級動作。
HelloBlankXamlApp方案_NuGet套件管理員1
當更新完成之後,請確認您的方案內的 起始專案 為 HelloBlankXamlApp.Droid
在接下來的過程,您需要修改這個應用程式,當這個應用程式執行之後,會有一個文字輸入盒和一個按鈕;您可以在文字輸入盒中輸入您的名字,接著點擊底下的按鈕,之後,就會出現 Hello, 您的名字,這樣具有互動性的應用程式。
接著,打開 MainPage.xaml 檔案,加入底下 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"
             xmlns:local="clr-namespace:HelloBlankXamlApp"
             x:Class="HelloBlankXamlApp.MainPage">

  <StackLayout
    Orientation="Vertical"
    Padding="20"
    >

    <Label Text="Welcome to Xamarin Forms!"
           VerticalOptions="Center"
           HorizontalOptions="Center" />
    <Label Text="Your Name" />
    <Entry x:Name="entYourName"
           Placeholder="Please input your name"
         />
    <Button x:Name="btnOK"
            Text="OK"
            Clicked="OnbtnOK_Clicked"
          />
    <Label x:Name="lblSayHello"
           Text="" 
          />
  </StackLayout>

</ContentPage>
打開 MainPage.cs 檔案,加入底下 C# 程式碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace HelloBlankXamlApp
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void OnbtnOK_Clicked(object sender, EventArgs e)
        {
            lblSayHello.Text = $"Hello, {entYourName.Text}";
        }
    }
}
此時,請按下 F5 ,就會在模擬器上執行該 Android 專案,底下分別是執行後與輸入姓名並且按下按鈕後的執行螢幕畫面截圖。
HelloBlankXamlApp.Droid執行前的畫面
HelloBlankXamlApp.Droid執行後的畫面
現在,可以來了解剛剛做了哪些變化,使得這個應用程式變得具有互動性。
在 MainPage.xaml 內,使用一個 StackLayout 這個版面配置控制項,讓 StackLayout 內的所有控制項,皆會呈現垂直方向的依序排列;會有這樣的效果,這是因為在 StackLayout 控制項內使用了Orientation="Vertical" 這個屬性定義。
StackLayout 版面配置控制項內,一共擁有五個控制項,Label 控制項主要的目的是會顯示文字之用;Entry 控制項用於顯示一個文字輸入盒,可以讓使用者在這個控制項內輸入文字;Button 這是一個按鈕控制項,當使用者按下這個按鈕,就會執行 Clicked 這個事件屬性所定義的 OnbtnOK_Clicked 事件,這個事件會定義在 Code Behind 檔案(也就是 MainPage.cs內) 。
這些控制項中,有些使用了 XAML 延伸功能 x:Name 用來定義這些控制項的名稱,而您可以在 Code Behind 的 .cs 檔案內,使用所定義的變數名字,存取該控制項,設定該控制項的各個屬性值,以變更其行為或者外觀。
這樣的應用,可參考 MainPage.cs 檔案內定義的事件方法 private void OnbtnOK_Clicked(object sender, EventArgs e) 這個 OnbtnOK_Clicked 事件方法會在使用者按下按鈕當時,就會呼叫這個方法;當這個方法執行的時候,會設定名稱為 lblSayHello 的 'Label' 控制項的 Text 屬性值。
最後,在 'MainPage.cs' 這個檔案內的類別 'MainPage',定義使用了這個屬性 '[XamlCompilation(XamlCompilationOptions.Compile)]' ;這樣的用法是相當的實用與重要,經過這樣定義之後,在 核心PCL 專案內的相關 .xaml 檔案之 XAML 定義,會在編譯時期進行檢查,讓您提早發現錯誤;也就是說,若沒有加入這樣的定義,您必須等候到執行時期,才能夠發現到是否 XAML 定義有任何錯誤,但往往在這個時候,您已經浪費了許多寶貴的開發時間,也會造成您的應用程式變得極度不穩定。

如何建立 Visual Stdio for Android的模擬器

想要使用 Visual Studio 提供的 Android 模擬器,請依照底下說明進行設定,完成設定之後,就可以在 Visual Studio 工具列上,看到您新產生的 Visual Studio for Android 模擬器。
請先在 Visual Studio 功能表中,點選 工具 > Visual Studio Emulatoro for Android 這個選項,如下圖所示:
等候一段時間,Visual Studio Emulator for Android 對話視窗將會顯示出來,在下圖,顯示出已經下載了三個 Visual Studio 的 Android 模擬器,您可以捲動這個對話窗內容,選擇您想要使用的 Visual Studio 的 Android 模擬器 ,接著點選該項目最右邊的 下載 小圖示;一旦下完成,這個模擬器就可以在 Visual Studio 的工具列上看到。
想要解除這個 Visual Studio 的 Android 模擬器,可以選擇已經安裝好的模擬器項目,在最右方的綠色箭頭的下方圖示 (Uninstall Profile),點選這個圖示,就可以解除這個 Visual Studio 的 Android 模擬器。

更新到最新的 Xamarin Toolkit

當您開啟 Xamarin.Forms 專案的時候,若在右下角看到下列畫面,這表示 Visual Studio 提醒您,Xamarin Toolkit 有新的更新推出了,此時,您可以點選右下角圖片區域,進行下載、更新。
當然,您也可以被動地進行檢查,是否 Xamarin Toolkit 是否有更新版本推出,這個時候,請透過功能表選擇工具 > 選項 ,當 選項 對話窗出現之後,在該對話窗的左半部選擇 Xamarin > Other > Check Now,進行線上檢查是否有最新的 Xamarin Toolkit 推出。
一旦有最新的 Xamarin for Visual Studio 更新發現到,就會跳出 Xamarin for Visual Studio Updated 對話窗,透過這個對話窗,您可以看到此次進行了那些更新內容,當您確認要進行更新到這個版本的 Xamarin for Visual Studio Updated ,此時,請點選右下角的 Download 按鈕,進行更新。

2015/10/04

Async Await , C# 編譯器做了些甚麼事情呢–2?

在上一篇文章中 Async Await , C# 編譯器做了些甚麼事情呢– 1,我們將 WPF 內的非同步程式碼,透過編譯器的處理,產生了相對應的狀態機程式碼,並且在產生後的程式碼中,加入了詳盡的註解說明,並且說明了編譯器的處理過程。

在這篇文章中,我們將要補足上篇文章的一些不足點,那就是,在我們呼叫非同步程式之前,也就是 await 關鍵字之前,若還有些 C# 程式碼的話,而且,在這 await 關鍵字之後,繼續有使用到這些變數,那麼,在編譯器中所產生的狀態機物件會如何呈現?

(至於,同一個非同步方法中,若有多個 await 關鍵字,則狀態機內會使用 goto 來切換到不同的部分還處理,有興趣的人,可以自行針對編譯器產生結果程式碼來觀察)

底下為我在 WPF 的按鈕事件內呼叫一個非同步方法,在呼叫非同步方法之前,我們會計算 x * y * z 的數學運算結果到本地變數 total 內;並且在呼叫完成非同步方法之後,再將 total 變數的值,輸出到 Console上。

        private async void btnDownload2_Click(object sender, RoutedEventArgs e)
{
int
x = 3, y = 1, z = 2, total = 0;
total = x * y * z;

string
fooStr = await GetString2Async();
Console.WriteLine(
string.Format("{0} {1}", total,fooStr));
}

接著我們來看看,經過了編譯器的轉換,狀態機的類別,會如何定義呢?


我們特別將呼叫 await 非同步方法前的程式碼使用黃底色標示出來,而呼叫完成非同步方法之後的程式碼使用橘底色標示出來,您可以參考上一篇文章 Async Await , C# 編譯器做了些甚麼事情呢– 1 ,試著找出其中異同點。

        [CompilerGenerated]
private sealed class
<btnDownload2_Click>d__3 : IAsyncStateMachine
{
public int
<>1__state;

public
AsyncVoidMethodBuilder <>t__builder;

public object
sender;

public
RoutedEventArgs e;

public
MainWindow <>4__this;

private int
<x>5__1;

private int
<y>5__2;

private int
<z>5__3;

private int
<total>5__4;

private string
<fooStr>5__5;

private string
<>s__6;

private TaskAwaiter<string
> <>u__1;

void
IAsyncStateMachine.MoveNext()
{
int num = this
.<>1__state;
try
{
TaskAwaiter<
string
> taskAwaiter;
if
(num != 0)
{
this
.<x>5__1 = 3;
this
.<y>5__2 = 1;
this
.<z>5__3 = 2;
this
.<total>5__4 = 0;
this.<total>5__4 = this.<x>5__1 * this.<y>5__2 * this
.<z>5__3;
taskAwaiter =
this
.<>4__this.GetString2Async().GetAwaiter();
if
(!taskAwaiter.IsCompleted)
{
this
.<>1__state = 0;
this
.<>u__1 = taskAwaiter;
MainWindow.<btnDownload2_Click>d__3 <btnDownload2_Click>d__ =
this
;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, MainWindow.<btnDownload2_Click>d__3>(ref taskAwaiter, ref
<btnDownload2_Click>d__);
return
;
}
}
else
{
taskAwaiter =
this
.<>u__1;
this.<>u__1 = default(TaskAwaiter<string
>);
this
.<>1__state = -1;
}
string
result = taskAwaiter.GetResult();
taskAwaiter =
default(TaskAwaiter<string
>);
this
.<>s__6 = result;
this.<fooStr>5__5 = this
.<>s__6;
this.<>s__6 = null
;
Console.WriteLine(
string.Format("{0} {1}", this.<total>5__4, this
.<fooStr>5__5));
}
catch
(Exception exception)
{
this
.<>1__state = -2;
this
.<>t__builder.SetException(exception);
return
;
}
this
.<>1__state = -2;
this
.<>t__builder.SetResult();
}

[DebuggerHidden]
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}

Async Await , C# 編譯器做了些甚麼事情呢–1 ?

在這篇文章,我將會解析 Async/Await 的非同步方法的神秘面紗,當我們使用 Async / Await 來進行非同步方法定義的時候,此時您所寫出來的程式碼,並不是最後程式要執行的程式碼,編譯器會自動幫您重寫您所定義的非同步方法,並且加入了 State Machine 狀態機物件到您的程式內,透過自動產生的 State Machine,讓您的非同步呼叫完成之後,可以回到原先的呼叫的地方,以便繼續執行下去。
要了解這篇文章,首先,您需要了解甚麼是 狀態機
底下為該WPF上 MainWindows 的類別定義,在這個類別定義中,我們定義了一個按鈕的事件,當使用者按下了這個按鈕,會呼叫我們自訂的方法,透過 Async/Await 的非同步方式,取得特定網址的網頁,該非同步方法會回傳所取得網頁的字串到按鈕事件內,接著,在按鈕事件內會將這個字串從 Console內列印出來。
在非同步方法中 GetStringAsync中,我們使用了 HttpClient 物件,呼叫了 GetStringAsync 非同步方法,取得了微軟首頁的內容。
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void btnDownload_Click(object sender, RoutedEventArgs e)
        {
            string fooStr = await GetStringAsync();
            Console.WriteLine(fooStr);
        }

        public async Task<string> GetStringAsync()
        {
            HttpClient client = new HttpClient();
            var result = await client.GetStringAsync("http://www.microsoft.com");
            return result;
        }
    }

接著,我們來看看,對於我們經常使用的 Async / Await 非同步呼叫,編譯器究竟做了甚麼處理,可以讓我們在寫非同步方法的時候,既然可以像使用同步方式來寫程式碼,變得這麼方便、好用。

我們先來看看按鈕事件的方法:
        private async void btnDownload_Click(object sender, RoutedEventArgs e)
        {
            string fooStr = await GetStringAsync();
            Console.WriteLine(fooStr);
        }


底下為編譯器處理過的上述方法,我們從底下程式碼中,看不到原來事件方法的任何程式碼了,例如: Console.WriteLine 。

我已經將編譯器產生的按鈕事件方法程式碼加上許多註解,您可以參考這些程式碼說明;其實,編譯器把原先該按鈕內的所有程式碼,都搬到另外一個新產生的類別中,這個類別其實就是一個狀態機的運作狀態。

底下的方法內容其實相當的簡單,它產生了一個狀態機物件,接著,對此狀態機進行初始化,並且設定這個狀態機的初始狀態值為 -1 (狀態機的型別為 int 整數),最後,呼叫 Start 方法,開始進行狀態機的運作。

狀態機的將會在第一行內產生  MainWindow.<btnDownload_Click>d__1 <btnDownload_Click>d__ = new MainWindow.<btnDownload_Click>d__1();

接著,針對這個狀態機變數 <btnDownload_Click>d__  進行初始化。
// 這裡重新改寫了 btnDownload_Click 方法
// AsyncStateMachine 請參考 https://msdn.microsoft.com/zh-tw/library/system.runtime.compilerservices.asyncstatemachineattribute(v=vs.110).aspx
[DebuggerStepThrough, AsyncStateMachine(typeof(MainWindow.<btnDownload_Click>d__1))]private void btnDownload_Click(object sender, RoutedEventArgs e)
{
    // 類別 <GetStringAsync>d__1 為編譯器產生的一個類別,其中,是運用狀態機 State Machine 觀念來處理非同步呼叫運作
    MainWindow.<btnDownload_Click>d__1 <btnDownload_Click>d__ = new MainWindow.<btnDownload_Click>d__1();
    // 底下的程式碼為進行該非同步呼叫的 State Machine的各種預設值初始化    // 
    // 呼叫非同步呼叫時候的物件本身
    < btnDownload_Click >d__.<>4__this = this;
    // 設定該事件的 sender 物件    <btnDownload_Click>d__.sender = sender;
    // 設定該事件的 RoutedEventArgs 物件    <btnDownload_Click>d__.e = e;
    // 建立 AsyncTaskMethodBuilder 類別的執行個體    < btnDownload_Click >d__.<>t__builder = AsyncVoidMethodBuilder.Create();
    // 該 State Machine 最初狀態值為 -1    <btnDownload_Click>d__.<>1__state = -1;
    // 表示非同步方法產生器,不會傳回值。
    // https://msdn.microsoft.com/zh-tw/library/system.runtime.compilerservices.asyncvoidmethodbuilder(v=vs.110).aspx    AsyncVoidMethodBuilder<>t__builder = <btnDownload_Click>d__.<>t__builder;
    // 開始執行具有相關聯狀態機器的產生器
    <>t__builder.Start<MainWindow.<btnDownload_Click>d__1>(ref <btnDownload_Click>d__);
}

原先按鈕事件內的方法都搬到了編譯器產生的狀態機類別內,也就是這個 <btnDownload_Click>d__1 類別,這個類別是繼承了 IAsyncStateMachine 這個類別,表示針對非同步方法所產生的狀態機器。 這個型別僅供編譯器使用;該類別程式碼如下所示:

這個編譯器產生的狀態機類別相當的長,不過,我也儘可能的在程式碼內加入註解說明。

這個類別所做的事情,我們簡單說明一下:狀態機最初的狀態值為 -1,一旦狀態機開始運作的時候(也就上面程式碼呼叫了 <>t__builder.Start 方法),就會開始進入狀態機的處理週期,也就是 MoveNext 這個方法;每次狀態機的狀態值有變更的時候,就會再次呼叫這個方法,進入到不同處理階段。

因為最初狀態值為 -1,所以,就會執行底下黃底內的 if 內程式碼,而這段程式碼也只會執行一次,之後就不會再進來了。在 if 內的程式碼,會呼叫我們自己寫的非同步方法 GetStringAsync(),而後會取得 等候非同步工作完成的物件 taskAwaiter,因為該非同步工作尚未完成,所以,就會將狀態機的狀態值變更為 0,接著透過 AwaitUnsafeOnCompleted 方法設定當工作完成後,繼續回到狀態機內繼續持行 MoveNext 方法。

當非同步工作完成之後,就會將非同步執行結果取出來,並且列應到 Console 內,也就是下面橘色底的程式碼,會執行這段程式法,是因為狀態機的值已經成為 0了。
        // 這是編譯器產生出來的類別,用來處理非同步需求的狀態機        [CompilerGenerated]
        private sealed class <btnDownload_Click>d__1 : IAsyncStateMachine
        {
            // 該 State Machine 狀態值
            public int <>1__state;

            // 建立 AsyncTaskMethodBuilder 類別的執行個體
            public AsyncVoidMethodBuilder<> t__builder;

            // 該事件的 sender 物件
            public object sender;

            // 該事件的 RoutedEventArgs 物件
            public RoutedEventArgs e;

            // 呼叫非同步呼叫時候的物件本身
            public MainWindow<>4__this;

            // 原先事件方法中,定義的本地變數 fooStr
            private string <fooStr>5__1;

            // 用來暫時儲存呼叫非同步方法的時候的字串值
            private string <>s__2;

            // 用來暫時儲存 提供等候非同步工作完成的物件
            // https://msdn.microsoft.com/zh-tw/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
            private TaskAwaiter<string> <>u__1;

            // 每當狀態機的狀態值有變動的時候,呼叫 MoveNext來執行下一個狀態機要執行的動作
            void IAsyncStateMachine.MoveNext()
            {
            // 暫時儲存現在狀態機內的狀態值
            int num = this.<>1__state;
            // 當在原先方法內用了 await 關鍵字,編譯器,會加入異常事件捕捉
            try            {
                TaskAwaiter<string> taskAwaiter;
                if (num != 0)
                {
                    // 一開始進入狀態機,狀態機值為-1,所以,會先進入到這裡,若狀態機值為 0 ,表示此非同步工作已經完成

                    // 執行 GetStringAsync 方法&取得用來等候這個 Task 的 awaiter,回傳值為 提供等候非同步工作完成的物件
                    // https://msdn.microsoft.com/zh-tw/library/system.threading.tasks.task.getawaiter(v=vs.110).aspx
                    taskAwaiter = this.<>4__this.GetStringAsync().GetAwaiter();
                    // 指出非同步工作是否已經完成
                    // https://msdn.microsoft.com/zh-tw/library/system.runtime.compilerservices.taskawaiter.iscompleted(v=vs.110).aspx
                    if (!taskAwaiter.IsCompleted)
                    {
                        // 非同步工作尚未完成
                        this.<>1__state = 0; // 設定狀態機狀態值,標明狀態值為 0,下一個週期,就不會進入到這段程式碼了
                        this.<>u__1 = taskAwaiter;  // 用來暫時儲存 提供等候非同步工作完成的物件
                        MainWindow.<btnDownload_Click>d__1 <btnDownload_Click>d__ = this;
                        // 排程狀態機器以在指定的 awaiter 完成時繼續下一個動作
                        // 也就是說,當非同步呼叫完成後,會再度回到 MoveNext() 方法重頭執行一次,不過,因為狀態值有變動了,所以,執行結果會不同
                        this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, MainWindow.<btnDownload_Click>d__1> (ref taskAwaiter, ref <btnDownload_Click>d__);
                        return;
                    }
                }
                else                {
                    // 因為 狀態機值為 0 ,表示此非同步工作已經完成
                    // 取得等候非同步工作完成的物件
                    taskAwaiter = this.<>u__1;
                    // 設定等候非同步工作完成的物件的預設值
                    this.<>u__1 = default(TaskAwaiter<string>);
                    // 因為非同步工作已經完成,所以,再將狀態機值設為 -1 
                    this.<>1__state = -1;
                }
                // --------------------------------------------------------
                // 底下的程式碼,為該事件內 await 呼叫後的相關程式碼,也就是說,當完成非同步呼叫之後,會繼續回到原先地方繼續執行
                // --------------------------------------------------------
                // 取得 等候非同步工作完成的物件 的執行結果
                string result = taskAwaiter.GetResult();
                taskAwaiter = default(TaskAwaiter<string>);
                this.<>s__2 = result;
                this.<fooStr>5__1 = this.<>s__2;
                this.<>s__2 = null;
                // 這段程式法為原先非同步呼叫方法內的,並沒做任何改變
                Console.WriteLine(this.<fooStr>5__1);
            }
            catch (Exception exception)
            {
                // await 的非同步工作,可以捕捉異常事件,並且回報給呼叫者
                this.<>1__state = -2; 
                this.<>t__builder.SetException(exception);
                return;
            }
            this.<>1__state = -2; // 變更狀態值,表示已經完成所有的非同步工作
            // 將方法產生器標記為成功完成。
            this.<>t__builder.SetResult();
        }

最後,我們來看看我們自己定義的非同步方法,編譯器會把它變成怎麼樣,首先,底下是原先我們在 WPF 內寫的非同步方法程式碼:

這個非同步方法相當的簡單,我們先定義了一個 HttpClient 物件,接著,透過該物件,取得微軟官網首頁內,並且傳回原先呼叫者。
        public async Task<string> GetStringAsync()
        {
            HttpClient client = new HttpClient();
            var result = await client.GetStringAsync("http://www.microsoft.com");
            return result;
        }

底下為編譯器重新將 GetStringAsync 非同步方法進行包裝,並且產生了另外一個狀態機類別,以便處理非同步呼叫的相關動作,如同上面按鈕事件一樣,編譯器一樣會把我們寫的非同步方法的所有程式碼都包裝在新產生的狀態機類別內,將GetStringAsync這個方法改寫成只有進行狀態機初始化與啟動狀態機的動作。

由於這兩個非同步呼叫(一個是由按鈕事件來呼叫,一個是由自訂非同步方法來呼叫)程式碼很類似,因此,這兩個狀態機的內容很類似,您可以參考底下的程式碼註解說明,來了解到這些功能。

在此,特別說明一下,狀態機內的 if (num != 0){ ... } 程式碼 (底下黃底程式碼),也就是當 if 條件成立的時候,要執行該 if 內的程式碼內容,您可以當作,在原先寫的非同步程式碼中,所有在 await ... 這個等候非同步呼救前的所有程式碼,都會搬移到這個地方,這個 if 內的程式碼,透過狀態機的狀態值控制,就僅僅提供狀態機第一次啟動的時候才會執行,而(這個時候狀態機內的狀態值為 -1)後,就不會再執行到這段程式碼(因為,狀態機的值就會為 0了)。因此,我們就看到了,第一次進入到狀態機內,會先建立一個 HttClient 物件。

由於這個非同步方法需要回傳一個字串,因此,底下橘底程式碼,就是在整個非同步工作處理完成之後,通知最上層呼叫這個非同步方法,您可以取得這個非同步方法的傳回結果了。

    // 這是編譯器產生出來的類別,用來處理非同步需求的狀態機    [CompilerGenerated]
    private sealed class <GetStringAsync>d__2 : IAsyncStateMachine
        {
            // State Machine 的狀態值,表示處理到哪裡了
            public int <>1__state;

            // 表示非同步方法產生器,會傳回工作
            // https://msdn.microsoft.com/zh-tw/library/system.runtime.compilerservices.asynctaskmethodbuilder(v=vs.110).aspx
            public AsyncTaskMethodBuilder<string> <>t__builder;

            // 當時呼叫非同步方法的物件
            public MainWindow <>4__this;

            // 這個非同步方法內用到的 HttpClient 物件
            private HttpClient <client>5__1;

            // 原先事件方法中,定義的本地變數 result
            private string <result>5__2;

            // 用來暫時儲存呼叫非同步方法的時候的字串值
            private string <>s__3;

            // 用來暫時儲存 提供等候非同步工作完成的物件
            // https://msdn.microsoft.com/zh-tw/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
            private TaskAwaiter<string> <>u__1;

            // 每當狀態機的狀態值有變動的時候,呼叫 MoveNext來執行下一個狀態機要執行的動作
            void IAsyncStateMachine.MoveNext()
            {
                // 暫時儲存現在狀態機內的狀態值
                int num = this.<>1__state;
                string result2;
                // 當在原先方法內用了 await 關鍵字,編譯器,會加入異常事件捕捉
               try               {
                    TaskAwaiter<string> taskAwaiter;
                    if (num != 0)
                    {
                        // 一開始進入狀態機,狀態機值為-1,所以,會先進入到這裡,若狀態機值為 0 ,表示此非同步工作已經完成
                        this.<client>5__1 = new HttpClient();
                        // 執行 GetStringAsync 方法&取得用來等候這個 Task 的 awaiter,回傳值為 提供等候非同步工作完成的物件
                        taskAwaiter = this.<client>5__1.GetStringAsync("http://www.microsoft.com").GetAwaiter();
                        // 指出非同步工作是否已經完成
                        if (!taskAwaiter.IsCompleted)
                        {
                           // 非同步工作尚未完成
                            this.<>1__state = 0;  // 設定狀態機狀態值,標明狀態值為 0,下一個週期,就不會進入到這段程式碼了
                            this.<>u__1 = taskAwaiter; // 用來暫時儲存 提供等候非同步工作完成的物件
                            MainWindow.<GetStringAsync>d__2 <GetStringAsync>d__ = this;
                            // 排程狀態機器以在指定的 awaiter 完成時繼續下一個動作
                            // 也就是說,當非同步呼叫完成後,會再度回到 MoveNext() 方法重頭執行一次,不過,因為狀態值有變動了,所以,執行結果會不同
                            this.<>t__builder.AwaitUnsafeOnCompleted <TaskAwaiter<string>, MainWindow.<GetStringAsync>d__2> (ref taskAwaiter, ref <GetStringAsync>d__);
                            return;
                        }
                    }
                    else                    {
                        // 因為 狀態機值為 0 ,表示此非同步工作已經完成
                        // 取得等候非同步工作完成的物件
                        taskAwaiter = this.<>u__1;
                        // 設定等候非同步工作完成的物件的預設值
                        this.<>u__1 = default(TaskAwaiter<string>);
                        // 因為非同步工作已經完成,所以,再將狀態機值設為 -1 
                        this.<>1__state = -1;
                    }
                    // --------------------------------------------------------
                    // 底下的程式碼,為該事件內 await 呼叫後的相關程式碼,也就是說,當完成非同步呼叫之後,會繼續回到原先地方繼續執行
                    // --------------------------------------------------------
                    // 取得 等候非同步工作完成的物件 的執行結果
                    string result = taskAwaiter.GetResult();
                    taskAwaiter = default(TaskAwaiter<string>);
                    this.<>s__3 = result;
                    this.<result>5__2 = this.<>s__3;
                    this.<>s__3 = null;
                    result2 = this.<result>5__2;
                }
                catch (Exception exception)
                {
                    this.<>1__state = -2;
                    this.<>t__builder.SetException(exception);
                    return;
                }
                this.<>1__state = -2;
                this.<>t__builder.SetResult(result2);
         }

    [DebuggerHidden]
    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }
}

2015/10/01

使用Material Theme來變更App主題與風格 Theme / Style

在Android 5.0之後,提出了 Material Theme的設計指引,提供了每個開發者有個統一規範可以遵循來設計每個App,透過這個指引,我們可以透過 Theme / Style 的XML定義檔案,不需要修正任何C#程式碼,就可以直接將您的App風格樣貌做個更換。

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

不過,Material Theme這樣的設計方式,只存在於 Android 5.0 (API 21)或者更新版本的作業系統環境上才能夠運行,若您想要使用這樣的方式來設計您的App,並且希望讓您的App可以在比Android 5.0更舊的版本上來運行,並且享受到同樣的好處,您需要透過 v7 Support Libraries的支援,它提供了 material 設計樣式的主題 theme,可以用來配置不同的UI控制項,並且可以讓您客製化不同的顏色配置。
在看到如何使用之前,我們先看看有哪些項目是我們可以透過 Theme / Style的方式來進行替換不同的風格,在下圖中,我們可以看到不同的屬性,例如:colorPrimary, colorPrimaryDark等等,透過定義這些屬性到不同的顏色色碼,我們就可以立即將您的App更換到不同的風格樣貌。

首先,我們需要在 Resources/values目錄內styles.xml,加入一些定義,如下所示。
在 styles.xml檔案內定義我們App要用的基礎主題 MyTheme.Base,這個主題內容是從 Theme.AppCompat.Light.DarkActionBar繼承而來;  Theme.AppCompat.Light.DarkActionBar 這個主題,是在 Meterial Theme內預設定義的。  我們在自行定義的基礎主題 MyTheme.Base 中,修改了許多顏色配置,這些顏色配置將會存在於 Resources/values/colors.xml內,  也就是說,我們要變更App的顏色風格配置,只需要修改Resources/values/colors.xml內的顏色色碼即可。
想要參考更詳盡的 系統提供的 Theme / Style ,可以參考 Android API開發指南的Styles / Themes部分內容
R.Style的參考文件中,其實並不是一個很好與方便查詢各 Style和屬性的地方,因為在其官方文件上,這部分並沒有寫得相當清楚;如果您想要更進一步、完整的了解與參考這些Android內的 Style 和 Theme,可以參考底下兩個連結,在這兩個連結中,完整的列出了系統內提供的各個 Style & Theme 與其屬性定義名稱,透過這樣的查詢,您可以廓中您的App成為更多采多姿的程式。
Android Styles (styles.xml)
Android Themes (themes.xml)
在上圖中所所標示出來的 colorPrimary, colorPrimaryDark, textColorPrimary, windowbackground, navigationbarcolor 這些屬性,就可以透過地下的方式來進行擴充,若想要自訂其他的項目,可以參考與查詢上述兩個 Android Style / Android Themes 這兩份文件來做到。
  <style name="MyTheme" parent="MyTheme.Base">
  </style>

  <!--
  定義我們App要用的基礎主題 MyTheme.Base,這個主題內容是從 Theme.AppCompat.Light.DarkActionBar繼承而來
  Theme.AppCompat.Light.DarkActionBar 這個主題,是在 Meterial Theme內預設定義的
  我們在自行定義的基礎主題 MyTheme.Base 中,修改了許多顏色配置,這些顏色配置將會存在於 Resources/values/colors.xml內
  也就是說,我們要變更App的顏色風格配置,只需要修改Resources/values/colors.xml內的顏色色碼即可
  -->
  <!-- Base theme applied no matter what API -->
  <style name="MyTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="windowNoTitle">true</item>
    <!--We will be using the toolbar so no need to show ActionBar-->
    <item name="windowActionBar">false</item>
    <!-- Set theme colors from http://www.google.com/design/spec/style/color.html#color-color-palette-->
    <!-- colorPrimary is used for the default action bar background -->
    <item name="colorPrimary">@color/primary</item>
    <!-- colorPrimaryDark is used for the status bar -->
    <item name="colorPrimaryDark">@color/primaryDark</item>
    <!-- colorAccent is used as the default value for colorControlActivated which is used to tint widgets -->
    <item name="colorAccent">@color/accent</item>    
    <!-- You can also set colorControlNormal, colorControlActivated colorControlHighlight and colorSwitchThumbNormal. -->
    <!-- Option to hide the drop shadow here    
      <item name="actionBarStyle">@style/MyTheme.ActionBar</item>
      <item name="android:windowContentOverlay">@null</item>
    -->
  </style>

  <style name="MyTheme.ActionBar" parent="style/Widget.AppCompat.Light.ActionBar.Solid">
    <!-- remove shadow below action bar -->
    <!-- <item name="android:elevation">0dp</item> -->
    <!-- Support library compatibility -->
    <item name="elevation">0dp</item>
  </style>
</resources>

底下為自行定義擴充的顏色定義,上面的Xml定義中,標示著黃底紫色的文字的屬性,其顏色代碼,就是參考底下這個XML定義。
<?xml version="1.0" encoding="utf-8"?>
<!--專案範本預設的樣式-->
<!--Get colors from: http://www.materialpalette.com/-->
<!--<resources>
  <color name="primary">#03A9F4</color>
  <color name="primaryDark">#0288D1</color>
  <color name="accent">#FFC107</color>
  <color name="lightPrimary">#B3E5FC</color>
  <color name="textIcon">#FFFFFF</color>
  <color name="primaryText">#212121</color>
  <color name="secondaryText">#727272</color>
  <color name="divider">#B6B6B6</color>
  <color name="activated_color">#E8E8E8</color>
</resources>-->

<!--自訂顏色-->
<resources>
  <!--Get colors from: http://www.materialpalette.com/-->
  <color name="primary">#4CAF50</color>
  <color name="primaryDark">#388E3C</color>
  <color name="accent">#FFEB3B</color>
  <color name="lightPrimary">#C8E6C9</color>
  <color name="textIcon">#FFFFFF</color>
  <color name="primaryText">#212121</color>
  <color name="secondaryText">#727272</color>
  <color name="divider">#B6B6B6</color>
  <color name="activated_color">#E8E8E8</color>
</resources>

上面的 Colors.xml 檔案,我們定義了兩種情境顏色配置,下面螢幕截圖則是執行結果;下圖左邊是 [專案範本預設的樣式]定義,下圖右邊則是[自訂顏色]的執行結果

image image

image image

當然,我們也有方便的視覺快速設定方式,您可以透過 material palette 網頁,得到底下的設定

image





image