• WPF学习(二) - 绑定


        绑定,这个看起来很神奇的东西,于我这种喜欢刨根儿的人而言,理解起来非常困难。
        WPF绑定的核心思想是:数据层属性值的改变,能反应给展示层,反之亦然,并且这个响应的过程能被分离出来。


        传统Winform编程更加原始,没有那么多隐藏的(implicate)技术。我就以winform的实现方式来领会WPF的机制。

    public class DataLayer
        {
            public delegate void TextChangedEventHandler ( object sender, EventArgs e );
    
            public event TextChangedEventHandler TextChanged;
    
            private string text = "";
    
            public string Text
            {
                get { return text; }
                set
                {
                    if ( text != value )
                    {
                        text = value;
                        if ( this.TextChanged != null )
                            this.TextChanged ( this, new EventArgs ( ) );
                    }
                }
            }
    
        }
    数据层代码
    public partial class PresentationLayer : Form
        {
            TextBox ui = new TextBox ( );
            DataLayer data = new DataLayer ( );
    
            public PresentationLayer ( )
            {
                InitializeComponent ( );
                this.Controls.Add ( ui );
    
                ui.TextChanged += new System.EventHandler ( this.PresentationLayerTextChanged );
                data.TextChanged += new DataLayer.TextChangedEventHandler ( this.DataLayerTextChanged );
            }
    
            private void PresentationLayerTextChanged ( object sender, EventArgs e )
            {
                data.Text = ui.Text;
            }
    
            private void DataLayerTextChanged ( object sender, EventArgs e )
            {
                ui.Text = data.Text;
            }
    
        }
    展示层代码

    这样就实现了前、后台数据的同步。缺点有三方面:

      1、每个属性的数据同步,功能单一,内容相同。没有必要在数据层对每个属性的响应事件都单独定义事件委托。
      2、为了保持数据同步,需要在展示层编写大量的数据同步代码。如果有很多个属性,重复的工作量非常大。
      3、data作为PresentationLayer的成员,增加了耦合度。数据层和展示层没有完全分隔开。

      问题1很好解决,从数据层抽象出一个接口事件,并在事件参数中要求说明激发事件的属性名

        public interface INotifyPropertyChanged
        {
            event PropertyChangedEventHandler PropertyChanged;
        }
    
        public delegate void PropertyChangedEventHandler ( object sender, PropertyChangedEventArgs e );
    
        public class PropertyChangedEventArgs : EventArgs
        {
            private readonly string propertyName;
    
            public PropertyChangedEventArgs ( string propertyName )
            {
                this.propertyName = propertyName;
            }
    
            public virtual string PropertyName
            {
                get
                {
                    return this.propertyName;
                }
            }
        }
    抽象出来的接口事件

    这样,原始的DataLayer就变成这样

        public class DataLayerExt : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
    
            private string text = "";
    
            public string Text
            {
                get { return text; }
                set
                {
                    if ( text != value )
                    {
                        text = value;
                        if ( this.PropertyChanged != null )
                            this.PropertyChanged ( this, new PropertyChangedEventArgs ( "Text" ) );
                    }
                }
            }
    
        }
    DataLayerExt

      问题2从逻辑上也不难解决,定义一个静态的方法,方法的参数要说明是哪两个对象的哪两个属性需要同步,并记录这种同步关系

        public class BindingOperations
        {
            static List<BindingRelative> lstBindingRelative = new List<BindingRelative>();
    
            // 源和目标的类型应该是object。这里只为表达语意,完全实现很困难
            public static void SetBinding ( TextBox uiObject, string uiPropertyName, DataLayerExt dataObject, string dataPropertyName )
            {
                // 用列表记录同步关系
                lstBindingRelative.Add ( new BindingRelative ( ) { UIObject = uiObject, UIObjectPropertyName = uiPropertyName, DataObject = dataObject, DataObjectPropertyName = dataPropertyName } );
                // 增加事件处理方法
                uiObject.TextChanged += new EventHandler ( uiObject_TextChanged );
                dataObject.PropertyChanged += new PropertyChangedEventHandler ( target_PropertyChanged );
            }
    
            static void uiObject_TextChanged ( object sender, EventArgs e )
            {
                foreach ( BindingRelative item in lstBindingRelative )
                {
                    if ( item.UIObjectPropertyName == "Text" )
                    {
                        // 通用的方式应该是这样
                        //item.Source.SourcePropertyName = item.Target.dataPropertyName;
                        item.DataObject.Text = item.UIObject.Text;
                        break;
                    }
                }
            }
    
            static void target_PropertyChanged ( object sender, PropertyChangedEventArgs e )
            {
                foreach ( BindingRelative item in lstBindingRelative )
                {
                    if ( item.DataObjectPropertyName == e.PropertyName )
                    {
                        // 通用的方式应该是这样
                        //item.Source.SourcePropertyName = item.Target.dataPropertyName;
                        item.UIObject.Text = item.DataObject.Text;
                        break;
                    }
                }
    
            }
        }
    
        //定义一个用来记录绑定关系的结构
        public class BindingRelative
        {
            public TextBox UIObject;
            public string UIObjectPropertyName;
            public DataLayerExt DataObject;
            public string DataObjectPropertyName;
        }
    建立数据同步关系

      这样,展示层只需要设计界面的风格,定义界面中的展示元素,与后台数据完全分离。不知不觉中,问题1也一并解决了。

        public partial class PresentationLayerExt : Form
        {
    
            public TextBox ui = new TextBox ( );
    
            public PresentationLayerExt ( )
            {
                InitializeComponent ( );
                this.Controls.Add ( ui );
            }
    
        }
    PresentationLayerExt

      当然,指定同步关系还是要用代码实现的,但不在展示层,而是在外部,比如在Main()函数处

        static class Program
        {
            /// <summary>
            /// 应用程序的主入口点。
            /// </summary>
            [STAThread]
            static void Main ( )
            {
                Application.EnableVisualStyles ( );
                Application.SetCompatibleTextRenderingDefault ( false );
    
                PresentationLayerExt p = new PresentationLayerExt ( );
                DataLayerExt d = new DataLayerExt ( );
                BindingOperations.SetBinding ( p.ui, "Text", d, "Text" );
    
                Application.Run ( p );
            }
        }
    Main()

      以上,用Winform实现了数据同步,并做到了数据层与展示层的分离。由于Winform中根据属性名称字符串获取对象的属性非常困难,定义同步关系的方法SetBinding有很大的缺陷,几乎没有通用性可言。但这不妨碍通过Winform的示例理解WPF绑定技术的运行机制。

      WPF的绑定技术,实现数据层和展示层的数据同步原理也是这样:
        数据层的类需要派生自System.ComponentModel中的INotifyPropertyChanged接口,
        每个类都必须包含PropertyChanged事件,并在属性值改变时,激发这个事件,事件参数中传入属性名。

        数据同步关系由BindingOperations的SetBinding方法建立。

        在WPF中SetBinding方法的后两个参数打包成Binding类型的对象

        这样,SetBinding方法的对象参数就可以使用最原始的DependencyObject类型的对象,提高通用性。
        很多WPF控件已封装过SetBinding方法,这样就在对象绑定自己的属性时,就可以调用自己的SetBinding方法省略目标这个参数

      一个完整的WPF应用Binding技术的例子

        // Data作为绑定的数据源头,总会接收绑定目标的改变
        // 但是,如果想将Data源头的改变告诉绑定目标,
        // 必须要从INotifyPropertyChanged接口派生,并实现响应事件的激发
        public class DataClass : System.ComponentModel.INotifyPropertyChanged
        {
            private string myString;
    
            public string MyString
            {
                get { return myString; }
                set
                {
                    myString = value;
                    
                    //激发事件
                    if ( PropertyChanged != null )
                        PropertyChanged ( this, new System.ComponentModel.PropertyChangedEventArgs ( "MyString" ) );
                }
            }
    
            public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    
        }
    定义数据类
        // 作为被绑定的目标类,必须从DependencyObject派生
        // 这样定义的类才能满足SetBinding方法的第一个参数的类型要求
        // 还要额外定义一个依赖属性,用来满足SetBinding方法的第二个参数要求
        // 用DependencyObject派生方法GetValue和SetValue,控制属性的存、取
        public class UIClass : System.Windows.DependencyObject
        {
            public string MyText
            {
                get { return ( string ) GetValue ( MyTextProperty ); }
                set { SetValue ( MyTextProperty, value ); }
            }
    
            public static readonly System.Windows.DependencyProperty MyTextProperty =
                System.Windows.DependencyProperty.Register ( "MyText", typeof ( string ), typeof ( UIClass ) );
    
        }
    展示类
    测试类

      相比Winform,WPF实现了按属性名称存/取对象属性的功能,并把这个技术叫做依赖属性。

      有了依赖属性,WPF的SetBinding方法就真正做到通用,这个技术在后面继续研究。

      程序的世界哪有什么神奇,只是有的人做了更多的工作,结果看起来很神奇而已。

      所谓的数据与展示分离,不过是在这两层之外,额外创建了一个管理机构。

      而WPF的绑定技术,就是这个管理机构中的一个部门,负责收、发快递!

  • 相关阅读:
    C#删除程序自身【总结】
    X86(32位)与X64(64位)有什么区别,如何选择对应的操作系统和应用程序?
    【转】关于C#接口和抽象类的一些说明
    C# 的可空合并运算符(??)到底是怎样的宝宝?
    第三章 “我要点爆”微信小程序云开发之点爆方式页面和爆炸之音页面制作
    微信小程序云开发之云函数的创建与环境配置
    第五章 “我要点爆”微信小程序云开发实例之从云端获取数据制作首页
    第一章 “我要点爆”微信小程序云开发之项目建立与我的页面功能实现
    第四章 “我要点爆”微信小程序云开发之疯狂点击与糖果点爆页面制作
    Git的使用方法与GitHub项目托管方法
  • 原文地址:https://www.cnblogs.com/ww960122/p/4590676.html
Copyright © 2020-2023  润新知