XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2017/05/06

Xamarin.UITest 在 Android 裝置上的實作練習

這篇筆記,將會帶領大家實際進行 Xamarin.UITest 的實際實機演練,您只需要跟著這個文件中的說明步驟操作,就會了解到 Xamarin.UITest 是要如何使用。

事前準備工作

首先,您先要完成您的 Xamarin.Forms 跨平台應用程式開發,並且這個 App 確實已經可以執行且正確無誤。
我們這個實機練習過程,將會以 Android 裝置作為 UI 自動測試的演練平台。
您可以使用這個 GitHub Repository 中的 https://github.com/vulcanlee/UITest/tree/master/XFUITest_Source 作為要測試的應用程式對象。
這個應用程式僅有兩個頁面,第一個頁面將會是使用者登入頁面,在這個頁面中,使用者必須輸入正確的帳號與密碼,否則,將會有錯誤訊息顯示出來。
若帳號與密碼輸入正確,此時,就會導航到這個應用程式的首頁
在每個重點過程中,將會把當時螢幕的畫面截圖下來,儲存到測試結果中。

建立 Xamarin.UITest 專案

首先,打開 XFUITest_Source 目錄下的 XFUITest.sln 專案
在 XFUITest 方案滑鼠右擊,選擇 加入 > 新增專案 > Visual C# > Cross-Platfom > UI 測試應用程式 (Xamarin.UITest | 跨平台) 選項。
在底下名稱欄位中,可以輸入您想要的專案名稱,在這裡,我們使用預設值
最後點選 確定 按鈕,完成這個專案的產生。

產生 Android 專案的佈署 APK 檔案

請滑鼠右擊 XFUITest.Droid 專案,選擇項目 設定為起始專案
接著,選擇方案組態為 Release
滑鼠右擊 XFUITest.Droid 專案,選擇項目 建置
確定這個專案可以成功建置後,滑鼠右擊 XFUITest.Droid 專案,選擇項目 封存...
當封存成功之後,在 封存管理員 的右下方,會看到 散發 按鈕,請點選他
當 散發 對話窗出現之後,請點選 臨機操作 按鈕
選擇這個 App 要使用的 簽署身分識別,若您還沒有建立您的專屬簽署身分識別,請建立一個。最後,點選 另存新檔 按鈕
選擇一個適當目錄之後,點選 存檔 按鈕
在 簽署密碼 對話窗出現之後,請輸入您的 簽署身分識別 密碼,之後點選 OK 按鈕。
若再 Visual Studio 左下角的狀態列中,出現了 已完成發行專案 'XFUITest.Droid 訊息,恭喜您,表示您已經產生了這個 Android 發行 APK 檔案了。

開啟測試總管

請點選功能表 測試 > 視窗 > 測試總管,顯示出測試總管的視窗

修正 AppInitializer.cs

請在 UITest1 專案內,找到 AppInitializer.cs 檔案,打開它
使用底下程式碼替換到 StartApp 方法的定義
這裡使用了 ApkFile 方法,指定了這個 Android 專案的 vulcanlab.xfuitest.apk 發行檔案所要的路徑
另外,由於我們需要在不同的時間點,進行當時手機畫面的螢幕截圖,所以,我們需要呼叫 EnableLocalScreenshots() 方法
        public static IApp StartApp(Platform platform)
        {
            if (platform == Platform.Android)
            {
                return ConfigureApp
                    .Android
                    .ApkFile("../../../XFUITest/XFUITest.Droid/bin/Release/com.vulcanlab.xfuitest.apk")
                    .EnableLocalScreenshots()
                    .StartApp();
            }

            return ConfigureApp
                .iOS
                .StartApp();
        }

加入這次做的 UI 自動測試腳本

請打開 Tests.cs 檔案,使用底下程式碼來替換。
我們將 [TestFixture(Platform.iOS)] 這個屬性註解起來
我們加入了一個新的測試腳本 LoginTest
    [TestFixture(Platform.Android)]
    //[TestFixture(Platform.iOS)]
    public class Tests
    {
        IApp app;
        Platform platform;

        public Tests(Platform platform)
        {
            this.platform = platform;
        }

        [SetUp]
        public void BeforeEachTest()
        {
            app = AppInitializer.StartApp(platform);
        }

        [Test]
        public void AppLaunches()
        {
            app.Screenshot("登入頁面");
        }

        [Test]
        public void LoginTest()
        {
            app.Screenshot("登入頁面");
            app.EnterText(x => x.Class("EntryEditText"), "vulcan");
            app.Tap(x => x.Class("EntryEditText").Index(1));
            app.EnterText(x => x.Class("EntryEditText").Index(1), "123");
            app.Tap(x => x.Text("登入"));
            app.WaitForElement(x => x.Id("message"));
            app.Screenshot("登入錯誤頁面");
            app.Tap(x => x.Id("button2"));
            app.WaitFor(() =>
            {
                Thread.Sleep(1000);
                return true;
            });
            app.Tap(x => x.Class("EntryEditText"));
            app.EnterText(x => x.Class("EntryEditText"), "1");
            app.Tap(x => x.Class("EntryEditText").Index(1));
            app.EnterText(x => x.Class("EntryEditText").Index(1), "1");
            app.Tap(x => x.Text("登入"));
            app.WaitFor(() =>
            {
                Thread.Sleep(4000);
                return true;
            });
            app.WaitForElement(x => x.Text("歡迎來到 Xamarin.Forms 的跨平台開發世界"));
            app.Screenshot("登入成功頁面");
        }
    }

