65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 教程 - 概念绑定

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (46投票s)

2010年12月28日

CPOL

10分钟阅读

viewsIcon

237714

downloadIcon

7644

绑定是WPF编程中最重要的话题。在本文中,我将演示如何使用数据绑定来确保表示逻辑与视图分离,并简单演示数据绑定概念的工作原理。

目录

引言

在此文之前,我已讨论过WPF的架构、标记扩展、依赖属性、逻辑树和视觉树、布局、变换等。今天,我将讨论我们称之为任何WPF应用程序中最重要部分的“绑定”。WPF具有卓越的DataBinding(数据绑定)功能,它使用户能够绑定对象,以便当其他对象发生变化时,主对象能够反映这些变化。DataBinding的主要目的是确保UI始终自动与内部对象结构同步。

在深入探讨之前,让我们回顾一下我们已经讨论过的内容。如果您是本文的新读者,您可以从下面的列表中我的其他文章开始阅读:

binding.jpg

DataBinding(数据绑定)在WPF出现之前就已存在。在ASP.NET中,我们绑定数据元素以从控件中呈现正确的数据。我们通常传递一个DataTable并绑定模板以从各个DataRows获取数据。另一方面,在传统的Windows Forms应用程序中,我们也可以将属性绑定到数据元素。可以将绑定添加到对象的属性中,以确保每当属性值更改时,数据会在内部得到反映。所以,一言以蔽之,DataBinding对系统来说并不是什么新鲜事。DataBinding的主要目标是向应用程序显示数据,从而减少应用程序开发人员为使应用程序正确显示数据而需要编写的工作量。在本文中,我将讨论如何在WPF应用程序中使用Databinding,并创建一个示例应用程序来深入演示该功能。

WPF 中的绑定

WPF将Binding(绑定)的概念进一步发展,并引入了新功能,以便我们可以广泛地使用绑定功能。绑定建立了应用程序和业务层之间的连接。如果您希望您的应用程序遵循严格的设计模式规则,DataBinding(数据绑定)概念将帮助您实现这一点。我们将在稍后更详细地探讨如何实现这一点。

在WPF中,我们可以绑定两个属性、一个属性和一个DependencyProperty(依赖属性)、两个DependencyProperties等。WPF还支持Command Binding(命令绑定)。让我们详细讨论如何实现它们。

绑定可分为几种类型。

数据绑定 / 对象绑定

最重要和最主要的绑定是Databinding(数据绑定)。WPF引入了ObjectDataProviderXMLDataProvider等对象,可在XAML中声明,以增强对象绑定的能力。DataBinding(数据绑定)可以通过多种方式实现。如Adnan在他的博客中所述[^],我们可以通过使用XAML、XAML和C#,或者仅使用C#来利用绑定功能。因此,WPF足够灵活,可以处理任何情况。

<TextBox x:Name="txtName" />
<TextBlock Text="{Binding ElementName=txtName, Path=Text.Length}" />

在上述情况中,我展示了绑定的最基本用法。TextBlockText属性绑定到TextBox txtName,因此当您在运行时在TextBox中输入任何内容时,TextBlock将显示该string的长度。

作为一种标记扩展,绑定实际上是一个带属性的类。在此,我们指定了ElementNamePath属性的值。ElementName确保了属性所属的对象。Path定义了对象需要查找的属性路径。

您可以使用ObjectDataProvider轻松地在XAML中处理数据。ObjectDataProvider可以添加为资源,然后使用StaticResource进行引用。让我们看看下面的代码:

<StackPanel Orientation="Vertical">
    <StackPanel.Resources>
      <ObjectDataProvider ObjectType="{x:Type m:StringData}"
         x:Key="objStrings" MethodName="GetStrings"/>
    </StackPanel.Resources>
    <ListBox Name="lstStrings" Width="200" Height="300"
           ItemsSource="{Binding Source={StaticResource objStrings}}" />

正如上面所示,ObjectType将获得一个类型,该类型是调用GetStrings方法的内部类结构。从ListBox中,我使用StaticResource引用了对象。现在在代码中,您可以声明一个类:

public class StringData
 {
  ObservableCollection<String> lst= new ObservableCollection<String>();

        public StringData()
        {
            lst.Add("Abhishek");
            lst.Add("Abhijit");
            lst.Add("Kunal");
            lst.Add("Sheo");
        }
        public ObservableCollection<String> GetStrings()
        {
             return lst;
        }
    }

这样,您可以看到列表已填充了string

为什么使用 ObservableCollection、INotifyPropertyChanged、INotifyCollectionChanged?

现在您可以看到,我使用了ObvervableCollection。这很重要。ObservableCollection在插入新项时发送自动通知。因此,它会通知ListBox更新列表。所以,如果您放置一个插入数据的按钮到ObservableCollection中,绑定将自动由集合通知,从而自动更新集合。您无需手动将相同的数据插入ListBox

