WPF基础学习 —— 2

本文是基于《深入浅出WPF》的第6章节的学习笔记,详细介绍了WPF中的Binding基础知识,包括属性、代码示例、Binding的源、控制Binding的方向及数据更新、Source、使用多种数据源如ADO.NET、XML等,并探讨了数据转换与校验以及多路Binding的应用。通过Binding,实现了数据和UI之间的桥梁,确保数据的双向同步。

参考书籍《深入浅出WPF》,原书第6章节学习笔记整理。
程序的本质是数据+算法

Binding基础

Binding是一座桥梁,它的源是逻辑层的对象、它的目标是UI层的控件对象。这样数据会不断的通过Binding送达UI层,被UI层展现。

1、属性

创建一个简单的类对象作为数据源,设置将类中的参数属性通过Binding传递给UI控件,则该属性即为路径(Path)。
除此之外,当属性值发生变化后需要自动通知Binding,将变化的数值传递给UI控件。

  • 在属性的set语句中激发一个PropertyChanged事件
  • 作为数据源的实现System.ComponentModel中的INotifyPropertyChanged接口

当为Bing设置了数据源后,会自动侦听来自该接口的PropertyChanged事件

2、代码

注意:粘贴xml代码时,上半部分xmls不能粘贴,他是针对具体的工程建立的。

<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication1_8_18.MainWindow"
        Title="Simple Binding" Height="110" Width="300" >
    <StackPanel>
        <TextBox x:Name="textBoxName" BorderBrush="Black" Margin="5"/>
        <Button Content="Add Name" Margin="5" Click="Button_Click"/>
    </StackPanel>
</Window>

窗体界面显示如下:
在这里插入图片描述

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;//以上为程序自动添加的引用
//以下为额外添加引用
using System.ComponentModel;//添加引用

class Student : INotifyPropertyChanged //让作为数据源的Student类实现System.ComponentModel空间的INotifyPropertyChanged接口
{
    public event PropertyChangedEventHandler PropertyChanged; //句柄
    private string name;  //Student类里面的局部变量
    public string Name
    {
        get { return name;}
        set
        {
            name = value;//value?
            //激发事件
            if(this.PropertyChanged!=null)
            {
                this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
            }
        }
    }
}

namespace WpfApplication1_8_18
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
 
    public partial class MainWindow : Window
    {
        Student stu ;
        
        public MainWindow()
        {
            InitializeComponent();
            //准备数据源
            stu = new Student();
            //准备Binding
            Binding binding = new Binding();
            binding.Source = stu;   //为Binding指定数据源
            binding.Path = new PropertyPath("Name");  //为Binding指定访问路径
            //使用Binding连接数据源与Binding目标:(目标,送达目标的哪个属性,使用的Binding实例)
            BindingOperations.SetBinding(this.textBoxName, TextBox.TextProperty, binding);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //使用Binding把数据源和UI元素连接起来
            stu.Name += "Name";
        }      
    }    
}

程序运行效果如下:
在这里插入图片描述

3、整个程序运行的流程如下:

在这里插入图片描述

4、借助Binding构造器进行简化

在这里插入图片描述

Binding的源
把控件作为Binding源

为了让UI产生一些联动效果,会用Binding在控件之间建立关联。

<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication1_8_18.MainWindow"
        Title="Control as Source" Height="110" Width="300" >
    <StackPanel>
        <TextBox x:Name="textbox1" Text="{Binding Path=Value, ElementName=Slider1}" BorderBrush="Black" Margin="5"/>
        <Slider x:Name="Slider1" Maximum="100" Minimum="0" Margin="5"/>
    </StackPanel>
</Window>

在这里插入图片描述
上述程序中Binding的其它等价形式
语句:

<TextBox x:Name="textbox1" Text="{Binding Path=Value, ElementName=Slider1}" BorderBrush="Black" Margin="5"/>

等价于

<TextBox x:Name="textbox1" BorderBrush="Black" Margin="5"/>
this.textbox1.SetBinding(TextBox.TextProperty, new Binding("Value") { ElementName = "Slider1" });
控制Binding的方向及数据更新

默认情况下为双向的。当控件只可读,不可修改时就需要设置为单向。
控制Binding数据流向为Mode:

  • TwoWay
  • OneWay
  • OnTime
  • OneWayToResource
  • Default

上述示例中,滑动Slider则TextBox会显示当前值。反之,在TextBox中输入数值(输入50,按下Tab键使焦点离开TextBox),Slider也会滑动到相应位置。

设置为OneWay之后:滑动Slider则TextBox会改变显示值,但是在TextBox中输入数值之后Slider不会滑动了。

<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication1_8_18.MainWindow"
        Title="Control as Source" Height="110" Width="300" >
    <StackPanel>
        <TextBox x:Name="textbox1" Text="{Binding Path=Value, ElementName=Slider1,Mode=OneWay}" BorderBrush="Black" Margin="5"/>
        <Slider x:Name="Slider1" Maximum="100" Minimum="0" Margin="5"/>
    </StackPanel>
</Window>
//等价的C#命令
this.textbox1.SetBinding(TextBox.TextProperty, new Binding("Value") { Source = this.textbox1, Mode = BindingMode.OneWay });

ps:TextBox失去焦点之后Slider才会移动,是因为默认Binding属性UpdateSourceTrigger:
包含了PropertyChanged、LostFocus、Explicit和Default。而TextBox的默认行为与LostFocus一致,当修改为PropertyChanged,则Slider位置会即刻改变。

<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication1_8_18.MainWindow"
        Title="Control as Source" Height="110" Width="300" >
    <StackPanel>
        <TextBox x:Name="textbox1" Text="{Binding Path=Value, ElementName=Slider1,UpdateSourceTrigger=PropertyChanged}" BorderBrush="Black" Margin="5"/>
        <Slider x:Name="Slider1" Maximum="100" Minimum="0" Margin="5"/>
    </StackPanel>
</Window>

pps: Binding还有NotifyOnSourceUpdated和NotifyOnTargetUpdated这2个bool类型属性,如果设为true,当源或者目标被更新后Binding会激发相应的SourceUpdated和TargetUpdated事件,可以通过监听这2个事件来找出有哪些数据源或者控件更新了。

Binding的路径Path

源对象有很多属性,Path决定Binding需要关注哪个属性。各种Path创建方式:

  • 最简单的方式:
 <TextBox x:Name="textbox1" Text="{Binding Path=Value, ElementName=Slider1}" BorderBrush="Black" Margin="5"/>

