Xamarin.Forms 觸發程序

Download Sample 下載範例

觸發程序可讓您用 XAML 以宣告方式表達動作,根據事件或屬性變更改變控制項的外觀。 此外,狀態觸發程序是一組特殊化觸發程序,其定義何時應套用 VisualState

您可以將觸發程序直接指派給控制項,或將它新增至頁面層級或應用程式層級的資源字典,以套用至多個控制項。

屬性觸發程序

簡單的觸發程序完全以 XAML 表達,新增 Trigger 項目至控制項的觸發程序集合。 此範例示範在 Entry 收到焦點時,變更其背景色彩的觸發程序:

<Entry Placeholder="enter name">
    <Entry.Triggers>
        <Trigger TargetType="Entry"
                 Property="IsFocused" Value="True">
            <Setter Property="BackgroundColor" Value="Yellow" />
            <!-- multiple Setters elements are allowed -->
        </Trigger>
    </Entry.Triggers>
</Entry>

觸發程序宣告的重要部分包括:

  • TargetType - 觸發程序適用的控制項類型。

  • Property - 控制項上受監視的屬性。

  • Value - 針對受監視的屬性發生時,導致啟動觸發程序的值。

  • Setter - 符合觸發程序條件時可以新增 Setter 項目的集合。 您必須指定要設定的 PropertyValue

  • EnterActions 和 ExitActions (未顯示) - 以程式碼撰寫,且可以和 Setter 項目一起使用,或是用來代替它。 其描述如下

使用樣式套用觸發程序

觸發程序也可以新增至控制項的 Style 宣告、頁面上或應用程式 ResourceDictionary。 此範例宣告隱含的樣式 (亦即,未設定 Key),這表示其將適用於頁面上的所有 Entry 控制項。

<ContentPage.Resources>
    <ResourceDictionary>
        <Style TargetType="Entry">
                        <Style.Triggers>
                <Trigger TargetType="Entry"
                         Property="IsFocused" Value="True">
                    <Setter Property="BackgroundColor" Value="Yellow" />
                    <!-- multiple Setters elements are allowed -->
                </Trigger>
            </Style.Triggers>
        </Style>
    </ResourceDictionary>
</ContentPage.Resources>

資料觸發程序

資料觸發程序使用資料繫結來監視另一個控制項,導致呼叫 Setter。 請設定 Binding 屬性來監視指定的值,而不是屬性觸發程序中的 Property 屬性。

下列範例使用資料繫結語法 {Binding Source={x:Reference entry}, Path=Text.Length} 這就是我們參考另一個控件屬性的方式。 當 entry 的長度為零時,會啟動觸發程序。 在此範例中,觸發程序會在輸入為空時停用按鈕。

<!-- the x:Name is referenced below in DataTrigger-->
<!-- tip: make sure to set the Text="" (or some other default) -->
<Entry x:Name="entry"
       Text=""
       Placeholder="required field" />

<Button x:Name="button" Text="Save"
        FontSize="Large"
        HorizontalOptions="Center">
    <Button.Triggers>
        <DataTrigger TargetType="Button"
                     Binding="{Binding Source={x:Reference entry},
                                       Path=Text.Length}"
                     Value="0">
            <Setter Property="IsEnabled" Value="False" />
            <!-- multiple Setters elements are allowed -->
        </DataTrigger>
    </Button.Triggers>
</Button>

提示

評估 Path=Text.Length 時一律會提供目標屬性的預設值 (例如 Text=""),否則會是 null ,而且觸發程式無法如您所預期般運作。

除了指定 Setter 之外,您也可以提供 EnterActionsExitActions

事件觸發程式

EventTrigger 項目只需要 Event 屬性,例如下列範例中的 "Clicked"

<EventTrigger Event="Clicked">
    <local:NumericValidationTriggerAction />
</EventTrigger>

請注意,沒有 Setter 項目,但是有 local:NumericValidationTriggerAction 所定義類別的參考,它需要在頁面的 XAML 中宣告 xmlns:local

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:WorkingWithTriggers;assembly=WorkingWithTriggers"

