XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2016/08/31

Xamarin XAML 靜態與動態資源的使用

XAML 靜態與動態資源的使用

在這份筆記,將會記錄下 XAML 樣式 Style 的各種應用,這包括了
  • 資源字典 Resource Dictionary 的使用
  • x:Static (取得公開靜態欄位值)
  • StaticResource (參考靜態樣式資源)
  • DynamicResource (參考動態樣式與資源)

參考專案

.NET 靜態屬性

這個專案內,定義了一個類別 PubStat,裡面有三個靜態屬性,將會在 XAML 內,使用 x:Static 來存取。

PubStat.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace DynaStat
{
    public class PubStat
    {
        public static Color 文字顏色 { get; set; } = Color.Red;
        public static Color 背景顏色 { get; set; } = Color.Yellow;
        public static double 文字尺寸 { get; set; } = 20;
    }
}

全域樣式 App.XAML

這個專案,定義了全域資源,定義了兩個顏色資源與一個 Double 資源。
而關於 Double 資源,您不能夠直接使用 .NET double 關鍵字,需要使用 XAML 標記延伸功能 x:Double

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="DynaStat.App">
  <Application.Resources>

    <ResourceDictionary>
      <Color x:Key="GlobalbackgroundColor">#ccffcc</Color>
      <Color x:Key="GlobaltextColor">#ff6600</Color>
      <x:Double x:Key="GlobalfontSize">24</x:Double>
    </ResourceDictionary>
  </Application.Resources>
</Application>

啟動頁面 MainPage.xaml

在這個頁面,定義了 ContentPage.Resources 的資源字典,裡面定義了頁面內使用的三個資源。
若要在頁面內的控制項目 (Control Element)引用資源字典內的資源項目,可以使用 StaticResource 或者DynamicResource
當套用 StaticResource 的屬性,在整個應用程式執行時期,出現的樣式都是一樣的
當套用 DynamicResource 的屬性,只要在 code-behind 內有變更這個資源屬性值,當時套用這個動態資源的屬性,也會立即變更
若您要設定的控制項屬性值,是要參考 .NET 靜態屬性值,可以使用 x:Static 標記擴充功能,來參考 .NET 的靜態屬性值。不過,當您在 code-behind 內變更了這些 .NET 靜態屬性值,當時的頁面或者控制項是無法立即變更已經修正的屬性值,必須要重新建立一個新頁面之後,才會看到醉心繫結的 .NET 靜態屬性值。

MainPage.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:DynaStat"
             x:Class="DynaStat.MainPage">

  <ContentPage.Resources>
    <ResourceDictionary>
      <Color x:Key="backgroundColor">#ccffcc</Color>
      <Color x:Key="textColor">#ff6600</Color>
      <x:Double x:Key="fontSize">24</x:Double>
    </ResourceDictionary>
  </ContentPage.Resources>

  <StackLayout
    Orientation="Vertical"
    HorizontalOptions="Center"
    VerticalOptions="Center"
  >
    <Label Text="使用 StaticResource"
           VerticalOptions="Center"
           HorizontalOptions="Center"
           TextColor="{StaticResource textColor}"
           BackgroundColor="{StaticResource backgroundColor}"
           FontSize="{StaticResource fontSize}"
         />
    <Label Text="使用 DynamicResource"
           VerticalOptions="Center"
           HorizontalOptions="Center"
           TextColor="{DynamicResource textColor}"
           BackgroundColor="{DynamicResource backgroundColor}"
           FontSize="{DynamicResource fontSize}"
         />
    <Button x:Name="btnDynamic" Text="變更動態資源"/>
    <Label Text="使用全域 DynamicResource"
           VerticalOptions="Center"
           HorizontalOptions="Center"
           TextColor="{DynamicResource GlobaltextColor}"
           BackgroundColor="{DynamicResource GlobalbackgroundColor}"
           FontSize="{DynamicResource GlobalfontSize}"
         />
    <Button x:Name="btnGlobalDynamic" Text="變更全域動態資源"/>
    <Label Text="使用 公開靜態屬性"
           VerticalOptions="Center"
           HorizontalOptions="Center"
           TextColor="{x:Static local:PubStat.文字顏色}"
           BackgroundColor="{x:Static local:PubStat.背景顏色}"
           FontSize="{x:Static local:PubStat.文字尺寸}"
         />
    <Button x:Name="btnStatic" Text="變更公開靜態屬性"/>
    <Button x:Name="btnReset" Text="重新啟動該頁面"/>
  </StackLayout>

