WPF学习之Binding(二)

前言:

(一)里面简单学习了Binding的基础、源、路径、如何控制Binding的方向及数据更新、使用DataContext作为Binding的源等

使用集合对象作为列表控件的ItemSource

WPF列表式控件派生自ItemControl类,自然继承了ItemSource这个属性。

例如:ListBox条目容器ListBoxItem,ComboBox条目容器ComboBoxItem。

ItemSource里存着一条条数据,想要显示出来必须借助条目容器。Binging就是用来联系条目容器和数据的。

只要我们为一个ItemsControl设置一个ItemsSource属性值,ItemsControl对象就会自动迭代其中的数据元素,为每个数据元素准备一条条目容器,

并使用Binding在条目容器和数据元素之间建立联系。

例子:UI代码如下:

 <StackPanel x:Name="stackPanel" Background="LightBlue">
        <TextBlock Text="Student ID:" FontWeight="Bold" Margin="5"/>
        <TextBox x:Name="textboxId" Margin="5"/>
        <TextBlock Text="Student List:" FontWeight="Bold" Margin="5"/>
        <ListBox x:Name="listBoxStudents" Height="110" Margin="5">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Path=Id}" Width="30"/>
                        <TextBlock Text="{Binding Path=Name}" Width="60"/>
                        <TextBlock Text="{Binding Path=Age}" Width="30"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>

C#代码如下:

 public DC()
        {
            InitializeComponent();
            List<Student1> stuList = new List<Student1>()
            {
                new Student1(){Id=0,Name="Tim",Age=23},
                new Student1(){Id=1,Name="Tom",Age=24},
                new Student1(){Id=2,Name="Aom",Age=24},
                new Student1(){Id=3,Name="Bom",Age=24},
                new Student1(){Id=4,Name="Vom",Age=24},
                new Student1(){Id=5,Name="Tom",Age=24},
                new Student1(){Id=6,Name="Tom",Age=24},
            };
            this.listBoxStudents.ItemsSource = stuList;
            this.listBoxStudents.DisplayMemberPath = "Name";
            Binding binding = new Binding("SelectedItem.Id") { Source = this.listBoxStudents };
            this.textboxId.SetBinding(TextBox.TextProperty, binding);
        }

使用Xml数据作为Binding的源

.Net Framework提供了两套处理XML数据的类库:

符合DOM标准的类库。特点:中规中矩,功能强大,但也背负了太多XML传统和复杂。

以LINQ为基础的类库。特点:可以使用LINQ进行查询和操作,方便快捷。

线性集合例子:

UI代码如下:

 <StackPanel Background="LightBlue">
        <ListView x:Name="listViewStudents" Height="130" Margin="5">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Id" Width="80"
                                     DisplayMemberBinding="{Binding [email protected]}"/>
                    <GridViewColumn Header="Name" Width="120"
                                     DisplayMemberBinding="{Binding XPath=Name}"/>
                </GridView>
            </ListView.View>
        </ListView>
        <Button Content="Load" Margin="5,0" Click="Button_Click_1" Height="25"/>
    </StackPanel>

Xml代码如下:

<?xml version="1.0" encoding="utf-8" ?>
<StudentList>
  <Student Id="1">
    <Name>Tom</Name>
  </Student>
  <Student Id="2">
    <Name>Tim</Name>
  </Student>
  <Student Id="3">
    <Name>Tqm</Name>
  </Student>
  <Student Id="4">
    <Name>Ter</Name>
  </Student>
  <Student Id="5">
    <Name>Te</Name>
  </Student>
  <Student Id="6">
    <Name>Tof</Name>
  </Student>
  <Student Id="7">
    <Name>Tf</Name>
  </Student>
</StudentList>

Button的Click处理事件代码如下:

private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(@"D:\WORK\Program\7.5\Demo\WPF12.23\RamData.xml");
            XmlDataProvider xdp = new XmlDataProvider();
            xdp.Document = doc;
            xdp.XPath = @"/StudentList/Sudent";
            this.listViewStudents.DataContext = xdp;
            this.listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding());
        }

效果图:

XML语言可方便的表示树型数据结构,下面的例子是用TreeView控件来显示有若干层目录的文件系统。

这次把XML数据和XmlDataProvider对象直接写在了Xaml代码里,代码中用到了HierarchicalDataTemplate类。

这个类具有ItemsSource属性,由这种Template展示的数据是可以拥有子级集合的。代码如下:

<Window.Resources>
        <XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder">
            <x:XData>
                <FileSystem xmlns="">
                    <Folder Name="Books">
                        <Folder Name="Windows">
                            <Folder Name="WPF"/>
                            <Folder Name="WCF"/>
                            <Folder Name="PDF"/>
                        </Folder>
                    </Folder>
                    <Folder Name="Tools">
                        <Folder Name="Develioment"/>
                        <Folder Name="WCF"/>
                        <Folder Name="PDF"/>
                    </Folder>
                </FileSystem>
            </x:XData>
        </XmlDataProvider>
    </Window.Resources>
    <Grid>
        <TreeView ItemsSource="{Binding Source={StaticResource xdp}}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}">
                    <TextBlock Text="{Binding [email protected]}"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Grid>

效果如图:

使用LINQ检索结果作为Binding的源

LINQ查询结果为IEnumerable<T>类型对象,而IEnumerable<T>又派生自IEnumerable,所以他可以作为列表控件的ItemsSource来使用。

先创建一个Student类

设计UI用于Button被点击时显示Student集合类型对象。

<StackPanel Background="LightBlue">
        <ListView x:Name="listViewStudents" Height="143" Margin="5">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Id" Width="60"
                                    DisplayMemberBinding="{Binding Id}"/>
                    <GridViewColumn Header="Name" Width="100"
                                    DisplayMemberBinding="{Binding Name}"/>
                    <GridViewColumn Header="Age" Width="80"
                                    DisplayMemberBinding="{Binding Age}"/>
                </GridView>
            </ListView.View>
        </ListView>
        <Button Content="Load" Click="Button_Click_1" Margin="5,0" Height="25"/>
 </StackPanel>

要从一个已经填充好的List<Student>中检索出所有名字已字母‘T’开头的学生,代码如下:

 List<Student2> stuList = new List<Student2>()
            {
                new Student2(){Id=0,Name="Tim",Age=23},
                new Student2(){Id=1,Name="Tom",Age=24},
                new Student2(){Id=2,Name="Aom",Age=24},
                new Student2(){Id=3,Name="Bom",Age=24},
                new Student2(){Id=4,Name="Vom",Age=24},
                new Student2(){Id=5,Name="Tom",Age=24},
                new Student2(){Id=6,Name="Tom",Age=24},
            };
            this.listViewStudents.ItemsSource = from stu in stuList where stu.Name.StartsWith("T") select stu;

效果如图:

使用ObjectDataProvider对象作为Binding的Source

ObjectDataprovider顾名思义就是把对象作为数据源提供给Binding。

前面还提到XmlDataProvider,也就是把XML数据作为数据源供给Binding。2个的父类都是DataSourceProvider抽象类。

现有一个Calu的类,具有计算加减乘除的方法:

 class Calu
    {
        public string Add(string a, string b)
        {
            double x = 0, y = 0, z = 0;
            if (double.TryParse(a, out x) && double.TryParse(b, out y))
            {
                z = x + y;
                return z.ToString();
            }
            return "input error";
        }
    }

这个例子需要实现的功能是在前2个TextBox输入数字后,第3个TextBox显示数字和。代码如下:

 public LINQ()
        {
            InitializeComponent();
            Setbinding();
        }
private void Setbinding()
        {
            ObjectDataProvider odp = new ObjectDataProvider();
            odp.ObjectInstance = new Calu();
            odp.MethodName = "Add";
            odp.MethodParameters.Add("0");
            odp.MethodParameters.Add("0");
            Binding bdToArg1 = new Binding("MethodParameters[0]")
            {
                Source = odp,
                BindsDirectlyToSource = true,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            };
            Binding bdToArg2 = new Binding("MethodParameters[1]")
            {
                Source = odp,
                BindsDirectlyToSource = true,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            };
            Binding bdResult = new Binding(".") { Source = odp };
            this.textBoxArg1.SetBinding(TextBox.TextProperty, bdToArg1);
            this.textBoxArg2.SetBinding(TextBox.TextProperty, bdToArg2);
            this.textBoxResult.SetBinding(TextBox.TextProperty, bdResult);
        }

效果:

使用Binding的RelativeSource

RelativeSource属性的数据类型为RelativeSource类,通过这个类的静态和非静态属性我们可以控制它搜索相对数据源的方式。

下面这段代码是多层布局控件内放置一个TextBox:

<Grid x:Name="g1" Background="Red" Margin="10">
        <DockPanel x:Name="d1" Background="Orange" Margin="10">
            <Grid x:Name="g2" Background="Yellow" Margin="10">
                <DockPanel x:Name="d2" Margin="10">
                    <TextBox x:Name="textBox1" Margin="10" FontSize="24"/>
                </DockPanel>
            </Grid>
        </DockPanel>
    </Grid>

把TextBox的Text属性关联到Name属性上。构造器里的代码如下:

  public Relative()
        {
            InitializeComponent();
            RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor);
            rs.AncestorLevel = 1;
            rs.AncestorType=typeof(DockPanel);
            Binding binding = new Binding("Name") { RelativeSource=rs};
            this.textBox1.SetBinding(TextBox.TextProperty, binding);
        }

效果图:

关联自身Name属性,代码为:

 RelativeSource rs = new RelativeSource();
            rs.Mode = RelativeSourceMode.Self;
            Binding binding = new Binding("Name") { RelativeSource=rs};
            this.textBox1.SetBinding(TextBox.TextProperty, binding);

效果:

Binding对数据的转换与校验

ValidationRule类是个抽象类,在使用的时候要创建他的派生类并实现它的Validate方法。

Validate方法的返回值是ValidationResult类型对象,如果校验通过就把IsValid属性设为true,反之设为false并为ErrorContent属性设置合适的消息内容。

下面这个程序是在UI上绘制一个TextBox和Slider,然后在后台C#代码里使用Binding关联起来,以Slider为源,TextBox为目标。

Slider的取值范围是0-100,我们要验证TextBox里的值是否为0-100.XAML代码如下:

<StackPanel>
        <TextBox x:Name="textBox1" Margin="5"/>
        <Slider x:Name="slider1" Minimum="0" Maximum="100" Margin="5"/>
    </StackPanel>

准备一个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, "数据有误");
            throw new NotImplementedException();
        }
    }

在构造器里建立Binding:

 public Valida()
        {
            InitializeComponent();
            Binding binding = new Binding("Value") { Source=this.slider1};
            binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            RangeValidationRule rvr = new RangeValidationRule();
            rvr.ValidatesOnTargetUpdated = true;
            binding.ValidationRules.Add(rvr);
            binding.NotifyOnValidationError = true;
            this.textBox1.SetBinding(TextBox.TextProperty, binding);

            this.textBox1.AddHandler(Validation.ErrorEvent,new RoutedEventHandler(this.VilidationError));
        }

效果:

           

Binding的数据转换

Binding还有一种机制成为数据转换(Data Convert)。

当Source端Path所关联的数据与Target目标数据类型不一,可以添加数据转换器(Data Converter)。

下面是一个Data Converter的综合实例,程序用途是向玩家显示飞机的状态。

首先创建几个自定义数据类型:

  //种类
    public enum Category
    {
        Bomber,Fighter
    }
    //状态
    public enum State
    {
        Available,Locked,Unkuown
    }
    //飞机
    public class Plane
    {
        public Category Category { get; set; }
        public String Name { get; set; }
        public State State { get; set; }
    }

飞机的State属性在UI被映射成CheckBox。所以我们要提供Converter,