類別本身會實作 TriggerAction,這表示每次觸發程序事件發生時,它應該提供所呼叫 Invoke 方法的覆寫。

觸發程序動作實作應該:

  • 實作泛型 TriggerAction<T> 類別,且泛型參數對應觸發程序將套用至的控制項類型。 您可以使用 Superclass,例如 VisualElement,來撰寫使用各種控制項的觸發程序動作,或指定如 Entry 控制項類型。

  • 覆寫 Invoke 方法 - 每當符合觸發程序準則時便會呼叫它。

  • 選擇性地公開可在宣告觸發程序時,於 XAML 中設定的屬性。 如需相關範例,請參閱隨附範例應用程式中的 VisualElementPopTriggerAction 類別。

public class NumericValidationTriggerAction : TriggerAction<Entry>
{
    protected override void Invoke (Entry entry)
    {
        double result;
        bool isValid = Double.TryParse (entry.Text, out result);
        entry.TextColor = isValid ? Color.Default : Color.Red;
    }
}

您可以從 XAML 取用事件觸發程序:

<EventTrigger Event="TextChanged">
    <local:NumericValidationTriggerAction />
</EventTrigger>

ResourceDictionary 中共用觸發程序時請小心,將會在控制項之間共用一個執行個體,因此設定一次的任何狀態將會適用於全部。

請注意,事件觸發程序不支援 EnterActionsExitActions如下所述

多重觸發程序

MultiTrigger 看起來類似於 TriggerDataTrigger,但它可以有多個條件。 所有條件必須都為 true 才會觸發 Setter

以下是繫結至兩個不同輸入 (emailphone) 的按鈕觸發程序範例:

<MultiTrigger TargetType="Button">
    <MultiTrigger.Conditions>
        <BindingCondition Binding="{Binding Source={x:Reference email},
                                   Path=Text.Length}"
                               Value="0" />
        <BindingCondition Binding="{Binding Source={x:Reference phone},
                                   Path=Text.Length}"
                               Value="0" />
    </MultiTrigger.Conditions>
    <Setter Property="IsEnabled" Value="False" />
    <!-- multiple Setter elements are allowed -->
</MultiTrigger>

Conditions 集合可能也會包含 PropertyCondition 項目,如下所示:

<PropertyCondition Property="Text" Value="OK" />

建置「需要全部」的多重觸發程序

多重觸發程序只有在所有條件都成立時,只會更新其控制項。 測試「所有欄位長度都是零」(例如必須完成所有輸入的登入頁面),是棘手的,因為您想要條件“其中 Text.Length > 0”,但無法在 XAML 中表示。

這可以使用 IValueConverter 完成。 以下的轉換器程式碼會將 Text.Length 繫結轉換成 bool,指出欄位是否為空白:

public class MultiTriggerConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        if ((int)value > 0) // length > 0 ?
            return true;            // some data has been entered
        else
            return false;            // input is empty
    }

    public object ConvertBack(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        throw new NotSupportedException ();
    }
}

若要在多重觸發程序中使用這個轉換器,先將其新增至頁面的資源字典 (附上自訂的 xmlns:local 命名空間定義):

<ResourceDictionary>
   <local:MultiTriggerConverter x:Key="dataHasBeenEntered" />
</ResourceDictionary>

XAML 如下所示。 請注意第一個多重觸發程序範例的下列差異:

  • 按鈕預設已設定 IsEnabled="false"
  • 多重觸發程序條件使用轉換器,將 Text.Length 值變為 boolean
  • 所有條件都為 true 時,setter 會讓按鈕的 IsEnabled 屬性成為 true
<Entry x:Name="user" Text="" Placeholder="user name" />

<Entry x:Name="pwd" Text="" Placeholder="password" />

<Button x:Name="loginButton" Text="Login"
        FontSize="Large"
        HorizontalOptions="Center"
        IsEnabled="false">
  <Button.Triggers>
    <MultiTrigger TargetType="Button">
      <MultiTrigger.Conditions>
        <BindingCondition Binding="{Binding Source={x:Reference user},
                              Path=Text.Length,
                              Converter={StaticResource dataHasBeenEntered}}"
                          Value="true" />
        <BindingCondition Binding="{Binding Source={x:Reference pwd},
                              Path=Text.Length,
                              Converter={StaticResource dataHasBeenEntered}}"
                          Value="true" />
      </MultiTrigger.Conditions>
      <Setter Property="IsEnabled" Value="True" />
    </MultiTrigger>
  </Button.Triggers>
