2017/3/9

Xamarin.Forms 教學系列文(八.壹)XAML 與 C# 完美配合


學習目標
  • 從 XAML 使用參數與呼叫方法 來建構物件
  • x:Name - Visual Elements 相對於 C# 的 ID
  • 客製化 XAML View,可重複利用的 View

在建立 XAML 時都會附帶一支 .cs 檔案,除了將關注點分離外,也讓我們在寫 XAML 時擁有更多的彈性,能夠利用 .cs 去控制 XAML 的物件。

這也是我們稱呼為 code-behind 的作法。

此章節會比較分散,每一段落講解的東西都較獨立,基本上就是了解一下 XAML 和 cs 之間如何互相運作。

先來看一下如何在 XAML 類 C#,達到 建構物件 的目的。


XAML 內建構 C# 物件

當你要建構 Color 物件時,會需要帶入 RGB 三個參數值,或是使用 FromHsla() 等方法來回傳 Color 物件。

這件事情通常是在 .cs 內做,但我們能不能在 XAML 內建構 Color 物件?

2009 XAML 規範提供了兩個屬性
  • x:Arguments
  • x:FactoryMethod

x:Arguments 使 XAML 擁有傳遞參數的功能,而 x:FactoryMethod 則使 XAML 擁有呼叫 C# 方法的功能,要注意的是 x:FactoryMethod 呼叫的方法必須回傳物件。

看程式會比較懂我在說啥...
<BoxView WidthRequest="100" 
         HeightRequest="100"                  
         HorizontalOptions="Center"                  
         VerticalOptions="CenterAndExpand">             
         <BoxView.Color>                 
             <Color>                     
                 <x:Arguments>                         
                     <x:Double>0</x:Double>                         
                     <x:Double>0</x:Double>                         
                     <x:Double>1</x:Double>                         
                     <x:Double>0.5</x:Double>                     
                 </x:Arguments>                 
             </Color>             
         </BoxView.Color>         
</BoxView>
如上,就能在 BoxView.Color 底下利用 x:Arguments 傳遞 RGBA 四個值,並建構 Color 物件。

而 XAML 提供以下的 Arguments 類別可以使用:
  • x : Object
  • x : Boolean
  • x : Byte
  • x : Int16
  • x : Int32
  • x : Int64
  • x : Single
  • x : Double
  • x : Decimal
  • x : Char
  • x : String
  • x : TimeSpan
  • x : Array
  • x : DateTime(supported by Xamarin.Forms but not the XAML 2009 specification)

接著來看要如何用 x:FactoryMethod 和 FromHsla() 來建構 Color:
<BoxView.Color>                 
    <Color x:FactoryMethod="FromHsla">                     
        <x:Arguments>                         
            <x:Double>0.67</x:Double>                         
            <x:Double>1.0</x:Double>                         
            <x:Double>0.5</x:Double>                         
            <x:Double>1.0</x:Double>                     
        </x:Arguments>                 
    </Color>             
</BoxView.Color>


x:Name - XAML 物件的ID

前面的章節有提到 Walking the tree 方法,當我們知道且確定 XAML 架構時,能夠從 Root 往下層追蹤到我們要修改的 Visual Elements。

但更好的方法是,我們直接幫 Visual Elements 取個唯一的名字,而這個屬性就是 x:Name

來看範例,
底下的 XAML 有兩個 Label,分別給予 x:Name 的值為 timeLabel dateLabel
<Label x:Name="timeLabel"                
       FontSize="Large"                
       HorizontalOptions="Center"                
       VerticalOptions="EndAndExpand" /> 
 
<Label x:Name="dateLabel" 
       HorizontalOptions="Center" 
       VerticalOptions="StartAndExpand" />

再來看一下 .cs:
namespace XamlClock 
{     
    public partial class XamlClockPage     
    {         
        public XamlClockPage()         
        {             
            InitializeComponent(); 
 
            Device.StartTimer(TimeSpan.FromSeconds(1), OnTimerTick);         
        } 
 
        bool OnTimerTick()         
        {             
            DateTime dt = DateTime.Now;             
            timeLabel.Text = dt.ToString("T");             
            dateLabel.Text = dt.ToString("D");             
            return true;         
        }  
    }   
} 
可以看到 .cs 內能直接將有設定 x:Name 的 Visual Element,視為可用的物件。
原文書 p163 頁有講解 Xamarin.Forms 是如何辦到的,有興趣可以看一下。
注意:同一個 XAML 內不能有重複的 x:Name


客製化 XAML Views - 重複使用

若今天要如下圖顯示多筆顏色時,標籤程式全部寫在同一支 XAML 內程式會變得很醜且不好維護。