将Category类型转换成String类型,State类型和Bool类型的双向转换。代码如下:

 public class CategoryToSourceConverter : IValueConverter
    {
        //将Catagory类型转换为Uri
        public object Convert(object value, Type targetType, object parameter,CultureInfo culture)
        {
            Category c = (Category)value;
            switch (c)
            {
                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 parameter,CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    public class StateToNullableBoolConverter : IValueConverter
    {
        //将State转换为bool
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            State s = (State)value;
            switch (s)
            {
                case State.Available: return false;
                case State.Locked: return true;
                case State.Unkuown:
                default: return null;
            }
        }
        //将bool转换为State
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool? nb = (bool?)value;
            switch (nb)
            {
                case true: return State.Available;
                case false: return State.Locked;
                case null:
                default: return State.Unkuown;
            }
        }
    }

下面看看怎么在XAML里消费这2个Converter,代码如下:

 <Window.Resources>
        <local:CategoryToSourceConverter x:Key="cts"/>
        <local:StateToNullableBoolConverter x:Key="stnb"/>
    </Window.Resources>
    <StackPanel Background="LightBlue">
        <ListBox x:Name="listBoxPlane" Height="160" Margin=" 5">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Image Width="20" Height="20" Source="{Binding Path=Category,Converter={StaticResource cts}}"/>
                        <TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/>
                        <CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stnb}}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button x:Name="ButtonLoad" Content="Load" Width="50" Margin="5,0" Click="Button_Click_1"/>
        <Button x:Name="ButtonSave" Content="Save" Width="50" Margin="5,5" Click="ButtonSave_Click_1"/>
    </StackPanel>

Load按钮的Click事件处理器负责把一组飞机的数据赋值给ListBox是ItemsSource属性,Save按钮的Click事件负责把修改后的数据写入文件:

  private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            List<Plane> planelist = new List<Plane>
            {
                new Plane(){Category=Category.Bomber,Name="B-1",State=State.Unkuown},
                new Plane(){Category=Category.Bomber,Name="B-2",State=State.Unkuown},
                new Plane(){Category=Category.Fighter,Name="F-22",State=State.Unkuown},
                new Plane(){Category=Category.Fighter,Name="Su-47",State=State.Unkuown},
                new Plane(){Category=Category.Bomber,Name="B-52",State=State.Unkuown},
                new Plane(){Category=Category.Fighter,Name="J-10",State=State.Unkuown}
            };
            this.listBoxPlane.ItemsSource = planelist;
        }

        private void ButtonSave_Click_1(object sender, RoutedEventArgs e)
        {
            StringBuilder sb = new StringBuilder();
            foreach (Plane p in listBoxPlane.Items)
            {
                sb.AppendLine(string.Format("Category={0},Name={1},State={2}",p.Category,p.Name,p.State));
            }
            File.WriteAllText(@"D:\WORK\Program\7.5\Demo\WPF12.29\Icons\Planlist.txt", sb.ToString());
        }

效果:

MultiBinding(多路Binding)

有时候UI显示的信息不止来自一个数据源,这时候就要用MultiBinding。

例:用户注册UI,第一、二个输入用户名三、四输入Email要求一致,Button可用。

XAML代码如下:

 <StackPanel Background="LightBlue">
        <TextBox x:Name="textbox1" Height="23" Margin="5"/>
        <TextBox x:Name="textbox2" Height="23" Margin="5,0"/>
        <TextBox x:Name="textbox3" Height="23" Margin="5"/>
        <TextBox x:Name="textbox4" Height="23" Margin="5,0"/>
        <Button x:Name="buuton1" Content="Submit" Width="80" Margin="5"/>
    </StackPanel>

用于设置MultiBinding的方法写在SetMultiBinding里并在构造器里调用,代码如下:

 public MultiBind()
        {
            InitializeComponent();
            this.SetMultiBinding();
        }
        private void SetMultiBinding()
        {
            //准备基础绑定
            Binding b1 = new Binding("Text") { Source = this.textbox1 };
            Binding b2 = new Binding("Text") { Source = this.textbox2 };
            Binding b3 = new Binding("Text") { Source = this.textbox3 };
            Binding b4 = new Binding("Text") { Source = this.textbox4 };

            //准备MulitiBinding
            MultiBinding mb = new MultiBinding() {Mode=BindingMode.OneWay};
            mb.Bindings.Add(b1);//multibinding对add的顺序是敏感的
            mb.Bindings.Add(b2);
            mb.Bindings.Add(b3);
            mb.Bindings.Add(b4);
            mb.Converter = new LogonMultiBindingConverter();

            //将Button与MultiBinding对象关联
            this.buuton1.SetBinding(Button.IsEnabledProperty,mb);
        }