</Button>

這些螢幕擷取畫面顯示上述兩個多重觸發程序範例之間的差異。 在畫面頂端的文字輸入,只需要有一個 Entry 即可啟用 [儲存] 按鈕。 在畫面底部,[登入] 按鈕保持非使用中,直到兩個欄位都包含資料為止。

MultiTrigger Examples

EnterActions 和 ExitActions

另一種在觸發程序發生時實作變更的方法,是藉由新增 EnterActionsExitActions 集合,並指定 TriggerAction<T> 實作。

EnterActions 集合可用來定義符合觸發條件時所叫用 TriggerAction 物件的 IListExitActions 集合可用來定義觸發條件不再符合之後所叫用 TriggerAction 物件的 IList

注意

EventTrigger 類別會忽略 EnterActionsExitActions 集合中定義的 TriggerAction 物件。

您可以「同時」在觸發程序中提供 EnterActionsExitActions,以及 Setter;但請注意,Setter 會立即呼叫 (其不會等待 EnterActionExitAction 完成)。 或者,您可以在程式碼中執行一切,完全不使用 Setter

<Entry Placeholder="enter job title">
    <Entry.Triggers>
        <Trigger TargetType="Entry"
                 Property="Entry.IsFocused" Value="True">
            <Trigger.EnterActions>
                <local:FadeTriggerAction StartsFrom="0" />
            </Trigger.EnterActions>

            <Trigger.ExitActions>
                <local:FadeTriggerAction StartsFrom="1" />
            </Trigger.ExitActions>
            <!-- You can use both Enter/Exit and Setter together if required -->
        </Trigger>
    </Entry.Triggers>
</Entry>

如往常,在 XAML 中參考類別時,應該宣告命名空間,例如此處所示的 xmlns:local

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:WorkingWithTriggers;assembly=WorkingWithTriggers"

FadeTriggerAction 程式碼如下所示:

public class FadeTriggerAction : TriggerAction<VisualElement>
{
    public int StartsFrom { set; get; }

    protected override void Invoke(VisualElement sender)
    {
        sender.Animate("FadeTriggerAction", new Animation((d) =>
        {
            var val = StartsFrom == 1 ? d : 1 - d;
            // so i was aiming for a different color, but then i liked the pink :)
            sender.BackgroundColor = Color.FromRgb(1, val, 1);
        }),
        length: 1000, // milliseconds
        easing: Easing.Linear);
    }
}

狀態觸發程序

狀態觸發程式是一組特殊的觸發程式,可定義應套用 的條件 VisualState

狀態觸發程序會新增至 VisualStateStateTriggers 集合。 此集合可以包含單一狀態觸發程序或多個狀態觸發程序。 當集合中的任何狀態觸發程序為作用中時,將會套用 VisualState

使用狀態觸發程式來控制視覺狀態時, Xamarin.Forms 會使用下列優先順序規則來判斷哪一個觸發程式 (以及對應的 VisualState) 為作用中:

  1. 衍生自 StateTriggerBase 的任何觸發程序。
  2. 因為符合 MinWindowWidth 條件,所以已啟用 AdaptiveTrigger
  3. 因為符合 MinWindowHeight 條件,所以已啟用 AdaptiveTrigger

如果多個觸發程序同時處於作用中狀態 (例如,兩個自訂觸發程序),則會優先使用標記中宣告的第一個觸發程序。

注意

狀態觸發程序可以在 Style 中設定,或直接在元素上設定。

如需視覺狀態的詳細資訊,請參閱 Xamarin.Forms Visual State Manager

狀態觸發程序

衍生自 StateTriggerBase 類別的 StateTrigger 類別具有 IsActive 可繫結屬性。 當 IsActive 屬性變更值時,StateTrigger 會觸發 VisualState 變更。