而 XAML 又沒有 for 或是 while 等商業邏輯面的程式可以使用...



這邊提供一個客製化 View 的做法,讓程式碼看起來更加精簡。

首先,我們先建立一個 ContentView 的 XAML 如ㄒ:
namespace 為 ColorViewList,class 為 ColorView
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"              
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"              
    x:Class="ColorViewList.ColorView">
 
    <Frame OutlineColor="Accent">         
     <StackLayout Orientation="Horizontal">             
      <BoxView x:Name="boxView" WidthRequest="70" HeightRequest="70" />
             <StackLayout>                 
              <Label x:Name="colorNameLabel"                        
                  FontSize="Large"                        
                  VerticalOptions="CenterAndExpand" /> 
                  
              <Label x:Name="colorValueLabel"                        
                  VerticalOptions="CenterAndExpand" />             
                </StackLayout>         
        </StackLayout>     
    </Frame> 
</ContentView>

接著 .cs 為:
public partial class ColorView : ContentView 
{     
 string colorName;     
 ColorTypeConverter colorTypeConv = new ColorTypeConverter(); 
 
    public ColorView()     
    {         
     InitializeComponent();     
    }
 
    public string ColorName     
    {         
     set         
     {             
      // Set the name.            
      colorName = value;             
      colorNameLabel.Text = value;
 
                             // Get the actual Color and set the other views.             
                             Color color = (Color)colorTypeConv.ConvertFrom(colorName);             
                             boxView.Color = color;             
                             colorValueLabel.Text = String.Format("{0:X2}-{1:X2}-{2:X2}",(                                                
                                               (int)(255 * color.R),                                                   
                                               (int)(255 * color.G),                                                   
                                               (int)(255 * color.B)));         
              }  
        
              get         
              {             
                  return colorName;         
              }
    } 
}
可以看程式內有個 string ColorName 的屬性,當這個屬性被指派值時,會去設定 XAML 上boxView 的 Color 和 colorValueLabel 的 Text。

這樣我們客製化的 View 就建立好了。

使用方式如下:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:ColorViewList" 
             x:Class="ColorViewList.ColorViewListPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" 
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <ScrollView>
        <StackLayout Padding="6, 0">
            <local:ColorView ColorName="Aqua" />
            <local:ColorView ColorName="Black" />
            <local:ColorView ColorName="Blue" />
            <local:ColorView ColorName="Fuchsia" />
            <local:ColorView ColorName="Gray" />
            <local:ColorView ColorName="Green" />
            <local:ColorView ColorName="Lime" />
            <local:ColorView ColorName="Maroon" />
            <local:ColorView ColorName="Navy" />
            <local:ColorView ColorName="Olive" />
            <local:ColorView ColorName="Purple" />
            <local:ColorView ColorName="Pink" />
            <local:ColorView ColorName="Red" />
            <local:ColorView ColorName="Silver" />
            <local:ColorView ColorName="Teal" />
            <local:ColorView ColorName="White" />
            <local:ColorView ColorName="Yellow" />
        </StackLayout>
    </ScrollView>
</ContentPage>
這裡最需要注意的是 ContentPage 內多出的那一行,
xmlns:local="clr-namespace:ColorViewList"

一定要先將 local 指派為 ColorViewList namespace,才能在 XAML 內使用同一層 namespace 底下的 class - ColorView


為什麼是 local?

這邊的 local 是一個 變數名稱 的概念,你要取其他名字也可以,舉例來說,ContentPage 內我先設定 xmlns:logan="clr-namespace:ColorViewList"

使用時就要寫 <logan:ColorView ColorName="White" />
一般來說使用 local ,不過命名爽就好了



6 則留言:

  1. Color color = (Color)colorTypeConv.ConvertFrom(colorName);

    又是過時的XD....

    而且我作完會跳出例外錯誤

    Unhandled Exception:

    Xamarin.Forms.Xaml.XamlParseException:

    回覆刪除
    回覆
    1. 晚點我再試試喔~ 畢竟這邊的code都是從第一版原文書上節錄下來的
      Xamarin都不知道更新到幾版了Orz

      刪除
    2. 好的,謝謝

      這真的更新太快 連書都跟不上

      網路上其他教學也是...

      刪除
    3. 我找到了 改用ConvertFromInvariantString("T")
      _(:3」∠)_

      刪除
  2. colorValueLabel.Text = String.Format("{0:X2}-{1:X2}-{2:X2}",(
    (int)(255 * color.R),
    (int)(255 * color.G),
    (int)(255 * color.B));

    似乎少了一個右括弧~雖然不太重要XD

    回覆刪除

注意:只有此網誌的成員可以留言。