• 从WPF的AttachProperty到Sliverlight3中的Behavior


                                 从WPF的AttachProperty到Sliverlight3中的Behavior

                                                         周银辉 

      说来很巧,最早接触到Behavior模式不是在Sliverlight中,而是我们在使用“Prism+MVVM”试图将界面和后台逻辑尽可能脱耦时,那时我们发现虽然WPF的Command、Prism的DelegateCommand能很好地帮助我们脱耦,但WPF的Command数量太少(比如Button的Command对应的是Click事件,但如果我需要在Loaded时也使用Command,其就无能为力了),于是我们用到了一个称为Behavior的模式来协助我们解决,不过当时我们总习惯哈哈大笑,因为我们认为这是一个很龌龊的技巧。如果还要向前追溯的话,那就得到很久以前了,当时我发现WPF拥有一个能力是将某个属性“附加(Attach)”到某个对象上,也就是Attach Property,那么我们能否用相同的原理将某个功能也附加到某个的对象上呢?可以的,这就是Attached Behavior,在当时我一直觉得这仅仅是一个小技巧,因为我从来只用它来为同事的代码增加功能或修改Bug,同事在用我写的功能函数时,感觉是在给对象打插件,非常方便。前几天,听说MS将其纳入到Sliverlight3中了,颇感惊异。

    1,从Attach属性开始 

    在继续阅读之前,建议下载Demo程序,并先看看源代码

     

        <StackPanel Orientation="Vertical">
            
    <Button x:Name="btn1" Content="I'm btn 1" 
                    loc:InfoService.Info
    ="hahaha, I'm btn 1"  
                    Click
    ="ShowInfo"/>
            
    <Button x:Name="btn2" Content="I'm btn 2" 
                    loc:InfoService.Info
    ="hehehe, I'm btn 2"  
                    Click
    ="ShowInfo"/>
        
    </StackPanel>

     从上面的代码看,你是不是可以猜到loc:InfoService.Info是一个AttachProperty,我们将一个字符串Attach到了一个Button控件上。恩,你的猜想是可行的,也是一般做法,但这里我们并不想这么做,看看我是怎么做的:

        public static class InfoService
        {
            
    private static Hashtable infoCache = new Hashtable();

            
    public static void SetInfo(Object obj, Object info)
            {
                
    if (infoCache.Contains(obj))
                {
                    infoCache[obj] 
    = info;
                }
                
    else
                {
                    infoCache.Add(obj, info);
                }
            }     

            
    public static Object GetInfo(Object obj)
            {
                
    if (infoCache.Contains(obj))
                {
                    Console.WriteLine(
    "get value from custom cache");
                    
    return infoCache[obj];
                }

                
    return null;
            }

                              
        }

    注意到了吗?InfoService并不包含任何AttachProperty,甚至Info属性都没有。能编译通过吗?不仅能编译,而且能很好地工作。

    为什么?

    我不能说这是技巧,我只能说这是MS的Xaml解析器玩的花招。

    当Xaml解析器发现myNamespace:MyClass.MyAttachProperty时,其并不会真正的去查找和调用MyClass. MyAttachProperty属性,而是会去看MyClass类中是否存在SetMyAttachProperty(arg1, arg2)方法,如果存在则将被Attach的对象作为arg1,Attach的属性值作为arg2,然后去调用SetMyAttachProperty方法,如果不存在,则报异常说“不存在MyDP这样的AttachProperty。

    按属性值被存放在上面地方了,WPF会为AttachProperty做一个缓存表,对属性值的查找和设置都在这个缓存表中进行(也就是DependencyObject的GetValue和SetValue两个方法所干的事情)。所以,上面的代码中,我们自己用Hashtable做了一个简易的缓存表,属性值便存放在这里。 

    2,Attach一个功能

    注意到上面关于“Xaml解析器”的那段话:“会去看MyClass类中是否存在SetMyAttachProperty(arg1, arg2)方法,如果存在则将被Attach的对象作为arg1,Attach的属性值作为arg2,然后去调用SetMyAttachProperty方法”, 既然被Attach的对象都被作为参数传递到后台代码了,那么后台代码就可以针对该对象“想干嘛,干嘛”。

    下面这个Demo将在TextBox上附加一个功能:当按下回车键的时候,弹出一个消息框并显示文本框的内容。

        <StackPanel Orientation="Vertical">
            
    <TextBox Text="i am a text box" 
                     loc:FunctionService.EnableReturnKeyFeature
    ="True"/>
        
    </StackPanel>

     FunctionService的代码一个如何写呢?非常简单:

    代码
        public static class FunctionService
        {
            
    public static void SetEnableReturnKeyFeature(object obj, bool enable)
            {
                var ui 
    = obj as TextBox;

                
    if (ui != null)
                {
                    ui.KeyDown 
    -= OnUIKeyDown;

                    
    if (enable)
                    {
                        ui.KeyDown 
    += OnUIKeyDown;
                    }
                }
            }

            
    static void OnUIKeyDown(object sender, KeyEventArgs e)
            {
                var ui 
    = sender as TextBox;
                
    if (ui != null)
                {
                    MessageBox.Show(ui.Text);
                }
            }
        }

     下载该示例代码

    3,Sliverlight中的Behavior 

     我们可以将一个个的功能从上面的函数形式“独立出来”而变成一个一个的对象,以便我可以简单地像添加删除对象一样添加删除功能,并且,如果对象化了,该对象中变可以包含无数的状态信息以及时间等等,这个被独立出来的对象就成为Behavior。

    似乎要在Sliverlight中使用Behavior,还要添加一个来自于Blend的 System.Windows.Interactivity.dll. 呵呵,没必要,搞清楚原理后自己写一个更方便。

    先写一个BehaviorBase,它的AssociatedObject表示当前Behavior将附加到哪个对象,其Attach(Obj)方法则实现”附加“操作。(注:为了避免干扰实现,我将BehaviorBase里面的许多代码都删掉了,比如事件通知等) 

        public abstract class BehaviorBase
        {
            
    protected object AssociatedObject
            {
                
    get;
                
    private set;
            } 
     
            
    public void Attach(object obj)
            {
                
    if (obj != AssociatedObject)
                {              
                    AssociatedObject 
    = obj;
                    OnAttached();
                }
            }

            
    protected virtual void OnAttached()
            {
            } 
        }
    我们提供了OnAttached方法,子类可以重写该方法,以便对象被关联进来以后,针对对象进行一些操作。
    其泛型版本:

        public abstract class Behavior<T> : BehaviorBase
        {
            
    protected T AssociatedObject
            {
                
    get
                {
                    
    return (T)base.AssociatedObject;
                }
            }
        }

    然后我们具体的Behavior实现则继承一下Behavior<T>, 比如下面的Behavior将对文本框增加一个功能: 当按下回车键的时候,弹出一个消息框并显示文本框的内容。

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

                AssociatedObject.KeyDown 
    += OnAssociatedObjectKeyDown;
            }

            
    void OnAssociatedObjectKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
            {
                var txtBox 
    = sender as TextBox;
                
    if (txtBox != null)
                {
                    MessageBox.Show(txtBox.Text);
                }
            }
        }

    恩,到目前为止,我们已经将一个功能完全对象化,那么紧接着的事情就是如果将该对象和界面元素(软件界面上的某个文本框控件)关联起来,很简单,调用该Behavior的Attach方法就可以了,但谁来调用呢,当然不是界面元素,我们可以写一个辅助类来专门负责关联,假设叫BehaviorService(也就是Sliverlight3中的Interaction类):

        public static class BehaviorService
        {      
            
    public static void SetBehavior(DependencyObject obj, BehaviorBase value)
            {
                value.Attach(obj);
            }
        }

    OK,搞定:

        <StackPanel Orientation="Vertical">
            
    <TextBox Text="I'm a text box">
                
    <loc:BehaviorService.Behavior>
                    
    <loc:ReturnKeyBehavior/>
                
    </loc:BehaviorService.Behavior>
            
    </TextBox>
        
    </StackPanel>

    这里下载Demo

    另外,无论是在WPF的MVVM中还是在Sliverlight中,个人觉得Behavior 始终是”无奈之举”,但只要明白原理都可以更好地进化出相对更容易使用的版本,希望Silverlight尽早走出浮躁期。

  • 相关阅读:
    最长回文 hdu3068(神代码)
    1297. Palindrome ural1297(后缀数组)
    705. New Distinct Substrings spoj(后缀数组求所有不同子串)
    Milk Patterns poj3261(后缀数组)
    Musical Theme poj1743(后缀数组)
    Conscription poj3723(最大生成树)
    Drying poj3104(二分)
    Finding LCM (最小公倍数)
    002 全局配置信息
    001 开始
  • 原文地址:https://www.cnblogs.com/zhouyinhui/p/1673342.html
Copyright © 2020-2023  润新知