数据绑定:
在前面的章节中,我们学习了各式各样的控件,这些控件拥有许多的属性,比如它们的基类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>