WPF绑定通常需要被通知何时修改。INotifyPropertyChangedINotifyCollectionChanged接口需要更新与数据绑定的UIElement。因此,如果您正在创建一个属性,并且该属性需要在其值修改时更新UI,那么最低要求是实现INotifyPropertyChanged,而对于集合(如ItemsSource),它需要实现INotifyCollectionChangedObservableCollection本身实现了INotifyCollectionChanged,因此它支持在列表插入新项或从string中移除旧项时更新控件。

我在一篇关于对象和集合的更改通知的文章中详细讨论了这两个接口。 [^]

相反,Sacha提出了一个通过Aspect Examples (INotifyPropertyChanged via aspects)来摆脱INotifyPropertyChanged接口的好方法。

XML 绑定

与对象绑定类似,XAML也支持XML绑定。您可以使用Binding类定义中的内置属性,如XPath,轻松绑定来自XMLDataProvider的数据。让我们看看代码:

<TextBlock Text="{Binding XPath=@description}"/>
<TextBlock Text="{Binding XPath=text()}"/>

因此,如果您在XYZ节点中,可以使用text()属性获取InnerText@符号用于属性。所以,使用XPath,您可以轻松处理您的XML。

如果您想了解更多关于XML绑定的信息,请查看:WPF中的XML绑定 [^]。

DataContext 的重要性

您可能会想,为什么我在讨论WPF绑定时提到了DataContext的上下文。DataContext实际上是一个依赖属性。它指向原始数据,因此我们作为DataContext传递的对象将继承给其所有子控件。我的意思是,如果您为Grid定义了DataContext,那么Grid内的所有元素都将获得相同的DataContext

<Grid DataContext="{StaticResource dtItem}">
<TextBox Text="{Binding MyProperty}" />
</Grid>

在这里,正如我为Grid定义了DataContext一样,网格内的TextBox可以将其MyProperty属性引用为dtItem对象将自动继承给其所有子元素。在使用绑定时,DataContext是您必须使用的最重要部分。

绑定成员

正如大家所知,绑定是一种标记扩展。它是一个带有几个属性的Binding类。让我们讨论一下Binding的成员:

  1. Source:源属性保存DataSource。默认情况下,它引用控件的DataContext。如果您为绑定设置了Source属性,它将代替原始DataContext元素。
  2. ElementName:在与另一个Element进行Binding时,ElementName用于获取XAML中定义的Element的名称以引用对象。ElementName充当Source的替代。如果未为绑定指定Path,它将使用ToString从作为Source传递的对象中获取数据。
  3. PathPath定义了获取字符串数据的实际属性路径。如果最终结果不是string,它还将调用ToString来获取数据。
  4. Mode:它定义数据如何流动。OneWay表示仅当源更新时对象才会被更新,反之,OneWayToSource则相反。TwoWay定义数据在两个方向上流动。
  5. UpdateSourceTrigger:这是任何Binding的另一个重要部分。它定义了何时更新源。UpdateSourceTrigger的值可以是:
    • PropertyChanged:这是默认值。结果是,当控件中的任何内容更新时,其他绑定的元素都会反映相同的内容。
    • LostFocus:这意味着当属性失去焦点时,属性将被更新。
    • Explicit:如果选择此选项,您需要明确设置何时更新源。您需要使用BindingExpressionUpdateSource来更新控件。
      BindingExpression bexp = mytextbox.GetBindingExpression(TextBox.TextProperty);
      bexp.UpdateSource();
      通过这种方式,源会得到更新。
  6. ConverterConverter为您提供一个接口,可以插入一个对象,该对象将在绑定对象更新时被调用。任何实现IValueConverter的对象都可以代替Converter使用。您可以从绑定中的转换器了解更多关于它的信息。 [^]
  7. ConverterParameter:它与Converter一起使用,将参数发送给Converter
  8. FallbackValue:定义在Binding无法返回任何值时将显示的值。默认情况下,它是空白的。
  9. StringFormat:一个格式化string,指示数据将遵循的Format
  10. ValidatesOnDataErrors:当指定此项时,将验证DataErrors。您可以使用IDataErrorInfo在数据对象更新时运行自定义验证块。您可以从:使用IDataErrorInfo验证您的应用程序了解更多关于IDataErrorInfo的信息。 [^]。

代码隐藏中的绑定

与您在XAML中所做的类似,您也可以在代码隐藏中定义绑定。要做到这一点,您需要使用

 Binding myBinding = new Binding("DataObject");
  myBinding.Source = myDataObject;
  myTextBlock.SetBinding(TextBlock.TextProperty, myBinding);

您也可以这样指定Binding属性。

命令绑定

WPF支持CommandBinding(命令绑定)。每个命令对象(如Button)都公开一个名为Command的属性,该属性接受一个实现ICommand接口的对象,并在对象命令触发时执行Execute方法。

例如,当窗口输入被调用时,您希望您的命令被执行。