等价的C#:

 Binding binding = new Binding() {Path=new PropertyPath("Value"),Source=this.Slider1 };
 this.textbox1.SetBinding(TextBox.TextProperty, binding);
 //或者使用Binding构造器
 Binding binding = new Binding("Value") { Source = this.Slider1 };
 this.textbox1.SetBinding(TextBox.TextProperty, binding);
  • 集合中的索引器作为Path
<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication1_8_18.MainWindow"
        Title="Control as Source" Height="110" Width="300" >
    <StackPanel>
        <TextBox x:Name="textbox1" BorderBrush="Black" Margin="5"/>
        <TextBox x:Name="textbox2" Text="{Binding Path=Text.[3], ElementName=textbox1, Mode=OneWay}" BorderBrush="Black" Margin="5"/>
        <!--上面的语句中Mode=OneWay必须填写,否则会报错-->
    </StackPanel>
</Window>

在这里插入图片描述

//等价的C#语句,不同之处:此处用Source 、BindingMode
this.textbox2.SetBinding(TextBox.TextProperty, new Binding("Text.[3]") { Source = this.textbox1, Mode = BindingMode.OneWay });
  • 集合或者DataView的默认元素作为Path
<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication1_8_18.MainWindow"
        Title="Control as Source" Height="120" Width="300" >
    <StackPanel>
        <TextBox x:Name="textbox1" BorderBrush="Black" Margin="5"/>
        <TextBox x:Name="textbox2"  BorderBrush="Black" Margin="5"/>
        <TextBox x:Name="textbox3" BorderBrush="Black" Margin="5"/>
    </StackPanel>
</Window>
//string_list 的默认元素为[0]=Tim
List<string> string_list = new List<string>() { "Tim", "Tom", "Tic" };
this.textbox1.SetBinding(TextBox.TextProperty, new Binding("/") { Source = string_list });
this.textbox2.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = string_list, Mode = BindingMode.OneWay });
this.textbox3.SetBinding(TextBox.TextProperty, new Binding("/[2]") { Source = string_list, Mode = BindingMode.OneWay });

在这里插入图片描述

  • 子级集合中元素作为Path——使用多级/
<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="WpfApplication1_8_18.MainWindow"
        Title="Control as Source" Height="120" Width="300" >
    <StackPanel>
        <TextBox x:Name="textbox1" BorderBrush="Black" Margin="5"/>
        <TextBox x:Name="textbox2"  BorderBrush="Black" Margin="5"/>
        <TextBox x:Name="textbox3" BorderBrush="Black" Margin="5"/>
    </StackPanel>
</Window>
class City
{
    public string Name { get; set; }
}

class Province
{
    public string Name { get; set; }
    public List<City> CityList { get; set; }
}

class Country
{
    public string Name { get; set; }
    public List<Province> ProvinceList { get; set; }
}

namespace WpfApplication1_8_18
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
 
    public partial class MainWindow : Window
    {       
        public MainWindow()
        {
            InitializeComponent();

            //Binding
            List<Country> countryList = new List<Country> 
            { 
                //初始化嵌套类
               new Country(){Name="China",ProvinceList=new List<Province>(){new Province(){ Name="HeBei", CityList=new List<City>(){new City(){Name="BeiJing" },new City(){Name="ShiJiaZhuang"} }}}},
               new Country(){},//多个Country类之间的书写方式,里面内容省略..
               new Country(){}
            };
            this.textbox1.SetBinding(TextBox.TextProperty, new Binding("/Name") { Source = countryList });
            this.textbox2.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/Name") { Source = countryList });
            //应为"/ProvinceList[0].Name",教材中给出的"/ProvinceList.Name",但是运行后并无结果输出
            this.textbox3.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList/Name") { Source = countryList });
            //应为:"/ProvinceList/CityList[1].Name",教材中给出的"/ProvinceList/CityList.Name",但是运行后并无结果输出    
        }   
    }    
}

程序运行结果如下:

在这里插入图片描述

此处默认显示“BeiJing”至于怎么显示“ShiJiaZhuang”??

//将显示命令由"/ProvinceList/CityList/Name"修改为"/ProvinceList/CityList[1].Name"即可
this.textbox3.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList[1].Name") { Source = countryList }); 

在这里插入图片描述

其中,嵌套类的初始化方法

//对一个class的正确赋值方法
List<City> city_1 = new List<City>() { new City() { Name = "city_a" }, new City { Name = "city_b" } };
//country的赋值方法
List<Country> countryList = new List<Country> ()
{ 
    new Country(){Name="China",ProvinceList=new List<Province>(){new Province(){ Name="HeBei", CityList=new List<City>(){new City(){Name="BeiJing" },new City(){Name="ShiJiaZhuang"} }}}},
    new Country(){},//需要初始化多个Country的示例
    new Country(){}
};
没有Path的Binding

Binding源本身就是数据且不需要path来指明,如string 、int。
这里书本中写的不明确,导致第一次没有出来效果。这里的sys是一个映射名,在Windows开始标签内定义。

<Window x:Class="WpfApplication1_8_26_1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="200" Width="300">
    <StackPanel>
        <StackPanel.Resources>
            <sys:String x:Key="myString">
                菩提本无树,明镜亦非台。
                本来无一物,何处惹尘埃。
            </sys:String>
        </StackPanel.Resources>
        <TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="{Binding Path=.,Source={StaticResource ResourceKey=myString}}" FontSize="16" Margin="5"/>
    </StackPanel>
</Window>

在这里插入图片描述
上面的代码也可以简写成:

<TextBlock x:Name="textBlock1" Text="{Binding.,Source={StaticResource ResourceKey=myString}}"/>
//或者
<TextBlock x:Name="textBlock2" Text="{Binding Source={StaticResource ResourceKey=myString}}"/> 
Binding的Source
1、没有source的Binding
<Window x:Class="WpfApplication1_8_21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1_8_21"
        Title="MainWindow" Height="120" Width="300">
    <StackPanel Background="LightBlue">
        <StackPanel.DataContext>
            <local:Student ID="6" Age="29" Name="Time"/>
        </StackPanel.DataContext>
        <Grid>
            <StackPanel>
                <TextBox Text="{Binding Path=ID}" Margin="5"/>
                <TextBox Text="{Binding Path=Name}" Margin="5"/>
                <TextBox Text="{Binding Path=Age}" Margin="5"/>
            </StackPanel>
        </Grid>
    </StackPanel>
