Триггеры Xamarin.Forms
Триггеры позволяют декларативно задавать действия в 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
при выполнении условия триггера. Необходимо указатьProperty
иValue
.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
, можно задать коллекции EnterActions
и ExitActions
.
Триггеры событий
Элементу EventTrigger
требуется только свойство Event
, такое как "Clicked"
в примере ниже.
<EventTrigger Event="Clicked">
<local:NumericValidationTriggerAction />
</EventTrigger>
Обратите внимание, что здесь нет элементов Setter
, а есть ссылка на класс, определяемый элементом local:NumericValidationTriggerAction
, для которого требуется объявить пространство имен xmlns:local
в XAML страницы.
<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>
с универсальным параметром, соответствующим типу элемента управления, к которому будет применяться триггер. Можно использовать суперклассы, например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
: один экземпляр будет общим для элементов управления, поэтому любое однократно настроенное состояние будет применяться ко всем элементам.
Обратите внимание, что триггеры событий не поддерживают коллекции EnterActions
и ExitActions
, описанные ниже.
Мультитриггеры
MultiTrigger
аналогичен Trigger
или DataTrigger
, за исключением того, что для него может существовать несколько условий. Setter
срабатывают при выполнении всех условий.
Ниже приведен пример триггера для кнопки, которая привязывается к двум различным записям (email
и phone
).
<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" />
Создание мультитриггера, требующего выполнения всех условий
Мультитриггер обновляет свой элемент управления только в том случае, если выполняются все условия. Тестирование на "все длины полей равно нулю" (например, страница входа, в которой все входные данные должны быть завершены) является сложной, так как требуется условие "where 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
, метод задания устанавливает для свойства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
, чтобы включить кнопку Сохранить.
В нижней части экранов кнопка Вход остается неактивной до тех пор, пока не будут заполнены оба поля.
EnterActions и ExitActions
Другим способом реализации изменений при срабатывании триггера является добавление коллекций EnterActions
и ExitActions
и указание реализаций класса TriggerAction<T>
.
Коллекция EnterActions
служит для определения списка IList
объектов TriggerAction
, которые будут вызываться при соблюдении условия триггера. Коллекция ExitActions
служит для определения списка IList
объектов TriggerAction
, которые будут вызываться, когда условие триггера больше не соблюдается.
Примечание.
Объекты TriggerAction
, определенные в коллекциях EnterActions
и ExitActions
, игнорируются классом EventTrigger
.
В триггер можно добавить обе коллекции EnterActions
и ExitActions
, а также Setter
, но учтите, что Setter
вызываются мгновенно (они не ожидают завершения EnterAction
или ExitAction
). Кроме того, можно выполнить все действия в коде и вообще не использовать 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
.
Триггеры состояния добавляются в коллекцию StateTriggers
VisualState
. Эта коллекция может содержать один или несколько триггеров состояния. При наличии активных триггеров состояния в коллекции будет применяться VisualState
.
При использовании триггеров состояния для управления визуальными состояниями Xamarin.Forms применяет следующие правила приоритета, чтобы определить, какой триггер (и соответственно VisualState
) будет активен:
- Любой триггер, производный от
StateTriggerBase
. AdaptiveTrigger
активируется из-за выполнения условияMinWindowWidth
.AdaptiveTrigger
активируется из-за выполнения условияMinWindowHeight
.
Если одновременно активны несколько триггеров (например, два пользовательских триггера), то у первого триггера, объявленного в разметке, будет приоритет.
Примечание.
Триггеры состояния можно задать в Style
или напрямую в элементах.
Дополнительные сведения о визуальных состояниях см. в статье Диспетчер визуального представления состояний Xamarin.Forms.
Триггер состояния
Класс StateTrigger
, производный от класса StateTriggerBase
, имеет привязываемое свойство IsActive
. StateTrigger
выполняет изменение VisualState
, когда свойство IsActive
изменяет значение.
Класс StateTriggerBase
, который является базовым классом для всех триггеров состояния, имеет свойство IsActive
и событие IsActiveChanged
. Это событие возникает при каждом изменении VisualState
. Кроме того, класс StateTriggerBase
имеет переопределяемые методы OnAttached
и OnDetached
.
Внимание
Привязываемое свойство StateTrigger.IsActive
скрывает унаследованное свойство StateTriggerBase.IsActive
.
В следующем примере XAML показан элемент Style
, предусматривающий объекты StateTrigger
:
<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>
В этом примере неявный элемент Style
направлен на объекты Grid
. Если у свойства IsToggled
привязанного объекта значение true
, цвет фона Grid
задается черным. Когда у свойства IsToggled
привязанного объекта значение false
, активируется изменение VisualState
, а цвет фона Grid
становится белым.
Кроме того, каждый раз при изменении VisualState
возникает событие IsActiveChanged
для VisualState
. Каждый элемент 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
и переопределения методов OnAttached
и OnDetached
для выполнения необходимых операций регистраций и очистки.
Адаптивный триггер
AdaptiveTrigger
активирует изменение VisualState
, когда окно имеет заданную высоту или ширину. Этот триггер имеет два привязываемых свойства:
MinWindowHeight
типаdouble
, которое указывает минимальную высоту окна, при которой следует применятьVisualState
.MinWindowWidth
типаdouble
, которое указывает минимальную ширину окна, при которой следует применятьVisualState
.
Примечание.
AdaptiveTrigger
является производным от класса StateTriggerBase
и может присоединить обработчик событий к событию IsActiveChanged
.
В следующем примере XAML показан элемент Style
, предусматривающий объекты AdaptiveTrigger
:
<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>
В этом примере неявный элемент Style
направлен на объекты StackLayout
. Если ширина окна — 0–800 аппаратно-независимых единиц, у объектов StackLayout
, к которым применяется Style
, будет вертикальная ориентация. Если ширина окна равна >800 устройствам, VisualState
изменения активируются, а StackLayout
ориентация изменяется на горизонтальную:
Свойства MinWindowHeight
и MinWindowWidth
можно использовать независимо друг от друга или совместно. Ниже представлен простой пример XAML для установки обоих свойств:
<AdaptiveTrigger MinWindowWidth="800"
MinWindowHeight="1200"/>
В этом примере указывается, AdaptiveTrigger
что соответствующее VisualState
будет применяться, если текущая ширина окна равна >800 устройствам независимо от устройств, а текущая высота окна равна >1200 единицам, независимым от устройства.
Сравнение триггера состояния
CompareStateTrigger
активирует изменение VisualState
, если свойство равно определенному значению. Этот триггер имеет два привязываемых свойства:
Property
типаobject
указывает свойство, сравниваемое триггером.Value
типаobject
указывает значение, при котором следует применитьVisualState
.
Примечание.
CompareStateTrigger
является производным от класса StateTriggerBase
и может присоединить обработчик событий к событию IsActiveChanged
.
В следующем примере XAML показан элемент Style
, предусматривающий объекты CompareStateTrigger
:
<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>
В этом примере неявный элемент Style
направлен на объекты Grid
. Если у свойства IsChecked
CheckBox
значение false
, устанавливается белый цвет фона Grid
. Когда у свойства CheckBox.IsChecked
значение true
, активируется изменение VisualState
, а цвет фона Grid
становится черным:
Триггер состояния устройства
DeviceStateTrigger
активирует изменение VisualState
в зависимости от платформы устройства, на которой работает приложение. У этого триггера одно привязываемое свойство:
Device
типаstring
указывает платформу устройства, на которой следует применитьVisualState
.
Примечание.
DeviceStateTrigger
является производным от класса StateTriggerBase
и может присоединить обработчик событий к событию IsActiveChanged
.
В следующем примере XAML показан элемент Style
, предусматривающий объекты DeviceStateTrigger
:
<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>
В этом примере явный элемент Style
направлен на объекты ContentPage
. Объекты ContentPage
, использующие этот стиль, устанавливают серебристый цвет фона в iOS, бледно-голубой — в Android и аквамариновый — на универсальной платформе Windows. На следующих снимках экрана показаны полученные страницы в iOS и Android:
Триггер состояния ориентации
OrientationStateTrigger
активирует изменение VisualState
при изменении ориентации устройства. У этого триггера одно привязываемое свойство:
Orientation
типаDeviceOrientation
указывает ориентацию, к которой следует применятьVisualState
.
Примечание.
OrientationStateTrigger
является производным от класса StateTriggerBase
и может присоединить обработчик событий к событию IsActiveChanged
.
В следующем примере XAML показан элемент Style
, предусматривающий объекты OrientationStateTrigger
:
<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>
В этом примере явный элемент Style
направлен на объекты ContentPage
. Объекты ContentPage
, использующие этот стиль, устанавливают серебристый цвет фона, если ориентация книжная, и белый — если ориентация альбомная.