wpf - Establecer una propiedad con un EventTrigger
xaml triggers (4)
Quiero ser capaz de establecer una propiedad con un EventTrigger, hay una serie de problemas con esto.
1) EventTriggers solo admite acciones, por lo que debo usar una storyBoard para establecer mis propiedades.
2) Una vez que uso un guión gráfico, tengo dos opciones:
- Detener: una vez que la animación se detiene, el valor vuelve a ser anterior a la animación iniciada
- HoldEnd: Bloquea la propiedad, de modo que ni el código ni la interacción del usuario pueden cambiar la propiedad que está conteniendo la animación.
En el siguiente ejemplo, quiero establecer la propiedad IsChecked en False cuando se hace clic en el botón y quiero que el usuario pueda cambiar el IsChecked y / o que yo desee poder cambiar la propiedad en el código.
Ejemplo:
<EventTrigger
SourceName="myButton"
RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetName="myCheckBox"
Storyboard.TargetProperty="IsChecked"
FillBehavior="Stop">
<DiscreteBooleanKeyFrame
KeyTime="00:00:00"
Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
Me doy cuenta de que puedo usar el evento "Completado" después de que el guión gráfico se complete para establecer el valor en False. Sin embargo, en este caso, quiero incluir la lógica dentro del XAML, ya que esta lógica se usará en un control personalizado y solo será específica para la UI.
La detención del Guión gráfico se puede hacer en el código subyacente o en el xaml, dependiendo de dónde provenga la necesidad.
Si el EventTrigger se mueve fuera del botón, podemos seguir adelante y apuntarlo con otro EventTrigger que le indicará al storyboard que se detenga. Cuando el guión gráfico se detiene de esta manera, no volverá al valor anterior.
Aquí he movido el botón. Haga clic en EventTrigger en un StackPanel circundante y agregue un EventTrigger nuevo en el CheckBox. Haga clic para detener el guión gráfico del botón cuando se haga clic en el CheckBox. Esto nos permite comprobar y desmarcar el CheckBox cuando se hace clic en él y también nos proporciona el comportamiento de desmarque deseado del botón.
<StackPanel x:Name="myStackPanel">
<CheckBox x:Name="myCheckBox"
Content="My CheckBox" />
<Button Content="Click to Uncheck"
x:Name="myUncheckButton" />
<Button Content="Click to check the box in code."
Click="OnClick" />
<StackPanel.Triggers>
<EventTrigger RoutedEvent="Button.Click"
SourceName="myUncheckButton">
<EventTrigger.Actions>
<BeginStoryboard x:Name="myBeginStoryboard">
<Storyboard x:Name="myStoryboard">
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="myCheckBox"
Storyboard.TargetProperty="IsChecked">
<DiscreteBooleanKeyFrame KeyTime="00:00:00"
Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="CheckBox.Click"
SourceName="myCheckBox">
<EventTrigger.Actions>
<StopStoryboard BeginStoryboardName="myBeginStoryboard" />
</EventTrigger.Actions>
</EventTrigger>
</StackPanel.Triggers>
</StackPanel>
Para detener el guión gráfico en el código subyacente, tendremos que hacer algo ligeramente diferente. El tercer botón proporciona el método donde detendremos el guión gráfico y restableceremos la propiedad IsChecked a True a través del código.
No podemos llamar a myStoryboard.Stop () porque no comenzamos el Storyboard a través del código que establece el parámetro isControllable. En cambio, podemos eliminar el Storyboard. Para hacer esto necesitamos el FrameworkElement en el que existe el guión gráfico, en este caso nuestro StackPanel. Una vez que se elimina el guión gráfico, una vez más podemos configurar la propiedad IsChecked persistiendo en la interfaz de usuario.
private void OnClick(object sender, RoutedEventArgs e)
{
myStoryboard.Remove(myStackPanel);
myCheckBox.IsChecked = true;
}
Modifiqué la solución de Neutrino para hacer que el xaml parezca menos detallado al especificar el valor:
Perdón por no tener imágenes del xaml renderizado, solo imagine un botón de hamburguesa [=] que haga clic y se convierta en [<-] un botón de retroceso y también alterne la visibilidad de una cuadrícula.
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
...
<Grid>
<Button x:Name="optionsButton">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<local:SetterAction PropertyName="Visibility" Value="Collapsed" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsBackButton}" Value="Visible" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsPanel}" Value="Visible" />
</i:EventTrigger>
</i:Interaction.Triggers>
<glyphs:Hamburger Width="10" Height="10" />
</Button>
<Button x:Name="optionsBackButton" Visibility="Collapsed">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<local:SetterAction PropertyName="Visibility" Value="Collapsed" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsButton}" Value="Visible" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsPanel}" Value="Collapsed" />
</i:EventTrigger>
</i:Interaction.Triggers>
<glyphs:Back Width="12" Height="11" />
</Button>
</Grid>
...
<Grid Grid.RowSpan="2" x:Name="optionsPanel" Visibility="Collapsed">
</Grid>
También puede especificar valores de esta manera, como en la solución de Neutrino:
<Button x:Name="optionsButton">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<local:SetterAction PropertyName="Visibility" Value="{x:Static Visibility.Collapsed}" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsBackButton}" Value="{x:Static Visibility.Visible}" />
<local:SetterAction PropertyName="Visibility" TargetObject="{Binding ElementName=optionsPanel}" Value="{x:Static Visibility.Visible}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<glyphs:Hamburger Width="10" Height="10" />
</Button>
Y aquí está el código.
using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Interactivity;
namespace Mvvm.Actions
{
/// <summary>
/// Sets a specified property to a value when invoked.
/// </summary>
public class SetterAction : TargetedTriggerAction<FrameworkElement>
{
#region Properties
#region PropertyName
/// <summary>
/// Property that is being set by this setter.
/// </summary>
public string PropertyName
{
get { return (string)GetValue(PropertyNameProperty); }
set { SetValue(PropertyNameProperty, value); }
}
public static readonly DependencyProperty PropertyNameProperty =
DependencyProperty.Register("PropertyName", typeof(string), typeof(SetterAction),
new PropertyMetadata(String.Empty));
#endregion
#region Value
/// <summary>
/// Property value that is being set by this setter.
/// </summary>
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(SetterAction),
new PropertyMetadata(null));
#endregion
#endregion
#region Overrides
protected override void Invoke(object parameter)
{
var target = TargetObject ?? AssociatedObject;
var targetType = target.GetType();
var property = targetType.GetProperty(PropertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
if (property == null)
throw new ArgumentException(String.Format("Property not found: {0}", PropertyName));
if (property.CanWrite == false)
throw new ArgumentException(String.Format("Property is not settable: {0}", PropertyName));
object convertedValue;
if (Value == null)
convertedValue = null;
else
{
var valueType = Value.GetType();
var propertyType = property.PropertyType;
if (valueType == propertyType)
convertedValue = Value;
else
{
var propertyConverter = TypeDescriptor.GetConverter(propertyType);
if (propertyConverter.CanConvertFrom(valueType))
convertedValue = propertyConverter.ConvertFrom(Value);
else if (valueType.IsSubclassOf(propertyType))
convertedValue = Value;
else
throw new ArgumentException(String.Format("Cannot convert type ''{0}'' to ''{1}''.", valueType, propertyType));
}
}
property.SetValue(target, convertedValue);
}
#endregion
}
}
Por mucho que ame XAML, para este tipo de tareas cambio al código. Los comportamientos vinculados son un buen patrón para esto. Tenga en cuenta que Expression Blend 3 proporciona una forma estándar de programar y usar comportamientos. Hay algunos existentes en el sitio de la comunidad Expression.
Solo crea tu propia acción.
namespace WpfUtil
{
using System.Reflection;
using System.Windows;
using System.Windows.Interactivity;
/// <summary>
/// Sets the designated property to the supplied value. TargetObject
/// optionally designates the object on which to set the property. If
/// TargetObject is not supplied then the property is set on the object
/// to which the trigger is attached.
/// </summary>
public class SetPropertyAction : TriggerAction<FrameworkElement>
{
// PropertyName DependencyProperty.
/// <summary>
/// The property to be executed in response to the trigger.
/// </summary>
public string PropertyName
{
get { return (string)GetValue(PropertyNameProperty); }
set { SetValue(PropertyNameProperty, value); }
}
public static readonly DependencyProperty PropertyNameProperty
= DependencyProperty.Register("PropertyName", typeof(string),
typeof(SetPropertyAction));
// PropertyValue DependencyProperty.
/// <summary>
/// The value to set the property to.
/// </summary>
public object PropertyValue
{
get { return GetValue(PropertyValueProperty); }
set { SetValue(PropertyValueProperty, value); }
}
public static readonly DependencyProperty PropertyValueProperty
= DependencyProperty.Register("PropertyValue", typeof(object),
typeof(SetPropertyAction));
// TargetObject DependencyProperty.
/// <summary>
/// Specifies the object upon which to set the property.
/// </summary>
public object TargetObject
{
get { return GetValue(TargetObjectProperty); }
set { SetValue(TargetObjectProperty, value); }
}
public static readonly DependencyProperty TargetObjectProperty
= DependencyProperty.Register("TargetObject", typeof(object),
typeof(SetPropertyAction));
// Private Implementation.
protected override void Invoke(object parameter)
{
object target = TargetObject ?? AssociatedObject;
PropertyInfo propertyInfo = target.GetType().GetProperty(
PropertyName,
BindingFlags.Instance|BindingFlags.Public
|BindingFlags.NonPublic|BindingFlags.InvokeMethod);
propertyInfo.SetValue(target, PropertyValue);
}
}
}
En este caso, me estoy vinculando a una propiedad llamada DialogResult en mi viewmodel.
<Grid>
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<wpf:SetPropertyAction PropertyName="DialogResult" TargetObject="{Binding}"
PropertyValue="{x:Static mvvm:DialogResult.Cancel}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Cancel
</Button>
</Grid>