</Window>
namespace WpfApplication1_8_21
{
    public class Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
    
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

在这里插入图片描述
xmlns:local=“clr-namespace:WpfApplication1_8_21"可以在XML中使用C#中定义的Student类。
当Path的值为”."时,也可以省略不写:DataContext是一个依赖属性,当没有给控件某个属性显示赋值时,控件会把自己容器的属性值借过来作为自己的属性值。

<Window x:Class="WpfApplication1_8_21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        FontSize="16" FontWeight="Bold"
        Title="MainWindow" Height="150" Width="300">
    <StackPanel Background="LightBlue">
        <StackPanel.DataContext>
            <sys:String>Hello DataContext!</sys:String>
        </StackPanel.DataContext>
        <Grid>
            <StackPanel>
                <TextBlock Text="{Binding}" Margin="5"/><!--TextBox则该写法会报错-->
                <TextBlock Text="{Binding}" Margin="5"/>
                <TextBlock Text="{Binding}" Margin="5"/>
            </StackPanel>
        </Grid>
    </StackPanel>
</Window>

在这里插入图片描述

有关DataContext的基础知识:
若干Grid嵌套,最里层放置一个Button控件,为最外层Grid设置了DataContext属性值。因为内层Grid没有设置DataContext属性值,所以外层Grid的DataContext属性值会一直传递下去。也即:Butn1.DataContext=Grid DataContext=“6”

<Window x:Class="WpfApplication1_8_21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        FontSize="16" FontWeight="Bold"
        Title="MainWindow" Height="150" Width="300">

    <Grid DataContext="6">
        <Grid>
            <Grid>
                <Button x:Name="Butn1" Content="OK" Click="Butn1_click"/>
            </Grid>
        </Grid>
    </Grid>
</Window>
private void Butn1_click(object sender, RoutedEventArgs e)
 {
     MessageBox.Show(Butn1.DataContext.ToString());
}

在这里插入图片描述
所以,当UI上的多个控件都通过Binding关注同一个对象时,不妨使用DataContext。(字太多,贴图吧)
在这里插入图片描述

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

在这里插入图片描述

  • 第一种方法
    这里书中写的没有实现:
 //为textBox设置Binding
Binding binding = new Binding("SelectedItem.Id") { Source = this.listBoxStu };//书中的示例,运行后没有结果显示,显示栏为空
Binding binding = new Binding("SelectedIndex") { Source = this.listBoxStu };//自己试过了listBox以Select开头的命令后,符合要求的

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

<Window x:Class="WpfApplication1_8_21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        Title="MainWindow" Height="300" Width="300">
    <StackPanel x:Name="StackP1" Background="LightBlue">
        <TextBlock Text="StudentID:" FontWeight="Bold" Margin="5"/>
        <TextBox x:Name="textBoxID" Margin="5"/>
        <TextBlock Text="StudentList:" FontWeight="Bold" Margin="5"/>
        <ListBox x:Name="listBoxStu" Height="110" Margin="5"/>
    </StackPanel>        
</Window>
namespace WpfApplication1_8_21
{
    public class Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
    
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //准备数据源
            List<Student> stulist = new List<Student>()
            {
                new Student(){ID=0,Name="Tim",Age=29},
                new Student(){ID=1,Name="Tom",Age=28},
                new Student(){ID=2,Name="Tic", Age=27},
                new Student(){ID=3,Name="Tinker",Age=26},
                new Student(){ID=4,Name="Tam",Age=25},
                new Student(){ID=5,Name="Titan",Age=24}
            };

            //为listBox设置Binding:source、path
            this.listBoxStu.ItemsSource = stulist;
            this.listBoxStu.DisplayMemberPath = "Name";

            //为textBox设置Binding
            Binding binding = new Binding("SelectedIndex") { Source = this.listBoxStu };
            this.textBoxID.SetBinding(TextBox.TextProperty, binding);
           
            //这段程序只在初始化时被执行一次,应该放在listbox的click事件中
            //if(this.listBoxStu.SelectedItem!=null)
            //{
            //    string a = this.listBoxStu.SelectedItem.ToString();
            //}
        }
  • 第二种方法
    删除上面的this.listBoxStu.DisplayMemberPath = “Name”,在XAML中通过代码实现。ListBox的ItemPlate属性的类型是DataTemplate
<Window x:Class="WpfApplication1_8_21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        Title="MainWindow" Height="350" Width="300">
    <StackPanel x:Name="StackP1" Background="LightBlue">
        <TextBlock Text="StudentID:" FontWeight="Bold" Margin="5"/>
        <TextBox x:Name="textBoxID" Margin="5"/>
        <TextBlock Text="StudentList:" FontWeight="Bold" Margin="5"/>
        <ListBox x:Name="listBoxStu" 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>        
</Window>
namespace WpfApplication1_8_21
{
    public class Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
    
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //准备数据源
            List<Student> stulist = new List<Student>()
            {
                new Student(){ID=0,Name="Tim",Age=29},
                new Student(){ID=1,Name="Tom",Age=28},
                new Student(){ID=2,Name="Tic", Age=27},
                new Student(){ID=3,Name="Tinker",Age=26},
                new Student(){ID=4,Name="Tam",Age=25},
                new Student(){ID=5,Name="Titan",Age=24}
            };

            //为listBox设置Binding:source、path
            this.listBoxStu.ItemsSource = stulist;
            //this.listBoxStu.DisplayMemberPath = "Name";//该条语句在XAML中实现

            //为textBox设置Binding
            Binding binding = new Binding("SelectedIndex") { Source = this.listBoxStu };
            this.textBoxID.SetBinding(TextBox.TextProperty, binding);         
        }

在这里插入图片描述

在这里插入图片描述

使用ADO.NET作为Binding的源

在这里插入图片描述

DataTable的DefaultView可以被赋值给ListBoxItemsSource
一般选择ListView控件显示DataTable

<Window x:Class="WpfApplication1_diushi.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="200" Width="300">
    <StackPanel Background="LightBlue">
        <ListBox x:Name="listBoxStudents" Height="130" Margin="5"/>
    </StackPanel>
</Window>
using System.Data;//DataTable的头文件
namespace WpfApplication1_diushi
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataTable dt = new DataTable();  //DataTable dt = this.Load();的用法见后面说明
        DataColumn id = new DataColumn("ID");
        DataColumn name = new DataColumn("Name");
        DataColumn age = new DataColumn("Age");
        dt.Columns.Add(id);
        dt.Columns.Add(name);
        dt.Columns.Add(age);

