WPF中System.Windows.Interactivity的使用

背景

   在我们进行WPF开发应用程序的时候不可避免的要使用到事件,很多时候没有严格按照MVVM模式进行开发的时候习惯直接在xaml中定义事件,然后再在对应的.cs文件中直接写事件的处理过程,这种处理方式写起来非常简单而且不用过多地处理考虑代码之间是否符合规范,但是我们在写代码的时候如果完全按照WPF规范的MVVM模式进行开发的时候就应该将相应的事件处理写在ViewModel层,这样整个代码才更加符合规范而且层次也更加清楚,更加符合MVVM规范。

常规用法

  1 引入命名空间

  通过在代码中引入System.Windows.Interactivity.dll,引入了这个dll后我们就能够使用这个里面的方法来将事件映射到ViewModel层了,我们来看看具体的使用步骤,第一步就是引入命名控件

 xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

  另外还可以通过另外一种方式来引入命名空间,其实这两者间都是对等的。

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

  2 添加事件对应的Command

  这里以TextBox的GetFocus和LostFocus为例来进行说明

<TextBox Text="CommandBinding">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="LostFocus">
            <i:InvokeCommandAction Command="{Binding OnTextLostFocus}"
                                   CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type TextBox}}}"/>
        </i:EventTrigger>
        <i:EventTrigger EventName="GotFocus">
            <i:InvokeCommandAction Command="{Binding OnTextGotFocus}"
                                   CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type TextBox}}}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TextBox>

  这个里面我们重点来看看这个InvokeCommandAction的代码结构

namespace System.Windows.Interactivity
{
    public sealed class InvokeCommandAction : TriggerAction<DependencyObject>
    {
        public static readonly DependencyProperty CommandProperty;
        public static readonly DependencyProperty CommandParameterProperty;

        public InvokeCommandAction();

        public string CommandName { get; set; }
        public ICommand Command { get; set; }
        public object CommandParameter { get; set; }

        protected override void Invoke(object parameter);
    }
}

  这里我们发现这里我们如果我们定义一个Command的话我们只能够在Command中获取到我们绑定的CommandParameter这个参数,但是有时候我们需要获取到触发这个事件的RoutedEventArgs的时候,通过这种方式就很难获取到了,这个时候我们就需要自己去扩展一个InvokeCommandAction了,这个时候我们应该怎么做呢?整个过程分成三步:

2.1 定义自己的CommandParameter

    public class ExCommandParameter
    {
        /// <summary>  
        /// 事件触发源  
        /// </summary>  
        public DependencyObject Sender { get; set; }
        /// <summary>  
        /// 事件参数  
        /// </summary>  
        public EventArgs EventArgs { get; set; }
        /// <summary>  
        /// 额外参数  
        /// </summary>  
        public object Parameter { get; set; }
    }

  这个对象除了封装我们常规的参数外还封装了我们需要的EventArgs属性,有了这个我们就能将当前的事件的EventArgs传递进来了。

2.2 重写自己的InvokeCommandAction

public class ExInvokeCommandAction : TriggerAction<DependencyObject>
    {

        private string commandName;
        public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ExInvokeCommandAction), null);
        public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(ExInvokeCommandAction), null);
        /// <summary>  
        /// 获得或设置此操作应调用的命令的名称。  
        /// </summary>  
        /// <value>此操作应调用的命令的名称。</value>  
        /// <remarks>如果设置了此属性和 Command 属性,则此属性将被后者所取代。</remarks>  
        public string CommandName
        {
            get
            {
                base.ReadPreamble();
                return this.commandName;
            }
            set
            {
                if (this.CommandName != value)
                {
                    base.WritePreamble();
                    this.commandName = value;
                    base.WritePostscript();
                }
            }
        }
        /// <summary>  
        /// 获取或设置此操作应调用的命令。这是依赖属性。  
        /// </summary>  
        /// <value>要执行的命令。</value>  
        /// <remarks>如果设置了此属性和 CommandName 属性,则此属性将优先于后者。</remarks>  
        public ICommand Command
        {
            get
            {
                return (ICommand)base.GetValue(ExInvokeCommandAction.CommandProperty);
            }
            set
            {
                base.SetValue(ExInvokeCommandAction.CommandProperty, value);
            }
        }
        /// <summary>  
        /// 获得或设置命令参数。这是依赖属性。  
        /// </summary>  
        /// <value>命令参数。</value>  
        /// <remarks>这是传递给 ICommand.CanExecute 和 ICommand.Execute 的值。</remarks>  
        public object CommandParameter
        {
            get
            {
                return base.GetValue(ExInvokeCommandAction.CommandParameterProperty);
            }
            set
            {
                base.SetValue(ExInvokeCommandAction.CommandParameterProperty, value);
            }
        }
        /// <summary>  
        /// 调用操作。  
        /// </summary>  
        /// <param name="parameter">操作的参数。如果操作不需要参数,则可以将参数设置为空引用。</param>  
        protected override void Invoke(object parameter)
        {
            if (base.AssociatedObject != null)
            {
                ICommand command = this.ResolveCommand();
                /* 
                 * ★★★★★★★★★★★★★★★★★★★★★★★★ 
                 * 注意这里添加了事件触发源和事件参数 
                 * ★★★★★★★★★★★★★★★★★★★★★★★★ 
                 */
                ExCommandParameter exParameter = new ExCommandParameter
                {
                    Sender = base.AssociatedObject,
                    Parameter = GetValue(CommandParameterProperty),
                    EventArgs = parameter as EventArgs
                };
                if (command != null && command.CanExecute(exParameter))
                {
                    /* 
                     * ★★★★★★★★★★★★★★★★★★★★★★★★ 
                     * 注意将扩展的参数传递到Execute方法中 
                     * ★★★★★★★★★★★★★★★★★★★★★★★★ 
                     */
                    command.Execute(exParameter);
                }
            }
        }
        private ICommand ResolveCommand()
        {
            ICommand result = null;
            if (this.Command != null)
            {
                result = this.Command;
            }
            else
            {
                if (base.AssociatedObject != null)
                {
                    Type type = base.AssociatedObject.GetType();
                    PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
                    PropertyInfo[] array = properties;
                    for (int i = 0; i < array.Length; i++)
                    {
                        PropertyInfo propertyInfo = array[i];
                        if (typeof(ICommand).IsAssignableFrom(propertyInfo.PropertyType) && string.Equals(propertyInfo.Name, this.CommandName, StringComparison.Ordinal))
                        {
                            result = (ICommand)propertyInfo.GetValue(base.AssociatedObject, null);
                        }
                    }
                }
            }
            return result;
        }

    }  

  这个里面的重点是要重写基类中的Invoke方法,将当前命令通过反射的方式来获取到,然后在执行command.Execute方法的时候将我们自定义的ExCommandParameter传递进去,这样我们就能够在最终绑定的命令中获取到特定的EventArgs对象了。

