視覺狀態

.NET 多平台應用程式介面(.NET MAUI)視覺狀態管理器提供一種結構化的方式,讓使用者能從程式碼中對使用者介面進行視覺化變更。 在大多數情況下,應用程式的使用者介面是以 XAML 定義的,而這個 XAML 可以包含描述視覺狀態管理器如何影響使用者介面視覺效果的標記。

視覺狀態管理器引入 了視覺狀態的概念。 像 Button 這類.NET MAUI視圖,根據其底層狀態(是否關閉、按下或有輸入焦點)可以呈現多種不同的視覺呈現。 這些就是按鈕的狀態。 視覺狀態會被收集到視覺 狀態群組中。 視覺狀態群中的所有視覺狀態互斥。 視覺狀態與視覺狀態群組皆以簡單的文字字串識別。

.NET MAUI 視覺狀態管理器定義了一個名為 CommonStates 的視覺狀態群組,具有以下視覺狀態:

  • 正常
  • Disabled
  • 焦點
  • 已選取
  • PointerOver

Normal 衍生出來的所有類別皆支援 DisabledFocusedPointerOverVisualElement 視覺狀態,而 NormalPage 和 的基底類別。 此外,你也可以定義自己的視覺狀態群組和視覺狀態。

使用視覺狀態管理器來定義外觀,而非直接從程式碼背後存取視覺元素的優點是,你可以完全控制視覺元素對不同狀態的反應,讓所有 UI 設計集中在同一個地方。

備註

觸發器也能根據視圖屬性的變更或事件觸發,對使用者介面中的視覺進行變更。 然而,使用觸發條件來處理這些變化的各種組合可能會變得令人困惑。 使用視覺狀態管理器時,視覺狀態群組中的視覺狀態總是互斥的。 在任何時候,每個群組中只有一個狀態是當前狀態。

常見的視覺狀態

Visual State Manager 允許你在 XAML 檔案中加入標記,若視圖為正常、停用、有輸入焦點、已選擇,或滑鼠游標懸停在畫面上方但未按壓,都能改變視覺外觀。 這些被稱為 共同狀態

舉例來說,假設你頁面上有一個 Entry 視圖,你希望它的 Entry 視覺外觀以以下方式改變:

  • Entry關閉時Entry應該會有粉紅色背景。
  • Entry通常應該有石灰色背景。
  • 當有輸入焦點時,Entry 應擴展至正常高度的兩倍。
  • Entry當滑鼠游標懸停在上面但未按時,應該會呈現淺藍色背景。

你可以將視覺狀態管理器的標記附加到單一視圖,或者如果它適用於多個視圖,也可以以樣式來定義。

定義視圖上的視覺狀態

這個 VisualStateManager 類別定義了一個 VisualStateGroups 附加屬性,用來將視覺狀態附加到視圖上。 屬性 VisualStateGroups 的型別 VisualStateGroupList為 ,即一組 VisualStateGroup 物件。 因此,附加屬性的 VisualStateManager.VisualStateGroups 子節點是一個 VisualStateGroup 物件。 這個物件定義了一個 x:Name 屬性,指示該群組的名稱。 或者,該 VisualStateGroup 類別定義了一個 Name 屬性,你可以用它來替代其他選項。 欲了解更多附屬物業資訊,請參閱 附屬物業

VisualStateGroup 類別定義了一個名為 的屬性,這是一個物件 States的集合 VisualStateStatesVisualStateGroups 類別的內容屬性,因此您可以將 VisualState 物件包含為 VisualStateGroup 的子物件。 每個 VisualState 物件應使用 x:NameName來識別。

VisualState 類別定義了一個名為 的屬性,這是一個物件 Setters的集合 Setter 。 這些正是你在Setter物件中使用的Style物件。 Setters 不是 VisualState 屬性的內容,因此必須為 Setters 屬性加上元素標籤。 Setter 物件應作為 Setters 的子節點插入。 每個 Setter 物件在該屬性狀態為當前時,表示該屬性的價值。 物件所參考的任何 Setter 屬性都必須有可綁定的屬性作為支持。

這很重要

為了讓視覺狀態 Setter 物件正常運作,VisualStateGroup 必須包含一個 VisualState 物件以支持Normal 狀態。 如果這個視覺狀態沒有 Setter 物件,則應該將其包含為空的視覺狀態(<VisualState Name="Normal" />)。

以下範例展示了定義在 Entry 上的視覺狀態:

<Entry FontSize="18">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroupList>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="Lime" />
                    </VisualState.Setters>
                </VisualState>

                <VisualState Name="Focused">
                    <VisualState.Setters>
                        <Setter Property="FontSize" Value="36" />
                    </VisualState.Setters>
                </VisualState>

                <VisualState Name="Disabled">
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="Pink" />
                    </VisualState.Setters>
                </VisualState>

                <VisualState Name="PointerOver">
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="LightBlue" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </VisualStateManager.VisualStateGroups>
</Entry>

以下截圖顯示了 Entry 的四種定義視覺狀態:

輸入框中三種已定義視覺狀態的截圖。

