Desencadenadores de Xamarin.Forms
Los desencadenadores permiten expresar acciones de forma declarativa en XAML que cambian la apariencia de controles en función de eventos o cambios en propiedades. Además, los desencadenadores de estado, que son un grupo especializado de desencadenadores, definen cuándo se debe aplicar la clase VisualState
.
Puede asignar un desencadenador directamente a un control o agregarlo a un diccionario de recursos de nivel de aplicación o página que se vaya a aplicar a varios controles.
Un desencadenador simple se puede expresar puramente en XAML, mediante la incorporación de un elemento Trigger
a la colección de desencadenadores de un control.
En este ejemplo se muestra un desencadenador que cambia un color de fondo Entry
cuando recibe el foco:
<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>
Las partes importantes de la declaración del desencadenador son:
TargetType: tipo de control al que se aplica el desencadenador.
Property: propiedad en el control que se supervisa.
Value: valor, cuando se produce para la propiedad supervisada, que hace que el desencadenador se active.
Setter: colección de elementos
Setter
que se puede agregar cuando se cumple la condición del desencadenador. Debe especificar los elementosProperty
yValue
que se van a establecer.EnterActions y ExitActions (no mostrados): se escriben en código y se pueden usar además de los elementos
Setter
(o en su lugar). Se describen abajo.
Los desencadenadores también se pueden agregar a una declaración Style
en un control, en una página o una aplicación ResourceDictionary
. Este ejemplo declara un estilo implícito (es decir, no se establece Key
), lo que significa que se aplica a todos los controles Entry
en la página.
<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>
Los desencadenadores de datos usan enlaces de datos para supervisar otro control a fin de que se llame a los elementos Setter
. En lugar del atributo Property
de un desencadenador de propiedad, establezca el atributo Binding
para supervisar el valor especificado.
El ejemplo siguiente usa la sintaxis de enlace de datos ,{Binding Source={x:Reference entry}, Path=Text.Length}
que es como se hace referencia a las propiedades de otro control. Cuando la longitud de entry
es cero, el desencadenador se activa. En este ejemplo el desencadenador deshabilita el botón cuando la entrada está vacía.
<!-- 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>
Sugerencia
Al evaluar Path=Text.Length
proporciona siempre un valor predeterminado para la propiedad de destino (por ejemplo, Text=""
) porque de lo contrario será null
y el desencadenador no funcionará como esperas.
Además de especificar los elementos Setter
, también puede proporcionar EnterActions
y ExitActions
.
El elemento EventTrigger
solo requiere una propiedad Event
, como "Clicked"
, en el ejemplo siguiente.
<EventTrigger Event="Clicked">
<local:NumericValidationTriggerAction />
</EventTrigger>
Tenga en cuenta que no hay elementos Setter
, sino una referencia a una clase definida por local:NumericValidationTriggerAction
que requiere que se declare xmlns:local
en el código XAML de la página:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WorkingWithTriggers;assembly=WorkingWithTriggers"
La propia clase implementa TriggerAction
, lo que significa que debe proporcionar una invalidación para el método Invoke
al que se llama cada vez que se produce el evento desencadenador.
Una implementación de acción de desencadenador debe:
Implementar la clase genérica
TriggerAction<T>
, con el parámetro genérico correspondiente al tipo de control al que se va a aplicar el desencadenador. Puede usar superclases comoVisualElement
para escribir acciones de desencadenador que funcionen con una serie de controles, o especificar un tipo de control comoEntry
.Invalidar el método
Invoke
: se llama a este método cada vez que se cumplen los criterios del desencadenador.Opcionalmente, exponer propiedades que se pueden establecer en el código XAML cuando se declara el desencadenador. Para obtener un ejemplo de esto, vea la clase
VisualElementPopTriggerAction
en la aplicación de ejemplo que lo acompaña.
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;
}
}
Después, el desencadenador de eventos se puede usar desde XAML:
<EventTrigger Event="TextChanged">
<local:NumericValidationTriggerAction />
</EventTrigger>
Tenga cuidado al compartir desencadenadores en una instancia de ResourceDictionary
, ya que una instancia se comparte entre controles, con lo que cualquier estado que se configure una vez se va a aplicar a todos ellos.
Tenga en cuenta que los desencadenadores de eventos no admiten los elementos EnterActions
y ExitActions
descritos abajo.
Un elemento MultiTrigger
se parece Trigger
o DataTrigger
, salvo que en él puede haber más de una condición. Todas las condiciones deben cumplirse para que se desencadenen los elementos Setter
.
Este es el ejemplo de un desencadenador de un botón que se enlaza a dos entradas diferentes (email
y 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>
La colección Conditions
también puede contener elementos PropertyCondition
como este:
<PropertyCondition Property="Text" Value="OK" />
El multi-desencadenador solo actualiza su control cuando se cumplen todas las condiciones. Las pruebas con "todas las longitudes de campo son cero" (como una página de inicio de sesión donde deben completarse todas las entradas) son complicadas, porque se quiere una condición "where Text.Length > 0", pero esto no se puede expresar en XAML.
Se puede hacer con un elemento IValueConverter
. El código convertidor siguiente transforma el enlace Text.Length
en un bool
que indica si un campo está vacío o no:
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 ();
}
}
Para usar este convertidor en un multi-desencadenador, primero agréguelo al diccionario de recursos de la página (junto con una definición de espacio de nombres personalizada xmlns:local
):
<ResourceDictionary>
<local:MultiTriggerConverter x:Key="dataHasBeenEntered" />
</ResourceDictionary>
A continuación se muestra el código XAML. Observe las siguientes diferencias con respecto al primer ejemplo de multi-desencadenador:
- El botón tiene
IsEnabled="false"
establecido de forma predeterminada. - Las condiciones del multi-desencadenador usan el convertidor para convertir el valor
Text.Length
en unboolean
. - Cuando todas las condiciones son
true
, el establecedor convierte entrue
la propiedadIsEnabled
del botón.
<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>
Estas capturas de pantalla muestran la diferencia entre los dos ejemplos de multi-desencadenadores anteriores. En la parte superior de las pantallas, la entrada de texto con un solo elemento Entry
basta para habilitar el botón Guardar.
En la parte inferior de las pantallas, el botón Iniciar sesión permanece inactivo hasta que ambos campos contienen datos.
Otra forma de implementar cambios cuando se produce un desencadenador es mediante la incorporación de colecciones EnterActions
y ExitActions
y la especificación de implementaciones TriggerAction<T>
.
La colección EnterActions
se usa para definir un elemento IList
de objetos TriggerAction
que se invocará cuando se cumpla la condición del desencadenador. La colección ExitActions
se usa para definir un elemento IList
de objetos TriggerAction
que se invocará cuando ya no se cumpla la condición del desencadenador.
Nota
La clase EventTrigger
omite los objetos TriggerAction
definidos en las colecciones EnterActions
y ExitActions
.
Puede proporcionar tanto EnterActions
como ExitActions
, así como Setter
en un desencadenador, pero tenga en cuenta que se llama a los elementos Setter
de inmediato (no se espera a que se completen EnterAction
o ExitAction
). También puede hacer todo en el código y no usar elementos Setter
en absoluto.
<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>
Como siempre, cuando se hace referencia a una clase en XAML, se debe declarar un espacio de nombres como xmlns:local
, como se muestra aquí:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WorkingWithTriggers;assembly=WorkingWithTriggers"
El código FadeTriggerAction
se muestra a continuación:
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);
}
}
Los desencadenadores de estado son un conjunto especializado de desencadenadores que definen las condiciones en las que se debe aplicar VisualState
.
Los desencadenadores de estado se agregan a la colección StateTriggers
de una clase VisualState
. Esta colección puede contener un único desencadenador de estado o varios desencadenadores de estado. Se aplicará una clase VisualState
cuando cualquier desencadenador de estado de la colección esté activo.
Al usar desencadenadores de estado para controlar los estados visuales, Xamarin.Forms usa las siguientes reglas de prioridad para determinar qué desencadenador, y la clase VisualState
correspondiente, se activará:
- Cualquier desencadenador derivado de
StateTriggerBase
. - Una clase
AdaptiveTrigger
activada debido a que se cumple la condiciónMinWindowWidth
. - Una clase
AdaptiveTrigger
activada debido a que se cumple la condiciónMinWindowHeight
.
Si hay varios desencadenadores activos simultáneamente (por ejemplo, dos desencadenadores personalizados), tiene prioridad el primer desencadenador declarado en el marcado.
Nota
Los desencadenadores de estado se pueden establecer en una clase Style
o directamente en los elementos.
Para obtener más información sobre los estados visuales, vea Administrador de estado visual de Xamarin.Forms.
La clase StateTrigger
, que se deriva de la clase StateTriggerBase
, tiene una propiedad enlazable IsActive
. Un elemento StateTrigger
desencadena un cambio de VisualState
cuando cambia el valor de la propiedad IsActive
.
La clase StateTriggerBase
, que es la clase base de todos los desencadenadores de estado, tiene una propiedad IsActive
y un evento IsActiveChanged
. Este evento se desencadena cuando se produce un cambio de VisualState
. Además, la clase StateTriggerBase
tiene métodos OnAttached
y OnDetached
que se pueden invalidar.
Importante
La propiedad enlazable StateTrigger.IsActive
oculta la propiedad StateTriggerBase.IsActive
heredada.
En el siguiente ejemplo de XAML, se muestra una clase Style
que incluye objetos 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>
En este ejemplo, la clase Style
implícita se destina a objetos Grid
. Cuando la propiedad IsToggled
del objeto enlazado es true
, el color de fondo de Grid
se establece en negro. Cuando la propiedad IsToggled
del objeto enlazado se convierte en false
, se desencadena un cambio de VisualState
, y el color de fondo de Grid
cambia a blanco.
Además, cada vez que cambia una clase VisualState
, se desencadena el evento IsActiveChanged
para el elemento VisualState
. Cada VisualState
registra un controlador de eventos para este evento:
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}");
}
En este ejemplo, cuando se desencadena un controlador para el evento IsActiveChanged
, el controlador indica si la clase VisualState
está activa o no. Por ejemplo, los mensajes siguientes aparecen en la ventana de la consola cuando cambian del estado visual Checked
al estado visual Unchecked
:
Checked state active: False
Unchecked state active: True
Nota
Para crear desencadenadores de estado personalizados, derive de la clase StateTriggerBase
e invalide los métodos OnAttached
y OnDetached
para realizar cualquier registro y limpieza necesarios.
Una clase AdaptiveTrigger
desencadena un cambio de VisualState
cuando la ventana tiene un alto o un ancho especificado. Este desencadenador tiene dos propiedades enlazables:
MinWindowHeight
, de tipodouble
, que indica la altura mínima de la ventana a la que debe aplicarseVisualState
.MinWindowWidth
, de tipodouble
, que indica la anchura mínima de la ventana a la que debe aplicarseVisualState
.
Nota
El elemento AdaptiveTrigger
deriva de la clase StateTriggerBase
y, por tanto, puede adjuntar un controlador de eventos al evento IsActiveChanged
.
En el siguiente ejemplo de XAML, se muestra una clase Style
que incluye objetos 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>
En este ejemplo, la clase Style
implícita se destina a objetos StackLayout
. Cuando el ancho de la ventana tiene entre 0 y 800 unidades independientes del dispositivo, los objetos StackLayout
a los que se aplica la clase Style
tendrán una orientación vertical. Cuando el ancho de la ventana es >= 800 unidades independientes del dispositivo, se desencadena el cambio de VisualState
y la orientación del StackLayout
cambia a horizontal:
Las propiedades MinWindowHeight
y MinWindowWidth
se pueden utilizar de forma independiente o combinándolas entre sí. En el siguiente ejemplo de XAML se muestra la configuración de ambas propiedades:
<AdaptiveTrigger MinWindowWidth="800"
MinWindowHeight="1200"/>
En este ejemplo, AdaptiveTrigger
indica que VisualState
correspondiente se aplicará cuando el ancho de la ventana actual es >= 800 unidades independientes del dispositivo y el alto de la ventana actual es >= 1200 unidades independientes del dispositivo.
CompareStateTrigger
desencadena un cambio de VisualState
cuando una propiedad es igual a un valor específico. Este desencadenador tiene dos propiedades enlazables:
Property
, de tipoobject
, que indica la propiedad comparada por el desencadenador.Value
, de tipoobject
, que indica el valor al que debe aplicarseVisualState
.
Nota
El elemento CompareStateTrigger
deriva de la clase StateTriggerBase
y, por tanto, puede adjuntar un controlador de eventos al evento IsActiveChanged
.
En el siguiente ejemplo de XAML, se muestra una clase Style
que incluye objetos 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>
En este ejemplo, la clase Style
implícita se destina a objetos Grid
. Cuando la propiedad IsChecked
de CheckBox
es false
, el color de fondo de Grid
se establece en blanco. Cuando la propiedad CheckBox.IsChecked
se convierte en true
, se desencadena un cambio de VisualState
, y el color de fondo de Grid
cambia a negro:
DeviceStateTrigger
desencadena un cambio de VisualState
basado en la plataforma del dispositivo en que se ejecuta la aplicación. Este desencadenador tiene una única propiedad enlazable:
Device
, de tipostring
, que indica la plataforma del dispositivo en la que debe aplicarseVisualState
.
Nota
El elemento DeviceStateTrigger
deriva de la clase StateTriggerBase
y, por tanto, puede adjuntar un controlador de eventos al evento IsActiveChanged
.
En el siguiente ejemplo de XAML, se muestra una clase Style
que incluye objetos 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>
En este ejemplo, la clase Style
explícita se destina a objetos ContentPage
. Los objetos ContentPage
que usan el estilo definen su color de fondo en plateado en iOS, en azul pálido en Android y en aguamarina en UWP. En las capturas de pantalla siguientes se muestran las páginas resultantes en iOS y Android:
OrientationStateTrigger
desencadena un cambio de VisualState
cuando varía la orientación del dispositivo. Este desencadenador tiene una única propiedad enlazable:
Orientation
, de tipoDeviceOrientation
, que indica la orientación a la que debe aplicarseVisualState
.
Nota
El elemento OrientationStateTrigger
deriva de la clase StateTriggerBase
y, por tanto, puede adjuntar un controlador de eventos al evento IsActiveChanged
.
En el siguiente ejemplo de XAML, se muestra una clase Style
que incluye objetos 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>
En este ejemplo, la clase Style
explícita se destina a objetos ContentPage
. Los objetos ContentPage
que usan el estilo definen su color de fondo en plateado cuando la orientación es vertical y en blanco cuando la orientación es horizontal.