2009-10-30 5 views
76

He creado un comportamiento de mezcla para el botón. ¿Cómo puedo configurar eso para todos mis botones en la aplicación?Cómo agregar un comportamiento de mezcla en un Setter de estilo

<Button ...> 
    <i:Interaction.Behaviors> 
    <local:MyBehavior /> 
    </i:Interaction.Behaviors> 
</Button> 

Sin embargo, cuando intento:

<Style> 
    <Setter Property="i:Interaction.Behaviors"> 
    <Setter.Value> 
     <local:MyBehavior /> 
    </Setter.Value> 
    </Setter> 
</Style> 

me sale el error

La propiedad "comportamientos" no tiene un colocador accesible.

Respuesta

0

El código de comportamiento espera un Visual, por lo que solo podemos agregarlo en forma visual. Así que la única opción que pude ver es agregar a uno de los elementos dentro de ControlTemplate para obtener el comportamiento agregado al Estilo y afectar a todas las instancias de un control en particular.

71

Tuve el mismo problema y he encontrado una solución. Encontré esta pregunta después de que la resolví y veo que mi solución tiene mucho en común con la de Mark. Sin embargo, este enfoque es un poco diferente.

El problema principal es que los comportamientos y desencadenantes se asocian con un objeto específico y, por lo tanto, no puede usar la misma instancia de comportamiento para múltiples objetos asociados diferentes. Cuando define su comportamiento en línea, XAML impone esta relación de uno a uno. Sin embargo, cuando intenta establecer un comportamiento en un estilo, el estilo puede reutilizarse para todos los objetos a los que se aplica y esto arrojará excepciones en las clases de comportamiento base. De hecho, los autores hicieron un esfuerzo considerable para evitar que intentáramos hacer esto, sabiendo que no funcionaría.

El primer problema es que ni siquiera podemos construir un valor de establecimiento de comportamiento porque el constructor es interno. Entonces necesitamos nuestro propio comportamiento y activar las clases de recolección.

El siguiente problema es que el comportamiento y las propiedades adjuntas de desencadenador no tienen definidores y, por lo tanto, solo se pueden agregar con XAML en línea. Este problema lo resolvemos con nuestras propias propiedades adjuntas que manipulan el comportamiento primario y las propiedades de activación.

El tercer problema es que nuestra recopilación de comportamiento solo es buena para un solo objetivo de estilo. Esto lo resolvemos mediante la utilización de una característica XAML poco utilizada x:Shared="False" que crea una nueva copia del recurso cada vez que se hace referencia a ella.

El problema final es que los comportamientos y desencadenantes no son como otros establecedores de estilo; no queremos reemplazar los viejos comportamientos con los nuevos comportamientos porque podrían hacer cosas completamente diferentes. Entonces, si aceptamos que una vez que agrega un comportamiento que no puede quitar (y esa es la forma en que los comportamientos funcionan actualmente), podemos concluir que los comportamientos y desencadenantes deben ser aditivos y esto puede ser manejado por nuestras propiedades adjuntas.

Aquí está un ejemplo que utiliza este enfoque:

<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> 

El ejemplo utiliza disparadores pero los comportamientos funcionan de la misma manera.En el ejemplo, se muestra:

  • el estilo se puede aplicar a varios bloques de texto
  • varios tipos de encuadernación todo el trabajo correctamente
  • una acción de depuración que genera el texto en la ventana de salida de datos

Aquí hay un comportamiento de ejemplo, nuestro DebugAction. Más correctamente es una acción, pero a través del abuso del lenguaje llamamos comportamientos, desencadenantes y acciones "comportamientos".

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, nuestras colecciones y propiedades adjuntas para que todo esto funcione. Por analogía con Interaction.Behaviors, la propiedad a la que se dirige se llama SupplementaryInteraction.Behaviors porque al establecer esta propiedad, agregará comportamientos a Interaction.Behaviors y lo mismo ocurrirá con los desencadenadores.

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); 
    } 
} 

y ahí lo tiene, los comportamientos totalmente funcionales y los factores desencadenantes aplicados a través de los estilos.

+0

Grandes cosas, esto funciona muy bien. Me di cuenta de que si coloca el estilo, por ejemplo, en los recursos de UserControl, e.NewValue puede ser nulo al principio (puede depender del control utilizado; estoy usando esto en XamDataTreeNodeControl en Infragistics XamDataTree). Así que agregué un pequeño control de cordura en OnPropertyTriggersChanged: if (e.NewValue! = Null) – MetalMikester

+0

¿Alguien ha tenido un problema con este enfoque al aplicar el Setter en un ** Estilo ** implícito? He conseguido que funcione bien con un estilo no implícito (uno con una clave), pero obtengo una excepción de referencia cíclica si está en un estilo implícito. –