<Window.InputBindings>
        <KeyBinding Command="{Binding CreateNewStudent}" Key="N" Modifiers="Ctrl" />
        <MouseBinding Command="{Binding CreateNewStudent}"
		MouseAction="LeftDoubleClick" />
    </Window.InputBindings>

在上面的代码中,CreateNewStudent是一个属性,它公开实现ICommand接口的对象,并且当窗口的Ctrl + N键或LeftDoubleClick被调用时,将调用Execute方法。

注意:在VS 2008中,InputBindings仅接受Static命令对象。 有一个关于此的bug报告 [^],将在后续版本中修复。

您可以使用CommandParameter将参数传递给构成ICommand接口的方法。

<Button Content="CreateNew" Command="{Binding CreateNewStudent}" />

InputBindings类似,您可以使用ButtonCommand。要执行,您需要创建一个实现ICommand的对象,如下所示:

public class CommandBase : ICommand
    {
        private Func<object, bool> _canExecute;
        private Action<object> _executeAction;
        private bool canExecuteCache;

        public CommandBase(Action<object>executeAction, Func<object, bool> canExecute)
        {
            this._executeAction = executeAction;
            this._canExecute = canExecute;
        }

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            bool tempCanExecute = _canExecute(parameter);
            canExecuteCache = tempCanExecute;
            return canExecuteCache;
        }
        private event EventHandler _canExecuteChanged;
        public event EventHandler CanExecuteChanged
        {
            add { this._canExecuteChanged += value; }
            remove { this._canExecuteChanged -= value; }
        }
        protected virtual void OnCanExecuteChanged()
        {
            if (this._canExecuteChanged != null)
                this._canExecuteChanged(this, EventArgs.Empty);
        }
        public void Execute(object parameter)
        {
            _executeAction(parameter);
        }

        #endregion
    }

我使用了一个CommandBase类来使对象看起来不那么笨拙。实际的对象类如下所示:

private CommandBase createNewstudent;
        public CommandBase CreateNewStudent
        {
            get
            {

                this.createNewstudent = this.createNewstudent ??
           new CommandBase(param => this.CreateStudent(), param => this.CanCreateStudent);
                return this.createNewstudent;
            }
        }

        private object CreateStudent()
        {
            this.CurrentStudent = new StudentItem();
            return this.CurrentStudent;
        }

        public bool CanCreateStudent
        {
            get { return true; }
        }

这样,您可以看到createNewCommand传递了CreateStudent lambda表达式,该表达式在对象更新时被调用。CanCreateStudent属性也将被调用,根据truefalse,WPF将允许命令执行。

unittesting.jpg

PropertyBinding(属性绑定)和CommandBinding(命令绑定)共同提供了一个完整的解决方案,用于将表示逻辑与表示层分离。这提供了将所有逻辑分离的架构。Microsoft创建了整个Expression blend,使用了MVVM模式,将视图与ViewModel分离,从而为表示层提供了轻松进行单元测试的机会。我们将在系列文章的后续内容中进一步讨论这个主题。

MultiBinding(多重绑定)

与单个Binding类似,WPF还引入了MultiBinding(多重绑定)的概念。在MultiBinding的情况下,绑定数据依赖于多个源。您可以指定多个绑定表达式,并且实际输出取决于每个绑定表达式。

<TextBlock DockPanel.Dock="Top" >
   <TextBlock.Text>
      <MultiBinding Converter="{StaticResource mbindingconv}">
        <Binding ElementName="lst" Path="Items.Count" />
        <Binding ElementName="txtName" Path="Text" />
        <Binding ElementName="txtAge" Path="Text" />
      </MultiBinding>
   </TextBlock.Text>
 </TextBlock>

这里,TextBlock的值依赖于3个元素:第一个是ListBox计数,然后是txtNametxtAge。我使用了Converter来确保我们在IMultiValueConverter块中找到所有单独的元素,并单独处理每个值。IMultiValueConverterIValueConverter类似,可以接受值并返回绑定到Text属性的对象。

public class MyMultiBindingConverter : IMultiValueConverter
    {
        #region IMultiValueConverter Members

        public object Convert(object[] values, Type targetType,
                object parameter, System.Globalization.CultureInfo culture)
        {
            string returnval = "Total no of Data {0}, NewData : ";

            if (values.Count() <= 0) return string.Empty;

            returnval = string.Format(returnval, values[0]);

            for (int i = 1; i < values.Count(); i++)
                returnval += "- " + values[i];

            return returnval;
        }

        public object[] ConvertBack(object value, Type[]
            targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

为简单起见,我只是连接了传递的每个值,并返回输出。

在示例应用程序中,我使用了最简单的绑定,以确保所有内容都来自模型。您可以在本文顶部找到示例应用程序的链接。

结论

我认为您一定很享受这个系列。也欢迎您发表评论。感谢您的阅读。

© . All rights reserved.