</ContentPage>

執行結果

以上說明可以從底下的執行操作更加清楚了解。
當應用程式執行的時候,會顯示如下畫面
您可以看到,不論使用靜態資源或者動態資源,所看到的樣貌都是一樣的。
靜態與動態資源
當按下了 變更動態資源 按鈕,會出現底下畫面
在這個按鈕處理事件內,使用頁面的 Resources 屬性值,修正了頁面內的動態資源內容,因此,頁面上立即會將有使用 DynamicResource 定義的屬性,反映出最新的內容。
            btnDynamic.Clicked += (s, e) =>
              {
                  this.Resources["backgroundColor"] = Color.White;
                  this.Resources["textColor"] = Color.Black;
                  this.Resources["fontSize"] = 30;
                  btnDynamic.Text = $"變更動態資源 " + this.Resources.Count.ToString();
              };
靜態與動態資源
當按下了 變更全域動態資源 按鈕,會出現底下畫面
在這個按鈕處理事件內,使用頁面的 App.Current.Resources 屬性,修正了整個應用程式全域的動態資源內容,因此,頁面上立即會將有使用 DynamicResource 定義的屬性,反映出最新的內容。
            btnGlobalDynamic.Clicked += (s, e) =>
              {
                  App.Current.Resources["GlobalbackgroundColor"] = Color.White;
                  App.Current.Resources["GlobaltextColor"] = Color.Black;
                  App.Current.Resources["GlobalfontSize"] = 30;
                  btnGlobalDynamic.Text = $"變更全域動態資源 "+ App.Current.Resources.Count.ToString();
              };
靜態與動態資源
不過,當您按下了按鈕 變更公開靜態屬性 按鈕,並且變更了 .NET 靜態屬性內容,會發現到頁面並沒有做出任何變化或者更新。
            btnStatic.Clicked += (s, e) =>
              {
                  PubStat.文字顏色 = Color.Blue;
                  PubStat.背景顏色 = Color.Pink;
                  PubStat.文字尺寸 = 30;
              };
靜態與動態資源
現在,請按下按鈕 重新啟動該頁面,使用這個 C# 敘述 App.Current.MainPage = new MainPage();,重新定義應用程式的起始頁面。
因為重新產生了一個新的 MainPage 物件執行個體,此時,在這個新產生的執行個體,使用了 x:Static 參考到的 .NET 靜態屬性,將會是剛剛變更後的內容,也就是會產生另外一個樣式結果。
            btnReset.Clicked += (s, e) =>
              {
                  App.Current.MainPage = new MainPage();
              };
靜態與動態資源

Xamarin XAML 的 樣式 style 應用

XAML 的 樣式 style 應用

在這份筆記,將會記錄下 XAML 樣式 Style 的各種應用,這包括了
  • 資源字典 Resource Dictionary 的使用
  • 隱含樣式 (Implicit Styles)的用法
  • 明確樣式 (Explicit Styles)的用法
  • 繼承樣式
  • 合併樣式與問題
  • 使用 OnPlatfom 定義不同平台的樣式
  • 在控制項內使用 Style 套用樣式
XamlStyle1
XamlStyle

參考專案

資源字典 Resource Dictionary

在 XAML 裡面,使用資源字典可以定義各種不同的 styles, control templates, data templates, colors, and converters
您可以在 Application, Page, View 等等項目內,指定資源字典內容;
  • 若您在 Application 類別內指定定義的資源字典項目,則這些項目是可以再全部的應用程式中使用,不過,那會有例外,那就是資源字典的合併機制。
  • 若您在 Page 這類類別中指定定義的資源字典項目,則這些項目是可以在這個頁面中來使用
  • 若您在某個 檢視控制項 View 或者 版面配置 Layout 中 指定定義的資源字典項目,則這些項目是可以在這個項目與其子項目內使用

全域樣式

