У меня была такая же проблема, и я нашел решение. Я нашел этот вопрос после того, как решил его, и я вижу, что мое решение имеет много общего с решением Марка. Однако такой подход немного отличается.
Основная проблема заключается в том, что поведения и триггеры связаны с конкретным объектом, и поэтому вы не можете использовать один и тот же экземпляр поведения для нескольких различных связанных объектов. Когда вы определяете свое поведение inline, XAML обеспечивает это взаимно-однозначное отношение. Однако, когда вы пытаетесь установить поведение в стиле, стиль может быть повторно использован для всех объектов, к которым он применяется, и это вызовет исключения в базовых классах поведения. Фактически авторы приложили значительные усилия, чтобы помешать нам даже попытаться сделать это, зная, что это не сработает.
Первая проблема заключается в том, что мы не можем даже создать значение установщика поведения, потому что конструктор является внутренним. Поэтому нам нужно наше собственное поведение и триггерные классы коллекции.
Следующая проблема заключается в том, что поведение и свойства, связанные с триггером, не имеют установщиков, поэтому их можно добавлять только с помощью встроенного XAML. Эту проблему мы решаем с помощью наших собственных прикрепленных свойств, которые управляют основным поведением и запускают свойства.
Третья проблема заключается в том, что наша коллекция поведений хороша только для одной цели стиля. Это мы решаем, используя малоиспользуемую функцию XAML x:Shared="False"
, которая создает новую копию ресурса каждый раз, когда на него ссылаются.
Последняя проблема заключается в том, что поведение и триггеры не похожи на другие установки стиля; мы не хотим заменять старое поведение новым поведением, потому что они могут делать совершенно разные вещи. Поэтому, если мы примем, что, добавив поведение, вы не сможете его убрать (и именно так сейчас работает поведение), мы можем заключить, что поведения и триггеры должны быть аддитивными, и это может быть обработано нашими присоединенными свойствами.
Вот пример, использующий этот подход:
<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>
В примере используются триггеры, но поведение работает аналогично. В примере мы показываем:
- стиль может быть применен к нескольким текстовым блокам
- все типы привязки данных работают правильно
- действие отладки, которое генерирует текст в окне вывода
Вот пример поведения, нашего DebugAction
. Точнее, это действие, но через злоупотребление языком мы называем поведение, триггеры и действия «поведением».
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);
}
}
Наконец, наши коллекции и прикрепленные свойства, чтобы все это работало. По аналогии с Interaction.Behaviors
целевое свойство называется SupplementaryInteraction.Behaviors
, поскольку, устанавливая это свойство, вы добавляете поведения к Interaction.Behaviors
и аналогично для триггеров.
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);
}
}
и вот оно, полнофункциональные поведения и триггеры, применяемые с помощью стилей.