Miałem ten sam problem i znalazłem rozwiązanie. Znalazłem to pytanie po rozwiązaniu go i widzę, że moje rozwiązanie ma wiele wspólnego z rozwiązaniem Marka. Jednak to podejście jest trochę inne.
Głównym problemem jest to, że zachowania i wyzwalacze są skojarzone z określonym obiektem, więc nie można użyć tego samego wystąpienia zachowania dla wielu różnych skojarzonych obiektów. Gdy definiujesz swoje zachowanie, wbudowany kod XAML wymusza tę relację jeden do jednego. Jednak gdy spróbujesz ustawić zachowanie w stylu, styl ten może zostać ponownie użyty dla wszystkich obiektów, do których ma zastosowanie, co spowoduje zgłoszenie wyjątków w podstawowych klasach zachowania. W rzeczywistości autorzy dołożyli wszelkich starań, aby powstrzymać nas przed próbą zrobienia tego, wiedząc, że to nie zadziała.
Pierwszym problemem jest to, że nie możemy nawet skonstruować wartości ustawiającej zachowanie, ponieważ konstruktor jest wewnętrzny. Potrzebujemy więc własnego zachowania i klas kolekcji wyzwalaczy.
Następnym problemem jest to, że właściwości związane z zachowaniem i wyzwalaczem nie mają metod ustawiających, dlatego można je dodawać tylko za pomocą wbudowanego kodu XAML. Ten problem rozwiązujemy za pomocą naszych własnych dołączonych właściwości, które manipulują podstawowym zachowaniem i właściwościami wyzwalacza.
Trzeci problem polega na tym, że nasza kolekcja zachowań jest dobra tylko dla jednego celu stylu. Ten problem rozwiązujemy, wykorzystując rzadko używaną funkcję XAML, x:Shared="False"
która tworzy nową kopię zasobu za każdym razem, gdy występuje do niego odwołanie.
Ostatnim problemem jest to, że zachowania i wyzwalacze nie są podobne do innych osób wyznaczających styl; nie chcemy zastępować starych zachowań nowymi, ponieważ mogą robić bardzo różne rzeczy. Jeśli więc zaakceptujemy, że po dodaniu zachowania nie można go usunąć (i tak właśnie obecnie działają zachowania), możemy wywnioskować, że zachowania i wyzwalacze powinny być addytywne i można to obsłużyć za pomocą naszych dołączonych właściwości.
Oto próbka wykorzystująca to podejście:
<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>
W przykładzie zastosowano wyzwalacze, ale zachowania działają w ten sam sposób. W przykładzie pokazujemy:
- styl można zastosować do wielu bloków tekstu
- kilka typów powiązań danych działa poprawnie
- akcja debugująca, która generuje tekst w oknie wyjściowym
Oto przykład zachowania, nasz DebugAction
. Właściwie jest to działanie, ale poprzez nadużycie języka nazywamy zachowania, wyzwalacze i działania „zachowaniami”.
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);
}
}
Wreszcie nasze kolekcje i dołączone właściwości, aby to wszystko działało. Analogicznie Interaction.Behaviors
do docelowej właściwości jest wywoływana, SupplementaryInteraction.Behaviors
ponieważ ustawiając tę właściwość, dodasz zachowania do Interaction.Behaviors
i podobnie dla wyzwalaczy.
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);
}
}
i oto masz w pełni funkcjonalne zachowania i wyzwalacze zastosowane poprzez style.
Podsumowując odpowiedzi i ten wspaniały artykuł Blend Behaviors in Styles , doszedłem do tego ogólnego, krótkiego i wygodnego rozwiązania:
Stworzyłem klasę ogólną, która może być odziedziczona przez dowolne zachowanie.
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()); } } } }
Możesz więc po prostu użyć go ponownie z wieloma komponentami takimi jak ten:
public class ComboBoxBehaviour : AttachableForStyleBehavior<ComboBox, ComboBoxBehaviour> { ... }
A w XAML wystarczy zadeklarować:
<Style TargetType="ComboBox"> <Setter Property="behaviours:ComboBoxBehaviour.IsEnabledForStyle" Value="True"/>
Tak więc zasadniczo klasa AttachableForStyleBehavior tworzyła elementy Xaml, rejestrując wystąpienie zachowania dla każdego składnika w stylu. Aby uzyskać więcej informacji, zobacz link.
źródło
1. Utwórz dołączoną właściwość
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. Stwórz zachowanie
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. Utwórz styl i ustaw dołączoną właściwość
<Style TargetType="{x:Type DataGridCell}"> <Setter Property="Behaviors:DataGridCellAttachedProperties.IsSingleClickEditMode" Value="True"/> </Style>
źródło
Mam inny pomysł, aby uniknąć tworzenia dołączonej właściwości dla każdego zachowania:
Interfejs kreatora zachowań:
public interface IBehaviorCreator { Behavior Create(); }
Mała kolekcja pomocnicza:
public class BehaviorCreatorCollection : Collection<IBehaviorCreator> { }
Klasa pomocnicza, która dołącza zachowanie:
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 }
Teraz twoje zachowanie, które implementuje 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(); } }
A teraz użyj go w XAML:
<Style TargetType="{x:Type DataGridCell}"> <Setter Property="helper:BehaviorInStyleAttacher.Behaviors" > <Setter.Value> <helper:BehaviorCreatorCollection> <behaviors:SingleClickEditDataGridCellBehavior/> </helper:BehaviorCreatorCollection> </Setter.Value> </Setter> </Style>
źródło
Nie mogłem znaleźć oryginalnego artykułu, ale udało mi się odtworzyć efekt.
#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>
źródło
Kod zachowania oczekuje wizualizacji, więc możemy dodać ją tylko do wizualizacji. Więc jedyną opcją, jaką mogłem zobaczyć, jest dodanie do jednego z elementów wewnątrz ControlTemplate, aby uzyskać zachowanie dodane do Style i wpłynąć na wszystkie wystąpienie określonej kontrolki.
źródło
Artykuł Wprowadzenie do dołączonych zachowań w WPF implementuje dołączone zachowanie tylko przy użyciu Style i może być również powiązany lub pomocny.
Technika opisana w artykule „Wprowadzenie do przywiązanych zachowań” całkowicie pozwala uniknąć tagów Interaktywność, używając stylu. Nie wiem, czy dzieje się tak tylko dlatego, że jest to bardziej przestarzała technika, czy też nadal zapewnia pewne korzyści, w przypadku których należy ją preferować w niektórych scenariuszach.
źródło
Podoba mi się podejście przedstawione w odpowiedziach Romana Dvoskina i Jonathana Allena w tym wątku. Jednak kiedy po raz pierwszy uczyłem się tej techniki, skorzystałem z tego wpisu na blogu, który zawiera więcej wyjaśnień na temat tej techniki. Aby zobaczyć wszystko w kontekście, oto cały kod źródłowy klasy, o którym autor mówi w swoim poście na blogu.
źródło
Zadeklaruj indywidualne zachowanie / wyzwalacz jako zasoby:
<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>
Wstaw je do kolekcji:
<Button x:Name="Btn1" Content="Button"> <i:Interaction.Triggers> <StaticResourceExtension ResourceKey="ET1"/> </i:Interaction.Triggers> </Button>
źródło
Na podstawie tej odpowiedzi stworzyłem prostsze rozwiązanie, wymagające tylko jednej klasy i nie ma potrzeby implementowania czegoś innego w swoich zachowaniach.
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 }
a przykładowe użycie to
<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>
Nie zapomnij dodać tych xmlns, aby używać ArrayList:
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
źródło