数据绑定:

在前面的章节中,我们学习了各式各样的控件,这些控件拥有许多的属性,比如它们的基类FrameworkElement,它有Width(宽度)和Height (高度)如果我们要改变控件的宽度或高度,有两种办法。

第一种,在XAML中修改属性值:

<Button x:Name="button" Content="确定" Width="100' Height="30"/>

第二种,在C#代码中修改属性值:

button.Width = 100;

button.Height = 30;

那么,还有没有第三种办法呢?——答案就是数据绑定。根据官方的定义,数据绑定是在应用UI与其显示的数据之间建立连接的过程。如果绑定具有正确的设置,并且数据提供适当的通知,则在数据更改其值时,绑定到该数据的元素会自动反映更改。数据绑定还意味着,如果元素中数据的外部表示形更改,则基础数据可以自动进行更新以反映更改。

简单点说,我们可以定义一个ViewModel,将ViewModel中的属性绑定(Binding)到控件上的属性,ViewModel的属性改变时,控件的属性也跟变,反过来说,控件的属性改变时,ViewModel的属性也会改变。

这就是数据绑定!

重点:

1、控件的属性一定是依赖属性。

2、ViewModel的属性一定要实现属性通知。

3、控件自带媒婆——Binding。

4、媒婆只能在控件的DataContext(数据上下文)中找数据源。

DataContext数据上下文:

DataContext是FrameworkElement基类的一个属性。其含义:获取或设置元素参与数据绑定时的数据上下文。通常情况下,我们把它设计成一个class,也就是所谓的ViewModel。

那么,什么是ViewModel?这一切,还要从WPF的MVVM模式说起。

View负责数据的输入与输出;ViewModel负责业务逻辑;Model则表示程序中具体要处理的数据。所以,Model将作为属性存在于ViewModel中,而Model最终是要显示在UI界面(View)上的,怎么办呢?将ViewModel赋值给View的DataContext(数据上下文)属性,View就可以引用ViewModel中的那些Model了

注:通常一个Window窗体中有很多控件,我们只需要给Window的DataContext赋值一个ViewModel,窗体中其它的控件的DataContext会共享Window的DataContext。

Binding(绑定):

UpdateSourceTrigger{get;set;}:枚举型,当前端View发生变化时,后端Model在何时也会变化

Converter{get;set;}:枚举型,转换器

Source{get;set;}:object类型,数据源

ElementName{get;set;}:string类型,绑定哪个控件

XPath{get;set;}:string类型,绑定控件的哪个属性

Path{get;set;}:枚举型,绑定控件的哪个属性

Mode{get;set;}:枚举型,绑定模式

Binding的数据源:

首先,控件的属性与Model的属性建立绑定,我们将Model及其属性称为数据源,而数据源大致可以分为以下4种方式进行绑定。

第一种数据源,也就是ViewModel中的Model。在写法上直接如下所示:

<Run Text="{Binding Person.Name}"/>

第二种数据源,指明某个具体的数据源对象及对象的属性。这种绑定方式要用了Binding类的Source属性和Path属性。通常写法如下:

<Run Text="{Binding Source={StaticResource Brush},Path=Color}"/>

第三种数据源,利用ElementName属性指明另一个控件作为数据源,这里仍然要用到Path来指定另一个控件的某个属性路径。(绝对路径)

<Run Text="{Binding ElementName=window,Path=Title}"/>

第四种数据源,利用RelativeSource属性绑定一个相对的数据源。这种绑定方式也经常使用,且实用价值很高,作为开发者,一定要掌握它的用法。(相对路径)

<Run Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},Path=Title}"/>

<Run Text="{Binding RelativeSource={RelativeSource Mode=Self},Path=FontSize}"/>

Binding的绑定模式:

当一个实体的属性绑定到控件的属性之后,还需要指明这两者之间的绑定关系。这个就是Binding类的Mode属性,Mode属性是一个枚举类型。它有如下几个情况。

枚举值

说明

TwoWay

双向绑定,即导致更改源属性或目标属性时自动更新另一方。

OneWay

单向绑定,在更改绑定源(源)时更新绑定目标(目标)。

OneTime

—次绑定,在应用程序启动或数据上下文更改时,更新绑定目标。

OneWayToSource

在目标属性更改时,更新源属性。

Default

默认绑定,文本框的默认绑定是双向的,而其他大多数属性默认为单向绑定。

这里面常用的是OneWay和TwoWay。如果是TwoWay (双向绑定),意味着前端控件的属性改变时,后端的Model也跟着改变,反之亦然。就拿前端改变去影响后端的Model值来说,这里会多出来一个概念——改变时机。

什么是改变时机?其实就是如果前端的值发生改变,后端的值在什么时候跟着改变。它由Binding类的UpdateSourceTigger属性的值决定。这个属性也是一个枚举类型。(前->后)

枚举值

说明

Default

采用控件各自的UpdateSourceTrigger默认值。

PropertyChanged

每当绑定目标属性发生更改时,都会更新绑定源。

LostFocus

每当绑定目标元素失去焦点时。都会更新绑定源。

Explicit

仅在调用System.Windows.Data.BindingExpression.UpdateSource方法时更新绑定源。

TextBox.Text 属性的默认值为LostFocus,我们经常会把TextBox的UpdateSourceTrigger改成PropertyChanged,即文本框在输入内容时,实时的更新后端的Model属性值。

INotifyPropertyChanged:当后端数据变化时,通知前端发生对于变化(后->前)