Entry 進入Normal狀態時,其背景為萊姆綠。 當 Entry 獲得輸入焦點時,其字體大小將放大至兩倍。 當它 Entry 被停用時,背景會變成粉紅色。 Entry 在獲得輸入焦點時,不會保留石灰色背景。 當滑鼠指標懸停在 Entry 之上但未按下時,Entry 的背景會變成淺藍色。 當視覺狀態管理器在視覺狀態間切換時,前一個狀態所設定的屬性會被解除設定。 因此,視覺狀態是互斥的。

如果你想要 EntryFocused 狀態時具有淺綠色背景,可以在該視覺狀態中添加另一個 Setter

<VisualState Name="Focused">
    <VisualState.Setters>
        <Setter Property="FontSize" Value="36" />
        <Setter Property="BackgroundColor" Value="Lime" />
    </VisualState.Setters>
</VisualState>

定義風格中的視覺狀態

通常需要在兩個或多個視圖中共享相同的視覺狀態。 在此情境中,視覺狀態可定義為 Style。 這可以透過在Setter屬性上新增VisualStateManager.VisualStateGroups物件來達成。 物件的內容 Setter 屬性即其 Value 屬性,因此可以指定為物件的 Setter 子屬性。 VisualStateGroups屬性是VisualStateGroupList型別,因此Setter物件的子節點是VisualStateGroupList,其中可以添加包含VisualStateGroup物件的VisualState

以下範例展示了一個定義常見視覺狀態的Entry隱含風格:

<Style TargetType="Entry">
    <Setter Property="FontSize" Value="18" />
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="Lime" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState Name="Focused">
                    <VisualState.Setters>
                        <Setter Property="FontSize" Value="36" />
                        <Setter Property="BackgroundColor" Value="Lime" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState Name="Disabled">
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="Pink" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState Name="PointerOver">
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="LightBlue" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

當此樣式被納入頁面層級資源字典時,該 Style 物件會套用到頁面上的所有 Entry 物件。 因此,頁面上的所有 Entry 物體都會以相同的方式回應它們的視覺狀態。

.NET MAUI 中的視覺狀態

下表列出 .NET MAUI 中定義的視覺狀態:

Class States 其他資訊
Button Pressed 按鈕視覺狀態
CarouselView DefaultItemCurrentItemPreviousItemNextItem 旋轉檢視視覺狀態
CheckBox IsChecked 勾選框視覺狀態
CollectionView Selected CollectionView 視覺狀態
ImageButton Pressed ImageButton 的視覺狀態
RadioButton CheckedUnchecked RadioButton 視覺狀態
Switch OnOff 切換視覺狀態
VisualElement NormalDisabledFocusedPointerOver 常見狀態

多個元素的狀態設定

在前述範例中,視覺狀態附加於單一元件並進行操作。 不過,也可以建立附著於單一元素的視覺狀態,但同時設定同一範圍內其他元素的屬性。 這樣可以避免在每個操作的元素上重複視覺狀態。

Setter 型別有一個 TargetName 屬性,型別 string為 ,代表 在 Setter 視覺狀態下所操作的目標物件。 當TargetName屬性被定義時,Setter會將在Property中定義的物件的TargetName設置為Value

<Setter TargetName="label"
        Property="Label.TextColor"
        Value="Red" />

在這個例子中,一個名為LabellabelTextColor屬性會被設定為Red。 設定屬性 TargetName 時,必須在 Property 中指定該屬性的完整路徑。 因此,要在TextColorLabel屬性上設定,則將Property指定為Label.TextColor

備註

物件所參考的任何 Setter 屬性都必須有可綁定的屬性作為支持。

以下範例展示了如何從單一視覺狀態群組設定多個物件的狀態:

<StackLayout>
    <Label Text="What is the capital of France?" />
    <Entry x:Name="entry"
           Placeholder="Enter answer" />
    <Button Text="Reveal answer">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal" />
                <VisualState Name="Pressed">
                    <VisualState.Setters>
                        <Setter Property="Scale"
                                Value="0.8" />
                        <Setter TargetName="entry"
                                Property="Entry.Text"
                                Value="Paris" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    </Button>
</StackLayout>

在這個例子中,Normal是活躍的,當Button未按下時,回應可以輸入到Entry。 當按下Pressed時,狀態Button會啟動,並指定其Scale屬性將從預設值1變更為0.8。 此外,名為 Entryentry 將其屬性 Text 設定為巴黎。 因此,當按下 Button 時,它會被重新縮放成稍微縮小的尺寸,然後 Entry 顯示巴黎:

按鈕按下狀態的截圖。

然後,當 Button 被釋放時,會重新縮放到預設值 1,並且 Entry 會顯示之前輸入的文字。

這很重要

在指定Setter屬性的TargetName元素中,不支援屬性路徑。

定義自訂視覺狀態

自訂視覺狀態的實現可以藉由像定義常見狀態的視覺狀態一樣來定義,但使用您選擇的名稱。然後,呼叫 VisualStateManager.GoToState 方法來啟動狀態。