開始進行 UI 自動測試

您現在的測試總管視窗,應該會如同下圖,沒有任何的測試方法可以選擇。
請滑鼠右擊 UITest1 專案,選擇 重建
當建置完成後,您現在的測試總管視窗,應該會如同下圖,有測試方法可以選擇。
滑鼠右擊 LoginTest 項目,接著選取 執行選取的測試 項目,此時,Visual Studio 將會開始進行自動化的 UI 測試,所有的過程,將會依照您所設定的腳本來執行。
等候一下,您的模擬器就會自動啟動這個應用程式,並且開始輸入錯誤的密碼,接著進行登入,而後顯示錯誤訊息;稍後,就會輸入正確的帳號與密碼,並且切換到應用程式首頁。
當UI自動測試作業完成之後,您會看到測試總管如下圖的畫面。
在測試總管的最下方,有個 輸出 連結,請點選它。
您會看到如下的測試紀錄日誌
所以,當您的UI自動測試有任何錯誤發生的時候,可以參考這個測試紀錄日誌,試圖找出可能的問題原因。

測試名稱:     LoginTest
測試結果:     成功
結果 StandardOutput:     Full log file: C:\Users\vulca\AppData\Local\Temp\uitest\log-2017-05-06_00-08-53-984.txt
Skipping IDE integration as important properties are configured. To force IDE integration, add .PreferIdeSettings() to ConfigureApp.
Android test running Xamarin.UITest version: 1.3.8
Initializing Android app on device 169.254.76.233:5555 with apk: D:\Vulcan\GitHub\temp\XFUITest_Source\XFUITest\XFUITest.Droid\bin\Release\com.vulcanlab.xfuitest.apk
Signing apk with Xamarin keystore.
Took screenshot. { Path: "D:\Vulcan\GitHub\temp\XFUITest_Source\UITest1\bin\Release\screenshot-1.png", Title: "登入頁面" }
Using first element (2 total) matching Class("EntryEditText").
Tapping coordinates [ 540, 736 ].
Using element matching Class("EntryEditText").Index(1).
Tapping coordinates [ 540, 938 ].
Using element matching Class("EntryEditText").Index(1).
Tapping coordinates [ 540, 938 ].
Using element matching Text("登入").
Tapping coordinates [ 540, 1183 ].
Waiting for element matching Id("message").
Took screenshot. { Path: "D:\Vulcan\GitHub\temp\XFUITest_Source\UITest1\bin\Release\screenshot-2.png", Title: "登入錯誤頁面" }
Using element matching Id("button2").
Tapping coordinates [ 893, 1051 ].
Using first element (2 total) matching Class("EntryEditText").
Tapping coordinates [ 540, 736 ].
Using first element (2 total) matching Class("EntryEditText").
Tapping coordinates [ 540, 736 ].
Using element matching Class("EntryEditText").Index(1).
Tapping coordinates [ 540, 938 ].
Using element matching Class("EntryEditText").Index(1).
Tapping coordinates [ 540, 938 ].
Using element matching Text("登入").
Tapping coordinates [ 540, 1183 ].
Waiting for element matching Text("歡迎來到 Xamarin.Forms 的跨平台開發世界"). 
Took screenshot. { Path: "D:\Vulcan\GitHub\temp\XFUITest_Source\UITest1\bin\Release\screenshot-3.png", Title: "登入成功頁面" }

2017/05/05

Xamarin 企業級行動化開發平台環境建置攻略 - Visual Studio 2017 安裝與設定

這份影片將會帶領大家,從無到有的進行 Visual Studio 2017 + Xamarin
開發環境的安裝與相關設定


ListView 的顯示紀錄客製化與互動應用