StateTriggerBase 類別 (所有狀態觸發程序的基底類別) 具有 IsActive 屬性與 IsActiveChanged 事件。 每當發生 VisualState 變更時,就會引發此事件。 此外,類別 StateTriggerBase 具有可 OnAttached 覆寫的 和 OnDetached 方法。

重要

StateTrigger.IsActive 可繫結屬性會隱藏繼承的 StateTriggerBase.IsActive 屬性。

下列 XAML 範例顯示包含 StateTrigger 物件的 Style

<Style TargetType="Grid">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup>
                <VisualState x:Name="Checked">
                    <VisualState.StateTriggers>
                        <StateTrigger IsActive="{Binding IsToggled}"
                                      IsActiveChanged="OnCheckedStateIsActiveChanged" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="Black" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Unchecked">
                    <VisualState.StateTriggers>
                        <StateTrigger IsActive="{Binding IsToggled, Converter={StaticResource inverseBooleanConverter}}"
                                      IsActiveChanged="OnUncheckedStateIsActiveChanged" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="White" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

在此範例中,隱含的 StyleGrid 物件為目標。 當繫結物件的 IsToggled 屬性為 true 時,Grid 的背景色彩會設定為黑色。 當繫結物件的 IsToggled 屬性變成 false 時,就會觸發 VisualState 變更,而且 Grid 的背景色彩會變成白色。

此外,每次發生 VisualState 變更時,就會引發 VisualStateIsActiveChanged 事件。 每個 VisualState 都會註冊此事件的事件處理常式:

void OnCheckedStateIsActiveChanged(object sender, EventArgs e)
{
    StateTriggerBase stateTrigger = sender as StateTriggerBase;
    Console.WriteLine($"Checked state active: {stateTrigger.IsActive}");
}

void OnUncheckedStateIsActiveChanged(object sender, EventArgs e)
{
    StateTriggerBase stateTrigger = sender as StateTriggerBase;
    Console.WriteLine($"Unchecked state active: {stateTrigger.IsActive}");
}

在此範例中,當引發 IsActiveChanged 事件的處理常式時,處理常式會輸出 VisualState 是否為作用中。 例如,從 Checked 視覺效果狀態變更為 Unchecked 視覺效果狀態時,下列訊息會輸出至主控台視窗:

Checked state active: False
Unchecked state active: True

注意

自定義狀態觸發程式可以藉由衍生自 StateTriggerBase 類別來建立,並覆寫 OnAttachedOnDetached 方法來執行任何必要的註冊和清除。

調適型觸發程序

當視窗具有指定的高度或寬度時,AdaptiveTrigger 會觸發 VisualState 變更。 此觸發程序有兩個可繫結的屬性:

注意

AdaptiveTrigger 衍生自 StateTriggerBase 類別,因此可將事件處理常式附加至 IsActiveChanged 事件。

下列 XAML 範例顯示包含 AdaptiveTrigger 物件的 Style

<Style TargetType="StackLayout">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup>
                <VisualState x:Name="Vertical">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="0" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="Orientation"
                                Value="Vertical" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Horizontal">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="800" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="Orientation"
                                Value="Horizontal" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

在此範例中,隱含的 StyleStackLayout 物件為目標。 當視窗寬度介於 0 到 800 裝置獨立單位之間時,套用 StyleStackLayout 物件將會有垂直方向。 當視窗寬度 >= 800 個裝置獨立單位時, VisualState 就會觸發變更,而 StackLayout 方向會變更為水準:

Vertical StackLayout VisualStateHorizontal StackLayout VisualState

MinWindowHeightMinWindowWidth 屬性可以單獨使用,也可以相互結合使用。 下列 XAML 顯示設定這兩個屬性的範例:

<AdaptiveTrigger MinWindowWidth="800"
                 MinWindowHeight="1200"/>

在這裡範例中,AdaptiveTrigger表示當目前的視窗寬度為 >= 800 個裝置獨立單位,而目前的視窗高度>為 = 1200 個裝置獨立單位時,就會套用對應的 VisualState

比較狀態觸發程序