以下範例說明如何使用視覺狀態管理器進行輸入驗證:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="VsmDemos.VsmValidationPage"
             Title="VSM Validation">
    <StackLayout x:Name="stackLayout"
                 Padding="10, 10">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup Name="ValidityStates">
                    <VisualState Name="Valid">
                        <VisualState.Setters>
                            <Setter TargetName="helpLabel"
                                    Property="Label.TextColor"
                                    Value="Transparent" />
                            <Setter TargetName="entry"
                                    Property="Entry.BackgroundColor"
                                    Value="Lime" />
                        </VisualState.Setters>
                    </VisualState>
                    <VisualState Name="Invalid">
                        <VisualState.Setters>
                            <Setter TargetName="entry"
                                    Property="Entry.BackgroundColor"
                                    Value="Pink" />
                            <Setter TargetName="submitButton"
                                    Property="Button.IsEnabled"
                                    Value="False" />
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
        <Label Text="Enter a U.S. phone number:"
               FontSize="18" />
        <Entry x:Name="entry"
               Placeholder="555-555-5555"
               FontSize="18"
               Margin="30, 0, 0, 0"
               TextChanged="OnTextChanged" />
        <Label x:Name="helpLabel"
               Text="Phone number must be of the form 555-555-5555, and not begin with a 0 or 1" />
        <Button x:Name="submitButton"
                Text="Submit"
                FontSize="18"
                Margin="0, 20"
                VerticalOptions="Center"
                HorizontalOptions="Center" />
    </StackLayout>
</ContentPage>

在此範例中,視覺狀態附加於 StackLayout,且存在兩個互斥的狀態,分別命名 Valid 為 和 Invalid。 如果Entry不包含有效的電話號碼,那麼目前的狀態是Invalid,因此Entry具有粉紅色背景,第二個Label可見,且Button被停用。 當輸入有效電話號碼時,當前狀態會變 Valid為 。 Entry 的背景變成青檸色,第二個 Label 會消失,而 Button 現在已啟用:

視覺狀態驗證範例的截圖。

程式碼背後檔案負責處理 TextChanged 來自 Entry的事件。 處理器會使用正規表達式來判斷輸入字串是否有效。 程式碼後置檔案中的GoToState方法呼叫VisualStateManager.GoToState物件上的靜態StackLayout方法:

public partial class VsmValidationPage : ContentPage
{
    public VsmValidationPage()
    {
        InitializeComponent();

        GoToState(false);
    }

    void OnTextChanged(object sender, TextChangedEventArgs args)
    {
        bool isValid = Regex.IsMatch(args.NewTextValue, @"^[2-9]\d{2}-\d{3}-\d{4}$");
        GoToState(isValid);
    }

    void GoToState(bool isValid)
    {
        string visualState = isValid ? "Valid" : "Invalid";
        VisualStateManager.GoToState(stackLayout, visualState);
    }
}

在此範例中, GoToState 方法由建構子呼叫以初始化狀態。 應該永遠有當前狀態。 程式碼背後的檔案接著呼叫 VisualStateManager.GoToState,並以狀態名稱呼叫定義視覺狀態的物件。

視覺狀態觸發器

視覺狀態支援狀態觸發器,這是一組專門的觸發器,定義應在何種條件下應應用 a VisualState

狀態觸發程式會新增至 StateTriggersVisualState 集合中。 此集合可以包含單一狀態觸發程式,或多個狀態觸發程式。 當集合中的任何狀態觸發程式處於作用中狀態時,就會套用 A VisualState

當使用狀態觸發器控制視覺狀態時,.NET MAUI 會使用以下優先順序規則來決定哪個觸發器(以及對應的 VisualState)會被啟動:

  1. 任何衍生自 StateTriggerBase的觸發器。
  2. 由於滿足AdaptiveTrigger 條件而激活MinWindowWidth
  3. 由於滿足AdaptiveTrigger 條件而激活MinWindowHeight

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

欲了解更多關於狀態觸發器的資訊,請參見 狀態觸發器

無效化並重新套用視覺狀態

當你在執行時修改 A VisualState 內的設定器值時,受影響的控制項不會自動反映變更。 此方法會強制 VisualElement 在所有視覺狀態群組中解除和重新執行當前狀態的屬性設置器,以確保更新後的值生效。

以下範例修改視覺狀態的設定值,然後呼叫 InvalidateVisualStates 來刷新控制項:

// Locate the "Pressed" visual state defined on the button.
var groups = VisualStateManager.GetVisualStateGroups(myButton);
var pressedState = groups
    .SelectMany(g => g.States)
    .First(s => s.Name == "Pressed");

// Modify a setter value in-place.
pressedState.Setters[0].Value = Colors.Orange;

// Force the control to reapply the current state's setters.
VisualStateManager.InvalidateVisualStates(myButton);

備註

InvalidateVisualStates 是一個呼叫者驅動的 API——.NET MAUI 不會自動偵測視覺狀態設定器值何時改變。 你必須對每個元素呼叫此方法,以反映更新的視覺狀態。