Converter代码如下:

 public class LogonMultiBindingConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (!values.Cast<string>().Any(text => string.IsNullOrEmpty(text))
                &&values[0].ToString()==values[1].ToString()
                &&values[2].ToString()==values[3].ToString())
            {
                return true;
            }
            return false;
        }
        public object[] ConvertBack(object values, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

效果:

                

Binding这一章好长好长,知识点太多,基本上梳理完了。累啊!!!!

时间: 2024-10-23 18:12:29

WPF学习之Binding(二)的相关文章

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学习之Binding的学习(一)

程序的本质是数据加算法.通俗一点来说呢,其实就是用户给一个输入,经过算法的处理之后,计算机反馈一个输出给用户.可以很清楚的看出,在这个过程中,处于主导地位的是数据.但是,当我们在进行图形用户界面(Graphic User Interface,GUI)编程的时,数据总是处于被动地位.也就是说,程序总是在等待接收来自UI的消息/事件,在这些事件被处理之后,才会反馈给用户一个输出.我们用Data Binding可以在GUI编程时让数据回到程序的核心. **1.Data Binding在WPF中的地位*

WPF学习笔记(二)----Events, Commands

1.配置事件和事件处理(Configuring Events and Event Handling) 事件在WPF编程是明显不同于那些在传统的Windows窗体的编程.WPF使用路由事件,可以存在多个控制和多个处理程序.路由事件允许您添加多个层次的复杂性和复杂的用户界面,响应用户输入.学习路由事件,包括如何处理一个路由事件,定义和注册一个新的路由事件,处理应用程序生命周期事件,并使用EventManager类. 1.1 下面使用一个实例说明如何定义.注册.封装路由事件 public abstra

WPF学习之Binding(一)

Binding WPF的核心理念是传统的变UI驱动程序为数据驱动UI,支撑这个理念的基础就是Data Binding和与之相关的数据校验和转换. 使用Binding时,最重要的是准确设置它的源和路径. Binding基础 Binding是数据的桥梁,两端分别是Source(源)和Target(目标) 例子:创建一个简单的数据源binding在UI元素上 创建数据源(student类) class Student : INotifyPropertyChanged { public event Pr

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

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

WPF入门教程系列(二) 深入剖析WPF Binding的使用方法

WPF入门教程系列(二) 深入剖析WPF Binding的使用方法 同一个对象(特指System.Windows.DependencyObject的子类)的同一种属性(特指DependencyProperty)只能拥有一个binding. 这一点可以通过设置binding对象的方法名得知: public static BindingExpressionBase SetBinding( DependencyObject target, DependencyProperty dp, BindingB

【转】WPF中的Binding技巧(二)

WPF中的Binding技巧(二)   接上篇, 我们来看一看Elementname,Source,RelativeSource 三种绑定的方式 1.ElementName顾名思义就是根据Ui元素的Name来进行绑定: 例子: <Window x:Name="MainWindow"> <Grid>               <Button Background="{Binding ElementName=MainWindow, Path=Bac

WPF学习09:数据绑定之 Binding to List Data

从WPF学习03:Element Binding我们可以实现控件属性与控件属性的绑定. 从WPF学习07:MVVM 预备知识之数据绑定 我们可以实现控件属性与自定义对象属性的绑定. 而以上两个功能在实际应用中还是不够的,我们经常需要将列表数据与控件属性进行绑定. 例子 ListBox切换人物,下面两个文本框跟随切换,很常用的功能. XAML代码: <Window x:Class="DataTemplate.MainWindow" xmlns="http://schema