使用扩展方法打造一套简单的WPF/SL绑定机制

简介:

在昨天的博文《WPF/Silverlight的数据绑定设计的真糟糕》中,分析了三大应用场景下WPF/SL下绑定的缺陷。我的应用中有95%以上(甚至99%以上)的绑定都属于那三种应用场景,因此无法接受WPF/SL繁琐的绑定机制。骂不是目的,想办法解决问题是黑客精神的所在。知道缺陷之后,就可以来弥补。刚才花了两个小时时间用扩展方法写了一套新的WPF/SL绑定机制。

本文只是一份简单的草案和思路探索,可以证明了这种机制的可行性。最近时间紧张,暂时还无法给出一份完备的解决方案,有兴趣的朋友可以沿着本文的思路写一套更完善的扩展方法出来。

一、朴素的绑定需求

为什么要骂?因为有大量“朴素”的绑定需求,WPF/SL做的不好。比如下图:

界面

这个界面有四个编辑状态:None 代表不能编辑,DrawRegion 代表在图片上画框,DrawFrontPoint 代表在图片上画前景点,DrawBackgroundPoint 代表在图片上画背景点。用枚举 EditMode 来代表编辑状态,将 EditMode 存储在属性 EditModeValue 中:

public enum EditMode 

    None, 
    DrawRegion, 
    DrawFrontPoint, 
    DrawBackgroundPoint 
}

一个很简单的需求就是把 EditModeValue 和右边的几个按钮的状态关联起来,比如说,当 EditModeValue  为 DrawRegion 时,“绘制分割区域”按钮呈现按下状态(IsEnabled 为false),其它按钮类似。当按下某个按钮时,自动将 EditModeValue 改为某种状态,

数据绑定是解决这个问题的最佳方案。然而,WPF/SL下的数据绑定实在太不给力了,写这种数据绑定很繁琐,让人敬而远之。

二、新的方案

下面用反射+扩展方法来写一套更简洁的数据绑定方案。直接上代码,代码不足一百行,实现了绑定属性链的功能(没做完全的测试,可能有错误,谨慎使用):

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Linq; 
using System.Text; 
using System.Reflection;

namespace Orc.Util 

    public static class BindingHelper 
    { 
        internal class PropertyChangedCallback 
        { 
            internal INotifyPropertyChanged Caller { get; set; } 
            internal String PropertyName { get; set; } 
            internal Action Callback { get; set; } 
            internal INotifyPropertyChanged Src { get; set; } 
            internal String Path { get; set; } 
            internal List<PropertyChangedCallback> CallbackList { get; set; } 
            internal void OnPropertyChanged(object sender, PropertyChangedEventArgs e) 
            { 
                if (e.PropertyName == PropertyName) 
                { 
                    // 移除事件,避免副作用 
                    if (CallbackList != null) 
                    { 
                        foreach (PropertyChangedCallback item in CallbackList) 
                        { 
                            if (item.Caller != null) 
                                item.Caller.PropertyChanged -= item.OnPropertyChanged; 
                        } 
                        CallbackList.Clear(); 
                    }

                    // 回调 
                    Callback();

                    // 添加事件 
                    AddCallback(Src, Callback, Path); 
                } 
            }

            internal static void AddCallback(INotifyPropertyChanged src, Action callback, String path) 
            { 
                if (src == null || callback == null || String.IsNullOrEmpty(path)) return;

                List<PropertyChangedCallback> list = new List<PropertyChangedCallback>(); 
                INotifyPropertyChanged current = src; 
                Type type = src.GetType(); 
                String[] pathes = path.Split('.'); 
                foreach (String item in pathes) 
                { 
                    if (current == null) return;

                    String name = item.Trim(); 
                    if (String.IsNullOrEmpty(name)) break;

                    PropertyChangedCallback c = new PropertyChangedCallback(); 
                    c.Callback = callback; 
                    c.Caller = current; 
                    c.Src = src; 
                    c.PropertyName = name; 
                    c.CallbackList = list; 
                    c.Path = path; 
                    current.PropertyChanged += c.OnPropertyChanged; 
                    list.Add(c);

                    PropertyInfo pi = type.GetProperty(name); 
                    if (pi == null) break;

                    Object p = pi.GetValue(current, null); 
                    INotifyPropertyChanged np = p as INotifyPropertyChanged; 
                    if (np == null) break;

                    current = np; 
                    type = np.GetType(); 
                } 
            } 
        }

        public static void SetBindingChain(this Object dst, INotifyPropertyChanged src, String path, Action callback) 
        { 
            if (src == null || callback == null || String.IsNullOrEmpty(path)) return; 
            PropertyChangedCallback.AddCallback(src, callback, path); 
        } 
    } 
}