        DataRow row1 = dt.NewRow();//1
        row1["ID"] = "1";
        row1["Name"] = "Tim";
        row1["Age"] = "29";
        dt.Rows.Add(row1);

        DataRow row2 = dt.NewRow();//2
        row2["ID"] = "2";
        row2["Name"] = "Tom";
        row2["Age"] = "28";
        dt.Rows.Add(row2);
        //// dt.Rows.Add("2", "Tom", "28");
         DataRow row3 = dt.NewRow();//3
        row3["ID"] = "3";
        row3["Name"] = "Tony";
        row3["Age"] = "27";
        dt.Rows.Add(row3);

        DataRow row4 = dt.NewRow();
        row4["ID"] = "4";
        row4["Name"] = "Kyle";
        row4["Age"] = "26";
        dt.Rows.Add(row4);

        DataRow row5 = dt.NewRow();
        row5["ID"] = "5";
        row5["Name"] = "Vina";
        row5["Age"] = "25";
        dt.Rows.Add(row5);

        DataRow row6 = dt.NewRow();
        row6["ID"] = "6";
        row6["Name"] = "Emily";
        row6["Age"] = "24";
        dt.Rows.Add(row6);

        this.listBoxStudents.DisplayMemberPath = "Name";
        this.listBoxStudents.ItemsSource = dt.DefaultView;
        //DataTable 的 DefaultView属性是一个DataView类型对象
    }        
  }
}

在这里插入图片描述

如果改为ListView控件显示一个DataTable

<Window x:Class="WpfApplication1_diushi.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="206" Width="250">
    <StackPanel Background="LightBlue">
        <ListView x:Name="listViewStudents" Height="130" Margin="5">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="ID" Width="60" DisplayMemberBinding="{Binding ID}"/>
                    <GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding Name}"/>
                    <GridViewColumn Header="Age" Width="60" DisplayMemberBinding="{Binding Age}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </StackPanel>
</Window>
this.listViewStudents.ItemsSource = dt.DefaultView;
//或者
this.listViewStudents.DataContext = dt;
this.listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding());

运行结果:
在这里插入图片描述
注意:原书中使用了一句DataTable dt=this.Load(),在开始的时候一直以为是引用了一个未声明函数,后来才意识到这是一个自定义函数,里面包含了DataTable 的数据。

using System.Data;
namespace WpfApplication1_8_26_1
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void button_click(object sender, RoutedEventArgs e)
        {
            DataTable dt = this.Load();
        }

        private DataTable Load()
        {
            DataTable dt = new DataTable();  
            DataColumn id = new DataColumn("ID");
            DataColumn name = new DataColumn("Name");
            DataColumn age = new DataColumn("Age");
            dt.Columns.Add(id);
            dt.Columns.Add(name);
            dt.Columns.Add(age);

            DataRow row1 = dt.NewRow();//1
            row1["ID"] = "1";
            row1["Name"] = "Tim";
            row1["Age"] = "29";
            dt.Rows.Add(row1);

            DataRow row2 = dt.NewRow();//2
            row2["ID"] = "2";
            row2["Name"] = "Tom";
            row2["Age"] = "28";
            dt.Rows.Add(row2);
            //// dt.Rows.Add("2", "Tom", "28");
            DataRow row3 = dt.NewRow();//3
            row3["ID"] = "3";
            row3["Name"] = "Tony";
            row3["Age"] = "27";
            dt.Rows.Add(row3);

            DataRow row4 = dt.NewRow();
            row4["ID"] = "4";
            row4["Name"] = "Kyle";
            row4["Age"] = "26";
            dt.Rows.Add(row4);

            DataRow row5 = dt.NewRow();
            row5["ID"] = "5";
            row5["Name"] = "Vina";
            row5["Age"] = "25";
            dt.Rows.Add(row5);

            DataRow row6 = dt.NewRow();
            row6["ID"] = "6";
            row6["Name"] = "Emily";
            row6["Age"] = "24";
            dt.Rows.Add(row6);

            return dt;
        }
    }
}
使用XML作为Binding的源
基于DOM(Document Object Model文档对象模型)

在这里插入图片描述

建立xml文件的方法:
在这里插入图片描述
注意:记事本文档中<xml…定义一行上面以及前面不可以有空格或者空行,否则转换完是一片空白。

<Window x:Class="WpfApplication1_8_21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        Title="MainWindow" Height="350" Width="300">

    <StackPanel Background="LightBlue">
        <ListView x:Name="listViewStu" Height="130" Margin="5">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="ID" Width="80" DisplayMemberBinding="{Binding XPath=@ID}"/>
                    <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding XPath=Name}"/>
                </GridView>
            </ListView.View>
        </ListView>
        <Button Content="load" Click="But_clic" Height="25" Margin="5"/>
    </StackPanel>
</Window>
using System.IO;//xml,否则找不到XmlDocument 类型
using System.Xml; //xml

namespace WpfApplication1_8_21
{
    public class Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
    
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //准备数据源
            List<Student> stulist = new List<Student>()
            {
                new Student(){ID=0,Name="Tim",Age=29},
                new Student(){ID=1,Name="Tom",Age=28},
                new Student(){ID=2,Name="Tic", Age=27},
                new Student(){ID=3,Name="Tinker",Age=26},
                new Student(){ID=4,Name="Tam",Age=25},
                new Student(){ID=5,Name="Titan",Age=24}
            };       
        }
        private void But_clic(object sender, RoutedEventArgs e)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(@"D:\RawData.xml");

            XmlDataProvider xdp = new XmlDataProvider();
            xdp.Document = doc;

            xdp.XPath = @"/StudentList/Student";
            this.listViewStu.DataContext = xdp;
            this.listViewStu.SetBinding(ListView.ItemsSourceProperty, new Binding());
        }     
    }
}

