У меня была такая же проблема, и я нашел решение. Я нашел этот вопрос после того, как решил его, и вижу, что мое решение имеет много общего с решением Марка. Однако этот подход немного отличается.
Основная проблема заключается в том, что поведения и триггеры связаны с определенным объектом, и поэтому вы не можете использовать один и тот же экземпляр поведения для нескольких разных связанных объектов. Когда вы определяете свое поведение, встроенный 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);
}
}
Вот и все, полнофункциональное поведение и триггеры, применяемые с помощью стилей.
Подводя итог ответам и этой замечательной статье Blend Behaviors in Styles , я пришел к этому общему короткому и удобному решению:
Я сделал общий класс, который мог быть унаследован любым поведением.
public class AttachableForStyleBehavior<TComponent, TBehavior> : Behavior<TComponent> where TComponent : System.Windows.DependencyObject where TBehavior : AttachableForStyleBehavior<TComponent, TBehavior> , new () { public static DependencyProperty IsEnabledForStyleProperty = DependencyProperty.RegisterAttached("IsEnabledForStyle", typeof(bool), typeof(AttachableForStyleBehavior<TComponent, TBehavior>), new FrameworkPropertyMetadata(false, OnIsEnabledForStyleChanged)); public bool IsEnabledForStyle { get { return (bool)GetValue(IsEnabledForStyleProperty); } set { SetValue(IsEnabledForStyleProperty, value); } } private static void OnIsEnabledForStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { UIElement uie = d as UIElement; if (uie != null) { var behColl = Interaction.GetBehaviors(uie); var existingBehavior = behColl.FirstOrDefault(b => b.GetType() == typeof(TBehavior)) as TBehavior; if ((bool)e.NewValue == false && existingBehavior != null) { behColl.Remove(existingBehavior); } else if ((bool)e.NewValue == true && existingBehavior == null) { behColl.Add(new TBehavior()); } } } }
Таким образом, вы можете просто повторно использовать его с множеством таких компонентов:
public class ComboBoxBehaviour : AttachableForStyleBehavior<ComboBox, ComboBoxBehaviour> { ... }
А в XAML достаточно объявить:
<Style TargetType="ComboBox"> <Setter Property="behaviours:ComboBoxBehaviour.IsEnabledForStyle" Value="True"/>
Итак, класс AttachableForStyleBehavior по сути создавал xaml-вещи, регистрируя экземпляр поведения для каждого компонента в стиле. Подробности по ссылке.
источник
1. создать прикрепленную собственность
public static class DataGridCellAttachedProperties { //Register new attached property public static readonly DependencyProperty IsSingleClickEditModeProperty = DependencyProperty.RegisterAttached("IsSingleClickEditMode", typeof(bool), typeof(DataGridCellAttachedProperties), new UIPropertyMetadata(false, OnPropertyIsSingleClickEditModeChanged)); private static void OnPropertyIsSingleClickEditModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var dataGridCell = d as DataGridCell; if (dataGridCell == null) return; var isSingleEditMode = GetIsSingleClickEditMode(d); var behaviors = Interaction.GetBehaviors(d); var singleClickEditBehavior = behaviors.SingleOrDefault(x => x is SingleClickEditDataGridCellBehavior); if (singleClickEditBehavior != null && !isSingleEditMode) behaviors.Remove(singleClickEditBehavior); else if (singleClickEditBehavior == null && isSingleEditMode) { singleClickEditBehavior = new SingleClickEditDataGridCellBehavior(); behaviors.Add(singleClickEditBehavior); } } public static bool GetIsSingleClickEditMode(DependencyObject obj) { return (bool) obj.GetValue(IsSingleClickEditModeProperty); } public static void SetIsSingleClickEditMode(DependencyObject obj, bool value) { obj.SetValue(IsSingleClickEditModeProperty, value); } }
2. создать поведение
public class SingleClickEditDataGridCellBehavior:Behavior<DataGridCell> { protected override void OnAttached() { base.OnAttached(); AssociatedObject.PreviewMouseLeftButtonDown += DataGridCellPreviewMouseLeftButtonDown; } protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.PreviewMouseLeftButtonDown += DataGridCellPreviewMouseLeftButtonDown; } void DataGridCellPreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { DataGridCell cell = sender as DataGridCell; if (cell != null && !cell.IsEditing && !cell.IsReadOnly) { if (!cell.IsFocused) { cell.Focus(); } DataGrid dataGrid = LogicalTreeWalker.FindParentOfType<DataGrid>(cell); //FindVisualParent<DataGrid>(cell); if (dataGrid != null) { if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow) { if (!cell.IsSelected) cell.IsSelected = true; } else { DataGridRow row = LogicalTreeWalker.FindParentOfType<DataGridRow>(cell); //FindVisualParent<DataGridRow>(cell); if (row != null && !row.IsSelected) { row.IsSelected = true; } } } } } }
3.Создайте стиль и установите прикрепленное свойство
<Style TargetType="{x:Type DataGridCell}"> <Setter Property="Behaviors:DataGridCellAttachedProperties.IsSingleClickEditMode" Value="True"/> </Style>
источник
У меня есть другая идея, чтобы избежать создания прикрепленного свойства для каждого поведения:
Интерфейс создателя поведения:
public interface IBehaviorCreator { Behavior Create(); }
Небольшая коллекция помощников:
public class BehaviorCreatorCollection : Collection<IBehaviorCreator> { }
Класс-помощник, который прикрепляет поведение:
public static class BehaviorInStyleAttacher { #region Attached Properties public static readonly DependencyProperty BehaviorsProperty = DependencyProperty.RegisterAttached( "Behaviors", typeof(BehaviorCreatorCollection), typeof(BehaviorInStyleAttacher), new UIPropertyMetadata(null, OnBehaviorsChanged)); #endregion #region Getter and Setter of Attached Properties public static BehaviorCreatorCollection GetBehaviors(TreeView treeView) { return (BehaviorCreatorCollection)treeView.GetValue(BehaviorsProperty); } public static void SetBehaviors( TreeView treeView, BehaviorCreatorCollection value) { treeView.SetValue(BehaviorsProperty, value); } #endregion #region on property changed methods private static void OnBehaviorsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e) { if (e.NewValue is BehaviorCreatorCollection == false) return; BehaviorCreatorCollection newBehaviorCollection = e.NewValue as BehaviorCreatorCollection; BehaviorCollection behaviorCollection = Interaction.GetBehaviors(depObj); behaviorCollection.Clear(); foreach (IBehaviorCreator behavior in newBehaviorCollection) { behaviorCollection.Add(behavior.Create()); } } #endregion }
Теперь ваше поведение, которое реализует IBehaviorCreator:
public class SingleClickEditDataGridCellBehavior:Behavior<DataGridCell>, IBehaviorCreator { //some code ... public Behavior Create() { // here of course you can also set properties if required return new SingleClickEditDataGridCellBehavior(); } }
А теперь используйте его в xaml:
<Style TargetType="{x:Type DataGridCell}"> <Setter Property="helper:BehaviorInStyleAttacher.Behaviors" > <Setter.Value> <helper:BehaviorCreatorCollection> <behaviors:SingleClickEditDataGridCellBehavior/> </helper:BehaviorCreatorCollection> </Setter.Value> </Setter> </Style>
источник
Мне не удалось найти исходную статью, но мне удалось воссоздать эффект.
#region Attached Properties Boilerplate public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(ScrollIntoViewBehavior), new PropertyMetadata(false, OnIsActiveChanged)); public static bool GetIsActive(FrameworkElement control) { return (bool)control.GetValue(IsActiveProperty); } public static void SetIsActive( FrameworkElement control, bool value) { control.SetValue(IsActiveProperty, value); } private static void OnIsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var behaviors = Interaction.GetBehaviors(d); var newValue = (bool)e.NewValue; if (newValue) { //add the behavior if we don't already have one if (!behaviors.OfType<ScrollIntoViewBehavior>().Any()) { behaviors.Add(new ScrollIntoViewBehavior()); } } else { //remove any instance of the behavior. (There should only be one, but just in case.) foreach (var item in behaviors.ToArray()) { if (item is ScrollIntoViewBehavior) behaviors.Remove(item); } } } #endregion
<Style TargetType="Button"> <Setter Property="Blah:ScrollIntoViewBehavior.IsActive" Value="True" /> </Style>
источник
Код поведения ожидает визуала, поэтому мы можем добавить его только на визуал. Итак, единственный вариант, который я мог видеть, - это добавить к одному из элементов внутри ControlTemplate, чтобы добавить поведение в Style и повлиять на все экземпляры определенного элемента управления.
источник
В статье « Введение в присоединенное поведение в WPF» присоединенное поведение реализуется только с использованием стиля, а также может быть связано или полезно.
Техника, описанная в статье «Введение в привязанное поведение», позволяет полностью избежать использования тегов интерактивности в Style. Я не знаю, просто ли это потому, что это более устаревшая техника, или, если она по-прежнему дает некоторые преимущества там, где ее следует предпочесть в некоторых сценариях.
источник
Мне нравится подход, показанный ответами Романа Двоскина и Джонатана Аллена в этой ветке. Когда я впервые изучал эту технику, я извлек для себя пользу из этого сообщения в блоге, в котором дается более подробное объяснение этой техники. И чтобы увидеть все в контексте, вот полный исходный код класса, о котором автор говорит в своем сообщении в блоге.
источник
Объявите индивидуальное поведение / триггер как Ресурсы:
<Window.Resources> <i:EventTrigger x:Key="ET1" EventName="Click"> <ei:ChangePropertyAction PropertyName="Background"> <ei:ChangePropertyAction.Value> <SolidColorBrush Color="#FFDAD32D"/> </ei:ChangePropertyAction.Value> </ei:ChangePropertyAction> </i:EventTrigger> </Window.Resources>
Вставьте их в коллекцию:
<Button x:Name="Btn1" Content="Button"> <i:Interaction.Triggers> <StaticResourceExtension ResourceKey="ET1"/> </i:Interaction.Triggers> </Button>
источник
Основываясь на этом ответе, я сделал более простое решение: нужен только один класс, и нет необходимости реализовывать что-то еще в вашем поведении.
public static class BehaviorInStyleAttacher { #region Attached Properties public static readonly DependencyProperty BehaviorsProperty = DependencyProperty.RegisterAttached( "Behaviors", typeof(IEnumerable), typeof(BehaviorInStyleAttacher), new UIPropertyMetadata(null, OnBehaviorsChanged)); #endregion #region Getter and Setter of Attached Properties public static IEnumerable GetBehaviors(DependencyObject dependencyObject) { return (IEnumerable)dependencyObject.GetValue(BehaviorsProperty); } public static void SetBehaviors( DependencyObject dependencyObject, IEnumerable value) { dependencyObject.SetValue(BehaviorsProperty, value); } #endregion #region on property changed methods private static void OnBehaviorsChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e) { if (e.NewValue is IEnumerable == false) return; var newBehaviorCollection = e.NewValue as IEnumerable; BehaviorCollection behaviorCollection = Interaction.GetBehaviors(depObj); behaviorCollection.Clear(); foreach (Behavior behavior in newBehaviorCollection) { // you need to make a copy of behavior in order to attach it to several controls var copy = behavior.Clone() as Behavior; behaviorCollection.Add(copy); } } #endregion }
и пример использования
<Style TargetType="telerik:RadComboBox" x:Key="MultiPeriodSelectableRadComboBox"> <Setter Property="AllowMultipleSelection" Value="True" /> <Setter Property="behaviors:BehaviorInStyleAttacher.Behaviors"> <Setter.Value> <collections:ArrayList> <behaviors:MultiSelectRadComboBoxBehavior SelectedItems="{Binding SelectedPeriods}" DelayUpdateUntilDropDownClosed="True" SortSelection="True" ReverseSort="True" /> </collections:ArrayList> </Setter.Value> </Setter> </Style>
Не забудьте добавить этот xmlns для использования ArrayList:
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
источник