SetBindingChain 就是用于绑定的扩展方法,src 是绑定源,path是绑定属性链,语法和属性调用语法类似(A.B.C.D),可以通过IDE提示写完后复制进去,callback是当属性发生变化后的回调事件,可以是lambda表达式。使用最好是在窗体Load时或某个事件时做个批量绑定。

也可以在这思路基础上写出多属性链绑定及多对象绑定扩展方法,本文就不写了,下面是原型:

        public static void SetBindingChain(this Object dst, INotifyPropertyChanged src, String[] pathes, Action callback) 
        { 
            throw new NotImplementedException(); 
        }

        public static void SetBindingChain(this Object dst, INotifyPropertyChanged src1, String path1, INotifyPropertyChanged src2, String path2, Action callback) 
        { 
            throw new NotImplementedException(); 
        }

        public static void SetBindingChain(this Object dst, INotifyPropertyChanged src1, String[] pathes1, INotifyPropertyChanged src2, String[] pathes2, Action callback) 
        { 
            throw new NotImplementedException(); 
        }

        public static void SetBindingChain(this Object dst, INotifyPropertyChanged src1, String path1, INotifyPropertyChanged src2, String path2, INotifyPropertyChanged src3, String path3, Action callback) 
        { 
            throw new NotImplementedException(); 
        }

        public static void SetBindingChain(this Object dst, INotifyPropertyChanged src1, String[] pathes1, INotifyPropertyChanged src2, String[] pathes2, INotifyPropertyChanged src3, String[] pathes3, Action callback) 
        { 
            throw new NotImplementedException(); 
        }

        public static void SetBindingChain(this Object dst, INotifyPropertyChanged src1, String path1, INotifyPropertyChanged src2, String path2, INotifyPropertyChanged src3, String path3, INotifyPropertyChanged src4, String path4, Action callback) 
        { 
            throw new NotImplementedException(); 
        }

        public static void SetBindingChain(this Object dst, INotifyPropertyChanged src1, String[] pathes1, INotifyPropertyChanged src2, String[] pathes2, INotifyPropertyChanged src3, String[] pathes3, INotifyPropertyChanged src4, String[] pathes4, Action callback) 
        { 
            throw new NotImplementedException(); 
        }

三、使用新方案进行数据绑定

EditModeValue属性:

private EditMode m_editModeValue = EditMode.None;

public EditMode EditModeValue 

    get { return m_editModeValue; } 
    set { 
        if (m_editModeValue == value) return; 
        m_editModeValue = value; 
        NotifyPropertyChanged("EditModeValue"); 
    } 
}

单向绑定:

this.SetBindingChain(this, "EditModeValue", 
   () => 
   { 
       btnDrawSegRegion.IsEnabled = EditModeValue != EditMode.DrawRegion; 
       btnDrawFPoints.IsEnabled = EditModeValue != EditMode.DrawFrontPoint; 
       btnDrawBPoints.IsEnabled = EditModeValue != EditMode.DrawBackgroundPoint; 
   });

然后是按下按钮时状态改变:

private void btnDrawSegRegion_Click(object sender, RoutedEventArgs e) 

    EditModeValue = EditMode.DrawRegion; 
}

private void btnDrawFPoints_Click(object sender, RoutedEventArgs e) 

    EditModeValue = EditMode.DrawFrontPoint; 
}

private void btnDrawBPoints_Click(object sender, RoutedEventArgs e) 

    EditModeValue = EditMode.DrawBackgroundPoint; 
}

搞定!

四、分析

使用扩展方法 SetBindingChain 可以简化 《WPF/Silverlight的数据绑定设计的真糟糕》 中所提到的前两种场景,使用方便了很多,且无学习成本,能够满足大部分的绑定需求。使用体验靠近了Flex的绑定方案,也有些区别:

(1)绑定写在后台,没有前台直观,同时,也不方便解耦;

(2)Flex是自动分析绑定链,这里还要手动输入绑定链,是一个比较大的遗憾;

尽管如此,相对于又难学又难用的WPF/SL源生绑定,要好太多了,简洁、明了、不用学、不用记、柔性、适合大部分应用场景。本文只是对属性链的绑定,如有其它需求,如对索引器的绑定等等还请自行扩展。

至于第三种场景,目前还没好的解决办法。