在这里插入图片描述
ps:XmlDataProvider 还有一个source的属性可以用来指定XML文档位置。故Click事件写可以:

  private void But_clic(object sender, RoutedEventArgs e)
        {
           // XmlDocument doc = new XmlDocument();
           // doc.Load(@"D:\RawData.xml");

            XmlDataProvider xdp = new XmlDataProvider();
           // xdp.Document = doc;
            xdp.Source = new Uri(@"D:\RawData.xml");
            xdp.XPath = @"/StudentList/Student";
            this.listViewStu.DataContext = xdp;
            this.listViewStu.SetBinding(ListView.ItemsSourceProperty, new Binding());
        }     

使用@符号加字符串表示的是XML元素的Attribute,不加@符号的字符串表示的是子级元素。

使用TreeView显示若干层目录的文件系统

待添加图片;

<Window x:Class="WpfApplication1_8_21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        Title="MainWindow" Height="260" Width="300">

    <Window.Resources>
        <XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder">
            <x:XData>
                <FileSystem xmlns="">
                    <Folder Name="Books">
                        <Folder Name="Programming">
                            <Folder Name="Windows">
                                <Folder Name="WPF"/>
                                <Folder Name="MFC"/>
                                <Folder Name="Delphi"/>
                            </Folder>
                        </Folder>
                        <Folder Name="Tools">
                            <Folder Name="Development"/>
                            <Folder Name="Designment"/>
                            <Folder Name="Players"/>
                        </Folder>
                    </Folder>
                </FileSystem>
            </x:XData>
        </XmlDataProvider>
    </Window.Resources>
    
    <Grid>
        <TreeView ItemsSource="{Binding Source={StaticResource xdp}}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}">
                    <TextBlock Text="{Binding XPath=@Name}"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Grid>
</Window>

如果直接把XmlDataProvider 写在XAML代码中,则需要使用<x:XData>…</x:XData>

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

LINQ的查询结果是一个IEnumerable类型对象,它可以作为列表控件ItemsSource来使用。
从一个填充好的list对象中检索出所有以字母T开头的名字

<Window x:Class="WpfApplication1_8_21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        Title="MainWindow" Height="260" Width="300">
    <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" Height="25" Margin="5" Click="Button_Click"/>
    </StackPanel>  
</Window>
namespace WpfApplication1_8_21
{
        public class Student
        {
         public int id { get; set; }
         public string Name { get; set; }
         public int Age { get; set; }
        }
        public MainWindow()
        {
            InitializeComponent()}
        private void Button_Click(object sender, RoutedEventArgs e) 
        {
            List<Student> stulist = new List<Student>()
            {
                new Student(){id=0,Name="Tim",Age=29},
                new Student(){id=1,Name="Tom",Age=28},
                new Student(){id=2,Name="Mic", Age=27},
                new Student(){id=3,Name="Minker",Age=26},
                new Student(){id=4,Name="Mam",Age=25},
                new Student(){id=5,Name="Mitan",Age=24}
            };

            this.listViewStudents.ItemsSource = from stu in stulist where stu.Name.StartsWith("T") select stu;            
        }

运行结果:
在这里插入图片描述

2、如果数据是存放在DataTable中
如果数据存放在已经填充好的DataTable中,首先引用头文件using System.Data;//DataTable需要该头文件

<Window x:Class="WpfApplication1_8_26.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        Title="MainWindow" Height="260" Width="300">
    <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" Height="25" Margin="5" Click="Button_Click"/>
    </StackPanel>
</Window>
using System.Data;//DataTable需要该头文件

class City
{
    public string Name { get; set; }
}

class Province
{
    public string Name { get; set; }
    public List<City> CityList { get; set; }
}

class Country
{
    public string Name { get; set; }
    public List<Province> ProvinceList { get; set; }
}

namespace WpfApplication1_8_26
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>

    public class Student
    {
        public int id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            DataTable dt = this.ret_dt();
            this.listViewStudents.ItemsSource = from row in dt.Rows.Cast<DataRow>()
                                                where Convert.ToString(row["Name"]).StartsWith("T")
                                                select new Student()
                                                {
                                                    id=int.Parse(row["Id"].ToString()),Name=row["Name"].ToString(),Age=int.Parse(row["Age"].ToString())
                                                };
        }

        private DataTable ret_dt()
        {
            DataTable dt = new DataTable();  //DataTable dt = this.Load();
            DataColumn id = new DataColumn("ID");
            DataColumn name = new DataColumn("Name");
            DataColumn age = new DataColumn("Age");
            dt.Columns.Add(id);
            dt.Columns.Add(name);
            dt.Columns.Add(age);

            DataRow row1 = dt.NewRow();//1
            row1["ID"] = "1";
            row1["Name"] = "Tim";
            row1["Age"] = "29";
            dt.Rows.Add(row1);

            DataRow row2 = dt.NewRow();//2
            row2["ID"] = "2";
            row2["Name"] = "Tom";
            row2["Age"] = "28";
            dt.Rows.Add(row2);
            //// dt.Rows.Add("2", "Tom", "28");

            DataRow row3 = dt.NewRow();//3
            row3["ID"] = "3";
            row3["Name"] = "Tony";
            row3["Age"] = "27";
            dt.Rows.Add(row3);

            DataRow row4 = dt.NewRow();
            row4["ID"] = "4";
            row4["Name"] = "Kyle";
            row4["Age"] = "26";
            dt.Rows.Add(row4);

            DataRow row5 = dt.NewRow();
            row5["ID"] = "5";
            row5["Name"] = "Vina";
            row5["Age"] = "25";
            dt.Rows.Add(row5);

            DataRow row6 = dt.NewRow();
            row6["ID"] = "6";
            row6["Name"] = "Emily";
            row6["Age"] = "24";
            dt.Rows.Add(row6);
            return dt;
        }
    }
}

在这里插入图片描述

3、如果数据是存放在XML中
如果数据是写在XAML中:

数据存储在XML文件D:\RawData.xml中,如下图所示:

在这里插入图片描述

<Window x:Class="WpfApplication1_8_26.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        Title="MainWindow" Height="260" Width="300">
    <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" Height="25" Margin="5" Click="Button_Click"/>
    </StackPanel>
</Window>
using System.Data;//DataTable需要该头文件
using System.Xml.Linq;//XDocument需要头文件

class City
{
    public string Name { get; set; }
}

class Province
{
    public string Name { get; set; }
    public List<City> CityList { get; set; }
}

class Country
{
    public string Name { get; set; }
    public List<Province> ProvinceList { get; set; }
}

namespace WpfApplication1_8_26
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>

    public class Student
    {
        public int id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            XDocument xdoc = XDocument.Load(@"D:\RawData.xml");//该语句可以跨越XAML层级
            this.listViewStudents.ItemsSource = from row in dt.Rows.Cast<DataRow>()
                                                where Convert.ToString(row["Name"]).StartsWith("T")
                                                select new Student()
                                                {
                                                    id = int.Parse(row["Id"].ToString()),
                                                    Name = row["Name"].ToString(),
                                                    Age = int.Parse(row["Age"].ToString())
                                                };
        }
    }
}

在这里插入图片描述

使用ObjectDataProvider对象作为Binding的Source

ObjectDataProvider把对象作为数据源提供给Binding。

 <Window x:Class="WpfApplication1_8_21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        Title="MainWindow" Height="260" Width="300">
    <StackPanel Background="LightBlue">
        <Button Content="Load" Height="25" Margin="5" Click="Button_Click"/>
    </StackPanel>
   
</Window>
namespace WpfApplication1_8_21
{
   // Calculator类具有加、减、乘、除的方法
    class Calculator
    {
        public string Add(string arg1,string arg2)//加法
        {
            double x = 0;
            double y = 0;
            double z = 0;
            if(double.TryParse(arg1,out x)&& double.TryParse(arg2,out y))
            {
                z = x + y;
                return z.ToString();
            }
            return "Input Error!";
        }
    }
   
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent()}
         private void Button_Click(object sender, RoutedEventArgs e)
        {
            ObjectDataProvider odp = new ObjectDataProvider();
            odp.ObjectInstance = new Calculator();
            odp.MethodName = "Add";
            odp.MethodParameters.Add("100");
            odp.MethodParameters.Add("200");
            MessageBox.Show(odp.Data.ToString());//返回结果为300
        }

在这里插入图片描述

上面2个textBox输入数字后,下面的textBox实时显示结果,把代码写在一个SetBinding的方法中,在窗体的构造器里调用该方法:

<Window x:Class="WpfApplication1_8_21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
        Title="MainWindow" Height="260" Width="300">
    <StackPanel Background="LightBlue">
        <TextBox x:Name="textBoxArg1" Margin="5"/>
        <TextBox x:Name="textBoxArg2" Margin="5"/>
        <TextBox x:Name="textBoxResult" Margin="5"/>
    </StackPanel>   
</Window>
namespace WpfApplication1_8_21
{
    //-----------------------------------------p112
   // Calculator类具有加、减、乘、除的方法
    class Calculator
    {
        public string Add(string arg1,string arg2)//加法
        {
            double x = 0;
            double y = 0;
            double z = 0;
            if(double.TryParse(arg1,out x)&& double.TryParse(arg2,out y))
            {
                z = x + y;
                return z.ToString();
            }
            return "Input Error!";
        }
    }
   
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.SetBinding();
            
        }

        private void SetBinding()
        {
            //创建并配置ObjectDataProvider对象
            ObjectDataProvider odp = new ObjectDataProvider();
            odp.ObjectInstance=new Calculator();
            odp.MethodName="Add";
            odp.MethodParameters.Add("0");//MethodParameters属性是类型敏感的
            odp.MethodParameters.Add("0");

            //以ObjectDataProvider对象为Source创建Binding
            Binding bindingToArg1=new Binding("MethodParameters[0]")
            {
                Source=odp,
                BindsDirectlyToSource=true,
                UpdateSourceTrigger=UpdateSourceTrigger.PropertyChanged
            };

            Binding bindingToArg2=new Binding("MethodParameters[1]")
            {
                Source=odp,
                BindsDirectlyToSource = true,//告诉Binding对象,从UI元素收集到的数据写入其直接Source(ObjectDataProvider对象),而不是ObjectDataProvider包装着的Calculator
                UpdateSourceTrigger=UpdateSourceTrigger.PropertyChanged
            };

            Binding bindingToResult=new Binding("."){Source=odp};//数据源本身就代表数据时,用.表示PATH,且.在XAML中可以省略不写

            //将Binding关联到UI元素上
            this.textBoxArg1.SetBinding(TextBox.TextProperty,bindingToArg1);
            this.textBoxArg2.SetBinding(TextBox.TextProperty,bindingToArg2);
            this.textBoxResult.SetBinding(TextBox.TextProperty,bindingToResult);
        }      
    }
}

在这里插入图片描述

使用Binding的RelativeSource

有时候不能明确作为Source的对象叫什么名字,但知道它与Binding目标对象在UI布局上有相对关系。
多层布局中放置一个textBox

<Window x:Class="WpfApplication1_8_24.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="210" Width="210">
    <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" Background="LawnGreen" Margin="10">                    
                    <TextBox x:Name="textBox1" FontSize="24" Margin="10"/>                                
                </DockPanel>
            </Grid>
        </DockPanel>
    </Grid>
</Window>

把textBox的text属性关联到外层容器的Name属性上:

namespace WpfApplication1_8_24
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
           // 告诉Binding从自己的第一层依次向外寻找,找到第一个Grid类型对象后把它当作自己的源
            RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor);
            rs.AncestorLevel = 1;    //以Binding目标控件为起点的层级偏移量——d2偏移量1、g2偏移量2
            rs.AncestorType = typeof(Grid);  //告诉Binding寻找哪个类型的对象作为自己的源
            Binding binding = new Binding("Name") { RelativeSource = rs };
            this.textBox1.SetBinding(TextBox.TextProperty, binding);
            }
         }
     }

或在XAML中插入等价代码:

<TextBox x:Name="textBox1" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid},AncestorLevel=1},Path=Name}"/>  

运行效果如下所示:

在这里插入图片描述
如果按照如下方式修改代码:

<Window x:Class="WpfApplication1_8_24.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="210" Width="210">
    <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" Background="LawnGreen" Margin="10">    
                    <TextBox x:Name="textBox1" Margin="10"/>
                </DockPanel>
            </Grid>
        </DockPanel>
    </Grid>
</Window>
namespace WpfApplication1_8_24
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
			 RelativeSource rs = new RelativeSource();
			 rs.AncestorLevel = 2;
			 rs.AncestorType = typeof(DockPanel);
			 Binding binding = new Binding("Name") { RelativeSource = rs };
			 this.textBox1.SetBinding(TextBox.TextProperty, binding);
            }
         }
     }

或者在XAML中插入等效代码:

<TextBox x:Name="textBox1" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type DockPanel},AncestorLevel=2},Path=Name}}"/>

运行效果如下:
在这里插入图片描述

如果Binding需要关联自身的Name属性:

<Window x:Class="WpfApplication1_8_24.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="210" Width="210">
    <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" Background="LawnGreen" Margin="10">             
                    <TextBox x:Name="textBox1" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type DockPanel},AncestorLevel=2},Path=Name}"/>
                </DockPanel>
            </Grid>
        </DockPanel>
    </Grid>
</Window>
namespace WpfApplication1_8_24
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //如果textBox需要关联自身的name属性
            RelativeSource rs = new RelativeSource();
            rs.Mode = RelativeSourceMode.Self;//其它取值:PreviousData、TemplatedParent、FindAncestor
            Binding binding = new Binding("Name") { RelativeSource = rs };
            this.textBox1.SetBinding(TextBox.TextProperty, binding);
        }
    }
}

图片显示如下:
在这里插入图片描述

Binding对数据的转换与校验

ValidationRules属性用于数据有效性的校验,Converter属性用于数据类型转换。
1、Binding对数据的校验
校验textBox中输入数值是否是0~100

<Window x:Class="WpfApplication1_8_24.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="210" Width="210">
    <StackPanel>
        <TextBox x:Name="textBox1" Margin="5"/>
        <Slider x:Name="slider1" Minimum="0" Maximum="100" Margin="5"/>
    </StackPanel>
</Window>
public class RangeValidationRule:ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        //需要实现Validate方法
        double d = 0;
        if(double.TryParse(value.ToString(),out d))
        {
            if(d>=0&&d<=100)
            {
                return new ValidationResult(true, null);
            }
        }
        return new ValidationResult(false, "Validation Failed");
    }
}
namespace WpfApplication1_8_24
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

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

程序运行效果图:
在这里插入图片描述在这里插入图片描述

当输入>100的数值时,textBox会显示红色边框。
注意,此处Binding 只在textBox控件(需要外部输入的UI目标)更新数据时检验,而Binding 的Source(slider)更新数据时,不会进行检验。
如果需要开启Source的检验,将ValidateOnTargetUpdated设为True

<Window x:Class="WpfApplication1_8_24.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="210" Width="210">
    <StackPanel>
        <TextBox x:Name="textBox1" Margin="5"/>
        <Slider x:Name="slider1" Minimum="-10" Maximum="110" Margin="5"/><!--(0,100)-->
    </StackPanel>
</Window>
public class RangeValidationRule:ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        //需要实现Validate方法
        double d = 0;
        if(double.TryParse(value.ToString(),out d))
        {
            if(d>=0&&d<=100)
            {
                return new ValidationResult(true, null);
            }
        }
        return new ValidationResult(false, "Validation Failed");
    }
}
namespace WpfApplication1_8_24
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Binding binding = new Binding("Value") { Source = this.slider1 };
            binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            RangeValidationRule rvr = new RangeValidationRule();
            rvr.ValidatesOnTargetUpdated = true;//当slider移出有效范围时,TextBox也会显示校验失败
            binding.ValidationRules.Add(rvr);
            this.textBox1.SetBinding(TextBox.TextProperty, binding);
        }
    }
}

运行效果:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

当需要显示校验错误时Validate方法返回的ValidationResult对象携带的错误信息时,需要用到路由事件:
在这里插入图片描述

<Window x:Class="WpfApplication1_8_24.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="210" Width="210">
    <StackPanel>
        <TextBox x:Name="textBox1" Margin="5"/>
        <Slider x:Name="slider1" Minimum="-10" Maximum="110" Margin="5"/><!--(0,100)-->
    </StackPanel>
</Window>
public class RangeValidationRule:ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        //需要实现Validate方法
        double d = 0;
        if(double.TryParse(value.ToString(),out d))
        {
            if(d>=0&&d<=100)
            {
                return new ValidationResult(true, null);
            }
        }
        return new ValidationResult(false, "Validation Failed");
    }
}
namespace WpfApplication1_8_24
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Binding binding = new Binding("Value") { Source = this.slider1 };
            binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            RangeValidationRule rvr = new RangeValidationRule();
            rvr.ValidatesOnTargetUpdated = true;//当slider移出有效范围时,TextBox也会显示校验失败
            binding.ValidationRules.Add(rvr);

            binding.NotifyOnValidationError = true;
            this.textBox1.SetBinding(TextBox.TextProperty, binding);
            this.textBox1.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(this.ValidationError));
        }
        void ValidationError(object sender, RoutedEventArgs e)   //这个路由事件是自己手敲的。。
        {
            if (Validation.GetErrors(this.textBox1).Count > 0)
            {
                this.textBox1.ToolTip = Validation.GetErrors(this.textBox1)[0].ErrorContent.ToString();
            }
        }
    }
}

如果校验失败,则TextBox的ToolTip就会提示用户,效果如下:
在这里插入图片描述

此处运行结果没有提示消息。。。。。。。p123

Binding 的数据转换
  • Source里的数据是Y、N、X三个值,UI上对应的是CheckBox控件,需要把这三个值映射为它的IsChecked属性值。
  • 当TextBox里已经输入了文字时用于登陆的Button才会出现,这是string类型与Visibility枚举类型或bool类型之间的转换。
  • Source里的数据可能是Male或者Female,UI上对应的是用于显示头像的Image控件,这时需要将Source里的值转换成对应的头像图片URI(oneway)

IValueConverter为单值转换的接口,IMultiValueConverter为多值转换的接口。它们都属于System.Windows.Data命名空间

此时需要自己动手写Converter,方法是创建一个类并让这个类实现IValueConverter接口。
在这里插入图片描述

  • Convert实现后台值向UI显示值之间的转换(原数据转换为目标数据格式)
  • ConvertBack实现UI值向后台之间的转换(目标数据格式转换为原数据格式)

示例:飞机的State属性在UI里被映射为CheckBox,这里需要2个Converter:

  • Category类型单项转换为String类型
  • State与Bool类型之间双向转换
<Window x:Class="WpfApplication1_8_24.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1_8_24"
        Title="MainWindow" Height="320" Width="300">
    <Window.Resources>
        <local:CategoryToSourceConverter x:Key="cts"/>
        <!--声明转换器类名称空间 并定义一个转换器资源-->
        <local:StateToNullableBoolConverter x:Key="stnb"/>
    </Window.Resources>
    
    <StackPanel Background="LightBlue">
        <ListBox x:Name="listBoxPlane" Height="200" 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" Height="25" Margin="5" Click="buttonLoad_click"/>
        <Button x:Name="buttonSave" Content="Save" Height="25" Margin="5" Click="buttonSave_click"/>
    </StackPanel>
</Window>
using System.Globalization;//否则CultureInfo类别无法识别
using System.IO;//File相关命令
//种类
public enum Category
{
    Bomber,
    Fighter
}

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

//飞机
public class Plane
{
    public Category Category{get;set;}
    public string Name{get;set;}
    public State State{get;set;}
}

namespace WpfApplication1_8_24
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public class CategoryToSourceConverter : IValueConverter
    {
        //将Category转换为Uri
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)//当culture参数(区域性信息)与转换无关,在自定义转换器中可以忽略该参数。
        {
            Category c = (Category)value;
            switch(c)
            {
                case Category.Bomber:
                    //return @"\Icons\Bomber.png";
                    return @"E:\HK\WpfApplication1_8-24\WpfApplication1_8-24\Icons\Bomber.png";//此处采用绝对地址
                case Category.Fighter:
                    //return @"\Icons\Fighter.png";
                    return @"E:\HK\WpfApplication1_8-24\WpfApplication1_8-24\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.Locked:
                    return false;
                case State.Available:
                    return true;
                case State.Unknown:
                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.Unknown;
            }
        }
    }

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

        }

        //把一组飞机数据赋值给ListBox的ItemsSource属性
        private void buttonLoad_click(object sender, RoutedEventArgs e)
        {
            List<Plane> planeList = 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.Unknown},
                new Plane(){Category=Category.Fighter,Name="Su-47",State=State.Unknown},
                new Plane(){Category=Category.Bomber,Name="B-52",State=State.Unknown},
                new Plane(){Category=Category.Fighter,Name="J-10",State=State.Unknown}
            };

            this.listBoxPlane.ItemsSource = planeList;
        }

        //用户更改过的数据写入文件
        private void buttonSave_click(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));//sb:Category=Bomber,Name=B-1,State=Locked
            }

            File.WriteAllText(@"D:\PlaneList.txt", sb.ToString());  //需要根据需要修改该地址,不需要事先创建.txt。
        }
    }
}

程序运行结果如下:

在这里插入图片描述
运行程序并单击CheckBox更改飞机的状态,单击Save按钮后,打开文件D:\PlaneList.txt,里面已经填写了所有显示的内容:

在这里插入图片描述程序运行流程如下:
在这里插入图片描述

程序运行中的其它注意事项

1、WPF中图标的添加方法:

  • 引用图标文件的绝对地址
  case Category.Bomber:
      return @"E:\HK\WpfApplication1_8-24\WpfApplication1_8-24\Icons\Bomber.png";//此处采用绝对地址

或者

<Image Source="E:\HK\WpfApplication1_8-24\WpfApplication1_8-24\Icons\Bomber.png"/>
 case Category.Bomber:
     return @"\Icons\Bomber.png";

在这里插入图片描述

如何添加上图中
在UI里,Plane的Category属性被影射为图标,该图标已经放入Icons文件夹。
我用Everything搜索.png,找到当前电脑上的许多图标文件。

2、CultureInfo头文件添加:
文档中未说明CultureInfo需要头文件:c#中的CultureInfo类,该博客中提到CultureInfo类位于System.Globalization命名空间内,这里需要注意添加该头文件。

3、IValueConverter接口继承类的书写位置:
因为此处在XAML中使用了local:CategoryToSourceConverter的表达方式,所以需要将继承的类写在namespace 空间中,否则会报错:
在这里插入图片描述在这里插入图片描述

程序集被映射为名称空间local,以资源的形式创建2个Converter实例。在ListBox控件中添加用于显示数据的DataTemplate。

在TextBlock 中,Margin书写形式对于结果的影响:前期运行中,一直出现下面的图。

<TextBlock Text="{Binding Path=Name}" Width="60" Margin="80"/>

在这里插入图片描述

后来才发现下列语句中的Margin=“80,0”。我以前一直写作“80”。更多Margin 的内容参考:Margin 在WPF中的用法

<TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/>

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

MutltiBinding——多路Binding

UI需要的信息不止由一个数据源决定,凡是能用Binding的场合都可以用MutltiBinding
在这里插入图片描述

将MutltiBinding代码写在SetMultiBing方法中,并在窗体的构造器中调用。

注意:MutltiBinding对于添加子级Binding的顺序是敏感的,这个顺序决定了汇聚到Converter中数据的顺序。
MutltiBinding的Converter实现的是IMultiValueConverter接口
在这里插入图片描述

注意:此处 public class LogonMultiMindingConverter : IMultiValueConverter函数尽量写在namespace里面。

<Window x:Class="WpfApplication1_8_25.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="190" Width="300">
    <Grid>
        <StackPanel Background="LightBlue">
            <TextBox x:Name="textbox1" Height="23" Margin="5"/>
            <TextBox x:Name="textbox2" Height="23" Margin="5"/>
            <TextBox x:Name="textbox3" Height="23" Margin="5"/>
            <TextBox x:Name="textbox4" Height="23" Margin="5"/>
            <Button x:Name="button1" Content="Submit" Width="80" Margin="5"/>
        </StackPanel>
    </Grid>
</Window>
using System.Globalization;//CultureInfo的头文件

namespace WpfApplication1_8_25
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>

    public class LogonMultiMindingConverter : 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 value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public partial class MainWindow : Window
    {        
        public MainWindow()
        {
            InitializeComponent();
            this.SetMultiBinding();
        }

        private void SetMultiBinding()
        {
            //准备基础Binding
            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 };

            //准备MultiBinding
            MultiBinding mb = new MultiBinding() { Mode = BindingMode.OneWay };
            mb.Bindings.Add(b1);  //MultiBinding对于子Binding的顺序是敏感的
            mb.Bindings.Add(b2);
            mb.Bindings.Add(b3);
            mb.Bindings.Add(b4);
            mb.Converter = new LogonMultiMindingConverter();

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

程序运行效果图:

在这里插入图片描述

其中,在编译的时候出现一个报错:
在这里插入图片描述
参看博客WPF进阶之接口(1):IValueConverter,IMultiValueConverter,发现在写ConvertBack函数的格式出现错误。

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
//注意IMultiValueConverter中Convert、ConvertBack二者的参数变化
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值