ビジュアルの状態

.NET マルチプラットフォーム アプリ UI (.NET MAUI) Visual State Manager は、コードからユーザー インターフェイスに視覚的な変更を加える構造化された方法を提供します。 ほとんどの場合、アプリのユーザー インターフェイスは XAML で定義され、この XAML には、Visual State Manager がユーザー インターフェイスの表示に与える影響を記述するマークアップを含めることができます。

Visual State Manager では、表示状態の概念が導入されています。 Button などの .NET MAUI ビューは、基になる状態 (無効になっているか、押されているか、入力フォーカスがあるか) に応じて、いくつかの異なる外観を持つことができます。 これらはボタンの状態です。 表示状態は、表示状態グループで収集されます。 表示状態グループ内のすべての表示状態は、互いに排他的です。 表示状態と表示状態グループはどちらも、単純なテキスト文字列によって識別されます。

.NET MAUI Visual State Manager は、次の表示状態を持つ CommonStates という名前の表示状態グループを定義します。

  • 正常
  • 無効
  • フォーカスがある
  • 選択済み
  • PointerOver

NormalDisabledFocusedPointerOver の表示状態は、ViewPage の基本クラスである VisualElement から派生するすべてのクラスでサポートされています。 さらに、独自の表示状態グループと表示状態を定義することもできます。

コードビハインドから表示要素に直接アクセスするのではなく、Visual State Manager を使用して外観を定義する利点は、ビジュアル要素がさまざまな状態にどのように反応するかを XAML 内で完全に制御できることです。これにより、すべての UI デザインが 1 か所に保持されます。

Note

トリガーは、ビューのプロパティの変更やイベントの発生に基づいて、ユーザー インターフェイスのビジュアルに変更を加えることもできます。 ただし、トリガーを使用してこれらの変更のさまざまな組み合わせに対処すると、混乱を招く可能性があります。 Visual State Manager では、表示状態グループ内の表示状態は常に相互に排他的です。 常に、各グループ内の 1 つの状態のみが現在の状態です。

一般的な表示状態

Visual State Manager を使用すると、ビューが正常である場合、無効である場合、入力フォーカスがある場合、選択されている場合、またはマウス カーソルがポイントしているが押されていない場合に、ビューの外観を変更できるマークアップを XAML ファイルに含めることができます。 これらは一般的な状態として知られています。

たとえば、ページに Entry ビューがあり、Entry の外観を次のように変更するとします。

  • Entry が無効になっている場合、Entry の背景はピンク色になります。
  • Entry の背景は通常、ライム色になっているはずです。
  • Entry は、入力フォーカスがある場合、通常の高さの 2 倍に拡張されます。
  • Entry の上にマウス カーソルが置かれているが押されていない場合、その背景は水色になります。

Visual State Manager マークアップを個々のビューにアタッチすることも、複数のビューに適用される場合はスタイルで定義することもできます。

ビューの表示状態の定義

VisualStateManager クラスは、表示状態をビューに添付するために使用される VisualStateGroups 添付プロパティを定義します。 VisualStateGroupList 型の VisualStateGroups プロパティは、VisualStateGroup オブジェクトのコレクションです。 そのため、VisualStateManager.VisualStateGroups 添付プロパティの子は VisualStateGroup オブジェクトです。 このオブジェクトは、グループの名前を示す x:Name 属性を定義します。 あるいは、VisualStateGroup クラスは、代わりに使用できる Name プロパティを定義します。 添付プロパティの詳細については、「添付プロパティ」をご覧ください。

VisualStateGroup クラスは、VisualState オブジェクトのコレクションである States という名前のプロパティを定義します。 StatesVisualStateGroups クラスのコンテンツ プロパティであるため、VisualState オブジェクトを VisualStateGroup の子として含めることができます。 各 VisualState オブジェクトは、x:Name または Name を使用して識別される必要があります。

VisualState クラスは、Setter オブジェクトのコレクションである Setters という名前のプロパティを定義します。 これらは、Style オブジェクトで使用するのと同じ Setter オブジェクトです。 SettersVisualState のコンテンツ プロパティではないため、Setters プロパティのプロパティ要素タグを含める必要があります。 Setter オブジェクトは Setters の子として挿入する必要があります。 各 Setter オブジェクトは、その状態が現在のときのプロパティの値を示します。 Setter オブジェクトによって参照されるすべてのプロパティは、バインド可能なプロパティによってサポートされている必要があります。

重要

表示状態の Setter オブジェクトが正しく機能するためには、VisualStateGroupNormal 状態の VisualState オブジェクトを含める必要があります。 この表示状態に Setter オブジェクトがない場合は、空の表示状態 (<VisualState Name="Normal" />) として含める必要があります。

次の例は、Entry に定義されている表示状態を示しています。

<Entry FontSize="18">
    <VisualStateManager.VisualStateGroups>
        <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>
    </VisualStateManager.VisualStateGroups>
</Entry>

次のスクリーンショットは、4 つの表示状態で定義された Entry を示しています。

Entry に対して定義された 3 つのビジュアルの状態のスクリーンショット。

EntryNormal 状態の場合、その背景は黄緑です。 Entry で入力フォーカスを得られたときに、フォント サイズが 2 倍になるとします。 Entry が無効になると、背景がピンクになります。 Entry が入力フォーカスを取得すると、黄緑の背景は保持されません。 マウス ポインターが Entry 上のに置かれ、押されていない場合は、Entry の背景が水色になります。 Visual State Manager によって表示状態が切り替わると、前の状態によって設定されたプロパティは設定解除されます。 したがって、表示状態は相互に排他的です。

