数据绑定
定义:数据绑定是在应用 UI 与其显示的数据之间建立连接的过程。
DataContext数据上下文
**定义:**一个属性继承自FrameworkElement,因此所有控件都有,类型为object,一般放viewmodel对象。
性质:
- DataContext具有继承性:子控件会自动继承父容器的DataContext。
- DataContext 是WPF中数据绑定的默认源
例:
XML
<Grid> <!-- 继承Window的DataContext -->
<StackPanel> <!-- 继承Grid的DataContext -->
<Slider Value="{Binding SliderValue}"/> <!-- 仍然绑定到DataModel -->
</StackPanel>
</Grid>
Viewmodel属性要在View中显示,要将ViewModel对象赋值给View的DataContext(数据上下文)属性

Binding(绑定)
查看Binding的定义中构造函数,WPF中一切都是类,而Binding也是一个类
cs
public Binding();
public Binding(string path);
Binding的4种数据源
总结:4种方式的path都是属性,绑定就是目标属性和被赋值的属性关联,只是数据源不同和写法不同
(1)DataContext(隐式源)
- 源:当前元素沿逻辑树继承下来的
DataContext。 - 场景:MVVM 的主方式 。整个页面/整块区域共享一个 ViewModel,所有绑定都只写
Path。
cs
Text="{Binding Person.Name}"
上面代码:实例化了一个Binding对象,Person.Name赋值给Path,等于当前的DataContext(默认数据源)中那个ViewModel对象的Person.Name属性
(2)Source(显式对象)
是Binding对象的一个object类型属性
- 源:你明确指定的某个对象,比如资源、单例、静态对象、
x:Static。 - 场景:这个值不在 DataContext 里 ,或者你想让绑定不受 DataContext 影响。
- 绑定到画刷资源的
Color; - 绑定到全局配置单例;
- 绑定到静态字段/系统参数。
- 绑定到画刷资源的
cs
Text="{Binding Source={StaticResource RedBrush},Path=Color}"
(3)ElementName(另一个控件)
是Binding对象的一个string类型属性
- 源:同一个 XAML 里另一个有
x:Name的控件。 - 场景:纯 UI 联动 ,不需要 ViewModel 参与。
- 一个 TextBlock 显示 Slider 的当前值;
- 一个预览区显示 TextBox 的输入;
- 一个按钮的
IsEnabled绑到某个 CheckBox; - 一个进度条绑到 Slider。
cs
Text="{Binding ElementName=panel, Path=Margin}"
(4) RelativeSource(相对源)
| Mode | 源是谁 | 典型场景 |
|---|---|---|
Self |
绑定目标自己 | 控件自己的两个属性联动,比如 ToolTip 显示自己的值、TextBlock 的 Text 绑自己的 Tag |
FindAncestor |
父链上某个类型的控件 | 子控件拿父容器/父控件的数据;DataGrid 单元格里的按钮找 DataGrid/Window |
TemplatedParent |
应用模板的那个控件 | ControlTemplate 内部,模板子元素绑控件的属性 |
PreviousData |
数据项列表里的上一项 | ItemsControl 的项模板里,当前项和上一项比较 |
自己的前景色显示到Text属性上:
XML
Text="{Binding RelativeSource={RelativeSource Mode=Self},Path=Foreground}"
从当前控件开始找上级,一个类型为StackPanel的控件,并把这个StackPanel控件的Margin显示到当前控件的Text属性上:
XML
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=StackPanel},Path=Margin}"
AncestorLevel:获取或设置要查找的上级级别。 使用 1 指示最靠近绑定目标元素的项。
绑定的四种模式(mode属性)
相对源的mode是RelativeSource自己的属性,不冲突。
|----------------|------------------------------------|
| 枚举值 | 说明 |
| TwoWay | 双向绑定,即导致更改源属性或目标属性时自动更新另一方。 |
| OneWay | 单向绑定,在更改绑定源(源)时更新绑定目标(目标)。 |
| OneTime | 一次绑定,在应用程序启动或数据上下文更改时,更新绑定目标。 |
| OneWayToSource | 在目标属性更改时,更新源属性。 |
| Default | 默认绑定,文本框的默认绑定是双向的,而其他大多数属性默认为单向绑定。 |
改变时机
什么是改变时机?其实就是如果前端的值发生改变,后端的值在什么时候跟着改变。它由Binding类的UpdateSourceTrigger属性的值决定 。这个属性也是一个枚举类型
|-----------------|-------------------------------------------------------------------|
| 枚举值 | 说明 |
| Default | 采用控件各自的UpdateSourceTrigger默认值。 |
| PropertyChanged | 每当绑定目标属性发生更改时,都会更新绑定源。 |
| LostFocus | 每当绑定目标元素失去焦点时,都会更新绑定源。 |
| Explicit | 仅在调用 System.Windows.Data.BindingExpression.UpdateSource 方法时更新绑定源。 |
双向绑定(属性通知)
引入:
前端属性值发生改变后,由UpdateSourceTrigger决定什么时候更新后端的Model属性值,但是,后端那些Model的属性值发生改变后,前端什么时候跟着改变?需不需要做特殊的处理?
答案是需要!如果Model中的那些属性没有实现属性通知,就算前后端成功的建立了绑定关系,后端Model属性值改变时,前端的显示是没有变化的,如果要实时跟着变化,则需要掌握WPF的INotifyPropertyChanged接口以及属性通知的相关知识点。
当不实现通知时,只是前端改变使得后端改变
属性通知
自己实现通知
要实现属性通知要继承INotifyPropertyChanged接口
cs
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
}
定义一个特别的类,来实现这个功能,要通知的话要继承这个类
委托:一组方法的引用列表
事件:委托的包装器,使得这个类以外的类只能对委托进行+=(订阅),-=(退订)操作
cs
public class ObservableObject : INotifyPropertyChanged
{
//定义一个事件
public event PropertyChangedEventHandler PropertyChanged;
//[CallerMemberName]属性中使用这个方法,自动填写属性名称给到propertyName
public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
//委托调用,同步、按订阅顺序依次调用这个委托中装的方法
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
使用CommunityToolkit.Mvvm工具包实现
ObservableCollection泛型集合
转换器
IValueConverter转换器
定义:Binding类的属性,是一个IValueConverter接口,前后端建立绑定时,定义一套自定义逻辑,让前端显示的数据与后端获取的数据建立一定的对应关系。
下方代码为一个bool类型属性对应textblock是否可见的实例
定义转换规则类
cs
//必须继承IValueConverter接口
public class BooltoVisiableConverst : IValueConverter
{
//源到页面
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is true? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is Visibility.Visible;
}
}
资源中实例化转换规则类
XML
<local:BooltoVisiableConverst x:Key="BooltoVisiableConverst"></local:BooltoVisiableConverst>
属性绑定函数计算结果
XML
<TextBox Visibility="{Binding Isbusy,Converter={StaticResource BooltoVisiableConverst}}"></TextBox>
IMultiValueConverter多值转换器
使用场景:
当你要给某个控件属性赋值时,它的值取决于"多个来源",而且其中至少一个来源 ViewModel 拿不到
| 来源 | 例子 | ViewModel 能拿到吗 |
|---|---|---|
| ① ViewModel 里的数据 | Ratio、Price、IsEdit |
✔ 拿得到 |
| ② 控件自己的尺寸/位置 | ActualWidth、ActualHeight、ActualTop、RenderSize |
✘ 拿不到 |
| ③ 其它控件的状态 | 另一个控件的 IsSelected、Value、Text、IsChecked |
✘ 拿不到 |
示例:一个占父控件百分之65宽带的色块
定义转换规则类
cs
//多值转换
public class RatioToWidth : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double hostWidth = (double)values[0]; // 容器宽度
double ratio = (double)values[1]; // 比例 0~1
return hostWidth * ratio;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
资源中实例化
XML
<local:RatioToWidth x:Key="RatioToWidth"></local:RatioToWidth>
控件使用
XML
<Grid x:Name="BarHost" Height="40" Margin="20" Background="#EEEEEE" >
<Rectangle Fill="DodgerBlue" HorizontalAlignment="Left">
<Rectangle.Width>
<MultiBinding Converter="{StaticResource RatioToWidth}">
<Binding Path="ActualWidth" ElementName="BarHost"/>
<Binding Path="Ratio"/>
</MultiBinding>
</Rectangle.Width>
</Rectangle>
</Grid>
ValidationRule验证规则
在"界面输入 → ViewModel"这一步拦截非法值。
**原始效果:**当规则验证为不通过时,框会变红
定义验证类
必须继承抽象类ValidationRule
必须重写Validate方法
返回类型ValidationResult构造函数如下:
cs
public ValidationResult(bool isValid, object errorContent)
{
_isValid = isValid;
_errorContent = errorContent;
}
cs
internal class AgeRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if(value == null)
{
return new ValidationResult(false,"为空");
}
else if (int.Parse((string)value) >= 150|| int.Parse((string)value) < 0)
{
return new ValidationResult(false, "我一眼就看出你不是人");
}
else {
return new ValidationResult(true, "对的对的");
}
}
}
控件绑定中指定验证规则
绑定Binding有一个ValidationRule类型的集合属性,可以放多条规则
XML
<TextBox Margin="10">
<TextBox.Text>
<Binding Path="Text" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<ValidationSpace:AgeRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
修改当验证为失败时的样式
(Validation.Errors)0.ErrorContent"
Validation.Errors是附加属性,是一个数组,(Validation.Errors)是附加属性的写法,ErrorContent是继承了ValidationRule的类的返回值类型构造函数的第二次参数
Validation.HasError = true
有错误的时候被置为true
绑定表达式内部链路
-
用户输入 → TextBox.Text 变化
-
Binding 执行转换
-
依次调用 ValidationRules 里的每个 Validate
-
结果:
├─ 全部通过 → 写入源属性,清空 Errors
└─ 某条失败 → 往 TextBox 上写 Validation.Errors (附加属性)
并设置 Validation.HasError = true
源属性不更新
代码实现
XML
<TextBox x:Name="aaa" Margin="10">
<TextBox.Text>
<Binding Path="Text" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<ValidationSpace:AgeRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Background" Value="#FFF0F0" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBlock>
<TextBlock.Text>
<Binding
ElementName="aaa"
Mode="OneWay"
Path="(Validation.Errors)[0].ErrorContent" />
</TextBlock.Text>
</TextBlock>