学习笔记 -- AttachedProperty, DependencyProperty, Trigger, Action, Behavior, Command [Original]
Attached Property DependencyProperty Trigger Action Behavior Command
1. 附加属性
> Attached Property
> 附加属性是XAML定义的一个概念.附加属性旨在用作可在任何对象上设置的一类全局属性.在 WPF中,附加属性通常定义为没有常规属性“包装”的一种特殊形式的依赖项属性. [MSDN]
> 附加属性不属于被附加的对象, 但是可以对被附加的对象产生影响.
> e.g. : 在C#代码中, 创建一个按钮btn, Button是没有Left属性的, 所以可以用Canvas为btn设置一个附加属性, Left=10;
Button btn = new Button(); Canvas.SetLeft(btn, 10);
<Canvas …> <Button x:Name="btn" Canvas.Left="10"> </Canvas>
<Button x:Name="btn" … > <i:Interaction.Triggers> <i:EventTrigger … /> </i:Interaction.Triggers> </Button>
> DependencyProperty
> 可通过方法设置的属性,如样式、数据绑定、动画和继承.[MSDN] 在UI上应用广泛, 可以实时和界面交互
> WPF提供了一组服务,这些服务可用于扩展CLR属性的功能.这些服务通常统称为 WPF 属性系统. 由 WPF 属性系统支持的属性称为依赖项属性.
> 只有继承了DependencyObject的类里面才能注册依赖性属性. 因为DependencyObject类提供了读写依赖性属性值的SetValue和GetValue的方法.
> UIElement类继承了DependencyObject类, 所以FrameworkElement, Control, UserControl等界面元素都是DependencyObject,依赖对象.
> 一般注册DependencyProperty, 只要在继承了DependencyObject的类中, 在VS敲入”propdp”就会自动生成模板代码;
> DevDiv中有篇详细的介绍很不错.
http://www.devdiv.com/thread-51606-1-1.html
http://www.devdiv.com/thread-51607-1-1.html
3. Trigger
> 表示应用属性值或有条件地执行操作的触发器. [MSDN ]
> Expression Blend 中提供了以下触发器:
DataStoreChangedTrigger, 根据对数据存储所做的修改调用操作.
DataTrigger, 根据数据绑定属性的值调用操作.
EventTrigger, 根据诸如鼠标单击、页面加载或其他交互等事件调用操作.
KeyTrigger, 在按下键盘上的组合键时调用操作.
PropertyChangedTrigger, 根据对元素或数据存储属性所做的修改调用操作.
StoryboardCompletedTrigger, 在情节要完成之后调用操作.
TimerTrigger, 根据计时器调用操作.
> WPF以及其他Toolkit, SDKs 都提供了很多的触发器.
http://www.cnblogs.com/luluping/archive/2011/07/26/2117681.html
> Trigger可以是单触发器(Trigger), 也可以是多触发器(MultiTrigger), 也可以限定任意个条件(Condition);
4. Action
> System.Windows.Interactivity中定义了很多的Action, 比如TriggerAction, InvokeCommandAction, CallMethodAction等;
> Action, 可以理解为, 只要相应的外部Trigger触发了,就去做一件事, 比如执行一段代码, 调用一个函数.
> 可以自定义Action, 如下, 自定义一个简单的TriggerAction, 最简单的Action就只需要继承TriggerAction, 然后指定作用的类型, 如DependencyObject, 然后override Invoke方法就行了; 下面的代码实现的就是在外面触发这个Action的时候, 弹出”Hello World”对话框;
public class NewAction : System.Windows.Interactivity.TriggerAction<DependencyObject> { protected override void Invoke(object parameter) { MessageBox.Show(“Hello World”); } }
当然, 也可以为Action设置属性, 可以是普通属性, 也可以是依赖性属性, 如果需要数据绑定的话, 可以定义成依赖项属性, 这个就很有用, 比如上面的MessageBox显示的是UI上某个需要被监测的属性值, 那就只需要把Action自己的属性值绑定到UI上那个属性值, 在Invoke方法中对这个属性值操作, 就可以在每次Action发生时动态显示. 比如, 加上下面这几句:
这几句定义了一个依赖性属性Msg, 加到NewAction类中, 那么NewAction就有了属性值, 在Blend就可以看到NewAction多了Msg这一项, 就可以把它绑定到任何地方. 比如我们可以把它绑定到一个数据源, 然后要在Action执行时显示这个数据, 就可以在Invoke中实现, 把上面的MessageBox.Show(Msg)就可以了.public string Msg { get { return (string)GetValue(MsgProperty); } set { SetValue(MsgProperty, value); } } public static readonly DependencyProperty MsgProperty = DependencyProperty.Register("Msg", typeof(string), typeof(NewAction), null);
> 总之, Action就是在外边Trigger触发的时候做出一个动作, 可以给Action定义属性, 最简单的Action实现只要override Invoke()方法即可, 当然还可以override OnAttached(), OnDetaching()等其他方法.
5. Behavior
> 可以理解为给AssociatedObject的一种能力, 不需要自行添加触发器, 也不用自定义Action, 而可以看做是Behavior 在内部封装了一个或多个触发和相应的Action响应.
> Behavior继承了IAttachedObject接口, 可以是任意类型的AssociatedObject成员, 作用于AssociatedObject对象.
> 自定义一个Behavior, 例如, 要求用鼠标拖拽对象时,对象的Width跟随变化, 可以如下这样设计:
public class NewBehaviors:System.Windows.Interactivity.Behavior<FrameworkElement> { // override OnAttached protected override void OnAttached() { // 注册AssociatedObject的一个或多个事件, 作为Behavior的内部Trigger this.AssociatedObject.MouseMove = new MouseEventHandler(AssociatedObject_MouseMove); base.OnAttached(); } Point oldPosition = new Point(-10000, -10000); // 定义AssociatedObject的事件响应方法, 相当于Behavior的内部Action // 实现的是, 当鼠标拖拽AssociatedObject时候,其Width跟随变化 void AssociatedObject_MouseMove(object sender, MouseEventArgs e) { var newPosition = e.GetPosition(AssociatedObject); var deltaPosition = new Point(newPosition.X - oldPosition.X, newPosition.Y - oldPosition.Y); AssociatedObject.Width = deltaPosition.X; AssociatedObject.Height = deltaPosition.Y; oldPosition = newPosition; } // override OnDetaching,在Behavior解除在AssociatedObject依附时执行,不再订阅事件消息 protected override void OnDetaching() { this.AssociatedObject.MouseMove -= new MouseEventHandler(AssociatedObject_MouseMove); base.OnDetaching(); } }
6. Command
> System.Windows.Input.ICommand接口:
. CanExecute : 此方法由Command定义, 在系统收到Command发送的CanExecuteChanged通知时由系统调用, 并返回bool值, 判断是否执行Execute()方法; 通常此方法的返回布尔值会自动关联设置了此Command的UI的IsEnabled属性;
. Execute : Command命令调用的执行方法.
. CanExecuteChanged : 此事件由此Command本身抛出, 只要UI设置了此Command, 事件就会被系统自动订阅, 然后系统即调用CanExecute()方法判断是否执行Execute().
> 在Silverlight中, 支持命令启动的UI的类有: ButtonBase, Hyperlink 及其派生类.
> 可以自己定义一个Command, 需要自己组织上面的CanExecute(), Execute(), CanExecuteChanged三者之间的关系, 并重写两个方法.
比如, 可以自定义一个绑定到Button上的Command, 要求延时10s后Button才会生效(Enable), 然后每次点击Button的时候, 都会弹出”hello world”的对话框作为响应.
// 创建一个MyCommand类, 继承自ICommand.
// 创建一个MyCommand类, 继承自ICommand. public class MyCommand : ICommand { // 此示例中,用的是比较简单的CanExecute方法, 直接返回can值, 当需要界定多个条件才返回true时, 此方法就可以改写为各个条件的组合, 可以有效扩展 public bool CanExecute(object parameter) { return can; } // 重写Excute()方法, 此方法就是弹出一个”Hello World”的MessageBox. public void Execute(object parameter) { MessageBox.Show("hello world"); } // 显式继承接口成员 CanExecuteChanged public event EventHandler CanExecuteChanged; // 定义一个1Tick/s的定时器 public System.Windows.Threading.DispatcherTimer tmr = new System.Windows.Threading.DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) }; // 定义一个开始计时的时间 private DateTime startTime { get; set; } // 构造函数, 注册定时器Tick事件, 并启动定时器,开始计时. public MyCommand() { this.tmr.Tick = new EventHandler(tmr_Tick); tmr.Start(); startTime = DateTime.Now; } bool can = false; // Tick事件处理函数, 定时检查是否10s已到, 并在条件成立时抛出CanExecuteChanged事件. void tmr_Tick(object sender, EventArgs e) { if (DateTime.Now - startTime >= TimeSpan.FromSeconds(10)) { can = true; tmr.Stop(); this.tmr.Tick -= new EventHandler(tmr_Tick); if (CanExecuteChanged != null) { CanExecuteChanged(this, null); } } } }
此例中, 有CanExecuteChanged事件抛出, 有CanExecute()判断,还有Execute()执行方法, 这就是一个简单完整的Command.
> Galasoft提供的MvvmLight Tookit提供了已经完成内部实现的ICommand命令: RelayCommand.
调用的时候, 任选上面的两个方法, 然后定义相应的Action 委托就可以了.. public RelayCommand(Action execute); . public RelayCommand(Action execute, Func<bool> canExecute);