Visual State Manager (менеджер визуальных состояний) позволяет организовать изменения визуального интерфейса. По умолчанию Visual State Manager прикрепляет к элементам управления группу из трех состояний:
Disabled: элемент отключен для использования
Focused: элемент получил фокус и используется в текущий момент
Normal: стандартное состояние элемента
PointerOver: указатель мыши находится над элементом
Состояния Normal
, Disabled
, Focused
и PointerOver
поддерживается для объектов всех классов, которые унаследованы от VisualElement (в том числе классов View и Page).
Кроме того, при необходимости можно определять свои визуальные состояния.
Итак, применим визуальные состояния к текстовому полю Entry:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="HelloApp.MainPage"> <StackLayout> <Entry x:Name="entry" Text="Hello METANIT.COM!"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CustomStates"> <VisualState x:Name="Focused"> <VisualState.Setters> <Setter Property="TextColor" Value="#004D40" /> <Setter Property="BackgroundColor" Value="#B2DFDB" /> </VisualState.Setters> </VisualState> <VisualState x:Name="PointerOver"> <VisualState.Setters> <Setter Property="TextColor" Value="#004D40" /> <Setter Property="FontAttributes" Value="Bold" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Entry> </StackLayout> </ContentPage>
Визуальные состояния определяются с помощью прикрепляемого свойства VisualStateManager.VisualStateGroups. Оно имеет
тип VisualStateGroupList
и представляет коллекцию объектов VisualStateGroup, то есть группу визуальных
состояний. В данном случае добавлена только одна группа состояний, которая называется CustomStates:
<VisualStateGroup Name="CustomStates">
Каждая группа VisualStateGroup определяет набор состояний. Каждое состояние определяется с помощью объекта VisualState. Этот объект принимает ряд объектов Setter, с помощью которых устанавливаются значения свойств визуального элемента, когда он принимает данное состояние. Например, когда поле Entry находится в состоянии "Focused" (то есть получило фокус), срабатывает следующее состояние:
<VisualState x:Name="Focused"> <VisualState.Setters> <Setter Property="TextColor" Value="#004D40" /> <Setter Property="BackgroundColor" Value="#B2DFDB" /> </VisualState.Setters> </VisualState>
То есть в данном случае, при получении фокуса текст окрашивается в темнозеленый цвет, а фон - в светлозеленый.
Когда поле Entry находится в состоянии "PointerOver" (то есть указатель мыши находится над элементом), срабатывает следующее состояние:
<VisualState x:Name="PointerOver"> <VisualState.Setters> <Setter Property="TextColor" Value="#004D40" /> <Setter Property="FontAttributes" Value="Bold" /> </VisualState.Setters> </VisualState>
Таким образом при наведении указателя мыши на элемент текст окрашивается в темнозеленый цвет и получает выделение жирным.
Стоит учитывать, что имена состояний в данном случае должны быть именно "Normal", "Disabled", "Focused", "PointerOver", так как это встроенные по умолчанию состояния. Название группы необязательно должно быть "CustomStates", оно может быть произвольным. Но следует учитывать, что и название состояний и название групп должны быть уникальными ( то есть нельзя определить для элемента Entry для состояния "Disabled" или две группы состояний "CustomStates").
При прикреплении группы визуальных состояний к элементам код элемента может сильно разбухать. Хотя визуальные состояния не относятся к структуре элемента, а только к его стилизации. Кроме того, возможно, мы захотим использовать одни и те же визуальные состояния для разных элементов. В этом случае удобнее выносить визуальные состояния в ресурсы. Например, изменим выше определение страницы xaml:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="HelloApp.MainPage"> <ContentPage.Resources> <Style TargetType="Entry"> <Setter Property="VisualStateManager.VisualStateGroups"> <VisualStateGroupList> <VisualStateGroup x:Name="CustomStates"> <VisualState x:Name="Focused"> <VisualState.Setters> <Setter Property="TextColor" Value="#004D40" /> <Setter Property="BackgroundColor" Value="#B2DFDB" /> </VisualState.Setters> </VisualState> <VisualState x:Name="PointerOver"> <VisualState.Setters> <Setter Property="TextColor" Value="#004D40" /> <Setter Property="FontAttributes" Value="Bold" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateGroupList> </Setter> </Style> </ContentPage.Resources> <StackLayout Padding="20"> <Entry Text="Hello METANIT.COM!" /> </StackLayout> </ContentPage>
Проект .NET MAUI по умолчанию содержит стили для элемента Entry в файле Resources/Styles/Styles.xaml, в которых определены эти состояния:
Но если мы определяем визуальные состояния через стили, то мы можем переопределить эти визуальные состояния по умолчанию:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="HelloApp.MainPage"> <ContentPage.Resources> <Style TargetType="Entry"> <Setter Property="Margin" Value="10" /> <Setter Property="VisualStateManager.VisualStateGroups"> <VisualStateGroupList> <VisualStateGroup x:Name="CustomStates"> <VisualState x:Name="Normal"> <VisualState.Setters> <Setter Property="TextColor" Value="#004D40" /> </VisualState.Setters> </VisualState> <VisualState x:Name="Focused"> <VisualState.Setters> <Setter Property="TextColor" Value="#004D40" /> <Setter Property="BackgroundColor" Value="#B2DFDB" /> </VisualState.Setters> </VisualState> <VisualState x:Name="Disabled"> <VisualState.Setters> <Setter Property="TextColor" Value="Gray" /> <Setter Property="BackgroundColor" Value="LightGray" /> </VisualState.Setters> </VisualState> <VisualState x:Name="PointerOver"> <VisualState.Setters> <Setter Property="TextColor" Value="#004D40" /> <Setter Property="FontAttributes" Value="Bold" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateGroupList> </Setter> </Style> </ContentPage.Resources> <StackLayout Padding="10"> <Entry Text="Hello METANIT.COM!" /> <Entry Text="Hello World!" /> <Entry Text="Hello Work!" IsEnabled="False" /> </StackLayout> </ContentPage>
В .NET MAUI для ряда элементов есть дополнительные состояния:
VisualElement
определяет общие для всех элементов состояния Normal
, Disabled
, Focused
и PointerOver
Button
определяет состояние Pressed
(нажатие кнопки)
CarouselView
определяет состояния DefaultItem
, CurrentItem
, PreviousItem
и NextItem
CheckBox
определяет состояние IsChecked
(флажок отмечен)
CollectionView
определяет состояние Selected
(элемент выделен)
ImageButton
определяет состояние Pressed
(нажатие кнопки)
RadioButton
определяет состояния Checked
(элемент выбран) и Unchecked
(элемент не выбран)
Switch
определяет состояния On
(включено) и Off
(выключено)
По умолчанию Visual State Manager предоставляет нам пять встроенных состояния: Normal, Focused, PointerOver и Disabled. Однако мы можем определять
свои состояния. Суть встроенных состояний состоит в том, что элемент отлеживает некоторое изменения. Например, он отслеживает получение фокуса и после этого
вызывает метод VisualStateManager.GoToState()
, в который передается название состояния:
VisualStateManager.GoToState(visualElement, "Focused");
В качестве первого параметра передается элемент, для которого вызываюся состояния. Это должен быть элемент типа, который унаследован от VisualElement.
Соответственно при создании своих состояний мы должны сами отслеживать переход в эти состояния и вызывать данный метод.
Например, определим визуальные состояния для валидации данных в текстовом поле. Для этого определим следующую страницу:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="HelloApp.MainPage"> <StackLayout Padding="20"> <Entry x:Name="myEntry" TextChanged="entry_TextChanged"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="ValidStates"> <VisualState x:Name="Valid"> <VisualState.Setters> <Setter Property="TextColor" Value="Green" /> </VisualState.Setters> </VisualState> <VisualState x:Name="Invalid"> <VisualState.Setters> <Setter Property="TextColor" Value="Red" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Entry> </StackLayout> </ContentPage>
Здесь к полю Entry прикреплена групаа состояний ValidStates (название группы может быть произвольным). Группа содержит два визуальных состояния: "Valid" и "Invalid". Каждое из состояний предусматривает определенные цвет и высоту шрифта.
Но по умолчанию Entry не отслеживает подобные состояния, и нам надо самостоятельно вызывать их. Для этого изменим код c# у страницы:
using System.Text.RegularExpressions; namespace HelloApp; public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); SetState(false); // изначально невалидное состояние } private void entry_TextChanged(object sender, TextChangedEventArgs e) { // проверяем введенное значение на соответствие регулярному выражению bool isValid = Regex.IsMatch(e.NewTextValue, @"^\+[1-9]-\d{3}-\d{3}-\d{4}$"); SetState(isValid); } // установка состояния Valid или Invalid void SetState(bool isValid) { string visualState = isValid ? "Valid" : "Invalid"; VisualStateManager.GoToState(myEntry, visualState); } }
Допустим, нам надо вввести в текстовое поле номер телефона в формате +1-234-567-8901. Для этого прикрепляем к текстовому полю обработчик изменения ввода entry_TextChanged, в котоом проверяем новый введенный текст. Если он соответствует регулярному выражению, то вызывается метод
VisualStateManager.GoToState(myEntry, "Valid");
То есть для элемента myEntry вызывается событие Valid, иначе вызывается событие Invalid. И для упрощения вызов данного метода инкапсулирован в методе SetState.
Поскольку при запуске приложения текстовое поле уже должно быть в определенном состоянии, то в конструкторе также вызывается метод VisualStateManager.GoToState.
Результат работы приложения: