zoukankan      html  css  js  c++  java
  • WPF学习(8)数据绑定

    说到数据绑定,其实这并不是一个新的玩意儿。了解asp.net的朋友都知道,在asp.net中已经用到了这个概念,例如Repeater等的数据绑定。那么,在WPF中的数据绑定相比较传统的asp.net中的数据绑定又有哪些优点呢?

    1)具有双向性,即从源到目标是双向的
    2)及时更新,源发生改变时,能够及时更新UI
    3)Validation和Converter,前者保证数据的合法性,后者保证数据的有效性
    接下来,我们将从这么几个方面来说明:Binding对象(对应xaml中的Binding扩展标记)、Binding的Path以及Source、Validation及Converter和MultiBinding。

    1.Binding对象

    在Binding对象中,主要成员可以分为这么几类:
    1)路径:Path属性和XPath属性
    2)源:Source、RelativeSource和ElementName
    3)更新通知:NotifyOnSourceUpdated、NotifyOnTargetUpdated和NotifyOnValidationError
    4)转换器:Converter、ConverterCulture和ConverterParameter
    5)验证:ValidatesOnDataErrors、ValidatesOnExceptions、ValidatesOnNotifyDataErrors和ValidationRules
    6)绑定方式:Mode,BindingMode枚举类型:TwoWay,OneWay,OneTime,OneWayToSource和Default
    需要注意的是:Binding的目标必须是依赖对象的某个依赖属性。

    2.Binding的Path以及Source

    对于Binding的Path及Source,并非要是依赖属性及依赖对象。几乎任何一个对象都可以作为Binding的Source,主要有普通CLR对象、ado.net对象、XML、Linq、依赖对象、容器的DataContext、RelativeSource和ObjectDataProvider等。
    而Path就是普通CLR对象、容器的DataContext、RelativeSource和Linq的某个属性、
    ado.net对象的某个字段、依赖对象的某个依赖属性和ObjectDataProvider的某个方法名。
    这里需要注意一下几点:

    1)有Path没Source,将去找其父元素有该Path的DataContext;
    2)有Source没Path,则将Source也作为其Path;

    3)无Source无Path,则将其父元素的DataContext既作为Source也作为Path。

    关于Binding的Source这里只说下Xml、Linq to Xml和ObjectDataProvider,其它几种略过。

    2.1使用Xml作为Binding Source

    首先,我们来准备xml数据,命名为Students.xml,如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <Students>
      <Student ID="1">
        <Name>Jello</Name>
        <Score>80</Score>
      </Student>
      <Student ID="2">
        <Name>Taffy</Name>
        <Score>100</Score>
      </Student>
    </Students>

    Xaml代码如下:

    <Window x:Class="DataBindingDemo.XmlWindow1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="XmlWindow1" Height="300" Width="300">
        <Grid>
            <ListBox x:Name="lb">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock x:Name="tbID" Text="{Binding XPath=@ID}" Width="20" Foreground="Red"/>
                            <TextBlock x:Name="tbName" Text="{Binding XPath=Name}" Width="40" Foreground="Green"/>
                            <TextBlock x:Name="tbScore" Text="{Binding XPath=Score}" />
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Window>

     cs代码如下:

        /// <summary>
        /// XmlWindow1.xaml 的交互逻辑
        /// </summary>
        public partial class XmlWindow1 : Window
        {
            public XmlWindow1()
            {
                InitializeComponent();
                /*第一种写法:
                XmlDocument doc = new XmlDocument();
                doc.Load(@"./Students.xml");
                XmlDataProvider xdp = new XmlDataProvider();
                xdp.Document = doc;
                xdp.XPath = "/Students/Student";
                this.lb.SetBinding(ListBox.ItemsSourceProperty, new Binding(".") { Source = xdp });
                 */
                //第二种写法:
                XmlDataProvider xdp = new XmlDataProvider();
                xdp.Source = new Uri(@"F:dotnetWPF学习WPFDemoWpfDemoDataBindingDemoStudents.xml");
                xdp.XPath = "/Students/Student";
                this.lb.DataContext = xdp;
                this.lb.SetBinding(ListBox.ItemsSourceProperty, new Binding());
            }
        }

     第一种写法借助于XmlDataProvider的Document属性,第二种写法借助于XmlDataProvider的Source属性。

    如果想直接在Xaml代码里面来直接使用XmlDataProvider来进行绑定的话,需要将Xml数据放在<x:XData>...</x:XData>标签内,代码如下:

    <Window x:Class="DataBindingDemo.XmlWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="XmlWindow" Height="300" Width="300">
        <Window.Resources>
            <XmlDataProvider x:Key="xdp" XPath="Students/Student">
                <x:XData>
                    <Students xmlns="">
                        <Student ID="1">
                            <Name>Jello</Name>
                            <Score>80</Score>
                        </Student>
                        <Student ID="2">
                            <Name>Taffy</Name>
                            <Score>100</Score>
                        </Student>
                    </Students>
                </x:XData>
            </XmlDataProvider>
        </Window.Resources>
        <Grid>
            <ListBox x:Name="lb" ItemsSource="{Binding Source={StaticResource xdp}}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding XPath=@ID}" Width="20" Foreground="Red"/>
                            <TextBlock Text="{Binding XPath=Name}" Width="40" Foreground="Green"/>
                            <TextBlock Text="{Binding XPath=Score}" />
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Window>

     效果如下:

    2.2使用Linq to Xml作为Binding Source

    在使用Linq to Xml作为Binding Source之前,我们当然需要准备model,这里创建一个Student实体类:

        public class Student
        {
            public int ID { get; set; }
            public string Name { get; set; }
            public int Score { get; set; }
        }

    Xaml代码如下:

    <Window x:Class="DataBindingDemo.XmlWindow2"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="XmlWindow2" Height="300" Width="300">
        <Grid>
            <ListBox x:Name="lb">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock x:Name="tbID" Text="{Binding Path=ID}" Width="20" Foreground="Red"/>
                            <TextBlock x:Name="tbName" Text="{Binding Path=Name}" Width="40" Foreground="Green"/>
                            <TextBlock x:Name="tbScore" Text="{Binding Path=Score}" />
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Window>

     cs代码如下:

        /// <summary>
        /// XmlWindow2.xaml 的交互逻辑
        /// </summary>
        public partial class XmlWindow2 : Window
        {
            public XmlWindow2()
            {
                InitializeComponent();
    
                XDocument doc = XDocument.Load(@"F:dotnetWPF学习WPFDemoWpfDemoDataBindingDemoStudents.xml");
                this.lb.ItemsSource = from e in doc.Descendants("Student")
                                      select new Student
                                      {
                                          ID = Int32.Parse(e.Attribute("ID").Value),
                                          Name = e.Element("Name").Value,
                                          Score = Int32.Parse(e.Element("Score").Value)
                                      };
            }
        }

     2.3使用ObjectDataProvider作为Binding Source

    其他对象都是针对属性作为Path的情况,而ObjectDataProvider对象主要是针对方法。所以,我们先准备一个具有Add方法的Calculate类:

        public class Calculate
        {
            public double Add(string d1, string d2)
            {
                double i, j;
                if (double.TryParse(d1, out i) && double.TryParse(d2, out j))
                    return i + j;
                else
                    return 0;
            }
        }

    Xaml代码如下:

    <Window x:Class="DataBindingDemo.ODPWindow1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="ODPWindow1" Height="300" Width="300">
        <Grid>
            <StackPanel>
                <TextBox x:Name="tb1"/>
                <TextBox x:Name="tb2"/>
                <TextBox x:Name="tbResult"/>
            </StackPanel>
        </Grid>
    </Window>

     cs代码如下:

        /// <summary>
        /// ODPWindow1.xaml 的交互逻辑
        /// </summary>
        public partial class ODPWindow1 : Window
        {
            public ODPWindow1()
            {
                InitializeComponent();
    
                ObjectDataProvider odp = new ObjectDataProvider();
                odp.ObjectInstance = new Calculate();
                odp.MethodName = "Add";
                odp.MethodParameters.Add("0");
                odp.MethodParameters.Add("0");
    
                this.tb1.SetBinding(TextBox.TextProperty, new Binding("MethodParameters[0]")
                {
                    Source = odp,
                    BindsDirectlyToSource = true,
                    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
                });
                this.tb2.SetBinding(TextBox.TextProperty, new Binding("MethodParameters[1]")
                {
                    Source = odp,
                    BindsDirectlyToSource = true,
                    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
                });
                this.tbResult.SetBinding(TextBox.TextProperty, new Binding(".")
                    {
                        Source = odp
                    });
            }
        }

     效果如下:

    3.Binding的Converter

    数据在Binding的Target端和Source端交换时,经常会出现类型或者格式不一致的情况,这时候,我们就可以使用Converter来处理。

    WPF内置了许多的Converter,例如:

    <Grid.Background>
        Red
    </Grid.Background>

     Backgroud属性是Brush抽象类型,而我们只是用一个Red字符串赋值就能达到效果,这是内置的从String类型到SolidColorBrush类型的Converter。

    我们也可以实现自己的Converter,只要实现IValueConverter接口即可。

    4.Binding的Validation

    数据在Binding的Target端和Source端交换时,除了经常出现类型或者格式不一致,还出现数据不合法的情况。为了避免脏数据的出现,需要在交换前进行Validate。例如:

    <StackPanel>
                <TextBox x:Name="tb" Text="{Binding Value,ElementName=slider,UpdateSourceTrigger=PropertyChanged}"/>
                <Slider x:Name="slider" Minimum="0" Maximum="99" />
            </StackPanel>

    这里,我们要实现的效果是在TextBox中输入一个0到99之间的数字,Slider会划到相应位置,若输入的数字不在该范围,则TextBox提示数据不合法。

    Xaml代码如下:

    <StackPanel>
                <TextBox x:Name="tb" />
                <Slider x:Name="slider" Minimum="0" Maximum="200" />
            </StackPanel>

     cs代码如下:

        /// <summary>
        /// ConverterWnd.xaml 的交互逻辑
        /// </summary>
        public partial class ConverterWnd : Window
        {
            public ConverterWnd()
            {
                InitializeComponent();
    
                Binding binding = new Binding("Value")
                    {
                        Source = this.slider,
                        UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                    };
                RangeValidation rv = new RangeValidation();
                rv.ValidatesOnTargetUpdated = true;//验证Source
                binding.NotifyOnValidationError = true;//触发Validation.ErrorEvent
                binding.ValidationRules.Add(rv);
                this.tb.SetBinding(TextBox.TextProperty, binding);
                this.tb.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(NotifyError));
            }
            protected void NotifyError(object sender, RoutedEventArgs e)
            {
                TextBox tb = sender as TextBox;
                if (tb != null)
                {
                    if (Validation.GetErrors(tb).Count > 0)
                        tb.ToolTip = Validation.GetErrors(tb)[0].ErrorContent.ToString();
                }
            }
        }
        public class RangeValidation : ValidationRule
        {
            public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
            {
                double d;
                if (double.TryParse(value.ToString(), out d))
                {
                    if (d >= 0 && d <= 99)
                        return new ValidationResult(true, null);
                }
                return new ValidationResult(false, "数据不合法");
            }
        }

    这里将Slider的最大值设为了200,当其值大于99时,由于ValidatesOnTargetUpdate=true,所以也会路由Validation.ErrorEvent事件。

    效果如下:

     其实,当未添加验证时,Slider的CoerceValue会强制处理。

    5.MultiBinding多路绑定

    经常会遇到这样的需求,要求显示“开始日期 -- 结束日期”这样的格式,这时候比较好的做法就是使用MultiBinding,当然,你也可以重新定义一个属性。

    Xaml代码如下:

    <Grid>
            <StackPanel>
                <TextBlock Text="{Binding Name}"/>
                <TextBlock>
                    <TextBlock.Text>
                        <MultiBinding StringFormat="{}{0:yyyy-MM-dd}至{1:yyyy-MM-dd}">
                            <Binding Path="StartDate" />
                            <Binding Path="EndDate" />
                        </MultiBinding>
                    </TextBlock.Text>
                </TextBlock>
            </StackPanel>
        </Grid>

     cs代码如下:

        /// <summary>
        /// MultiBindingWnd.xaml 的交互逻辑
        /// </summary>
        public partial class MultiBindingWnd : Window
        {
            public MultiBindingWnd()
            {
                InitializeComponent();
    
                Fruit fruit = new Fruit() { Name = "Apple", StartDate = DateTime.Today, EndDate = DateTime.Today.AddYears(1) };
                this.DataContext = fruit;
            }
        }

     这里需要注意两点:

    1)MultiBinding添加Binding的顺序会影响Converter

    2)MultiBinding的Converter实现的是IMultiValueConverter接口

  • 相关阅读:
    启用和配置 FILESTREAM
    SQL SERVER的锁机制(四)——概述(各种事务隔离级别发生的影响)
    SQL SERVER的锁机制(三)——概述(锁与事务隔离级别)
    系统性能测试 IO--AIX
    存储前端
    Markdown的css样式源码
    Typora +google + Markdown Here 公众号
    存储学习
    ChsLLVMDocs
    Parallel Database for OLTP and OLAP
  • 原文地址:https://www.cnblogs.com/jellochen/p/3541197.html
Copyright © 2011-2022 走看看