//首先,在Model文件夹中创建一个专门的类用于加载通知方法

namespace WpfApp3.Model

{

public class ObserverObject : INotifyPropertyChanged

{

public event PropertyChangedEventHandler PropertyChanged;

public void RaisePropertyChanged([CallerMemberName] string propertyName="")//括号内输入属性名

{

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

}

}

}

//之后,在需要通知的Model类中继承上述类

namespace WpfApp3.Model

{

public class Person: ObserverObject

{

// 定义一个属性来表示人的姓名

private string name;

private int age;

private string address;

public int Age

{

get { return age; }

set { age = value;

RaisePropertyChanged();//由于用了[CallerMemberName],可以不输入内容

}

}

public String Name

{

get { return name; }

set { name = value;

RaisePropertyChanged("Name");

}

}

public String Address

{

get { return address; }

set { address = value;

RaisePropertyChanged("Address");

}

}

}

}

ObservableCollection:用于ViewModel中搭建列表,因为List作为普通的泛性集合,无法在后端更新时通知前端,所以集合在后端用ObservableCollection

public ObservableCollection<Person> Persons { get; set; } = new ObservableCollection<Person>();

IValueConverter:WPF单值转换器(将一个值转换为另一个不同类型的值)

//首先,在Model中生成一个转换器的类,用于转换你需要的内容,这里以年龄为例

namespace WpfApp3.Model

{

public class AgeToColorConverter : IValueConverter

{

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

{

SolidColorBrush background = Brushes.Black;

if (value != null && int.TryParse(value.ToString(), out int age))

{

if (age < 20)

{

background = Brushes.Green;

}

else if (age < 40)

{

background = Brushes.LightBlue;

}

else if (age < 60)

{

background = Brushes.DarkGreen;

}

else if (age < 80)

{

background = Brushes.LightGray;

}

else

{

background = Brushes.Red; // 80岁以上为红色

}

}

return background;

}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

{

throw new NotImplementedException();

}

}

}

//之后,将上述类放入View中的Window静态资源中

<Window.Resources>

<model:AgeToColorConverter x:Key="AgeToColorConverter"/>

</Window.Resources>

//然后用{Binding 属性,Converter={StaticResource 类名}}这样的格式转换

{Binding Person.Age,Converter={StaticResource AgeToColorConverter}}

上述方法只能将一个数据转换为另一个数据,当需要对多个数据进行结合转换时,就要用其他方法

IMultiValueConverter:WPF多值转换器(将多个值经过计算转换为另一个不同类型的值)

//首先,在Model中生成一个转换器的类,用于转换你需要的内容,这里以年龄和钱为例

namespace WpfApp3.Model

{

public class MultiColorConverter : IMultiValueConverter

{

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)

{

if(values!=null && values.Length == 2)

{

var age_result = int.TryParse(values[0].ToString(), out int age);

var money_result = double.TryParse(values[1].ToString(), out double money);

if(age_result && money_result)

{

if(age < 20 && money == 0)

{

return "没钱的年轻人";

}

else if(age<20 && money < 100)

{

return "有钱的年轻人";

}

else if (age < 20 && money < 1000)

{

return "富有的年轻人";

}

else if (age > 60 && money > 1000)

{

return "富有的老年人";

}

else

{

return "平凡的人";

}

}

}

return String.Empty; // 返回空字符串或 null

}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)

{

throw new NotImplementedException();

}

}

}

//之后,将上述类放入View中的Window静态资源中

<Window.Resources>

<model:MultiColorConverter x:Key="MultiColorConverter"/>

</Window.Resources>

//然后用下面这样的格式转换

<Run.Text>

<MultiBinding Converter="{StaticResource MultiColorConverter}">

<Binding Path="Person.Age"/>

<Binding Path="Person.Money"/>

</MultiBinding>

</Run.Text>

ValidationRule验证规则:用于验证信息,信息错误则后端不予更改且返回错误提示

//首先,在Model中生成一个转换器的类,用于接收需要验证的数据,这里以姓名为例

namespace WpfApp3.Model

{

internal class NameValidationRule : ValidationRule

{

public override ValidationResult Validate(object value, CultureInfo cultureInfo)

{

if (value != null && value.ToString().Length >= 1 && value.ToString().Length <= 10)

{

return new ValidationResult(true, "通过");

}

return new ValidationResult(false, "名字不超过10个字符,且不能小于1");

}

}

}

//之后,将上述类放入View,具体如下

<TextBox Width="200" Height="30">

<TextBox.Text>

<Binding Path="Person.Name" UpdateSourceTrigger="PropertyChanged">

<Binding.ValidationRules>

<model:NameValidationRule ValidatesOnTargetUpdated="True"/>

</Binding.ValidationRules>

</Binding>

</TextBox.Text>

<Validation.ErrorTemplate>

<ControlTemplate>

<DockPanel>

<Grid DockPanel.Dock="Right" Width="auto" Height="auto" VerticalAlignment="Center" Margin="3,0,0,0">

<TextBlock Width="auto" Height="auto" Foreground="Red"

Text="{Binding ElementName=AdornedElementPlaceholder,Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"/>

</Grid>

<Border BorderBrush="Red" BorderThickness="0" CornerRadius="2">

<AdornedElementPlaceholder x:Name="AdornedElementPlaceholder"/>

</Border>

</DockPanel>

</ControlTemplate>

</Validation.ErrorTemplate>

</TextBox>