本文只是一份简单的草案和思路探索,可以证明了这种机制的可行性。最近时间紧张,暂时还无法给出一份完备的解决方案,有兴趣的朋友可以沿着本文的思路写一套更完善的扩展方法出来。
一、朴素的绑定需求
为什么要骂?因为有大量“朴素”的绑定需求,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;SetBindingChain 就是用于绑定的扩展方法,src 是绑定源,path是绑定属性链,语法和属性调用语法类似(A.B.C.D),可以通过IDE提示写完后复制进去,callback是当属性发生变化后的回调事件,可以是lambda表达式。使用最好是在窗体Load时或某个事件时做个批量绑定。
namespace Orc.Util { public static class BindingHelper { public class PropertyChangedCallback { public INotifyPropertyChanged Caller { get; set; } public String PropertyName { get; set; } public Action Callback { get; set; } public INotifyPropertyChanged Src { get; set; } public String Path { get; set; } public List CallbackList { get; set; } public void _disibledevent=> // 回调 Callback();
// 添加事件 AddCallback(Src, Callback, Path); } }
public static void AddCallback(INotifyPropertyChanged src, Action callback, String path) { if (src == null || callback == null || String.IsNullOrEmpty(path)) return;
List list = new List(); 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 || dst == null || callback == null || String.IsNullOrEmpty(path)) return; PropertyChangedCallback.AddCallback(src, callback, path); } } }
也可以在这思路基础上写出多对象绑定扩展方法。本文就不写了。
三、使用新方案进行数据绑定
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, ActioncallbackOnFilePath, 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(); }
最新评论