+1

Buena solución, pero desafortunadamente no funciona en WinRT, porque x: Shared no existe en esta plataforma ... –

4

No pude encontrar el artículo original pero pude recrear el efecto.

#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> 
+0

teniendo que escribir th es para cada comportamiento es un poco de un PITA sin embargo. –

0

El artículo Introduction to Attached Behaviors in WPF implementa un comportamiento adjunto utilizando sólo estilo, y también pueden estar relacionados o útiles.

La técnica en el artículo "Introducción a comportamientos adjuntos" evita por completo las etiquetas de Interactividad, utilizando Style. No sé si esto se debe solo a que es una técnica más anticuada, o, si eso todavía le confiere algunos beneficios donde uno debería preferirlo en algunos escenarios.

+1

Esto no es un comportamiento de mezcla, es un "comportamiento" a través de una propiedad adjunta simple. –

16

1.Create adjunta Propiedad

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.Crear un comportamiento

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.Create un estilo y establezca la propiedad adjunta

 <Style TargetType="{x:Type DataGridCell}"> 
      <Setter Property="Behaviors:DataGridCellAttachedProperties.IsSingleClickEditMode" Value="True"/> 
     </Style> 
+0

Cuando intento acceder a DependencyProperty desde el estilo, dice que IsSingleClickEditMode no se reconoce o no se puede acceder a él. –

+0

Lo siento mal ... tan pronto como comenté, me di cuenta de que GetIsSingleClickEditMode debería coincidir con la cadena que pasa a DependencyProperty.RegisterAttached –

0

Me gusta el enfoque mostrado por las respuestas de Roman Dvoskin y Jonathan Allen en este hilo. Sin embargo, cuando estaba aprendiendo esa técnica por primera vez, me beneficié de this blog post que brinda más explicaciones sobre la técnica. Y para ver todo en contexto, here is the entire source code para la clase de la que el autor habla en su publicación de blog.

7

Tengo otra idea, para evitar la creación de una propiedad asociada para cada comportamiento:

  1. interfaz Comportamiento creador:

    public interface IBehaviorCreator 
    { 
        Behavior Create(); 
    } 
    
  2. Pequeña colección ayudante:

    public class BehaviorCreatorCollection : Collection<IBehaviorCreator> { } 
    
  3. Clase de ayuda que atribuye el comportamiento:

    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. Ahora su comportamiento, 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(); 
        } 
    } 
    
  5. Y ahora utilizarlo en xaml:

    <Style TargetType="{x:Type DataGridCell}"> 
        <Setter Property="helper:BehaviorInStyleAttacher.Behaviors" > 
        <Setter.Value> 
         <helper:BehaviorCreatorCollection> 
         <behaviors:SingleClickEditDataGridCellBehavior/> 
         </helper:BehaviorCreatorCollection> 
        </Setter.Value> 
        </Setter> 
    </Style> 
    
14

respuestas suma y este gran artículo Blend Behaviors in Styles, me llegó a esta solución genérica corta y conveniente:

Hice la clase genérica, que podría ser heredada por cualquier comportamiento.

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()); 
       }  
      } 
     } 
    } 

por lo que podría simplemente volver a utilizarlo con gran cantidad de componentes como esto:

public class ComboBoxBehaviour : AttachableForStyleBehavior<ComboBox, ComboBoxBehaviour> 
    { ... } 

Y en XAML suficiente para declarar:

<Style TargetType="ComboBox"> 
      <Setter Property="behaviours:ComboBoxBehaviour.IsEnabledForStyle" Value="True"/> 

cosas xaml Así que básicamente la clase AttachableForStyleBehavior hecho, registrando la instancia de comportamiento para cada componente en estilo. Para más detalles, mira el enlace.

+0

¡Funciona como un amuleto! Con mi comportamiento de desplazamiento combinado, me deshice de Inner RowDetailsTemplate-Datagrids sin desplazar las Datagrids primarias. –

+0

Me complace ayudar, disfrutar =) –

+0

¿Qué ocurre con el enlace de datos con propiedades de dependencia en el Comportamiento? – JobaDiniz

1

Declarar el comportamiento/gatillo individuo 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> 

insertarlos en la colección:

<Button x:Name="Btn1" Content="Button"> 

     <i:Interaction.Triggers> 
      <StaticResourceExtension ResourceKey="ET1"/> 
     </i:Interaction.Triggers> 

</Button> 
+0

¿Cómo responde el OP? El disparador no se agrega a través de un estilo en su respuesta. – Kryptos

Cuestiones relacionadas