Come aggiungere un comportamento di fusione in un setter di stile

Ho creato un comportamento Blend per Button. Come posso impostarlo su tutti i miei pulsanti nell’app.

 

Tuttavia, quando provo:

        

Ottengo l’errore

La proprietà “Behaviors” non ha un setter accessibile.

Ho avuto lo stesso problema e ho trovato una soluzione. Ho trovato questa domanda dopo averlo risolto e vedo che la mia soluzione ha molto in comune con Mark’s. Tuttavia, questo approccio è leggermente diverso.

Il problema principale è che comportamenti e trigger si associano a un object specifico e quindi non è ansible utilizzare la stessa istanza di un comportamento per più oggetti associati diversi. Quando definisci il tuo comportamento XAML in linea applica questa relazione uno a uno. Tuttavia, quando si tenta di impostare un comportamento in uno stile, lo stile può essere riutilizzato per tutti gli oggetti a cui si applica e questo genererà eccezioni nelle classi di comportamento di base. In effetti, gli autori si sono impegnati in un notevole sforzo per impedirci di provare a farlo, sapendo che non avrebbe funzionato.

Il primo problema è che non possiamo nemmeno build un valore di settaggio del comportamento perché il costruttore è interno. Quindi abbiamo bisogno del nostro comportamento e innescare classi di raccolta.

Il prossimo problema è che il comportamento e le proprietà associate al trigger non hanno setter e quindi possono essere aggiunti solo con XAML in linea. Questo problema lo risolviamo con le nostre proprietà associate che manipolano il comportamento primario e le proprietà di innesco.

Il terzo problema è che la nostra collezione comportamentale va bene solo per un singolo target di stile. Questo risolviamo utilizzando una caratteristica XAML poco utilizzata x:Shared="False" che crea una nuova copia della risorsa ogni volta che viene fatto riferimento.

Il problema finale è che i comportamenti e i trigger non sono come altri setter di stile; non vogliamo sostituire i vecchi comportamenti con i nuovi comportamenti perché potrebbero fare cose molto diverse. Quindi, se accettiamo che una volta aggiunto un comportamento non è ansible portarlo via (e questo è il modo in cui funzionano attualmente i comportamenti), possiamo concludere che i comportamenti e i trigger dovrebbero essere additivi e questo può essere gestito dalle nostre proprietà allegate.

Ecco un esempio che utilizza questo approccio:

   stringResource1                

L’esempio utilizza i trigger, ma i comportamenti funzionano allo stesso modo. Nell’esempio, mostriamo:

  • lo stile può essere applicato a più blocchi di testo
  • diversi tipi di dati vincolanti tutti funzionano correttamente
  • un’azione di debug che genera testo nella finestra di output

Ecco un esempio di comportamento, la nostra DebugAction . Più propriamente è un’azione ma attraverso l’abuso del linguaggio chiamiamo comportamenti, trigger e azioni “comportamenti”.

 public class DebugAction : TriggerAction { 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); } } 

Infine, le nostre collezioni e le proprietà allegate per far funzionare tutto questo. Per analogia con Interaction.Behaviors , la proprietà target viene chiamata SupplementaryInteraction.Behaviors perché impostando questa proprietà, si aggiungeranno comportamenti a Interaction.Behaviors e allo stesso modo trigger.

 public class Behaviors : List { } public class Triggers : List { } 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 lì ce l’hai, comportamenti e trigger completamente funzionali applicati attraverso gli stili.

1. Creare una proprietà associata

 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. Crea un comportamento

 public class SingleClickEditDataGridCellBehavior:Behavior { 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(cell); //FindVisualParent(cell); if (dataGrid != null) { if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow) { if (!cell.IsSelected) cell.IsSelected = true; } else { DataGridRow row = LogicalTreeWalker.FindParentOfType(cell); //FindVisualParent(cell); if (row != null && !row.IsSelected) { row.IsSelected = true; } } } } } } 

3. Crea uno stile e imposta la proprietà associata

   

Sommando le risposte e questo fantastico articolo Blend Behaviors in Styles , sono arrivato a questa soluzione generica, breve e comoda:

Ho creato una class generica, che potrebbe essere ereditata da qualsiasi comportamento.

 public class AttachableForStyleBehavior : Behavior where TComponent : System.Windows.DependencyObject where TBehavior : AttachableForStyleBehavior , new () { public static DependencyProperty IsEnabledForStyleProperty = DependencyProperty.RegisterAttached("IsEnabledForStyle", typeof(bool), typeof(AttachableForStyleBehavior), 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()); } } } } 

Quindi potresti semplicemente riutilizzarlo con molti componenti come questo:

 public class ComboBoxBehaviour : AttachableForStyleBehavior { ... } 

E in XAML basta dichiarare:

   

Quindi, in modo basilare, la class AttachableForStyleBehavior ha reso le cose xaml, registrando l’istanza di comportamento per ogni componente in stile. Per maggiori dettagli, si prega di consultare il link.

Ho un’altra idea, per evitare la creazione di una proprietà associata per ogni comportamento:

  1. Interfaccia di creazione del comportamento:

     public interface IBehaviorCreator { Behavior Create(); } 
  2. Piccola collezione di aiuto:

     public class BehaviorCreatorCollection : Collection { } 
  3. Classe helper che attacca il 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 } 
  4. Ora il tuo comportamento, che implementa IBehaviorCreator:

     public class SingleClickEditDataGridCellBehavior:Behavior, IBehaviorCreator { //some code ... public Behavior Create() { // here of course you can also set properties if required return new SingleClickEditDataGridCellBehavior(); } } 
  5. E ora usalo in xaml:

      

Non sono riuscito a trovare l’articolo originale ma sono riuscito a ricreare l’effetto.

 #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().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 
  

Dichiarare comportamento / trigger individuale come risorse:

          

Inseriscili nella raccolta:

  

Il codice di comportamento si aspetta un Visual, quindi possiamo aggiungerlo solo su un visual. Quindi l’unica opzione che ho potuto vedere è quella di aggiungere a uno degli elementi all’interno del ControlTemplate in modo da ottenere il comportamento aggiunto allo stile e influire su tutte le istanze di un particolare controllo.

L’articolo Introduzione ai comportamenti associati in WPF implementa un comportamento collegato utilizzando solo lo stile e può anche essere correlato o utile.

La tecnica contenuta nell’articolo “Introduzione ai comportamenti attaccati” evita completamente i tag di Interattività, utilizzando su Stile. Non so se questo è solo perché è una tecnica più datata, o, se questo ancora conferisce alcuni benefici dove uno dovrebbe preferirlo in alcuni scenari.

Mi piace l’approccio mostrato dalle risposte di Roman Dvoskin e Jonathan Allen in questa discussione. Tuttavia, quando stavo imparando questa tecnica per la prima volta, ho tratto beneficio da questo post sul blog che fornisce ulteriori spiegazioni sulla tecnica. E per vedere tutto nel contesto, ecco l’intero codice sorgente per la class di cui parla l’autore nel suo post sul blog.