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方法省略目标这个参数

  相比Winform,WPF实现了按属性名称存/取对象属性的功能。

  WPF如何实现按名称存/取对象属性的方法,用到了依赖属性的技术,这部分在后面继续研究。

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

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

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

时间: 2024-10-10 14:35:19

WPF学习(二) - 绑定的相关文章

WPF学习二:TextBlock和Label的区别

TextBlock和Label都是用来显示少量数据的.好多文章对Label存在的描述都是它允许使用"快速获取"."快速获取"就是允许你用Alt加上其它的按键快速和UI界面的某个控件交互,比如你可以用ALT加上O键来点击一个OK按钮. TextBlock直接继承于FrameworkElement,而Label继承于ContentControl.这样看来,Label可以做这样的事情: 1.可以定义一个控件模板(通过Template属性) 2.可以显示出string以外的

【WPF学习】第二十九章 元素绑定——将元素绑定到一起

原文:[WPF学习]第二十九章 元素绑定--将元素绑定到一起 数据banding的最简单情形是,源对象时WPF元素而且源属性是依赖性属性.前面章节解释过,依赖项属性具有内置的更改通知支持.因此,当在源对象中改变依赖项属性的值时,会立即更新目标对象中的绑定属性.这正是我们所需要的行为--而且不必为此构建任何额外的基础结构. 为理解如何将一个元素绑定到另一个元素,下面创建一个简单的示例.该示例窗口包含了两个控件:一个Slider控件和一个具有单行文本的TextBlock控件.如果向右拖动滑动条上的滑

【WPF学习】第三十章 元素绑定——绑定到非元素对象

原文:[WPF学习]第三十章 元素绑定--绑定到非元素对象 前面章节一直都在讨论如何添加链接两个各元素的绑定.但在数据驱动的应用程序中,更常见的情况是创建从不可见对象中提取数据的绑定表达式.唯一的要求是希望显示的信息必须存储在公有属性中.WPF数据绑定数据结构不能获取私有信息或公有字段. 当绑定到非元素对象时,需要放弃Binding.ElementName属性,并使用以下属性中的一个: Source:该属性是指向源对象的引用--换句话说,是提供数据的对象. RelativeSource:这是引用

【WPF学习】第三十二章 执行命令

原文:[WPF学习]第三十二章 执行命令 前面章节已经对命令进行了深入分析,分析了基类和接口以及WPF提供的命令库.但尚未例举任何使用这些命令的例子. 如前所述,RoutedUICommand类没有任何硬编码的功能,而是只表达命令,为触发命令,需要有命令源(也可使用代码).为响应命令,需要有命令绑定,命令绑定将执行转发给普遍的事件处理程序. 一.命令源 命令库中的命令始终可用.触发他们的最简单的方法是将它们关联到实现了ICommandSource接口的控件,其中包括继承自ButtonBase类的

WPF学习拾遗(二)TextBlock换行

原文:WPF学习拾遗(二)TextBlock换行 下午在帮组里的同事解决一个小问题,为了以后方便,把就把它收集一下吧. 新建一个TextBlock作为最基础的一个控件,他所携带的功能相对于其他的控件要来的比较少, 比较值得关注的属性出了布局用的以外和数据绑定意外,就只剩下的Text属性来. TextBlock的默认内容属性是Text. 对于换行来说,需要关注的也就是Text和Inline2个属性. 常见的TextBlock换行方法 1)转义字符换行 在XAML的后台文件中加入代码 1 Me.Te

WPF学习系列之二 (依赖项属性)

依赖属性;(dependency property)  它是专门针对WPF创建的,但是WPF库中的依赖项属性都使用普通的.NET属性过程进行了包装.从而可能通过常规的方式使用它们,即使使用他们的代码不理解WPF依赖项属性系统也是如此,使用旧技术包装新技术看起来有些奇怪,但这正是WPF能够改变基础组成部分,而不会扰乱.NET领域中其他部分的原因.三步:一:定义依赖项属性.public static readonly DependencyProperty MarginProperty;二:.在静态构

wpf学习(二)

1.命名空间xmlns xmlns:x其只是MS的一个命名而已,没有任何特殊的意义 xmlns:local="clr-namespace:myNamespace" --每个XAML元素都是一个CLR类型,通过自定义的命名空间,可以使用命名空间里的类 xmlns:sys="clr-namespace:System;assembly=System"--通过这种方式,可以在XAML中使用几乎所有的DOTNET框架类 2.通常用的xaml元素 Root元素:Windows和

WPF学习日记——Window的DataContext绑定ViewModel

1.全局的ViewModel绑定: a)设定全局的ViewModel(App.xaml中): 1 <Application x:Class="MyTest.App" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:local=

【WPF学习】第二十章 内容控件

原文:[WPF学习]第二十章 内容控件 内容控件(content control)是更特殊的控件类型,它们可包含并显示一块内容.从技术角度看,内容控件时可以包含单个嵌套元素的控件.与布局容器不同的是,内容控件只能包含一个子元素,而布局容器主要愿意可以包含任意多个牵头元素. 正如前面所介绍,所有WPF布局容器都继承自抽象类Panel,该类提供了对包含多个元素的支持.类似地,所有内容控件都继承自抽象类ContentControl.下图显示了ContentControl类的层次结构. 图 Conten

【WPF学习】第三十一章 WPF命令模型

原文:[WPF学习]第三十一章 WPF命令模型 WPF命令模型由许多可变的部分组成.总之,它们都具有如下4个重要元素: 命令:命令表示应用程序任务,并且跟踪任务是否能够被执行.然而,命令实际上不包含执行应用程序任务的代码. 命令绑定:每个命令绑定针对用户界面的具体区域,将命令连接到相关的应用程序逻辑.这种分解的设计是非常重要的,因为单个命令可用于应用程序中的多个地方,并且在每个地方具有不同的意义.为处理这一问题,需要将同一命令与不同的命令绑定. 命令源:命令源触发命令.例如,MenuItem和B