Tuve el mismo problema y se me ocurrió una solución. Encontré esta pregunta después de resolverla y veo que mi solución tiene mucho en común con la de Mark. Sin embargo, este enfoque es un poco diferente.
El problema principal es que los comportamientos y los desencadenantes se asocian con un objeto específico y, por lo tanto, no se puede usar la misma instancia de un comportamiento para varios objetos asociados diferentes. Cuando define su comportamiento, XAML en línea aplica esta relación uno a uno. Sin embargo, cuando intenta establecer un comportamiento en un estilo, el estilo se puede reutilizar para todos los objetos a los que se aplica y esto arrojará excepciones en las clases de comportamiento base. De hecho, los autores hicieron un esfuerzo considerable para evitar que intentáramos hacer esto, sabiendo que no funcionaría.
El primer problema es que ni siquiera podemos construir un valor de establecimiento de comportamiento porque el constructor es interno. Entonces necesitamos nuestro propio comportamiento y clases de colección de disparadores.
El siguiente problema es que el comportamiento y las propiedades adjuntas del desencadenador no tienen establecedores, por lo que solo se pueden agregar con XAML en línea. Este problema lo resolvemos con nuestras propias propiedades adjuntas que manipulan el comportamiento primario y las propiedades de activación.
El tercer problema es que nuestra colección de comportamientos solo es buena para un solo objetivo de estilo. Esto lo resolvemos utilizando una característica XAML poco utilizada x:Shared="False"
que crea una nueva copia del recurso cada vez que se hace referencia a él.
El problema final es que los comportamientos y los factores desencadenantes no son como los que establecen otros estilos; no queremos reemplazar los viejos comportamientos con los nuevos porque podrían hacer cosas tremendamente diferentes. Entonces, si aceptamos que una vez que agrega un comportamiento, no puede eliminarlo (y esa es la forma en que funcionan los comportamientos actualmente), podemos concluir que los comportamientos y los desencadenantes deben ser aditivos y esto puede ser manejado por nuestras propiedades adjuntas.
Aquí hay una muestra que utiliza este enfoque:
<Grid>
<Grid.Resources>
<sys:String x:Key="stringResource1">stringResource1</sys:String>
<local:Triggers x:Key="debugTriggers" x:Shared="False">
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:DebugAction Message="DataContext: {0}" MessageParameter="{Binding}"/>
<local:DebugAction Message="ElementName: {0}" MessageParameter="{Binding Text, ElementName=textBlock2}"/>
<local:DebugAction Message="Mentor: {0}" MessageParameter="{Binding Text, RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}}"/>
</i:EventTrigger>
</local:Triggers>
<Style x:Key="debugBehavior" TargetType="FrameworkElement">
<Setter Property="local:SupplementaryInteraction.Triggers" Value="{StaticResource debugTriggers}"/>
</Style>
</Grid.Resources>
<StackPanel DataContext="{StaticResource stringResource1}">
<TextBlock Name="textBlock1" Text="textBlock1" Style="{StaticResource debugBehavior}"/>
<TextBlock Name="textBlock2" Text="textBlock2" Style="{StaticResource debugBehavior}"/>
<TextBlock Name="textBlock3" Text="textBlock3" Style="{StaticResource debugBehavior}"/>
</StackPanel>
</Grid>
El ejemplo usa disparadores pero los comportamientos funcionan de la misma manera. En el ejemplo, mostramos:
- el estilo se puede aplicar a varios bloques de texto
- varios tipos de enlace de datos funcionan correctamente
- una acción de depuración que genera texto en la ventana de salida
Aquí hay un comportamiento de ejemplo, nuestro DebugAction
. Más propiamente es una acción, pero a través del abuso del lenguaje llamamos comportamientos, disparadores y acciones "comportamientos".
public class DebugAction : TriggerAction<DependencyObject>
{
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(string), typeof(DebugAction), new UIPropertyMetadata(""));
public object MessageParameter
{
get { return (object)GetValue(MessageParameterProperty); }
set { SetValue(MessageParameterProperty, value); }
}
public static readonly DependencyProperty MessageParameterProperty =
DependencyProperty.Register("MessageParameter", typeof(object), typeof(DebugAction), new UIPropertyMetadata(null));
protected override void Invoke(object parameter)
{
Debug.WriteLine(Message, MessageParameter, AssociatedObject, parameter);
}
}
Finalmente, nuestras colecciones y propiedades adjuntas para que todo esto funcione. Por analogía con Interaction.Behaviors
, la propiedad a la que se dirige se llama SupplementaryInteraction.Behaviors
porque al establecer esta propiedad, agregará comportamientos Interaction.Behaviors
y también para los desencadenantes.
public class Behaviors : List<Behavior>
{
}
public class Triggers : List<TriggerBase>
{
}
public static class SupplementaryInteraction
{
public static Behaviors GetBehaviors(DependencyObject obj)
{
return (Behaviors)obj.GetValue(BehaviorsProperty);
}
public static void SetBehaviors(DependencyObject obj, Behaviors value)
{
obj.SetValue(BehaviorsProperty, value);
}
public static readonly DependencyProperty BehaviorsProperty =
DependencyProperty.RegisterAttached("Behaviors", typeof(Behaviors), typeof(SupplementaryInteraction), new UIPropertyMetadata(null, OnPropertyBehaviorsChanged));
private static void OnPropertyBehaviorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behaviors = Interaction.GetBehaviors(d);
foreach (var behavior in e.NewValue as Behaviors) behaviors.Add(behavior);
}
public static Triggers GetTriggers(DependencyObject obj)
{
return (Triggers)obj.GetValue(TriggersProperty);
}
public static void SetTriggers(DependencyObject obj, Triggers value)
{
obj.SetValue(TriggersProperty, value);
}
public static readonly DependencyProperty TriggersProperty =
DependencyProperty.RegisterAttached("Triggers", typeof(Triggers), typeof(SupplementaryInteraction), new UIPropertyMetadata(null, OnPropertyTriggersChanged));
private static void OnPropertyTriggersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var triggers = Interaction.GetTriggers(d);
foreach (var trigger in e.NewValue as Triggers) triggers.Add(trigger);
}
}
y ahí lo tiene, comportamientos completamente funcionales y disparadores aplicados a través de estilos.