WPF中System.Windows.Interactivity的使用
背景
在我們進行WPF開發應用程式的時候不可避免的要使用到事件,很多時候沒有嚴格按照MVVM模式進行開發的時候習慣直接在xaml中定義事件,然後再在對應的.cs檔案中直接寫事件的處理過程,這種處理方式寫起來非常簡單而且不用過多地處理考慮程式碼之間是否符合規範,但是我們在寫程式碼的時候如果完全按照WPF規範的MVVM模式進行開發的時候就應該將相應的事件處理寫在ViewModel層,這樣整個程式碼才更加符合規範而且層次也更加清楚,更加符合MVVM規範。
常規用法
1 引入名稱空間
通過在程式碼中引入System.Windows.Interactivity.dll,引入了這個dll後我們就能夠使用這個裡面的方法來將事件對映到ViewModel層了,我們來看看具體的使用步驟,第一步就是引入命名控制元件
1 |
xmlns:i= "clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
|
另外還可以通過另外一種方式來引入名稱空間,其實這兩者間都是對等的。
1 |
xmlns:i= "http://schemas.microsoft.com/expression/2010/interactivity"
|
2 新增事件對應的Command
這裡以TextBox的GetFocus和LostFocus為例來進行說明
1 2 3 4 5 6 7 8 9 10 11 12 |
<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的程式碼結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<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>