2009-10-30 20 views
76

我已经为按钮设置了混合行为。我如何将它设置为应用程序中的所有按钮。如何在样式设置器中添加混合行为

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

然而,当我尝试:

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

我得到的错误

属性 “行为” 不具有可访问的制定者。

回答

0

行为代码需要Visual,因此我们只能将其添加到视觉上。因此,我可以看到的唯一选择是添加到ControlTemplate中的一个元素,以便将行为添加到样式并影响特定控件的所有实例。

71

我有同样的问题,我想出了一个解决方案。在解决问题后我发现了这个问题,我发现我的解决方案与Mark的很多共同之处。但是,这种方法有点不同。

主要问题是行为和触发器与特定对象关联,因此您不能将同一行为实例用于多个不同的关联对象。当您定义行为时,内联XAML会强制执行此一对一关系。但是,当您尝试在样式中设置某个行为时,该样式可以重用于其应用于的所有对象,这会在基本行为类中引发异常。事实上,作者们付出了相当大的努力来阻止我们甚至试图做到这一点,知道它不会工作。

第一个问题是,我们甚至无法构造行为设置器值,因为构造器是内部的。所以我们需要我们自己的行为并触发集合类。

下一个问题是行为和触发器附加属性没有setter,因此它们只能通过行内XAML添加。我们用我们自己的附加属性来解决这个问题,该属性操纵主要行为和触发属性。

第三个问题是我们的行为集合仅适用于单个样式目标。我们通过利用少量使用的XAML功能x:Shared="False"来解决这个问题,每次引用时都会创建资源的新副本。

最后一个问题是,行为和触发器不像其他样式制定者;我们不想用新行为取代旧行为,因为他们可以做很大不同的事情。因此,如果我们接受一旦添加了一个行为就不能将其拿走(这就是当前行为的工作方式),我们可以得出结论,行为和触发器应该是叠加的,这可以通过我们附加的属性来处理。

下面是使用这种方法的例子:

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

的示例使用触发器,但行为的工作方式相同。在这个例子中,我们展示:

  • 风格可以适用于多个文本块
  • 几种类型的数据绑定的所有工作正常
  • ,在输出窗口中生成的文本调试行动

下面是一个示例行为,我们的DebugAction。更恰当地说,这是一种行动,但通过滥用语言我们称之为行为,触发器和行为“行为”。

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

最后,我们的集合和附加属性,使这一切工作。通过与Interaction.Behaviors类比,您定位的属性被称为SupplementaryInteraction.Behaviors,因为通过设置此属性,您将为Interaction.Behaviors添加行为,同样也为触发器添加行为。

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

并且您拥有它,通过样式应用功能齐全的行为和触发器。

+0

伟大的东西,这个精美的作品。我注意到,如果您将样式放在UserControl资源中,那么e.NewValue最初可能为null(可能取决于所使用的控件 - 我在Infragistics XamDataTree中的XamDataTreeNodeControl上使用它)。所以我在OnPropertyTriggersChanged中添加了一点理智检查:if(e.NewValue!= null) – MetalMikester 2011-12-09 17:10:43

+0

在**隐式** Style中应用Setter时,有没有人有过这种方法的问题?我已经使用非隐式样式(使用Key的一种样式)正常工作,但如果使用隐式样式,我会得到循环引用异常。 – 2013-04-01 15:35:52

+1

不错的解决方案,但不幸的是它不能在WinRT中运行,因为x:Shared在此平台上不存在... – 2013-07-06 01:54:47

4

我找不到原始文章,但我能够重新创建效果。

#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

必须写对于每个行为来说都是一个PITA。 – 2012-07-08 22:39:40

0

本文仅Introduction to Attached Behaviors in WPF使用样式实现附加的行为,也可以与或有帮助。

“附加行为简介”文章中的技术完全避免了使用Style的Interactivity标签。我不知道这是否仅仅是因为它是一种更为过时的技术,或者,如果在某些情况下仍然会给予某些好处,那么它们应该更喜欢它。

+1

这不是混合行为,它是通过一个简单的附加属性的“行为”。 – 2012-07-08 22:30:25

16

1.创建附加属性

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.创建行为

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.创建一个样式,并设置附加属性

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

当我尝试从样式访问DependencyProperty它说IsSingleClickEditMode不被识别或不可访问? – 2017-02-02 11:36:48

+0

对不起我的坏..只要我评论,我意识到,我意识到GetIsSingleClickEditMode应匹配您传递给DependencyProperty.RegisterAttached – 2017-02-02 11:40:13

0

我喜欢所示的方法由罗曼Dvoskin和乔纳森艾伦在这个线程的答案。当我第一次学习这种技术时,我受益于this blog post,它提供了关于该技术的更多解释。并且看看作者在他的博客文章中谈到的那个班级的情况,here is the entire source code

7

我还有一个想法,以避免对每一个行为建立一个附加属性:

  1. 行为创造者接口:

    public interface IBehaviorCreator 
    { 
        Behavior Create(); 
    } 
    
  2. 小帮手集合:

    public class BehaviorCreatorCollection : Collection<IBehaviorCreator> { } 
    
  3. 附加行为的助手类:

    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. 现在你的行为,它实现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. 现在在XAML中使用它:

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

总结答案,这篇大文章Blend Behaviors in Styles,我来到这个通用简短且方便的解决方案:

我做了泛型类,它可以被任何行为继承。

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

而在XAML足够声明:

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

所以,你可以简单地用大量的组件这样的重用

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

所以basicly的AttachableForStyleBehavior类所做的XAML的事情,在样式中注册每个组件的行为实例。有关更多详细信息,请参阅链接。

+0

作为一个魅力的字符串!与我的Scrollingbehavior结合我摆脱了Inner RowDetailsTemplate-Datagrids不滚动父Datagrids。 – 2015-07-28 11:44:46

+0

很高兴帮助,享受=) – 2015-12-06 16:28:38

+0

数据绑定与行为中的依赖属性呢? – JobaDiniz 2016-12-02 11:09:31

1

声明个体行为/触发的资源:

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

将其插入集合中:

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

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

</Button> 
+0

它如何回答OP?触发器不会通过答案中的样式添加。 – Kryptos 2017-09-11 06:09:03