CompareStateTrigger 會在屬性等於特定值時,觸發 VisualState 變更。 此觸發程序有兩個可繫結的屬性:

  • Property (object 型別),指出將由觸發程序比較的屬性。
  • Value (object 型別),指出應套用 VisualState 的值。

注意

CompareStateTrigger 衍生自 StateTriggerBase 類別,因此可將事件處理常式附加至 IsActiveChanged 事件。

下列 XAML 範例顯示包含 CompareStateTrigger 物件的 Style

<Style TargetType="Grid">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup>
                <VisualState x:Name="Checked">
                    <VisualState.StateTriggers>
                        <CompareStateTrigger Property="{Binding Source={x:Reference checkBox}, Path=IsChecked}"
                                             Value="True" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="Black" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Unchecked">
                    <VisualState.StateTriggers>
                        <CompareStateTrigger Property="{Binding Source={x:Reference checkBox}, Path=IsChecked}"
                                             Value="False" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="White" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>
...
<Grid>
    <Frame BackgroundColor="White"
           CornerRadius="12"
           Margin="24"
           HorizontalOptions="Center"
           VerticalOptions="Center">
        <StackLayout Orientation="Horizontal">
            <CheckBox x:Name="checkBox"
                      VerticalOptions="Center" />
            <Label Text="Check the CheckBox to modify the Grid background color."
                   VerticalOptions="Center" />
        </StackLayout>
    </Frame>
</Grid>

在此範例中,隱含的 StyleGrid 物件為目標。 當 CheckBoxIsChecked 屬性為 false 時,Grid 的背景色彩會設定為白色。 當 CheckBox.IsChecked 屬性變成 true 時,就會觸發 VisualState 變更,而 Grid 的背景色彩會變為黑色:

Screenshot of a triggered visual state change, on iOS and Android, with trigger unchecked.Screenshot of a triggered visual state change, on iOS and Android, with trigger checked.

裝置狀態觸發程序

DeviceStateTrigger 會根據應用程式執行所在的裝置平台,觸發 VisualState 變更。 此觸發程序具有單一可繫結屬性:

注意

DeviceStateTrigger 衍生自 StateTriggerBase 類別,因此可將事件處理常式附加至 IsActiveChanged 事件。

下列 XAML 範例顯示包含 DeviceStateTrigger 物件的 Style

<Style x:Key="DeviceStateTriggerPageStyle"
       TargetType="ContentPage">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup>
                <VisualState x:Name="iOS">
                    <VisualState.StateTriggers>
                        <DeviceStateTrigger Device="iOS" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="Silver" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Android">
                    <VisualState.StateTriggers>
                        <DeviceStateTrigger Device="Android" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="#2196F3" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="UWP">
                    <VisualState.StateTriggers>
                        <DeviceStateTrigger Device="UWP" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="Aquamarine" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

在此範例中,明確的 StyleContentPage 物件為目標。 取用樣式的 ContentPage 物件,在 iOS 上會將其背景色彩設定為銀色、在 Android 上設定為淺藍色,而在 UWP 上設定為青綠色。 下列螢幕擷取畫面顯示在 iOS 與 Android 上產生的頁面:

Screenshot of a triggered visual state change, on iOS and Android

方向狀態觸發程序

當裝置的方向變更時,OrientationStateTrigger 會觸發 VisualState 變更。 此觸發程序具有單一可繫結屬性:

注意

OrientationStateTrigger 衍生自 StateTriggerBase 類別,因此可將事件處理常式附加至 IsActiveChanged 事件。

下列 XAML 範例顯示包含 OrientationStateTrigger 物件的 Style

<Style x:Key="OrientationStateTriggerPageStyle"
       TargetType="ContentPage">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup>
                <VisualState x:Name="Portrait">
                    <VisualState.StateTriggers>
                        <OrientationStateTrigger Orientation="Portrait" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="Silver" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Landscape">
                    <VisualState.StateTriggers>
                        <OrientationStateTrigger Orientation="Landscape" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="White" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

在此範例中,明確的 StyleContentPage 物件為目標。 取用樣式的 ContentPage 物件,會在方向為直向時將其背景色彩設定為銀色,並在方向為橫向時將其背景色彩設定為白色。