Tive o mesmo problema e encontrei uma solução. Descobri essa questão depois de resolvê-la e vejo que minha solução tem muito em comum com a de Mark. No entanto, essa abordagem é um pouco diferente.
O principal problema é que comportamentos e gatilhos se associam a um objeto específico e, portanto, você não pode usar a mesma instância de um comportamento para vários objetos associados diferentes. Quando você define seu comportamento em linha, o XAML impõe esse relacionamento um a um. No entanto, quando você tenta definir um comportamento em um estilo, o estilo pode ser reutilizado para todos os objetos aos quais se aplica e isso lançará exceções nas classes de comportamento base. Na verdade, os autores fizeram um esforço considerável para nos impedir de tentar fazer isso, sabendo que não funcionaria.
O primeiro problema é que não podemos nem construir um valor configurador de comportamento porque o construtor é interno. Portanto, precisamos de nosso próprio comportamento e classes de coleção de gatilho.
O próximo problema é que o comportamento e as propriedades anexadas ao gatilho não têm configuradores e, portanto, só podem ser adicionados com XAML in-line. Resolvemos esse problema com nossas próprias propriedades anexadas que manipulam o comportamento primário e as propriedades do gatilho.
O terceiro problema é que nossa coleção de comportamento só é boa para um único destino de estilo. Isso nós resolvemos utilizando um recurso XAML pouco usado x:Shared="False"
que cria uma nova cópia do recurso cada vez que ele é referenciado.
O problema final é que os comportamentos e gatilhos não são como outros definidores de estilo; não queremos substituir os antigos comportamentos pelos novos, porque eles poderiam fazer coisas totalmente diferentes. Portanto, se aceitarmos que, depois de adicionar um comportamento, você não pode removê-lo (e é assim que os comportamentos funcionam atualmente), podemos concluir que os comportamentos e os gatilhos devem ser aditivos e isso pode ser tratado por nossas propriedades anexadas.
Aqui está um exemplo usando esta abordagem:
<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>
O exemplo usa gatilhos, mas os comportamentos funcionam da mesma maneira. No exemplo, mostramos:
- o estilo pode ser aplicado a vários blocos de texto
- vários tipos de vinculação de dados funcionam corretamente
- uma ação de depuração que gera texto na janela de saída
Aqui está um exemplo de comportamento, nosso DebugAction
. Mais propriamente, é uma ação, mas através do abuso da linguagem chamamos comportamentos, gatilhos e ações de "comportamentos".
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, nossas coleções e propriedades anexadas para fazer tudo isso funcionar. Por analogia com Interaction.Behaviors
, a propriedade de destino é chamada SupplementaryInteraction.Behaviors
porque, ao definir essa propriedade, você adicionará comportamentos a Interaction.Behaviors
e da mesma forma para gatilhos.
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);
}
}
e aí está, comportamentos totalmente funcionais e gatilhos aplicados por meio de estilos.
Resumindo as respostas e este ótimo artigo Combine Behaviors in Styles , cheguei a esta solução genérica curta e conveniente:
Fiz uma classe genérica, que poderia ser herdada por qualquer comportamento.
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()); } } } }
Então, você pode simplesmente reutilizá-lo com muitos componentes como este:
public class ComboBoxBehaviour : AttachableForStyleBehavior<ComboBox, ComboBoxBehaviour> { ... }
E em XAML o suficiente para declarar:
<Style TargetType="ComboBox"> <Setter Property="behaviours:ComboBoxBehaviour.IsEnabledForStyle" Value="True"/>
Então, basicamente, a classe AttachableForStyleBehavior fez coisas xaml, registrando a instância do comportamento para cada componente no estilo. Para mais detalhes, consulte o link.
fonte
1. Criar propriedade anexada
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. Crie um comportamento
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.Crie um estilo e defina a propriedade anexada
<Style TargetType="{x:Type DataGridCell}"> <Setter Property="Behaviors:DataGridCellAttachedProperties.IsSingleClickEditMode" Value="True"/> </Style>
fonte
Tenho outra ideia, para evitar a criação de uma propriedade anexada para cada comportamento:
Interface do criador de comportamento:
public interface IBehaviorCreator { Behavior Create(); }
Coleção pequena de ajudantes:
public class BehaviorCreatorCollection : Collection<IBehaviorCreator> { }
Classe auxiliar que anexa o comportamento:
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 }
Agora seu comportamento, que implementa 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(); } }
E agora use em xaml:
<Style TargetType="{x:Type DataGridCell}"> <Setter Property="helper:BehaviorInStyleAttacher.Behaviors" > <Setter.Value> <helper:BehaviorCreatorCollection> <behaviors:SingleClickEditDataGridCellBehavior/> </helper:BehaviorCreatorCollection> </Setter.Value> </Setter> </Style>
fonte
Não consegui encontrar o artigo original, mas consegui recriar o efeito.
#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>
fonte
O código de comportamento espera um Visual, portanto, podemos adicioná-lo apenas em um visual. Portanto, a única opção que vejo é adicionar a um dos elementos dentro do ControlTemplate para obter o comportamento adicionado ao Style e afetar todas as instâncias de um controle específico.
fonte
O artigo Introdução aos comportamentos anexados no WPF implementa um comportamento anexado usando apenas Estilo e também pode ser relacionado ou útil.
A técnica no artigo "Introdução aos comportamentos anexados" evita as tags de interatividade por completo, usando em Estilo. Não sei se é por se tratar de uma técnica mais antiquada, ou se ainda confere alguns benefícios onde se deve preferir em alguns cenários.
fonte
Eu gosto da abordagem mostrada pelas respostas de Roman Dvoskin e Jonathan Allen neste tópico. Porém, quando eu estava aprendendo essa técnica pela primeira vez, aproveitei esta postagem do blog que fornece mais explicações sobre a técnica. E para ver tudo em contexto, aqui está o código-fonte completo da classe sobre a qual o autor fala em sua postagem no blog.
fonte
Declare comportamento individual / gatilho como recursos:
<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>
Insira-os na coleção:
<Button x:Name="Btn1" Content="Button"> <i:Interaction.Triggers> <StaticResourceExtension ResourceKey="ET1"/> </i:Interaction.Triggers> </Button>
fonte
Com base nessa resposta, criei uma solução mais simples, com apenas uma classe necessária e não há necessidade de implementar outra coisa em seus comportamentos.
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 }
e o uso da amostra é
<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>
Não se esqueça de adicionar este xmlns para usar ArrayList:
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
fonte