2.3 在代码中应用自定义InvokeCommandAction

  <ListBox x:Name="lb_selecthistorymembers"                          
           SnapsToDevicePixels="true" 
           ItemsSource="{Binding DataContext.SpecificHistoryMembers,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=my:AnnouncementApp},Mode=TwoWay}" 
           HorizontalAlignment="Stretch" 
           ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
           Background="#fff"
           BorderThickness="1">
           <i:Interaction.Triggers>
              <i:EventTrigger EventName="SelectionChanged">
                 <interactive:ExInvokeCommandAction Command="{Binding DataContext.OnSelectHistoryMembersListBoxSelected,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=my:AnnouncementApp},Mode=TwoWay}"
                              CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}">
                 </interactive:ExInvokeCommandAction>
              </i:EventTrigger>
          </i:Interaction.Triggers>        
</ListBox>

  注意这里需要首先引入自定义的interactive的命名空间,这个在使用的时候需要注意,另外在最终的Command订阅中EventArgs根据不同的事件有不同的表现形式,比如Loaded事件,那么最终获取到的EventArgs就是RoutedEventArgs对象,如果是TableControl的SelectionChanged事件,那么最终获取到的就是SelectionChangedEventArgs对象,这个在使用的时候需要加以区分。

3  使用当前程序集增加Behavior扩展

  System.Windows.Interactivity.dll中一个重要的扩展就是对Behavior的扩展,这个Behavior到底该怎么用呢?我们来看下面的一个例子,我们需要给一个TextBlock和Button增加一个统一的DropShadowEffect,我们先来看看最终的效果,然后再就具体的代码进行分析。

图一 为控件增加Effect效果

  代码分析

  1 增加一个EffectBehavior

public class EffectBehavior : Behavior<FrameworkElement>
    {
        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;
            AssociatedObject.MouseLeave += AssociatedObject_MouseLeave;
        }

        private void AssociatedObject_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            var element = sender as FrameworkElement;
            element.Effect = new DropShadowEffect() { Color = Colors.Transparent, ShadowDepth = 2 }; ;
        }

        private void AssociatedObject_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
        {
            var element = sender as FrameworkElement;
            element.Effect = new DropShadowEffect() { Color = Colors.Red, ShadowDepth = 2 };
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;
            AssociatedObject.MouseLeave -= AssociatedObject_MouseLeave;

        }
    }

  这里我们继承自System.Windows.Interactivity中的Behavior<T>这个泛型类,这里我们的泛型参数使用FrameworkElement,因为大部分的控件都是继承自这个对象,我们方便为其统一添加效果,在集成这个基类后我们需要重写基类的OnAttached和OnDetaching方法,这个里面AssociatedObject就是我们具体添加Effect的元素,在我们的示例中这个分别是TextBlock和Button对象。

  2 在具体的控件中添加此效果

<Window x:Class="WpfBehavior.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfBehavior"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock Text="测试文本" Margin="2" Height="30">
                <i:Interaction.Behaviors>
                    <local:EffectBehavior></local:EffectBehavior>
                </i:Interaction.Behaviors>
            </TextBlock>
            <Button Content="测试" Width="80" Height="30" Margin="2">
                <i:Interaction.Behaviors>
                    <local:EffectBehavior></local:EffectBehavior>
                </i:Interaction.Behaviors>
            </Button>
        </StackPanel>
    </Grid>
</Window>

 

posted @ 2021-01-17 22:04  Hello——寻梦者!  阅读(7762)  评论(0编辑  收藏  举报