DataBinding快速入门(3)——数据的转换与校验

        Binding就是搭建在Source与Target之间的桥梁,数据就是桥梁上来往的车辆,但是某些情况下,桥梁上可设置关卡即对数据的有效性进行校验。同时对于桥梁两端不同类型的数据,可以设置转换器。Binding的有效性校验的关卡是ValidationRules属性,数据类型转换的关卡是Converter属性。

DataBinding数据校验

        Binding的ValidationRules属性类型为Collection<ValidationRule>,从类型和名称可以得出,它可以为每个Bingding设置多个数据校验条件,每个条件是ValidationRule类型对象。ValidationRule是抽象类,所以需要创建它的派生类并实现它的Validate方法。Validate方法返回值是ValidationResult类型对象,如果校验通过,把ValidationResult的IsValid属性设为true,反正为false并为ErrorContent属性设置消息字符串。

        下面已Slider为源,TextBox为目标。Slider取值范围是0到100,因此需要校验TextBox里输入的值是不是在0到100这个范围。

         XAML代码如下:

<Window x:Class="Demo.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="120" Width="300">
    <StackPanel Background="AliceBlue" Margin="10">
        <TextBox Name="textBox1" Margin="5"/>
        <Slider  Name="slider1" Margin="5" Maximum="100" Minimum="0"/>
    </StackPanel>
</Window>

        创建ValidationRule派生类:

   public class RangeValidationRule : ValidationRule
    {

        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            double d = 0;
            if (double.TryParse(value.ToString(), out d))
            {
                if (d >= 0 && d <= 100)
                {
                    return new ValidationResult(true, null);
                }
            }
            return new ValidationResult(false, "ErrorContent");
        }
    }

        建立Binding:

    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            Binding bind = new Binding("Value") { Source = slider1};
            bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            RangeValidationRule rvr = new RangeValidationRule();
            bind.ValidationRules.Add(rvr);
            this.textBox1.SetBinding(TextBox.TextProperty, bind);
        }
    }

        运行程序,当输入0到100之间的值的时候正常显示,当不在这个范围内时,TextBox会显示红色边框,表示值是错误的,不能把它传递给源,如下所示:

  

        Binding校验时默认总是认为Source的数据总是正确的,即不进行校验。因此只有来自Target的数据才有可能有问题,为了不让有问题的数据影响Source,才需要校验Target。如果想校验Source,则需要将校验条件ValidatesOnTargetUpdated属性设为true。

      设置错误消息提示,如何显示消息呢?这里需要用到--路由事,(Routed Event)。创建Binding时设置NotifyOnValidationError属性设为true。当数据校验失败,Binding会发出报警信号,从Binding对象的Target为起点在UI元素树路由,即信号在树上传递的过程就称为路由(Route)。信号会被拦截到每个被设置信号侦听器(事件处理器)的节点,即触发侦听器。信号处理完毕,你可以选择信号继续向下传播还是终止(路由事件)。

       Binding代码如下:

扫描二维码关注公众号,回复: 11122769 查看本文章
        public Window3()
        {
            InitializeComponent();

            Binding bind = new Binding("Value") { Source = slider1 };
            bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            RangeValidationRule rvr = new RangeValidationRule();
            rvr.ValidatesOnTargetUpdated = true;
            bind.ValidationRules.Add(rvr);
            bind.NotifyOnValidationError = true;
            this.textBox1.SetBinding(TextBox.TextProperty, bind);
            this.textBox1.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(ValidationError));
        }

        用于侦听错误的事件处理器如下:

        private void ValidationError(object sender, RoutedEventArgs e)
        {
            if (Validation.GetErrors(textBox1).Count > 0)
            {
                this.textBox1.ToolTip = Validation.GetErrors(textBox1)[0].ErrorContent.ToString();
            }
            else
            {
                this.textBox1.ToolTip = "";
            }
        }

        Validation.GetErrors(textBox1)[0],这里稍作解释,GetErrors返回ReadOnlyObservableCollection<ValidationError>类型,即静态的ObservableCollection<T> 类,它是集合。因为只有一个ValidationError对象,所有只能有索引0。

         如果校验失败,ToolTip就会显示,如下图所示:

 

Binding的数据转换

        Binding存在一种称为数据转换(Data Convert)的机制,当Source端Path所关联的数据与Target端目标属性数据类型不一致时,可以添加数据转换器(Data Converter)。比较简单的类型,如上面的例子double转string,即系统的基本类型,WPF类库就自动实现了。但遇到某些复杂的情况,如:

  • Source数据对象有a、b、c三个值(可能是int、string、自定义类型),UI上比如对应CheckBox控件,需要把这三个值映射为它的IsChecked属性值(bool类型);
  • 当TextBox里已经输入了文字时才有Button出现,这是string类型与Visibility枚举类型或者bool类型之间的转换(Binding的Mode是OneWay);
  • Source里数据比如是Male或者Female(string或者枚举),UI上对应的是用于显示头像的Image控件,这时候需要把Source里的值转换成对应的头像图片URI(OneWay的Mode)。

       遇到以上诸多复杂的情况,就需要自己实现Converter,创建一个类并让该类实现IValueConverter接口。IValueConverter接口定义如下:

public interface IValueConverter
{
	object Convert(object value, Type targetType, object parameterm, CultureInfo culture);
	object ConvertBack(object value, Type targetType, object parameterm, CultureInfo culture);
}

        当数据从Binding的Source流向Target时,Convert方法将被调用;反之,ConvertBack方法将被调用。这两个方法基本一致,参数一模一样。重点第一个参数是object,它是转化的源对象;第二个参数targetType,即方法的返回类型,同时也是目标类型;第三个参数用于把额外信息传入方法;第四个参数是提供有关转换器中区域性的信息(对于非托管代码开发,则称为“区域设置”)。这些信息包括区域性的名称、书写系统、使用的日历以及对日期和排序字符串的格式化设置。

        Binding对象的Mode属性会影响到这两个方法的调用。如果Mode为TwoWay,两个方法都会被调用;如果OneWay则是一个方法被调用。

        下面是一个Converter实例,在列表里显示一些军用飞机的状态。

        首先创建几个自定义类:

    /// <summary>
    /// 种类
    /// </summary>
    public enum Category
    {
        Bomber,
        Fighter
    }

    /// <summary>
    /// 状态
    /// </summary>
    public enum State
    {
        Available,
        Locked,
        Unknown
    }

    /// <summary>
    /// 飞机
    /// </summary>
    public class Plane
    {
        public Category category { get; set; }
        public State state { get; set; }
        public string name { get; set; }
    }

        在解决方案里加入轰炸机和战斗机的图标,如下所示:

        这里要做的是飞机的State属性映射为CheckBox,即State自定义类型与bool之间的双向转换;以及飞机的Category类型单向转换为图标,即Category自定义类型转换为string类型,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;

namespace Demo.BLL
{
    public class CategoryToSourceConverter:IValueConverter
    {
        // 将Category转化为Uri
        public object Convert(object value, Type targetType, object parameters, System.Globalization.CultureInfo culture)
        {
            Category category = (Category)value;
            switch (category)
            {
                case Category.Bomber:
                    return @"ICONS/Bomber.png";
                   
                case Category.Fighter:
                    return @"ICONS/Fighter.png";
                   
                default:
                    return null;
                    
            }
        }

        // 不会被调用
        public object ConvertBack(object value, Type targetType, object parameters, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;

namespace WpfApplication1.BLL
{
    public class StateToNullableBoolConverter:IValueConverter
    {
        // 将State转化为bool
        public object Convert(object value, Type targetType, object parameters, System.Globalization.CultureInfo culture)
        {
            State state = (State)value;
            switch (state)
            {
                case State.Available:
                    return true;
                    
                case State.Locked:
                    return false;
                case State.Unknown:
                    
                default:
                    return null;
            }
        }

        // 将bool转化为State
        public object ConvertBack(object value, Type targetType, object parameters, System.Globalization.CultureInfo culture)
        {
            bool? nb = (bool?)value;
            switch (nb)
            {
                case true:
                    return State.Available;
                case false:
                    return State.Locked;
                case null:
                default:
                    return State.Unknown;
                    
            }
        }
    }
}

        XAML代码如下:

<Window x:Class="Demo.Window4"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Demo.BLL"
        Title="Data Convert" Height="266" Width="300">
    <Window.Resources>
        <local:CategoryToSourceConverter x:Key="cts" />
        <local:StateToNullableBoolConverter x:Key="snb" />
    </Window.Resources>
    <StackPanel Background="LightBlue">
        <ListBox  Name="listBoxPlane" Height="160" Margin="5"/>

        <Button Content="Load" Height="25" Name="Loadbutton" Margin="5,0" Click="Loadbutton_Click" />
        <Button Content="Save" Height="25" Name="Savebutton"  Margin="5,5" Click="Savebutton_Click" />
    </StackPanel>
</Window>

        XAML代码中添加了对程序集的引用并映射为名称空间local,以资源的形式创建两个Converter实例。ListBox是该例子的重点,需要为它添加显示数据的DataTemplate。如下:

    <StackPanel Background="LightBlue">
        <ListBox  Name="listBoxPlane" Height="160" Margin="5">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Image Height="20" Width="20" Source="{Binding Path=category,Converter={StaticResource cts}}"/>
                        <TextBlock Text="{Binding Path=name}" Margin="80,0" Width="60"/>
                        <CheckBox IsChecked="{Binding Path=state,Converter={StaticResource snb}}" IsThreeState="True"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        Load按钮的Click事件处理器负责把一组飞机的数据赋值给ListBox的ItemsSource属性,Save按钮的Click时间处理器负责把用户更改过得数据写入文件:

    public partial class Window17 : Window
    {
        public Window17()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Load按钮事件处理器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            List<Plane> infos = new List<Plane>() { 
            new Plane(){ category= Category.Bomber,name="B-1", state= State.Unknown},
            new Plane(){ category= Category.Bomber,name="B-2", state= State.Unknown},
            new Plane(){ category= Category.Fighter,name="F-22", state= State.Locked},
            new Plane(){ category= Category.Fighter,name="Su-47", state= State.Unknown},
            new Plane(){ category= Category.Bomber,name="B-52", state= State.Available},
            new Plane(){ category= Category.Fighter,name="J-10", state= State.Unknown},
            };
            this.listBox1.ItemsSource = infos;
        }
        /// <summary>
        /// Save按钮事件处理器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, RoutedEventArgs e)
        {
            StringBuilder sb = new StringBuilder();
            foreach (Plane item in listBox1.Items)
            {
                sb.AppendLine(string.Format("Categroy={0},State={1},Name={2}",item.category,item.state,item.name));
            }
            File.WriteAllText(@"D:\PlaneList.text",sb.ToString());
        }

    }

        运行程序并单击CheckBox更改飞机的State,效果如下:

        单击Save按钮打开D:\PlaneList.txt,如下所示:

发布了34 篇原创文章 · 获赞 12 · 访问量 2536

猜你喜欢

转载自blog.csdn.net/u013946061/article/details/81627459