J'ai eu le même problème et j'ai trouvé une solution. J'ai trouvé cette question après l'avoir résolue et je vois que ma solution a beaucoup en commun avec celle de Mark. Cependant, cette approche est un peu différente.
Le problème principal est que les comportements et les déclencheurs s'associent à un objet spécifique et que vous ne pouvez donc pas utiliser la même instance d'un comportement pour plusieurs objets associés différents. Lorsque vous définissez votre comportement en ligne, XAML applique cette relation un-à-un. Cependant, lorsque vous essayez de définir un comportement dans un style, le style peut être réutilisé pour tous les objets auxquels il s'applique et cela lèvera des exceptions dans les classes de comportement de base. En fait, les auteurs ont déployé des efforts considérables pour nous empêcher même d'essayer de le faire, sachant que cela ne fonctionnerait pas.
Le premier problème est que nous ne pouvons même pas construire une valeur de paramètre de comportement car le constructeur est interne. Nous avons donc besoin de notre propre comportement et de déclencher des classes de collecte.
Le problème suivant est que le comportement et les propriétés jointes du déclencheur n'ont pas de setters et ne peuvent donc être ajoutés qu'avec du XAML en ligne. Nous résolvons ce problème avec nos propres propriétés attachées qui manipulent le comportement principal et déclenchent les propriétés.
Le troisième problème est que notre collection de comportements n'est valable que pour une seule cible de style. Nous résolvons ce problème en utilisant une fonctionnalité XAML peu utilisée x:Shared="False"
qui crée une nouvelle copie de la ressource chaque fois qu'elle est référencée.
Le dernier problème est que les comportements et les déclencheurs ne sont pas comme les autres créateurs de style; nous ne voulons pas remplacer les anciens comportements par les nouveaux, car ils pourraient faire des choses très différentes. Donc, si nous acceptons qu'une fois que vous avez ajouté un comportement, vous ne pouvez pas le supprimer (et c'est ainsi que les comportements fonctionnent actuellement), nous pouvons conclure que les comportements et les déclencheurs doivent être additifs et cela peut être géré par nos propriétés attachées.
Voici un exemple utilisant cette approche:
<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>
L'exemple utilise des déclencheurs mais les comportements fonctionnent de la même manière. Dans l'exemple, nous montrons:
- le style peut être appliqué à plusieurs blocs de texte
- plusieurs types de liaison de données fonctionnent tous correctement
- une action de débogage qui génère du texte dans la fenêtre de sortie
Voici un exemple de comportement, notre DebugAction
. Plus exactement, c'est une action, mais par l'abus du langage, nous appelons les comportements, les déclencheurs et les actions des «comportements».
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);
}
}
Enfin, nos collections et propriétés attachées pour que tout cela fonctionne. Par analogie avec Interaction.Behaviors
, la propriété que vous ciblez est appelée SupplementaryInteraction.Behaviors
car en définissant cette propriété, vous ajouterez des comportements à Interaction.Behaviors
et de même pour les déclencheurs.
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);
}
}
et voilà, des comportements et des déclencheurs entièrement fonctionnels appliqués à travers les styles.
En résumant les réponses et cet excellent article Blend Behaviors in Styles , je suis arrivé à cette solution générique courte et pratique:
J'ai créé une classe générique, qui pourrait être héritée par n'importe quel comportement.
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()); } } } }
Vous pouvez donc simplement le réutiliser avec de nombreux composants comme celui-ci:
public class ComboBoxBehaviour : AttachableForStyleBehavior<ComboBox, ComboBoxBehaviour> { ... }
Et en XAML assez pour déclarer:
<Style TargetType="ComboBox"> <Setter Property="behaviours:ComboBoxBehaviour.IsEnabledForStyle" Value="True"/>
Donc, fondamentalement, la classe AttachableForStyleBehavior a fait des choses xaml, enregistrant l'instance de comportement pour chaque composant dans le style. Pour plus de détails, veuillez consulter le lien.
la source
1.Créer une propriété attachée
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. créer un comportement
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.Créez un style et définissez la propriété attachée
<Style TargetType="{x:Type DataGridCell}"> <Setter Property="Behaviors:DataGridCellAttachedProperties.IsSingleClickEditMode" Value="True"/> </Style>
la source
J'ai une autre idée, pour éviter la création d'une propriété attachée pour chaque comportement:
Interface de création de comportement:
public interface IBehaviorCreator { Behavior Create(); }
Petite collection d'aide:
public class BehaviorCreatorCollection : Collection<IBehaviorCreator> { }
Classe d'assistance qui attache le comportement:
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 }
Maintenant votre comportement, qui implémente 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(); } }
Et maintenant, utilisez-le en xaml:
<Style TargetType="{x:Type DataGridCell}"> <Setter Property="helper:BehaviorInStyleAttacher.Behaviors" > <Setter.Value> <helper:BehaviorCreatorCollection> <behaviors:SingleClickEditDataGridCellBehavior/> </helper:BehaviorCreatorCollection> </Setter.Value> </Setter> </Style>
la source
Je n'ai pas trouvé l'article original, mais j'ai pu recréer l'effet.
#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>
la source
Le code de comportement attend un visuel, nous ne pouvons donc l'ajouter que sur un visuel. Donc, la seule option que je pouvais voir est d'ajouter à l'un des éléments à l'intérieur du ControlTemplate afin d'obtenir le comportement ajouté au Style et d'affecter toute l'instance d'un contrôle particulier.
la source
L'article Introduction aux comportements attachés dans WPF implémente un comportement attaché à l'aide de Style uniquement et peut également être lié ou utile.
La technique de l'article «Introduction aux comportements attachés» évite complètement les balises d'interactivité, en utilisant sur Style. Je ne sais pas si c'est simplement parce que c'est une technique plus datée, ou si cela confère encore des avantages là où on devrait la préférer dans certains scénarios.
la source
J'aime l'approche montrée par les réponses de Roman Dvoskin et Jonathan Allen dans ce fil. Quand j'ai appris cette technique pour la première fois, j'ai bénéficié de ce billet de blog qui fournit plus d'explications sur la technique. Et pour tout voir en contexte, voici le code source complet de la classe dont l'auteur parle dans son article de blog.
la source
Déclarez le comportement individuel / le déclencheur en tant que ressources:
<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>
Insérez-les dans la collection:
<Button x:Name="Btn1" Content="Button"> <i:Interaction.Triggers> <StaticResourceExtension ResourceKey="ET1"/> </i:Interaction.Triggers> </Button>
la source
Sur la base de cette réponse, j'ai créé une solution plus simple, avec une seule classe nécessaire et il n'est pas nécessaire d'implémenter autre chose dans vos comportements.
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 }
et l'utilisation de l'échantillon est
<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'oubliez pas d'ajouter ce xmlns pour utiliser ArrayList:
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
la source