Focused 状態で Entry の背景を黄緑にしたい場合は、その表示状態に別の Setter を追加します。

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

スタイルで表示状態を定義する

多くの場合、2 つ以上のビューで同じ表示状態を共有する必要があります。 このシナリオでは、表示状態を Style で定義できます。 これは、VisualStateManager.VisualStateGroups プロパティの Setter オブジェクトを追加することで実現できます。 Setter オブジェクトのコンテンツ プロパティはその Value プロパティであるため、Setter オブジェクトの子として指定できます。 VisualStateGroups プロパティは VisualStateGroupList 型でるため、Setter オブジェクトの子は VisualStateGroupList で、VisualState オブジェクトを含む VisualStateGroup を追加できます。

次の例は、一般的な表示状態を定義する 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 で定義されている表示状態を示します。

クラス 状態 その他の情報
Button Pressed ボタンの表示状態
CarouselView DefaultItemCurrentItemPreviousItemNextItem CarouselView の表示状態
CheckBox IsChecked CheckBox の表示状態
CollectionView Selected CollectionView の表示状態
ImageButton Pressed ImageButton の表示状態
RadioButton CheckedUnchecked RadioButton の表示状態
Switch OnOff 表示状態を切り替える
VisualElement NormalDisabledFocusedPointerOver 一般的な状態

複数の要素に状態を設定する

前の例では、視覚的な状態は 1 つの要素にアタッチされ、操作されていました。 ただし、1 つの要素にアタッチされているものの、同じスコープ内の他の要素にプロパティを設定する表示状態を作成することもできます。 これにより、状態が動作する各要素で表示状態を繰り返す必要がなくなります。

Setter 型には string 型の TargetName プロパティがあり、表示状態の Setter が操作する対象になるオブジェクトを表します。 TargetName プロパティが定義されている場合、SetterTargetName で定義されたオブジェクトの PropertyValue に設定します。

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

この例では、label の名前付きの LabelTextColor プロパティを Red に設定します。 TargetName プロパティを設定するときは、プロパティへの完全なパスを Property で指定する必要があります。 したがって、TextColor プロパティを Label で設定するには、Property は、Label.TextColor のように指定します。

Note

Setter オブジェクトによって参照されるすべてのプロパティは、バインド可能なプロパティによってサポートされている必要があります。

次の例は、1 つのビジュアル状態グループから複数のオブジェクトに状態を設定する方法を示しています。

<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>

この例では、Button を押していないときに Normal 状態がアクティブになり、Entry に応答を入力できます。 Button を押すと Pressed 状態は がアクティブになり、その Scale プロパティが既定値の 1 から 0.8 に変わることを指定します。 さらに、entry と名付けられた Entry は、その Text プロパティをパリに設定します。 したがってその結果、Button を押すと、少し小さいサイズに再スケーリングし、Entry はパリを表示します。

ボタンの Pressed 状態のスクリーンショット。

次に Button を解放すると、既定値の 1 に再スケーリングし、Entry は以前に入力したテキストを表示します。

重要

プロパティ パスは、TargetName プロパティを指定する Setter 要素ではサポートされていません。

カスタムの表示状態を定義する

カスタムの表示状態を実装するには、共通状態の表示状態を定義するのと同様に定義しますが、選択した名前で、状態をアクティブ化する VisualStateManager.GoToState メソッドを呼び出します。

次の例は、入力の検証に Visual State Manager を使用する方法を示しています。

<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 にアタッチされ、2 つの相互排他的な状態、ValidInvalid があります。 Entry に有効な電話番号が含まれていない場合、現在の状態は Invalid なので、Entry はピンク色の背景で、2 番目の Label が表示され、Button は無効になります。 有効な電話番号を入力すると、現在の状態は Valid になります。 Entry はライムの背景になり、2 番目の Label が消え、Button が有効になります。

ビジュアルの状態の検証例のスクリーンショット。

分離コード ファイルは、Entry から TextChanged イベントを処理する責任があります。 ハンドラーは正規表現を使用して、入力文字列が有効かどうかを判断します。 分離コード ファイル内の GoToState メソッドは、StackLayout オブジェクトの静的 VisualStateManager.GoToState メソッドを呼び出します。

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 を状態名で呼び出します。

表示状態トリガー

表示状態は、VisualState が適用される条件を定義する特殊なトリガーのグループである、状態トリガーをサポートします。

状態トリガーは、VisualStateStateTriggers コレクションに追加されます。 このコレクションには、1 つの状態トリガーを含めることも、複数の状態トリガーを含めることもできます。 コレクション内のいずれかの状態トリガーがアクティブになっていると、VisualState が適用されます。

状態トリガーを使用してビジュアルの状態を制御する場合、.NET MAUI では、次の優先順位規則を使用して、アクティブにするトリガー (VisualState に対応する) を決定します。

  1. StateTriggerBase から派生したトリガー。
  2. MinWindowWidth 条件の適用によってアクティブにされた AdaptiveTrigger
  3. MinWindowHeight 条件の適用によってアクティブにされた AdaptiveTrigger

複数のトリガーが同時にアクティブにされた場合 (たとえば、2 つのカスタム トリガー)、マークアップで最初に宣言されたトリガーが優先されます。

状態トリガーの詳細については、「状態トリガー」を参照してください。