我們使用 Xamarin.Forms 進行對於 ListView 的使用,很多人都會覺得有些好奇,究竟相關 ListView 上的不同操作與應用,該如何撰寫 View / ViewModel 的 XAML / C# 程式碼呢?
我撰寫的一個範例專案(原始碼位於 https://github.com/vulcanlee/xamarin-forms-develop-notes-example/tree/master/XFListShowImg ),用來說明底下需求:
  1. 在Listview新增判斷邏輯,假如是值True,呈現某張圖,若是False,則不秀圖出來
  2. 當使用者點選某個紀錄上的 BoxView 控制項的時候,若該圖片沒有顯示出來,則需要修正,經圖片顯示出來。
這是一個上過課的學員提問的問題,我使用底下範例專案做出說明。

ListView 的 XAML

由於,我們需要將集合資料使用 ListView 顯示出來,當然,我們需要孰悉 ListView 的各種屬性的操作;在這裡,我們需要使用 ListView 的這些屬性
  • ItemsSource : 將會綁訂到 ListView 上的 ObservableCollection<T> 的 C# 屬性,只要我們有增/修/刪 這個 ObservableCollection<T> 的物件內容,ListView 則會自動進行更新。
  • SelectedItem : 當使用者點選某個紀錄項目的時候,當時點選的紀錄項目,將會透過資料綁定方法,把當時點選的紀錄物件值,綁定到 ViewModel 上的指定屬性物件上。
  • HasUnevenRows : 這個屬性用來指定,每個紀錄可以不用具有相同的高度。

每筆紀錄的顯示內容 ViewCell

要設計每筆紀錄在 ListView 上要呈現甚麼樣貌,我們就需要使用 ViewCell 來宣告;在底下,列出了這個 ViewCell 的完整定義。
我們使用了 Grid 將 ViewCell 切割成為 2 Rows X 2 Columns,並且將 Label / BoxView / Image 這三個控制項,使用Grid附加屬性,指定這些控制項是要位於 Grid 的哪個格子上。
在這些控制項上,要顯示的內容或者顏色,將會透過資料綁定的方式,指定到當時紀錄的ViewModel 中的屬性上,在這裡要特別注意到,此時,ViewCell 的 BindingContext 物件,不是整個 ListView 要顯示的集合物件,而是,要顯示的那筆紀錄的物件。
關於圖片在某些情境(在這裡是依據 ViewModel 內的某個屬性值),不需要顯示出來;這樣的需求有很多種作法,也需要是開發環境的各種條件而定,在這裡,我們使用了 IsVisible="{Binding ShowImage}" 這樣的方式來處理,只要該記錄在 ViewModel 內的 ShowImage 的值為 False 的布林值,他就不會顯示在螢幕上,反之,就會顯示在螢幕上。
                    <ViewCell>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="*"/>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="100"/>
                            </Grid.ColumnDefinitions>

                            <Label 
                                Grid.Row="0" Grid.Column="0"
                                Text="{Binding Number}"/>
                            <BoxView
                                Grid.Row="1" Grid.Column="0"
                                Color="{Binding BoxColor}">
                            </BoxView>
                            <Image
                                Grid.Row="0" Grid.Column="1"
                                Grid.RowSpan="2"
                                HorizontalOptions="Fill" VerticalOptions="Fill"
                                Aspect="AspectFill"
                                Source="{Binding ImageUrl}"
                                IsVisible="{Binding ShowImage}"/>
                        </Grid>
                    </ViewCell>

如何指定 ViewCell 內的命令,要綁定到 ViewModel 中的命令物件

我們這裡有個需求,那就是,使用者可以點選 BoxView 這個控制項,接著,我們需要判斷這個紀錄的圖片是否沒有顯示出來,若沒有,我們須將這個圖片切換成為可以顯示的模式。
我們想要將這個手勢操作命令定義在頁面用的 ViewModel 上,而不是在每筆紀錄的 ViewModel 上。
為了解決這個問題,我們需要在設定綁定命令的時候,變更當時的 BindingContext 的來源,在這裡,我們使用了底下方法:
我們設定了 BoxView.GestureRecognizers 的屬性,加入了 TapGestureRecognizer 這個物件;不過,當要使用 TapGestureRecognizer 的 Command 屬性的時候,我們使用了 Source 這個屬性,指定此次綁定的 BindingContext 的來源,是這個頁面的 ViewModel,接著,我們使用了 Path 這個屬性,指定了要綁定的命令路徑。
其中,x:Reference ThisPage 的 ThisPage 指的就是這個頁面,因為,我們在頁面上,設定了 x:Name="ThisPage" 這個延伸標記屬性設定。
                            <BoxView
                                Grid.Row="1" Grid.Column="0"
                                Color="{Binding BoxColor}">
                                <BoxView.GestureRecognizers>
                                    <TapGestureRecognizer 
                                        Command="{Binding Path=BindingContext.TapBoxCommand, Source={x:Reference ThisPage}}"
                                        CommandParameter="{Binding}"/>
                                </BoxView.GestureRecognizers>
                            </BoxView>

ViewModel 內的程式碼

在這裡,我們使用了 PropertyChanged.Fody 這個套件,因此,簡化了整個 ViewModel 的程式碼數量,關於要綁定到 View 中的各個屬性,其定義如下。
在這裡,要綁定到 ListView 上的集合資料,需要使用 ObservableCollection<MyItem> 這個型別,而命令的部分,我們使用了 Prism 提供的 DelegateCommand<MyItem> 的命令型別,這個泛型命令型別,可以讓我們接收到,來自 XAML 的 CommandParameter 屬性所綁定的內容,在這個應用範例中,CommandParameter 將會綁定當時所顯示的紀錄物件。
若您不想使用 DelegateCommand<MyItem> 泛型命令型別,您也可以使用 DelegateCommand 這個型別,不過,要取得使用者當時所點選的物件,您可以使用 SelectedMyItem 這個物件來取得當時點選的紀錄物件。
        public MyItem SelectedMyItem { get; set; }
        public ObservableCollection<MyItem> MyItems { get; set; } = new ObservableCollection<MyItem>();

        public DelegateCommand<MyItem> TapBoxCommand { get; set; }
當使用點選某筆紀錄後,將會執行底下程式碼:
            TapBoxCommand = new DelegateCommand<MyItem>(x =>
              {
                  if(x.ShowImage == false)
                  {
                      x.ShowImage = true;
                      x.BoxColor = Color.BlueViolet;
                  }
              });

執行結果的畫面

2017/05/01

Xamarin.Forms 使用 Prism 框架開發,如何修改 View 的名稱

之前上課的時候,有學員提問,若想要修改 View 的名稱,是不是只要修改 ViewName.xaml 這個檔案名稱就好了;不過,答案可不是如此。
在這個練習中,我們使用 Prism Template Pack 專案樣板,建立一個 Xamarin.Forms 的專案。在這個專案內,預設會幫我們產生一個 MainPage.xaml / MainPageViewModel 兩個檔案,分別是 View / ViewModel;若我們想要把這個頁面,修改成為 HomePage 的話,有哪些地方需要注意的,以及該如何做呢?

將 View 的名稱改名

首先,可以在 Visual Studio 的 核心PCL 專案內的 Views 資料夾內,找到 MainPage.xaml 檔案,滑鼠右擊這個項目,選擇 重新命名,接著輸入 HomePage
接著,打開這個 HomePage.xaml 檔案,找到 ContentPage 這個根節點。
<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="PrismUnityApp2.Views.MainPage"
             Title="MainPage">
將 x:Class="PrismUnityApp2.Views.MainPage" 修改成為 x:Class="PrismUnityApp2.Views.HomePage",並且按下 Ctrl+S 將此次修改的內容存檔,而 ContentPage 的定義將會成為
<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="PrismUnityApp2.Views.HomePage"
             Title="MainPage">
在 核心PCL專案內的 Views 資料夾內,打開 HomePage.xaml.cs 檔案。
將 MainPage 類別,修正成為 HomePage,如下所示:
按下 Ctrl + S 將此次修改的內容存檔。
除了類別名稱之外,建構式名稱也需要一併修正。
    public partial class HomePage : ContentPage
    {
        public HomePage()
        {
            InitializeComponent();
        }
    }

修正 App.xaml.cs 的導航頁面與注入定義

在 核心PCL 專案內,找到並打開 App.xaml.cs 檔案,在 OnInitialized 方法內,呼叫 NavigationService.NavigateAsync 內的字串引數,把 MainPage 內容改成 HomePage
在 RegisterTypes 方法內,將 Container.RegisterTypeForNavigation<MainPage>(); 修改成為 Container.RegisterTypeForNavigation<HomePage>();
最後按下 Ctrl + S 存檔
    public partial class App : PrismApplication
    {
        public App(IPlatformInitializer initializer = null) : base(initializer) { }

        protected override void OnInitialized()
        {
            InitializeComponent();

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

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

將 ViewModel 的名稱改名

首先,可以在 Visual Studio 的 核心PCL 專案內的 ViewModels 資料夾內,找到 MainPageViewModel 檔案,滑鼠右擊這個項目,選擇 重新命名,接著輸入 HomePageViewModel
此時,Visual Studio 會提示您:
您在正重新命名檔案,您是否也要更新命名此專案中對於程式碼項目 'MainPageViewModel' 的所有參考?
請在這個對話窗中,點選 是(Y)

建置與執行

最後,請將 Android 專案設定為預設起始專案,並且執行這個專案。
若您的操作步驟都是正確的話,這個 App 是可以正常執行的。