只要在 App.xaml 檔案內使用 ResourceDictionary 定義的樣式,都可以在應用程式內使用,在這個範例專案內,App.xaml 檔案內容如下所示:
在這個 App.xaml 檔案內,定義了一個 PinkLabel 的樣式,另外,也使用了 OnPlatform 功能,依據當時執行的不同平台,定義了一個顏色與 Double 的資源值;接著,定義了一個 MyTitleLabel 樣式,其中這個樣式的 TextColor & FontSize 這兩個屬性值,是由剛剛定義的資源值來取代。也就是說,當應用程式有套用了 MyTitleLabel 樣式的頁面,在不同的行動平台下執行( Android, iOS, UWP),這個樣式所表現出來的視覺效果是不一樣的。
以上資源定義都可以在整個應用程式內使用,不過,在 ResourceDictionary 項目 (Element) 有使用一個屬性 MergedWith,其可以把其他定義的資源合併到這個 ResourceDictionary 下來使用。
不過,當在 App.xaml 下使用了 MergedWith 屬性,僅能夠在這個 App.xaml 下有效,但是整個應用程式是看不到使用 MergedWith 內合併進來的資源定義;關於 MergedWith 的應用,可以在頁面上來說明,就會更加清楚。

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"
             xmlns:themes="clr-namespace:MergedDict.Themes"
             x:Class="MergedDict.App">
  <Application.Resources>
    <ResourceDictionary MergedWith="themes:RedTheme">
      <!--在合併內定義的預設 Label 樣式,將無法在頁面中使用;
      需要在頁面使用 MergedWith 引用合併樣式,才能夠使用
      若要定義全域樣式,需要類似底下方式宣告(合併無法成為預設樣式)
      -->
      <Style x:Key="PinkLabel" TargetType="Label">
        <Setter Property="TextColor" Value="Pink" />
        <Setter Property="FontSize" Value="24" />
      </Style>

      <!--底下說明,如何根據不同平台特性,設定不同的樣式內容-->
      <OnPlatform x:TypeArguments="Color" Android="Lime" iOS="Olive" WinPhone="Fuchsia" x:Key="ByPlatformColor" />
      <OnPlatform x:TypeArguments="x:Double" Android="30" iOS="15" WinPhone="50" x:Key="ByPlatformSize" />
      <Style x:Key="MyTitleLabel" TargetType="Label" BaseResourceKey="PinkLabel">
        <Setter Property="TextColor" Value="{StaticResource ByPlatformColor}" />
        <Setter Property="FontSize" Value="{StaticResource ByPlatformSize}" />
      </Style>
    </ResourceDictionary>

    <!-- Application resource dictionary -->

  </Application.Resources>
</Application>

獨立定義的資源字典

在這個專案內的資源夾 Themes 內,產生了三個獨立資源字典資源定義檔案 BlueTheme.xaml,GreenTheme.xamlRedTheme.xaml。這三個檔案分別定義了
  • 一個隱含樣式 (Implicit Styles),定義了所有 Label 檢視的文字顏色,字體大小為 24。所謂的隱含樣式,就是這個應用程式內所有的 Label 檢視,都會使用這個隱含樣式的屬性設定來呈現。
  • 一個明確樣式 (Explicit Styles),其代表名稱定義在 x:Key 內,這個明確樣式定義了字體顏色與字體大小為24。明確樣式則是當 Label 檢視有定義需要使用這個樣式,其檢視才會使用這個樣式定義的屬性來呈現。

BlueTheme.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MergedDict.Themes.BlueTheme">
  <Style TargetType="Label">
    <Setter Property="TextColor" Value="Blue" />
    <Setter Property="FontSize" Value="24" />
  </Style>
  <Style x:Key="BlueLabel" TargetType="Label">
    <Setter Property="TextColor" Value="Blue" />
    <Setter Property="FontSize" Value="24" />
  </Style>
</ResourceDictionary>

GreenTheme.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MergedDict.Themes.GreenTheme">
  <Style TargetType="Label">
    <Setter Property="TextColor" Value="Green" />
    <Setter Property="FontSize" Value="24" />
  </Style>
  <Style x:Key="GreenLabel" TargetType="Label">
    <Setter Property="TextColor" Value="Green" />
    <Setter Property="FontSize" Value="24" />
  </Style>
</ResourceDictionary>

RedTheme.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MergedDict.Themes.RedTheme">
  <Style TargetType="Label">
    <Setter Property="TextColor" Value="Red" />
    <Setter Property="FontSize" Value="24" />
  </Style>
  <Style x:Key="RedLabel" TargetType="Label">
    <Setter Property="TextColor" Value="Red" />
    <Setter Property="FontSize" Value="24" />
  </Style>
