【问题标题】:How to add a Blend Behavior in a Style Setter如何在样式设置器中添加混合行为
【发布时间】:2010-12-11 11:51:04
【问题描述】:

我为 Button 创建了一个混合行为。如何将其设置为应用程序中的所有按钮。

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

但是,当我尝试时:

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

我得到了错误

“行为”属性没有可访问的设置器。

【问题讨论】:

    标签: c# wpf xaml expression-blend


    【解决方案1】:

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

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

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

    下一个问题是行为和触发器附加属性没有设置器,因此只能使用内联 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);
        }
    }
    

    你已经拥有了,功能齐全的行为和通过样式应用的触发器。

    【讨论】:

    • 好东西,效果很好。我注意到,例如,如果您将样式放在 UserControl 资源中,则 e.NewValue 一开始可能为空(可能取决于使用的控件 - 我在 Infragistics XamDataTree 中的 XamDataTreeNodeControl 上使用它)。所以我在 OnPropertyTriggersChanged 中添加了一点完整性检查: if (e.NewValue != null)
    • 在以 隐式 样式应用 Setter 时,有人遇到过这种方法的问题吗?我已经让它在非隐式样式(带有键的样式)下正常工作,但如果它是隐式样式,我会得到一个循环引用异常。
    • 不错的解决方案,但不幸的是它在 WinRT 中不起作用,因为 x:Shared 在此平台上不存在...
    • 我可以确认此解决方案有效。非常感谢您分享它。不过,我还没有尝试过隐式风格。
    • @Jason Frank,谢谢,就像其他人的参考......我让它在两种情况下都有效:隐式和显式。事实上,我问了一个问题,我会把我的所有代码放在哪里来帮助别人,但有人估计我的问题是重复的。给出我所找到的一切,我无法回答我自己的问题。我想我发现了一些不错的东西。 :-( ...我希望它不会经常发生,因为这种行为会剥夺其他用户的有用信息。
    【解决方案2】:

    总结答案和这篇很棒的文章Blend Behaviors in Styles,我得出了这个通用的简短而方便的解决方案:

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

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

    所以你可以简单地用很多这样的组件重用它:

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

    并且在 XAML 中足以声明:

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

    所以基本上 AttachableForStyleBehavior 类制作了 xaml 的东西,在 style 中注册每个组件的行为实例。更多详情,请查看链接。

    【讨论】:

    • 像魅力一样工作!结合我的 Scrollingbehavior,我摆脱了 Inner RowDetailsTemplate-Datagrids 不滚动父 Datagrids。
    • 乐于助人,享受=)
    • 在 Behavior 中使用依赖属性进行数据绑定怎么样?
    • 我不知道如何联系用户或亲自拒绝编辑负面反馈。所以亲爱的@Der_Meister 和其他编辑,请在尝试编辑之前仔细阅读代码。它也可能影响其他用户和我的声誉。在这种情况下,通过删除 IsEnabledForStyle 属性并坚持用静态方法替换它,您破坏了在 xaml 中绑定到它的可能性,这是这个问题的重点。所以看起来你直到最后都没有阅读代码。遗憾的是,我不能拒绝你的编辑,所以请以后小心。
    • @RomaBorodov,一切都在 XAML 中运行。这是定义附加属性(与依赖属性不同)的正确方法。请参阅文档:docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/…
    【解决方案3】:

    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>
    

    【讨论】:

    • 当我尝试从显示 IsSingleClickEditMode 无法识别或无法访问的样式中访问 DependencyProperty 时?
    • 对不起,我的错误.. 我一评论就意识到 GetIsSingleClickEditMode 应该与您传递给 DependencyProperty.RegisterAttached 的字符串匹配
    • OnDetaching 添加了另一个事件处理程序,这应该是固定的(编辑帖子时不能修改单个字符...)
    【解决方案4】:

    我有另一个想法,避免为每个行为创建附加属性:

    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>
      

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 不得不为每个行为写这个有点像 PITA。
      【解决方案6】:

      根据this 的回答,我做了一个更简单的解决方案,只需要一个类,不需要在你的行为中实现其他东西。

      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
      }
      

      示例用法是

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

      不要忘记添加这个xmlns来使用ArrayList:

      xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
      

      【讨论】:

        【解决方案7】:

        行为代码需要一个视觉对象,因此我们只能将它添加到视觉对象上。所以我能看到的唯一选择是添加到 ControlTemplate 内的元素之一,以便将行为添加到 Style 并影响特定控件的所有实例。

        【讨论】:

          【解决方案8】:

          文章Introduction to Attached Behaviors in WPF 实现了仅使用样式的附加行为,也可能是相关的或有帮助的。

          “附加行为简介”文章中的技术完全避免了交互性标签,使用样式。我不知道这是否只是因为它是一种更过时的技术,或者,如果它仍然带来一些好处,在某些情况下人们应该更喜欢它。

          【讨论】:

          • 这不是 Blend 行为,而是通过简单的附加属性实现的“行为”。
          【解决方案9】:

          我喜欢 Roman Dvoskin 和 Jonathan Allen 在此线程中的回答所显示的方法。不过,当我第一次学习该技术时,我受益于this blog post,它提供了有关该技术的更多解释。要查看上下文中的所有内容,请here is the entire source code 作者在他的博客文章中谈到的课程。

          【讨论】:

            【解决方案10】:

            将个人行为/触发器声明为资源:

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

            【讨论】:

            • 它如何回答 OP?触发器不是通过答案中的样式添加的。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-02-05
            • 2020-05-07
            • 1970-01-01
            • 2018-04-21
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多