視覚状態

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

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

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

  • Normal
  • Disabled
  • 集中している
  • 入選
  • ポインターオーバー

NormalDisabledFocused、およびPointerOverのビジュアル状態は、VisualElementViewの基底クラスであるPageから派生するすべてのクラスでサポートされます。 さらに、独自の表示状態グループと表示状態を定義することもできます。

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

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

一般的な視覚状態

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

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

  • Entryが無効になっている場合、Entryにはピンクの背景が必要です。
  • Entryは、通常、ライムの背景を持つ必要があります。
  • 入力フォーカスがある場合、 Entry は通常の高さの 2 倍に拡大する必要があります。
  • Entry は、マウスのカーソルが上に置かれているが、押下されていない場合、薄い青色の背景を表示する必要があります。

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

ビューで表示状態を定義する

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

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

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

Important

ビジュアル状態Setterオブジェクトが正しく機能するためには、VisualStateGroupVisualState状態の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>

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

エントリの 4 つの定義された表示状態のスクリーンショット。

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

EntryFocused状態のライム背景を設定する場合は、その表示状態に別のSetterを追加します。

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

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

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

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

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

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

Setter型には、TargetName型のstring プロパティがあり、ビジュアル状態のSetterが操作するターゲット オブジェクトを表します。 TargetName プロパティを定義すると、Setterは、Propertyで定義されているオブジェクトのTargetNameValueに設定します。

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

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

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>

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

ボタンの押された状態のスクリーンショット。

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

Important

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

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

カスタムの表示状態を実装するには、共通の状態の表示状態を定義するのと同様に定義しますが、選択した名前を指定して、 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にアタッチされ、 ValidInvalidという名前の 2 つの相互排他的な状態があります。 Entryに有効な電話番号が含まれていない場合、現在の状態はInvalidされるため、Entryの背景はピンク色になり、2 番目のLabelが表示され、Buttonは無効になります。 有効な電話番号を入力すると、現在の状態が Validになります。 Entryは石灰の背景を取得し、2 番目の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を呼び出します。

視覚状態トリガー

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

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

状態トリガーを使用して視覚的な状態を制御する場合、.NET MAUIは次の優先順位規則を使用して、アクティブになるトリガー (および対応する VisualState) を決定します。

  1. StateTriggerBaseから派生するすべてのトリガー。
  2. AdaptiveTrigger MinWindowWidth条件が満たされたためにアクティブ化されます。
  3. AdaptiveTrigger MinWindowHeight条件が満たされたためにアクティブ化されます。

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

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

ビジュアル ステートを無効にして再適用する

実行時に VisualState 内のセッター値を変更しても、影響を受けるコントロールは変更を自動的に反映しません。 InvalidateVisualStates(VisualElement) メソッドは、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は、ビジュアル状態セッター値が変更されたときに自動的には検出されません。 更新された表示状態を反映する各要素で、このメソッドを呼び出す必要があります。