</ResourceDictionary>

頁面樣式 MainPage

這個頁面是應用程式一啟動的時候,會顯示的頁面。
這個頁面的根項目為 ContentPage,並且有定義資源字典;其分別定義了一個明確樣式 GrayLabel,不過,在這個明確樣式的所有定義內容,是根據另外一個明確樣式 BlueLabel 繼承而來。
而明確樣式 LabelBlue,其為在 ResourceDictionary 內,使用了屬性 MergedWith 合併了themes:BlueTheme 資源字典內容。
因為這裡合併了 themes:BlueTheme 資源字典,所以,在這個頁面下的所有子項目,都可以使用themes:BlueTheme 資源字典內定義的所有樣式,例如,這裡使用了 BlueLabel 樣式。
在 ContentPage 內的 ResourceDictionary 內,有個註解說明,若您將 <Style TargetType="Label">...</Style> 內容註解解除,那麼,Label 的隱含樣式將會使用原來 Xamarin.Forms 內建的樣式,並不會採用剛剛合併進來的隱含樣式定義,也就是說,若要覆蓋隱含樣式定義,合併進來的隱含樣式定義,不會影響這次定義。
在這個 XAML 範例中,展示了:隱含樣式、明確樣式、繼承樣式、合併樣式的使用方式。
若想要讓某個控制項套用明確樣式,可以使用 Style 屬性值來指定要套用的明確樣式。
最後,定義了一個按鈕,將會顯示另外一個頁面 Main2Page

MainPage.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:MergedDict"
             xmlns:themes="clr-namespace:MergedDict.Themes"
             Title="有資源字典合併"
             x:Class="MergedDict.MainPage">

  <ContentPage.Resources>
    <!--在這個頁面,會合併其他樣式資源-->
    <ResourceDictionary MergedWith="themes:BlueTheme">
      <!--解除底下註解,將會導致預設 Label 的樣式為 Xamarin.Forms 內建,
      不會從 全域樣式中繼承而來
      -->
      <!--<Style TargetType="Label">
        <Setter Property="TextColor" Value="Purple" />
      </Style>-->

      <!--這個樣式,將會從 BlueLabel 繼承而來,而該樣式,則是合併而來-->
      <Style x:Key="GrayLabel" TargetType="Label"
             BasedOn="{StaticResource BlueLabel}">
        <Setter Property="TextColor" Value="Gray" />
      </Style>
    </ResourceDictionary>
  </ContentPage.Resources>

  <StackLayout
    VerticalOptions="Center" 
    HorizontalOptions="Center">

    <!--加入其他 GreenLabel 樣式定義
    若把底下 XAML 註解解除,則預設的 Label 樣式,會變成 Green
    -->
    <!--<StackLayout.Resources>
      <ResourceDictionary MergedWith="themes:GreenTheme">
      </ResourceDictionary>
    </StackLayout.Resources>
    <Label Text="使用 頁面GreenLabel 樣式"
             VerticalOptions="Center"
             HorizontalOptions="Center"
             Style="{StaticResource GreenLabel}" />-->

    <!--這裡 Label 的樣式,將會由頁面合併樣式中定義-->
    <Label Text="沒有設定任何 樣式"
             VerticalOptions="Center"
             HorizontalOptions="Center" />
    <Label Text="使用 全域PinkLabel 樣式"
             VerticalOptions="Center"
             HorizontalOptions="Center"
             Style="{StaticResource PinkLabel}" />
    <Label Text="使用 頁面BlueLabel 樣式"
             VerticalOptions="Center"
             HorizontalOptions="Center"
             Style="{StaticResource BlueLabel}" />
    <!--底下 Label 定義會產生錯誤,這是因為 RedLabel 
    是在全域使用 合併方式進來的-->
    <!--<Label Text="使用 頁面RedLabel 樣式"
             VerticalOptions="Center"
             HorizontalOptions="Center"
             Style="{StaticResource RedLabel}" />-->
    <Label Text="使用 GrayLabel 樣式(繼承 BlueLabel)"
           VerticalOptions="Center"
           HorizontalOptions="Center"
           HorizontalTextAlignment="Center"
           Style="{StaticResource GrayLabel}"/>
    <Label Text="使用 RedLabel 樣式(自已引用資源)"
           VerticalOptions="Center"
           HorizontalOptions="Center"
           HorizontalTextAlignment="Center"
           Style="{StaticResource RedLabel}">
      <Label.Resources>
        <ResourceDictionary MergedWith="themes:RedTheme">
        </ResourceDictionary>
      </Label.Resources>
    </Label>
    <Label Text="沒有設定任何 樣式(自已引用資源)"
           VerticalOptions="Center"
           HorizontalOptions="Center"
           HorizontalTextAlignment="Center"
           Style="{StaticResource RedLabel}">
      <Label.Resources>
        <ResourceDictionary MergedWith="themes:RedTheme">
        </ResourceDictionary>
      </Label.Resources>
    </Label>
    <Label Text="使用不同平台特性定義樣式"
           VerticalOptions="Center"
           HorizontalOptions="Center"
           HorizontalTextAlignment="Center"
           Style="{StaticResource MyTitleLabel}"/>

    <Button x:Name="btn" Text="下一頁" />

  </StackLayout>