五、其它

喜欢简洁明了的解决方案,比如说上面界面中的打开图像功能,我的代码是:

private void btnOpen_Click(object sender, RoutedEventArgs e) 

    this.OpenImageFile((String path) => { 
        tbImgPath.Text = path; 
        EditModeValue = EditMode.None; 
    }); 
}

非常简单明了,不用拖个 OpenFileDialog 出来,也不用记忆 filter 的语法。当然,在背后有个扩展方法在默默的提供服务:

public static void OpenImageFile(this Window element, Action<String> callbackOnFilePath, String filter = "图像文件|*.bmp;*.jpg;*.gif;*.png") 

    String filePath; 
    OpenFileDialog dlg = new OpenFileDialog();

    dlg.Filter = filter; 
    dlg.FileOk += (object sender, CancelEventArgs e) => 
    { 
        filePath = dlg.FileName; 
        if(callbackOnFilePath != null) 
            callbackOnFilePath(filePath); 
    }; 
    dlg.ShowDialog(); 
}

本文转自xiaotie博客园博客,原文链接http://www.cnblogs.com/xiaotie/archive/2011/02/15/1955051.html如需转载请自行联系原作者


xiaotie 集异璧实验室(GEBLAB)

相关文章
WPF—多重绑定和跨层级绑定
WPF—多重绑定和跨层级绑定
|
C# 数据格式 XML
WPF 资源(StaticResource 静态资源、DynamicResource 动态资源、添加二进制资源、绑定资源树)
原文:WPF 资源(StaticResource 静态资源、DynamicResource 动态资源、添加二进制资源、绑定资源树) 一、WPF对象级(Window对象)资源的定义与查找 实例一: StaticR...
8490 0
|
C# 数据库
WPF中DataGrid控件绑定数据源
WPF中DataGrid控件绑定数据源
184 0
|
C#
WPF更新绑定字段
WPF更新绑定字段
110 0
|
前端开发 C#
WPF 之 数据与命令绑定 (MVVM方式)
WPF 之 数据与命令绑定 (MVVM方式)
206 0
WPF 之 数据与命令绑定 (MVVM方式)
|
C#
WPF 绑定父类属性
原文:WPF 绑定父类属性 1.绑定父控件的属性. 1 2 3 4 5 6 7 8 9 发现问题,父控件的属性如果是后期加载的,别如说Width或者Height不是固定的数值,那么绑定时没有效果的。
1285 0
|
C# 前端开发
wpf中的datagrid绑定操作按钮是否显示或者隐藏
如图,需要在wpf中的datagrid的操作那列有个确认按钮,然后在某些条件下确认按钮可见,某些情况下不可见的,放在mvc里直接在cshtml页面中if..else就行了。 但是在wpf里不行。。网上搜索了好久才找到解决方法,原来只是binding那个visiable属性就行了,
6901 0
|
C#
WPF的5种绑定模式(mode)
原文:WPF的5种绑定模式(mode) WPF的绑定模式(mode)是枚举的 枚举值共有5个 1:OneWay(源变就更新目标属性) 2:TwoWay(源变就更新目标并且目标变就更新源) 3:OneTime(只根据源来设置目标,以后都不会变) 4:OneWayToSource(与OneWay相反) 5:Default(可以单向或双向,是靠被值定的源或目标是否有get或set来指定的) 所以绑定的话是需要选上面5个中的一个模式的,根据你的需要来选择,不选的话就会自动选择第五个的。
1174 0
|
C# .NET 开发框架
WPF笔记 ( xmlns引用,Resource、Binding 前/后台加载,重新绑定) 2013.6.7更新
原文:WPF笔记 ( xmlns引用,Resource、Binding 前/后台加载,重新绑定) 2013.6.7更新 1、xmlns Mapping URI的格式是 clr-namespace:[;assembly=] (1)如果自定义类和XAML处在同一个Assembly之中,只还需要提供clr-namespace值。
1455 0
|
C#
潜移默化学会WPF(难点控件treeview)--改造TreeView(CheckBox多选择版本),递归绑定数据
原文:潜移默化学会WPF(难点控件treeview)--改造TreeView(CheckBox多选择版本),递归绑定数据 目前自己对treeview的感慨很多 今天先讲 面对这种 表结构的数据 的其中一种绑定方法,后面多几列其他属性都没关系,例如多个字段, 1  A  0 2  B  0 3  C  0 4  D  1 5  E  2 6  F  4 7  G 1 .
2408 0