WPF 数据绑定(下)

我们在WPF数据绑定(上)中研究了数据绑定的一些基础知识,以及如何建立不同绑定源的几种情况。本文继续讲一些绑定的有关内容。


1.ObjectDataProvider  对象

上文中我们使用了XmlDataProvider,把xml数据作为数据源提供给Binding。根据名字我们可以显然的知道,ObjectDataProvider是用来包装和创建一个对象来提供给Binding。那我们一般在什么情况下会使用这个对象来作为绑定源呢?
我们知道一般情况下,要绑定的源对象通过属性将数据暴露出来,然后Binding通过Source和Path建立绑定。但是 我们很难保证所有的业务类都是为WPF项目准备的。譬如说我们需要绑定一个类中的方法的返回值,因为它不是类的属性,那么如何实现绑定呢。而重新设计底层类的风险和成本会比较高,So可以使用ObjectDataProvider 来实现方法作为数据绑定源。我们来写一个例子看一下。
首先我们写一个BasketBallPlayer类,类中实现一个方法。计算球员一个赛季的平均分。代码如下:
public class BasketBallPlayer : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private string name;
        public string Name// 每个球员的名字
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
                }
            }
        }


        // 计算一个球员场均得分的情况
        public string AveragePoint(string game, string point)
        {

            double x = 0;
            double y = 0;
            double z = 0;

            if (double.TryParse(game, out x) && double.TryParse(point, out y))
            {
                z = x / (y * 1.0);
                return z.ToString();
            }
            return "Input Error!";
        }
}
我们在XAML中定义三个对话框

<StackPanel>
        <TextBox Margin="5" x:Name="textbox1" Height="25"/>
        <TextBox Margin="5" x:Name="textbox2" Height="25"/>
        <TextBox Margin="5" x:Name="textbox3" Height="25"/>
    </StackPanel>
图:

我们希望可以做到,输入球员得分和球员参赛场数得到球员的平均分, 那么如何做到呢?TextBox需要绑定到方法的返回值。OK,ObjectDataProvider派上用场了。

ObjectDataProvider的几个属性:
公共属性 MethodName 获取或设置要调用的方法的名称。
公共属性 MethodParameters 获取要传递给该方法的参数列表。
公共属性 ObjectInstance 获取或设置用作绑定源的对象。
公共属性 ObjectType 获取或设置要创建其实例的对象的类型。
公共属性 Data 获取基础数据对象。 (继承自 DataSourceProvider。)
   //创建并且配置ObjectDataProvider对象
            ObjectDataProvider myodp = new ObjectDataProvider();
            myodp.ObjectInstance = new BasketBallPlayer(); //设置对象实例
            myodp.MethodName = "AveragePoint"; // 设置调用的方法
            myodp.MethodParameters.Add("100"); //添加方法的参数
            myodp.MethodParameters.Add("200");
            //以ObjectDataProvider为Source创建Binding,Path为参数列表的第一个参数,并配置一些属性
            Binding bindToArg1 = new Binding("MethodParameters[0]")
            {
                Source = myodp,
                BindsDirectlyToSource = true,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged // 设置触发时间
            };

            //已ObjectDataProvider对象为Binding源
            Binding bindToArg2 = new Binding("MethodParameters[1]")
            {
                Source = myodp,
                BindsDirectlyToSource = true,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            };
 	    Binding bindingResult = new Binding(".") { Source = myodp };
            //绑定到关联UI元素上
            this.textbox1.SetBinding(TextBox.TextProperty, bindToArg1);
            this.textbox2.SetBinding(TextBox.TextProperty, bindToArg2);
            this.textbox3.SetBinding(TextBox.TextProperty, bindingResult);
我们来分析下运行过程,首先将2个TextBox的内容分别绑定myodp上,Path为参数列表的参数,当内容发生变化,参数也发生变化。将最后一个TextBox的内容绑定到myodp,Path为空。因为这个对象本身就是一个数据。
运行结果:


注意一下这个属性:BindsDirectlyToSource(字面意思 直接绑定到源,也就是ObjectDataProvider对象)
获取或设置一个值,该值指示是否计算相对于数据项或 DataSourceProvider 对象的 Path
BindsDirectlyToSource = true的意思就是告诉Binding对象 从UI元素(这里是两个TextBox)收集到的元素写入直接Source(即myodp这个ObjectDataProvider对象,而不是被它包装的BasketBallPlayer类)
可以把 这两个属性改为 false试一试,初始运行是这个样子:(因为没有直接绑定到源,所以没有显示出来我们初始的时候添加的参数)


创建包装对象的另外一种方法,(这是在XAML代码中)
 <ObjectDataProvider 	MethodName="GetValues"
                        ObjectType="{x:Type sys:Enum}"
                        x:Key="AlignmentValues">
      <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="HorizontalAlignment" />
      </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
创建一个ObjectDataProvider对象,MethodName为GetValuses,对象实例类型为Enum,MethodParameters参数为HorizontalAlignment类型。OK 基本的东西就这么多。(和C#代码所需要的是一样的)。

下面我们尝试一下把刚才的绑定在 XAML代码中实现一遍。

首先声明类:
public class BasketBallPlayer
    {
        // 计算一个球员场均得分的情况
        public string AveragePoint(string game, string point)
        {

            double x = 0;
            double y = 0;
            double z = 0;

            if (double.TryParse(game, out x) && double.TryParse(point, out y))
            {
                z = x / (y * 1.0);
                return z.ToString("F3");
            }
            return "Input Error!";
        }
}

注意函数的参数是String类型,因为从TextBox绑定过来的话是String,如果使用别的类型的话,需要使用类型转换器。


定义资源ObjectDataProvider对象
   <Window.Resources>
        <ObjectDataProvider ObjectType="{x:Type local:BasketBallPlayer}"
                            MethodName="AveragePoint"
                            x:Key="Calculate"
                            >
            <ObjectDataProvider.MethodParameters>
                <s:String>1992</s:String>
                <s:String>2</s:String>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
由于使用了本地的类和 字符串类型,需要添加
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:DataBindingExample"
然后绑定TextBox
 <StackPanel>
        <TextBox Margin="5" x:Name="textbox1" Height="25" >
            <TextBox.Text>
                <Binding Source="{StaticResource Calculate}" Path="MethodParameters[0]"
                         BindsDirectlyToSource="true" UpdateSourceTrigger="PropertyChanged"
                         Mode="TwoWay" />
            </TextBox.Text>
        </TextBox>
                 
        <TextBox Margin="5" x:Name="textbox2" Height="25"
                 Text="{Binding Source={StaticResource Calculate},Path=MethodParameters[1],
                 BindsDirectlyToSource=true, UpdateSourceTrigger=PropertyChanged,
                 Mode = TwoWay
                 }"/>
        <TextBox Margin="5" x:Name="textbox3" Height="25"
                 Text="{Binding Source={StaticResource Calculate},Path=.,Mode = TwoWay}"/>
    </StackPanel>
注意 前2个TextBox绑定的区别。推荐使用第一种(因为第二种方式容易打错字,而且系统是不会报错的)。
运行结果:




上面我们类中采用的是String的参数,但是我们这个类设计本身的话是希望参数为double类型的。
我们需要来实现一个类型转换器。把输入的String参数转换到Double类型。而且我们希望输入的值是在一定的范围内。所以要加以一些限制
下面来引入Binding对数据的转换与校验

数据转换需要动手写一个Converter类并且让它实现IValueConveter接口。接口定义如下:
 public interface IValueConverter
    {
        // Summary:
        //     Converts a value.
        //
        // Parameters:
        //   value:
        //     The value produced by the binding source.
        //
        //   targetType:
        //     The type of the binding target property.
        //
        //   parameter:
        //     The converter parameter to use.
        //
        //   culture:
        //     The culture to use in the converter.
        //
        // Returns:
        //     A converted value. If the method returns null, the valid null value is used.
        object Convert(object value, Type targetType, object parameter, CultureInfo culture);
        //
        // Summary:
        //     Converts a value.
        //
        // Parameters:
        //   value:
        //     The value that is produced by the binding target.
        //
        //   targetType:
        //     The type to convert to.
        //
        //   parameter:
        //     The converter parameter to use.
        //
        //   culture:
        //     The culture to use in the converter.
        //
        // Returns:
        //     A converted value. If the method returns null, the valid null value is used.
        object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
    }

数据从Binding的Source流向Target的时候,Convert方法被调用。当从Target流向Source的时候,ConvertBack被调用.我们来自定义使用这个类。
public class DoubleToStringConverter : IValueConverter
{

}
然后手动添加接口代码:


添加代码如下,
 public class DoubleToStringConverter : IValueConverter
    {

        #region IValueConverter Members
        //source to target(double to string)
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            double d = (double)value;
            return d.ToString();
            //throw new NotImplementedException();
        }
        //String to Double
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string s = (string)value;
            double x;
            if (double.TryParse(s, out x))
                return x;
            else
                return 0.0;
            //throw new NotImplementedException();
        }

        #endregion

    }
然后以资源的形式在XAML代码中添加一个实例。
<local:DoubleToStringConverter x:Key="myConvert"/>
在Texbox的Text属性内,引用它。
<Binding Source="{StaticResource Calculate}" Path="MethodParameters[0]"
                         BindsDirectlyToSource="true" UpdateSourceTrigger="PropertyChanged"
                         Mode="TwoWay" Converter="{StaticResource myConvert}"/>
<TextBox Margin="5" x:Name="textbox2" Height="25"
                 Text="{Binding Source={StaticResource Calculate},Path=MethodParameters[1],
                 BindsDirectlyToSource=true, UpdateSourceTrigger=PropertyChanged,
                 Mode = TwoWay,Converter={StaticResource myConvert}
                 }"/>

OK,类型转换这里就实现了。
下面我们希望在输入的时候能够有一个范围,比如说输入0到3000这个范围,则需要设置Binding的ValidationRules属性,这个属性类型是Collection<ValidationRule>
,我们可以为每个Binding设置多个数据校验条件。每个条件都是一个ValidationRule类型对象(抽象类)。我们要做的是创建它的派生类并实现它的Validate方法。方法返回值是ValidationResult类型对象,如果验证通过,需要设置ValidationResult类型对象的IsValid属性为True,没通过需要设置为false,并为ErrorContent属性设置一个合适的消息内容(一般是字符串)

例子:

 public class myValidation1 : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            double d = 0;
            if(double.TryParse(value.ToString(),out d))
            {
                if(d <=5000 && d>0)
                {
                    return new ValidationResult(true,null);
                }
               
            }
            return new ValidationResult(false, "Validation Failed");
        }
    }
在Text中绑定这个

<local:myValidation1 x:Key="myValidation1"/>
 <TextBox.Text>
                <Binding Source="{StaticResource Calculate}" Path="MethodParameters[0]"
                         BindsDirectlyToSource="true" UpdateSourceTrigger="PropertyChanged"
                         Mode="TwoWay" Converter="{StaticResource myConvert}"
                         NotifyOnValidationError="True"
                            >
                    <Binding.ValidationRules>    //这里是添加ValidationRules的一个ValidationRule
                        <local:myValidation1 ></local:myValidation1>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
但是这样做不能弹出出错的信息,需要写路由事件,这个后续来写~









2.MultiBinding(多路Binding)

有的时候UI需要显示的信息由不止一个数据源来决定,这个时候就需要多路Binding.
一年一度的NBA选秀报名大赛开始了,报名者需要注册填写一些必要的信息才可以报名参加选秀。需要填写的信息如下图所示:



我们看到现在这个提交的按钮是灰的。也就是说现在是不能提交信息的。当你4个文本框都输入内容的时候,我们才可以提交,那么这个按钮的IsEnabled这个属性就由4个数据源来决定的。这个时候就需要用到多路绑定。
xaml代码:
<StackPanel Background="AntiqueWhite" Orientation="Vertical">
        <TextBlock Background="BlanchedAlmond" Margin="10" FontWeight="Bold" FontSize="16.5" Text="Welcome To NBA Draft:Please first Sign up "/>
        <StackPanel Orientation="Horizontal">
            <TextBlock Margin="10" Width="50" Text="Name:" 
                       HorizontalAlignment="Center" VerticalAlignment="Center"
                       FontSize="16"
                       />
            <TextBox Height="30" x:Name="textBoxName" Width="300"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Margin="10" Width="50" Text="Age:" 
                       HorizontalAlignment="Center" VerticalAlignment="Center"
                       FontSize="16"
                       />
            <TextBox Height="30" x:Name="textBoxAge" Width="300"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Margin="10" Width="50" Text="Phone:" 
                       HorizontalAlignment="Center" VerticalAlignment="Center"
                       FontSize="16"
                       />
            <TextBox Height="30" x:Name="textBoxPhone" Width="300"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Margin="10" Width="50" Text="Email:" 
                       HorizontalAlignment="Center" VerticalAlignment="Center"
                       FontSize="16"
                       />
            <TextBox Height="30" x:Name="textBoxEmail" Width="300"/>
        </StackPanel>
        <Button  x:Name="buttonSubmit" HorizontalAlignment="Center" VerticalAlignment="Center" 
                Content="Submit" Width="50" Height="30" IsEnabled="False" />
    </StackPanel>

C#代码:

public Window1()
        {
            InitializeComponent();
            this.SetMultiBinding();
        }
        private void SetMultiBinding()
        {
            //准备基础的Binding
            Binding bindingName = new Binding("Text") { Source = this.textBoxName };
            Binding bindingAge = new Binding("Text") { Source = this.textBoxAge };
            Binding bindingPhone = new Binding("Text") { Source = this.textBoxPhone };
            Binding bindingEmail = new Binding("Text") { Source = this.textBoxEmail };

            //准备MultiBinding
            MultiBinding myMultiBinding = new MultiBinding() { Mode = BindingMode.OneWay };
            myMultiBinding.Bindings.Add(bindingName);
            myMultiBinding.Bindings.Add(bindingAge);
            myMultiBinding.Bindings.Add(bindingPhone);
            myMultiBinding.Bindings.Add(bindingEmail);

            myMultiBinding.Converter = new myMultiBindingConverter();

            this.buttonSubmit.SetBinding(Button.IsEnabledProperty, myMultiBinding);

        }
Converter类为:

public class myMultiBindingConverter : IMultiValueConverter
        {

            #region IMultiValueConverter Members

            public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if ((values[0].ToString().Length != 0) && (values[1].ToString().Length != 0)
                    &&(values[2].ToString().Length != 0) && (values[3].ToString().Length != 0))
                {
                    return true;
                }
                else
                    return false;

                //throw new NotImplementedException();
            }

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

            #endregion
        }
运行结果:




需要注意的地方 MultiBinding对于添加子级Binding的顺序是敏感的。因为这个顺序决定了汇集到Converter里数据的顺序

Convert函数中最重要的就是它的values参数。这个参数是一个数组,这个数组里包含的就是从一对一子Binding里送来的值(在我们的程序里,就是4个TextBox的Text属性值)。数组是可被索引的,这就意味着values里面的值是有顺序的!那么这个顺序是什么呢?这个顺序就是你调用MultiBinding.Bindings.Add(...)添加子Binding顺序。本例中,values[0]对应的是textBox1.Text属性值、values[1]对应的是textBox2.Text属性值、values[2]对应的是textBox3.Text属性值、values[3]对应的是textBox4.Text属性值。


另外本例的Converter基接口是IMultiValueConverter,而不是IValueConverter。这个接口仍然只有两个函数需要实现一下。又因为我们这次使用的是OneWay模式,所以,代码中只有Convert函数中包含逻辑。
 
































  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值