</ContentPage>

頁面樣式 Main2Page

當顯示了這個 Main2Page 頁面,所有在剛剛 MainPage 頁面中的定義樣式,將不會影響到這個 Main2Page 頁面;在 Main2Page 頁面,只有定義在 App.xaml 內的全域的樣式,才可以在這個 Main2Page頁面內使用。
在 ContentPage 項目內的 ResourceDictionary 有註解一段 XAML 樣式定義,若您解除這段樣式註解,將會在執行時其發生例外異常錯誤,會產生這樣的錯誤,是因為明確樣式 RedLabel 在這裡無法被參考到 (Xamarin.Forms.Xaml.XamlParseException: Position 16:14. StaticResource not found for key RedLabel),就算參考了 BlueLabel 樣式,也是會一樣發生例外異常錯誤。

Main2Page

<?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:themes="clr-namespace:MergedDict.Themes"
             Title="沒有頁面資源字典"
             x:Class="MergedDict.Main2Page">

  <ContentPage.Resources>
    <!--在這個頁面,將不會合併其他樣式資源-->
    <ResourceDictionary >
      <!--在底下定義,RedLabel會找不到,雖然在 App.xaml 中也合併該樣式,
      不過,合併的全域樣式,無法在頁面中引用

      解除底下註解,執行時候,將會產生例外異常
      Xamarin.Forms.Xaml.XamlParseException: Position 16:14. StaticResource not found for key RedLabel
      -->
      <!--<Style x:Key="GrayLabel" TargetType="Label"
             BasedOn="{StaticResource RedLabel}">
        <Setter Property="TextColor" Value="Gray" />
      </Style>-->
    </ResourceDictionary>
  </ContentPage.Resources>

  <StackLayout
    VerticalOptions="Center"
    HorizontalOptions="Center">
    <!--這裡 Label 的樣式,將會由全域合併樣式中定義-->
    <Label Text="沒有設定任何 樣式"
             VerticalOptions="Center"
             HorizontalOptions="Center" />
    <Label Text="使用 全域PinkLabel 樣式"
             VerticalOptions="Center"
             HorizontalOptions="Center"
             Style="{StaticResource PinkLabel}" />
    <!--
    在全域 App.xaml 中,合併進來的明確樣式,無法在其他頁面中使用
    若解除底下 Label 註解,執行期間,將會發生例外異常
    -->
    <!--<Label Text="使用 全域合併RedLabel 樣式"
             VerticalOptions="Center"
             HorizontalOptions="Center"
             Style="{StaticResource RedLabel}" />-->
    <Label Text="使用 RedLabel 樣式(自已引用資源)"
           VerticalOptions="Center"
           HorizontalOptions="Center"
           HorizontalTextAlignment="Center"
           Style="{StaticResource RedLabel}">
      <Label.Resources>
        <ResourceDictionary MergedWith="themes:RedTheme">
        </ResourceDictionary>
      </Label.Resources>
    </Label>
    <Label Text="沒有設定任何 樣式(自已引用資源)"
           VerticalOptions="Center"
           HorizontalOptions="Center"
           HorizontalTextAlignment="Center"
           Style="{StaticResource RedLabel}">
      <Label.Resources>
        <ResourceDictionary MergedWith="themes:RedTheme">
        </ResourceDictionary>
      </Label.Resources>
    </Label>

  </StackLayout>

</ContentPage>