时间:2026-03-16 11:05
人气:
作者:admin
[!IMPORTANT]
在开始之前,我觉得我们非常有必要要先了解一下
ViewModel
ViewModel:专门给界面(View)使用的数据对象# ViewModel = 专门给界面(View)使用的数据对象 如果只讲绑定,可以简单理解为数据源对象 在这里先留一个简单的印象,后面会详细讲解,在看完本篇随笔之后,你也会对这个东西有一个较为深刻的印象 # 常用于MVVM架构(此架构我们以后再详细讲解) Model → ViewModel → View 数据 UI数据 界面
[!NOTE]
WPF中,绑定的本质实际上就是在找东西
换句话就是:**WPF的一切绑定,本质都是在找 数据源 **
只不过 —— 数据源到底是 对象里的数据,还是 界面里的控件,这个就得看你的代码了
# 根据数据源的位置,WPF绑定通常会被分成两大类 绑定 ├─ 元素绑定(Element Binding) └─ 非元素绑定(Non-Element Binding)
[!NOTE]
WPF —— 绑定
这里,我们来看看官方对于绑定的解释
- WPF 元素绑定:将UI元素属性与数据源对象建立连接的机制,能在数据变化时自动更新界面,或在界面修改时同步数据源
- 它支持
.NET 对象、XML、集合等多种数据源,并可通过Binding对象灵活配置- ????
Binding= 在UI属性 和 数据源 之间建立连接示例:将按钮背景色绑定到数据对象的属性
- 此处
Background是绑定目标属性,ColorName是绑定源属性,通过Path指定<DockPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="clr-namespace:SDKSample"> <DockPanel.Resources> <c:MyData x:Key="myDataSource" ColorName="Red"/> </DockPanel.Resources> <Button Background="{Binding Source={StaticResource myDataSource}, Path=ColorName}" Width="150" Height="30"> 我会变成红色! </Button> </DockPanel>绑定的核心要素
- 目标对象与属性:必须是依赖属性(DependencyProperty)
- 源对象与路径:可为对象、集合、XML等,通过
Path或XPath指定- 数据上下文(DataContext):未显式指定源时,从父元素继承
- 模式(Mode):
OneWay:源 → 目标TwoWay:双向同步OneWayToSource:目标 → 源OneTime:初始化一次- 触发器(UpdateSourceTrigger):如
PropertyChanged、LostFocus控制何时更新源集合绑定与视图
- 绑定到集合时使用
ItemsSource:<ListBox ItemsSource="{Binding MyItems}" />
- 若需排序、筛选、分组,可用
CollectionViewSource:<CollectionViewSource x:Key="view" Source="{Binding MyItems}" /> <ListBox ItemsSource="{Binding Source={StaticResource view}}" />数据转换与验证
- 类型不匹配时可实现
IValueConverter转换值- 可通过
ValidationRule添加验证逻辑,并结合ErrorTemplate提供视觉反馈注意事项
- 源对象应实现
INotifyPropertyChanged,集合应实现INotifyCollectionChanged以支持动态更新- 合理选择绑定模式和触发器可优化性能与交互体验
这样,WPF 数据绑定不仅能减少手动更新 UI 的代码量,还能保持业务逻辑与界面的清晰分离
元素绑定:让一个 UI 控件的属性直接依赖另一个 UI 控件的属性,即:一个 UI 控件属性绑定到另一个 UI 控件属性
<!-- 绑定语法:{Binding ElementName=源控件名, Path=源属性, Mode=绑定模式} -->
[!CAUTION]
当你手动给一个绑定属性赋值时,WPF 会把这个 Binding 直接移除
这里用一个实例代码演示一下,如果你无法看明白,可以当作元素绑定的一个引题,在看完绑定模式后,会看明白的
现在有一张图片,还有两个滑块,还有一个
TextBox用于显示slider.Value的数值
# 两个元素绑定 slider.Value ↓ Image.Opacity # 图片透明度slider.Value
↓
TextBox.Text<Button Content="滑块value变变变" Click="Button_Click"/> slider.Value = 0.2;<Button Content="图片Opacity变变变" Click="Button_Click_1"/> img.Opacity = 0.8;❓为什么当我点击第二个按钮后,两个按钮都无法改变图片的透明度了呢?
由于
Opacity是被绑定控制的:img.Opacity ← slider.Value而我们直接:
img.Opacity = 0.8这会触发 WPF 的一个规则:
直接设置属性 = 覆盖绑定于是系统直接变成了:
img.Opacity = 0.8 (本地值)最后滑块与图片透明度之间的绑定就被移除了
slider.Value ❌ img.Opacity # 于是乎,按钮仍然在工作,只是 UI 不再联动会出现这种情况的本质原因:
WPF 的依赖属性内部其实有一个 值优先级系统:
Animation # 优先级最高 LocalValue Binding Style Default # 优先级最低当我们写:
img.Opacity = 0.8就产生了 LocalValue(本地值),而 LocalValue 的优先级 高于 Binding,于是 Binding 就被覆盖了
MainWindow.xaml
<Window x:Class="Binding.MainWindow" 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" xmlns:local="clr-namespace:Binding" mc:Ignorable="d" SizeToContent="Height" Title="MainWindow" Height="470" Width="800"> <Grid> <!-- slider(源属性) 绑定到 img(目标属性) --> <StackPanel> <Image x:Name="img" Source="/Images/1.png" Opacity="{Binding ElementName=slider, Path=Value, Mode=OneWay}"/> <TextBox HorizontalAlignment="Center" Text="{Binding ElementName=slider, Path=Value, Mode=Default}"/> <Slider x:Name="slider" Minimum="0" Maximum="1" Value="0.5"/> <Button Content="滑块value变变变" Margin="0, 3" Height="20" Width="120" Click="Button_Click"/> <Button Content="图片Opacity变变变" Margin="0, 3" Height="20" Width="120" Click="Button_Click_1"/> </StackPanel> </Grid> </Window>
MainWindow.xaml.cs
using System.Text; 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; namespace Binding { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { slider.Value = 0.2; } private void Button_Click_1(object sender, RoutedEventArgs e) { img.Opacity = 0.8; } } }
在此之前,问一个问题,你觉得绑定是必须双向的吗,还是默认双向的?
答案都不是,你以为是搞嵌入式TX,RX,RS485数据可以双向传输吗,拜托,这是上位机
WPF 绑定不是必须双向,而且默认也不是双向
大多数绑定其实是 单向(OneWay)默认绑定模式其实是“控件决定的”
默认模式不是Binding决定的,而是由 控件属性的DependencyProperty决定的
绑定模式一共分为5种,1种自动挡,4种手动挡
BindingMode
├─ Default # 自动挡
├─ OneWay # 数据源 → UI
├─ TwoWay # 数据源 ⇿ UI
├─ OneWayToSource # UI → 数据源
└─ OneTime # 只初始化一次
# 这只狐狸????还是这么喜欢树状图
从数据流行为 看:真正的模式只有 4 种,但从API 的角度 看:BindingMode 一共有 5 个枚举值
| 绑定模式 | 数据流向 |
|---|---|
OneWay |
数据源 → UI |
TwoWay |
数据源 ↔ UI |
OneWayToSource |
UI → 数据源 |
OneTime |
只初始化一次 |
Default |
OneWay — 单向绑定数据流向:数据 只从数据源流向 UI
ViewModel → UI
# 如果数据改变
VM.UserName 改变
↓
TextBlock.Text 自动更新
# 如果 UI 改变
UI 改变
不会写回 VM
# 示例
<TextBlock Text="{Binding UserName, Mode=OneWay}" />
适用场景:显示数据,状态显示,只读 UI
TwoWay — 双向绑定数据流向:数据 双向同步(数据源 ⇿ UI)
ViewModel ⇿ UI
# 数据改变
VM → UI
# 用户输入(UI改变)
UI → VM
# 示例
<TextBox Text="{Binding UserName, Mode=TwoWay}" />
适用场景:输入框,表单,参数设置
OneWayToSource — 单向反向绑定数据流向:数据 只从 UI 写回数据源
UI → ViewModel
# 数据源改变
不会更新 UI
# 用户输入(UI改变)
UI → VM
# 示例
<TextBlock Tag="{Binding WidthValue, Mode=OneWayToSource}" />
适用场景(比较少见):获取 UI 尺寸,获取控件状态,UI 信息回传
OneTime — 仅进行一次的绑定数据绑定:只在初始化进行一次绑定
# 初始化
Data → UI
# 示例
<TextBlock Text="{Binding Version, Mode=OneTime}" />
适用场景:版本号,初始化数据,静态信息
Default — 自动档本质:延迟决定绑定模式,它实际上是一个占位符
当没有显式指定 Mode 时,Binding 的默认值就是 Default
# 你写的代码
<TextBox Text="{Binding UserName}" />
# 实际上等价于
<TextBox Text="{Binding UserName, Mode=Default}" />
工作方式
当 Mode = Default 时,WPF 会去查询这个属性的 DependencyProperty 元数据
你可能会问DependencyProperty是什么鬼东西,这个鬼东西其实就是依赖属性
# DependencyProperty只是一种带规则的属性系统
# 它并不能直接控制绑定模式,而是由它下面的BindsTwoWayByDefault决定的
# 当控件定义一个 依赖属性 时,会注册一段 Metadata(元数据)
# 元数据中有很多配置,其中有一个非常关键的标志BindsTwoWayByDefault
# 这个标志决定了 Default 的 绑定模式
DependencyProperty(依赖属性)
│
└─ Metadata(元数据)
│
└─ BindsTwoWayByDefault
│
└─ 决定 Default 绑定模式
看不懂?那我们用代码来表示一下
if (BindsTwoWayByDefault == true)
Mode = TwoWay
else
Mode = OneWay
BindingMode.Default 并不是一种新的数据流模式
MainWindow.xaml
<Window x:Class="Binding.MainWindow"
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"
xmlns:local="clr-namespace:Binding"
mc:Ignorable="d"
SizeToContent="Height"
Title="MainWindow" Height="470" Width="800">
<Grid>
<!-- slider(源属性) 绑定到 img(目标属性) -->
<StackPanel>
<!-- ????你可以修改这里的Mode枚举值来尝试上面讲述的5种数据模式 -->
<!-- 绑定语法:{Binding ElementName=源控件名, Path=源属性, Mode=绑定模式} -->
<Image x:Name="img" Source="/Images/1.png"
Opacity="{Binding ElementName=slider, Path=Value, Mode=TwoWay}"/>
<TextBox HorizontalAlignment="Center" Text="{Binding ElementName=slider, Path=Value, Mode=Default}"/>
<Slider x:Name="slider" Minimum="0" Maximum="1" Value="0.5"/>
<Button Content="滑块value变变变" Margin="0, 3" Height="20" Width="120"
Click="Button_Click"/>
<Button Content="图片Opacity变变变" Margin="0, 3" Height="20" Width="120"
Click="Button_Click_1"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Text;
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;
namespace Binding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
slider.Value = 0.2;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
img.Opacity = 0.8;
}
}
}
[!WARNING]
当我们以上面的代码使用
OneWay模式时,会出现一种特殊的情况:绑定失效
这是单向绑定,当我们使用第一个按钮时,数据是正常单向传递的
但是当我们使用第二个按钮时,数据反向传输后,使用其他控件,会发现:没有任何变化,绑定生效了
为什么会出现这种情况呢?
因为:当你手动给一个绑定属性赋值时,WPF 会把这个 Binding 直接移除
// 这里的代码干了一件很关键的事:删除了 Opacity 上的 Binding private void Button_Click_1(object sender, RoutedEventArgs e) { img.Opacity = 0.8; }这里涉及到一个优先级的问题,也就是WPF 会把这个 Binding 直接移除的原因
- 第一个按钮:
slider.Value改变 →img.Opacity自动变化- 第二个按钮:
img.Opacity = 0.8;WPF 的依赖属性系统会执行一个优先级规则
Local Value(本地值) > Binding > Style > Default而这句代码:
// 设置的是 Local Value(本地值) // 本地值的优先级高于 Binding img.Opacity = 0.8; // 于是 WPF 做了一个很干脆的动作 // 移除 Binding // 保留 Local Value
随笔参考:
1.WPF数据绑定深度解析:告别冗余事件,掌握5种绑定模式的精髓 - blfbuaa - 博客园
2.59.第8章_绑定模式_哔哩哔哩_bilibili
很多开发文档将多绑定,绑定更新,延迟绑定统称为高级数据绑定(Advanced Data Binding),
或者绑定行为控制(Binding Behavior Control),但是不管怎么说,他们都是在做三件事情
数据从哪里来?什么时候更新?如何组合?
| 功能 | 控制内容 |
|---|---|
多绑定MultiBinding |
控制 数据来源数量 |
绑定更新UpdateSourceTrigger |
控制 更新时机 |
延迟绑定Delay |
控制 更新节奏 |
高级绑定特性
├─ 多源绑定
│ └─ MultiBinding
│
├─ 绑定更新控制
│ └─ UpdateSourceTrigger
│
└─ 更新节流控制
└─ Delay
======================================================================
# 这只小狐狸????永远忘不了他的树状图了
======================================================================
# 逻辑框架理解
# WPF 的 Binding 系统其实像一条 数据管道系统
# 不同机制负责不同控制点:
数据源
↓
Binding
↓
[ ????多绑定 MultiBinding ] ← # 控制数据来源数量
↓
Converter
↓
UI
↓
[ ????UpdateSourceTrigger ] ← # 控制更新时机
↓
[ ????Delay ] ← # 控制更新节奏
↓
ViewModel
一般的绑定只有一个数据源,如果我们想要多个数据源便无法实现,于是就有了多绑定
多绑定实际上就是将 多个数据源合成一个值
# 一般的绑定 => 只有一个数据源
Source → Target
# 多绑定 => 多个数据源合成一个值
多个数据 → Converter → 一个UI值
# 多个 Source → 合成一个 Target
Source1
Source2
Source3
↓
Converter
↓
Target
那么,下面这段代码是多绑定吗?
严格意义上来说,这里并不是多绑定,只是2个独立的单绑定的同时存在而已
TextBox.Text ← slider.Value
TextBox.FontSize ← sliderSize.Value
<!-- 绑定语法:{Binding ElementName=源控件名, Path=源属性, Mode=绑定模式} -->
<StackPanel>
<TextBox
Text="{Binding ElementName=slider, Path=Value, Mode=TwoWay}"
FontSize="{Binding ElementName=sliderSize, Path=Value, Mode=OneWay}" />
<Slider
x:Name="slider" Margin="0,20"
Minimum="0" Maximum="1"
Value="0.5" />
<Slider
x:Name="sliderSize" Margin="0,20"
Minimum="10" Maximum="50"
Value="20" />
</StackPanel>
下面这段代码才是真正意义上的多绑定
# 这里的多绑定使用流程
1. 准备数据源
2. 创建转换器
3. 注册转换器
4. 编写 MultiBinding
5. 在 Converter 中处理数据
# 这里的整体结构
Binding1
Binding2
↓
MultiBinding # 多绑定
↓
Converter
↓
TextBlock.Text
# 翻译一下
sliderValue.Value
sliderSize.Value
↓
SliderInfoConverter
↓
TextBlock.Text
MainWindow.xaml
<Window x:Class="Binding_Advanced_features.MainWindow"
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"
xmlns:local="clr-namespace:Binding_Advanced_features"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<!-- 3.注册转换器 -->
<local:SliderInfoConverter x:Key="SliderInfoConverter"/>
</Window.Resources>
<StackPanel Margin="20">
<!-- 显示两个Slider组合后的结果 -->
<!-- 这里是一个单绑定,用于控制字体大小 -->
<TextBlock FontWeight="Bold"
FontSize="{Binding ElementName=sliderSize, Path=Value}"
HorizontalAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource SliderInfoConverter}">
<!-- 4.多绑定 => 用于组成字符串(TextBlock.Text) -->
<Binding ElementName="sliderValue" Path="Value"/>
<Binding ElementName="sliderSize" Path="Value"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!-- 1.准备多个数据源 -->
<!-- 控制数值 -->
<Slider x:Name="sliderValue"
Minimum="0"
Maximum="100"
Value="50"
Margin="0,20"/>
<!-- 控制字体大小 -->
<Slider x:Name="sliderSize"
Minimum="10"
Maximum="40"
Value="20"
Margin="0,20"/>
</StackPanel>
</Window>
转换器实现
SliderInfoConverter.cs多绑定必须通过
IMultiValueConverter进行数据转换
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Globalization;
namespace Binding_Advanced_features
{
// 2.创建多值转换器
// 转换器必须实现 IMultiValueConverter 接口
// 它与常规的转换器接口不同,因为 Convert 方法必须接受一个值数组,该数组的顺序必须与 XAML 中指定的顺序完全相同
// 即: values[0] 对应第一个 Binding
// values[1] 对应第二个 Binding
public class SliderInfoConverter : IMultiValueConverter
{
// 多个值 → 一个值
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double sliderValue = (double)values[0];
double fontSize = (double)values[1];
return $"当前数值: {sliderValue:F0} | 字体大小: {fontSize:F0}";
}
// 反向转换(这里不用)
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
# 数据流
# 当滑块变化时
sliderValue.Value 改变
sliderSize.Value 改变
↓
MultiBinding 监听到变化
↓
调用 Convert()
↓
返回字符串
↓
TextBlock.Text 更新
# 同时
sliderSize.Value
↓
TextBlock.FontSize 更新
多绑定适用场景
MultiBinding 常用于 组合计算 UI或者UI状态判断
MultiBinding 在真实项目里最常见的用途其实是做 UI 状态判断
例如:
# 组合计算 UI
宽 × 高 → 面积
单价 × 数量 → 总价
名字 + 姓氏 → 全名
多个条件 → 控件是否可用
# UI 状态判断
用户名是否填写
密码是否填写
验证码是否填写
↓
全部满足
↓
登录按钮 Enable
# 本质
多个 Source → 一个 Target
随笔参考:
1.DataBinding:绑定多属性MultiBinding、IMultiValueConverter - 知乎
2.60.第8章_多绑定_绑定更新_绑定延迟_哔哩哔哩_bilibili
3.数据绑定概述 - WPF | Microsoft Learn
所谓的绑定更新,实际上就是考虑了一件事情:
绑定更新(UpdateSourceTrigger)常用枚举值
| 值 | 说明 |
|---|---|
PropertyChanged |
目标属性一变化就立刻更新 |
LostFocus |
当目标属性发生变化,且失去焦点时才更新 |
Explicit |
手动触发更新 |
Default |
自动档 大多数默认行为是 PropertyChanged但是 TextBox.Text属性的默认行为是LostFocus |
PropertyChanged —— 实时更新<TextBox Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}" />
适用于:实时搜索,实时计算,实时过滤等
# 行为
输入一个字
↓
立刻写回 ViewModel
LostFocus (TextBox 默认) —— 失去焦点才更新<TextBox Text="{Binding UserName, UpdateSourceTrigger=LostFocus}" />
# 行为
用户输入
↓
离开 TextBox
↓
更新数据
# 优点:减少更新次数
Explicit —— 手动更新# 数据流
UI改变
↓
什么都不会发生
↓
手动触发 # GetBindingExpression + UpdateSource()
↓
更新 Source
代码示例:
# 逻辑交互
TextBox → 输入
Button → 提交
TextBlock → 显示 ViewModel 数据
MainViewModel.cs
using System.ComponentModel;
namespace Binding_UpdateSourceTrigger
{
public class MainViewModel : INotifyPropertyChanged
{
private string _userName = string.Empty;
public string UserName
{
get => _userName;
set
{
_userName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UserName)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
MainWindow.xaml
<Window x:Class="Binding_UpdateSourceTrigger.MainWindow"
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"
xmlns:local="clr-namespace:Binding_UpdateSourceTrigger"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Margin="20">
<TextBlock FontSize="16" Margin="0,0,0,10">
输入用户名(不会立即更新):
</TextBlock>
<TextBox x:Name="tbUserName"
Text="{Binding UserName,
Mode=TwoWay,
UpdateSourceTrigger=Explicit}"
Height="30"/>
<Button Content="提交数据"
Click="Submit_Click"
Margin="0,15,0,0"
Height="30"/>
<TextBlock Margin="0,20,0,0"
FontSize="16"
Text="{Binding UserName}"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Text;
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;
namespace Binding_UpdateSourceTrigger
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainViewModel vm = new MainViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
}
private void Submit_Click(object sender, RoutedEventArgs e)
{
// 1.找到 TextBox.Text 的绑定
BindingExpression be =
tbUserName.GetBindingExpression(System.Windows.Controls.TextBox.TextProperty);
// 2.手动更新数据源
be.UpdateSource();
}
}
}
Default —— 自动档Default 本质上不是一种策略,而是一个 占位符(占坑的家伙)
即:Default = 让控件自己决定
# 下面两个等价
<TextBox Text="{Binding UserName}" />
<TextBox Text="{Binding UserName, UpdateSourceTrigger=Default}" />
关于工作方式,我们可以查看绑定模式中的Default,非常类似 那我就copy了,我觉得我copy我自己的东西没毛病
工作方式
当 UpdateSourceTrigger = Default 时,WPF 会去查询这个属性的 DependencyProperty 元数据
你可能会问DependencyProperty是什么鬼东西,这个鬼东西其实就是依赖属性
# DependencyProperty只是一种带规则的属性系统
# 它并不能直接控制绑定模式,而是由它下面的UpdateSourceTrigger决定的
# 当控件定义一个 依赖属性 时,会注册一段 Metadata(元数据)
# 元数据中有很多配置,其中有一个非常关键的标志UpdateSourceTrigger
# 这个标志决定了 Default 的 绑定模式
DependencyProperty(依赖属性)
│
└─ Metadata(元数据) (FrameworkPropertyMetadata)
│
└─ UpdateSourceTrigger
│
└─ 决定 Default 绑定模式
UpdateSourceTrigger.Default 并不是一种新的数据流模式
大多数属性的默认行为都是PropertyChanged,但是也有例外:TextBox.Text 的默认更新方式是LostFocus
有些 UI 更新太频繁,比如搜索框,如果每敲一个字都触发查询,服务器负担可能较大:
a → 查询
aw → 查询
aws → 查询
awsl → 查询
这时候可以使用 Delay 减少负担
用户停止输入 100ms
↓
才更新数据源
<TextBox Text="{Binding SearchText,
UpdateSourceTrigger=PropertyChanged,
Delay=1000}" />
在此之前,我们来重新认识一下
Binging.elementName属性(元素名称)
Binging.elementName属性
- elementName,翻译一下就是:元素名称
- 它的设计目标就是——绑定到“界面元素”
- 如果数据源不是 UI 元素,这个属性基本就该退场了
# 再来回顾一下,元素绑定绑定语法: {Binding ElementName=源控件名, Path=源属性, Mode=绑定模式} # 示例 <TextBlock Text="{Binding ElementName=slider, Path=Value}" /> <Slider x:Name="slider" /> Slider.Value ↓ TextBlock.Text
这一小节,我们来讲解非元素绑定的三种方式,当然,你也可以根据你的理解
根据不同的分类方式分类,
- 根据绑定的对象类型,可以分为 :
- 1.
DataContext对象(最常用,数据上下文对象)- s2.静态对象
- 3.资源对象(
Resource)- 根据绑定的数据源获取途径(入口),分为:
- 1.
Source(显式数据源)- 2.
RelativeSource(相对对象)- 3.
DataContext(默认数据源)在本小节中,我们会根据数据源获取途径继续讲解
因为刚好有现成的Demo可以白嫖.......
Source(显式数据源 —— 直接指定)这种数据源用最直接的方式告诉你,我们使用的是什么数据源
[!WARNING]
❗特别注意
Source理论上可以用于 任何对象 和 任何属性
- 静态对象,资源字典对象,普通对象,
ObjectDataProvider,x:Reference对象,代码对象 等
Source更多是 特殊情况的精确绑定工具,而不是主力绑定方式# Source本质结构 Binding │ └─ Source = object # 一切对象的母亲 │ └─ Path = 属性
这里我们简单介绍5种数据源,最后我会将五种数据源的整合代码放出来
<Window x:Class="Non_element_binding.MainWindow"
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"
xmlns:local="clr-namespace:Non_element_binding"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<FontFamily x:Key="my_font">
微软雅黑
</FontFamily>
</Window.Resources>
<Grid>
<StackPanel>
<!-- 1.静态绑定 - 将系统默认字体格式绑定到TextBlock-Text -->
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=Source}"
Margin="50" HorizontalAlignment="Center"/>
<!-- 2.绑定到资源对象 -->
<TextBlock Text="{Binding Source={StaticResource my_font}, Path=Source}"
Margin="10" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</Window>
User.cs我们添加一个额外的类,仅做演示
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Non_element_binding
{
public class User
{
public string Name { get; set; } = string.Empty;
}
}
MainWindow.xaml
<Window x:Class="Non_element_binding.MainWindow"
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"
xmlns:local="clr-namespace:Non_element_binding"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<!-- 3.普通对象(最常见) -->
<local:User x:Key="my_user" Name="史蒂夫"/>
</Window.Resources>
<Grid>
<StackPanel>
<!-- 3.普通对象(最常见) -->
<TextBlock Text="{Binding Source={StaticResource my_user}, Path=Name}"
Margin="10" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</Window>
ObjectDataProvider(据说是一种老派 WPF 技术)<Window x:Class="Non_element_binding.MainWindow"
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"
xmlns:local="clr-namespace:Non_element_binding"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<!-- 4.ObjectDataProvider -->
<ObjectDataProvider x:Key="now"
ObjectType="{x:Type sys:DateTime}"
MethodName="Now"/>
</Window.Resources>
<Grid>
<StackPanel>
<!-- 4.ObjectDataProvider -->
<TextBlock FontSize="30" Margin="10"
HorizontalAlignment="Center"
Text="{Binding Source={x:Static sys:DateTime.Now}}"/>
</StackPanel>
</Grid>
</Window>
[!WARNING]
这里有一个需要注意的点
DateTime.Now不是方法,而是属性<Window.Resources> <!-- 4.ObjectDataProvider --> <ObjectDataProvider x:Key="now" ObjectType="{x:Type sys:DateTime}" MethodName="Now"/> </Window.Resources> <TextBlock Text="{Binding Source={StaticResource now}}"/>所以如果要正确使用,有两种修改方法(上面代码使用的是第二种)
1.
MethodName="Now"=>MethodName="get_Now"<Window.Resources> <!-- 4.ObjectDataProvider --> <ObjectDataProvider x:Key="now" ObjectType="{x:Type sys:DateTime}" MethodName="get_Now"/> </Window.Resources>2.
Binding Source={StaticResource now}=>Binding Source={x:Static sys:DateTime.Now}<TextBlock Text="{Binding Source={x:Static sys:DateTime.Now}}"/>
x:Reference标记扩展[!IMPORTANT]
x:Reference
x:Reference这个东西其实是 XAML 世界里的“指针”- 作用:拿到某个已经存在的对象实例,然后当作 绑定的资源
- 和常见的
ElementName很像,但机制不一样x:Reference属于 XAML 标记扩展,来自XAML体系
x:Reference → 找到某个对象实例
Binding → 从这个实例读取属性
# 数据流
对象实例
↓
Binding
↓
目标属性
这里我们使用一个滑块,让 TextBlock 实时显示它的值
<Window x:Class="Non_element_binding.MainWindow"
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"
xmlns:local="clr-namespace:Non_element_binding"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<!-- 5.标记扩展 -->
<Slider x:Name="slider" Value="50"
Minimum="0" Maximum="100"/>
<TextBlock Text="{Binding Source={x:Reference slider}, Path=Value}"
FontSize="30" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</Window>
[!IMPORTANT]
ElementName和x:Reference的区别
<!-- ElementName --> <TextBlock Text="{Binding ElementName=slider, Path=Value}"/> <!-- x:Reference --> <TextBlock Text="{Binding Source={x:Reference slider}, Path=Value}"/>主要是底层机制不同
ElementName是WPF Binding自带功能,依赖 元素名字表(NameScope)x:Reference是XAML级别机制,它可以引用 任何带x:Key / x:Name的对象
// User.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Non_element_binding
{
public class User
{
public string Name { get; set; } = string.Empty;
}
}
<Window x:Class="Non_element_binding.MainWindow"
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"
xmlns:local="clr-namespace:Non_element_binding"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<!-- 2.静态资源字典对象 -->
<FontFamily x:Key="my_font">
微软雅黑
</FontFamily>
<!-- 3.普通对象(最常见) -->
<local:User x:Key="my_user" Name="史蒂夫"/>
<!-- 4.ObjectDataProvider -->
<ObjectDataProvider x:Key="now"
ObjectType="{x:Type sys:DateTime}"
MethodName="get_Now"/>
</Window.Resources>
<Grid>
<StackPanel>
<!-- 1.静态绑定 - 将系统默认字体格式绑定到TextBlock-Text -->
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=Source}"
Margin="50" HorizontalAlignment="Center"/>
<!-- 2.绑定到资源字典对象 -->
<TextBlock Text="{Binding Source={StaticResource my_font}, Path=Source}"
Margin="10" HorizontalAlignment="Center"/>
<!-- 3.普通对象(最常见) -->
<TextBlock Text="{Binding Source={StaticResource my_user}, Path=Name}"
Margin="10" HorizontalAlignment="Center"/>
<!-- 4.ObjectDataProvider -->
<TextBlock FontSize="30" Margin="10"
HorizontalAlignment="Center"
Text="{Binding Source={StaticResource now}}"/>
<!-- 5.标记扩展 -->
<Slider x:Name="slider" Value="50"
Minimum="0" Maximum="100"/>
<TextBlock Text="{Binding Source={x:Reference slider}, Path=Value}"
FontSize="30" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</Window>
我们先来翻译一下这个单词
Relative = 相对的,Source = 数据源换句话说:数据源不是外部对象,而是“和当前控件有关系的对象”
这种关系通常来自 UI 树(控件层级),WPF 的绑定系统会在控件树里找目标
当前控件
│
└─ 向某个方向查找
│
└─ 找到对象
│
└─ 读取属性
# 他还是忘不了他的树状图
RelativeSource 有四种模式,即:枚举体RelativeSourceMode有4种数值
Self
FindAncestor
TemplatedParent
PreviousData
Self — 绑定自己# 这两个绑一起了,然后永远都是一个正方形了,永远.....永远.....(海绵宝宝口音)
TextBox.Width
↓
TextBox.Height
<TextBlock Height="50" Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"/>
FindAncestor — 寻找先祖控件欲先利其器,必然先翻译
Find = 寻找,Ancestor = 祖先这个感觉是最常用的一种,它会沿着 UI 树往上找指定类型
# 语法示例
# Path:表示 要读取祖先控件的哪个属性
# AncestorType:表示 要找哪种类型的祖先控件
# AncestorLevel:表示 第几个祖先(默认 1)
# Mode:绑定模式(OneWay / TwoWay 等)
{Binding Path=源属性, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=祖先控件类型,
AncestorLevel=祖先层级},
Mode=绑定模式}
例如下面这段代码:
# 假设它的UI树是这样的
Window
└─ Grid (Background=OrangeRed)
├─ TextBlock
│ └─ Width ← Height # 之前的Self
│
└─ TextBlock # 现在的FindAncestor
└─ Background ← Window.Background
<TextBlock Height="100" Width="100" Grid.Row="1"
Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
Path=Background,
AncestorType={x:Type Window}}}"/>
然后,我们再来看另一个例子:
# UI树
Grid
└─ StackPanel
└─ Grid
└─ TextBlock
# 先祖层级
Grid (第2个) ← 绑定目标
│
StackPanel
│
Grid (第1个)
│
TextBlock
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Grid},
AncestorLevel=2}
先上翻译:
这是控件模板专用模式,当你写 ControlTemplate 时,模板里的元素需要访问外部控件属性
这里我对WPF中的模板不是特别了解,所以前面的内容以后再来探索吧
Button.Content
↓
TextBlock.Text
<Button Content="Hello">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Background="LightBlue">
<TextBlock
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
集合里的上一个数据项
{Binding RelativeSource={RelativeSource PreviousData}}
这里偷个懒,就不做过多的解释了,再解释,我感觉我资料查不完了啊
在讲解数据上下文之前,我们先给出一个示例,来解释这个东西到底是个啥
<Grid>
<StackPanel>
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=Source}"/>
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=LineSpacing}"/>
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=FamilyTypefaces[0].Style}"/>
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=FamilyTypefaces[0].Weight}"/>
</StackPanel>
</Grid>
但是我们这样写非常麻烦,每次都要写绑定这个Source={x:Static SystemFonts.IconFontFamily}
于是,我们就可以使用数据上下文(在上一级控件上声明数据上下文)减少代码量
<Grid>
<StackPanel DataContext="{x:Static SystemFonts.IconFontFamily}">
<TextBlock Text="{Binding Path=Source}"/>
<TextBlock Text="{Binding Path=LineSpacing}"/>
<TextBlock Text="{Binding Path=FamilyTypefaces[0].Style}"/>
<TextBlock Text="{Binding Path=FamilyTypefaces[0].Weight}"/>
</StackPanel>
</Grid>
<!-- 当然,你还可以更加简洁 -->
<Grid>
<StackPanel DataContext="{x:Static SystemFonts.IconFontFamily}">
<TextBlock Text="{Binding Source}"/>
<TextBlock Text="{Binding LineSpacing}"/>
<TextBlock Text="{Binding FamilyTypefaces[0].Style}"/>
<TextBlock Text="{Binding FamilyTypefaces[0].Weight}"/>
</StackPanel>
</Grid>
<!-- 当然的当然,你还可以在父控件的父控件上使用数据上下文 -->
<Grid DataContext="{x:Static SystemFonts.IconFontFamily}">
<StackPanel>
<TextBlock Text="{Binding Source}"/>
<TextBlock Text="{Binding LineSpacing}"/>
<TextBlock Text="{Binding FamilyTypefaces[0].Style}"/>
<TextBlock Text="{Binding FamilyTypefaces[0].Weight}"/>
</StackPanel>
</Grid>
<!-- 当然的当然的当然(你还有完没完),只要是上层控件都可以使用,但是层级越高,性能消耗越高 -->
<Window x:Class="DataContext.MainWindow"
DataContext="{x:Static SystemFonts.IconFontFamily}"
......
<Grid>
<StackPanel>
<TextBlock Text="{Binding Source}"/>
<TextBlock Text="{Binding LineSpacing}"/>
<TextBlock Text="{Binding FamilyTypefaces[0].Style}"/>
<TextBlock Text="{Binding FamilyTypefaces[0].Weight}"/>
</StackPanel>
</Grid>
</Window>
所以此时此刻,我们可以得出结论
数据上下文(DataContext)作用:给一片 UI 区域指定默认数据源
DataContext 理解为某片UI区域中默认绑定对象为什么数据上下文只要是在上层控件就可以使用呢,因为它可以继承
即:DataContext 会从父控件自动传给子控件
如图: 树状图也是图!
Window # 假设数据上下文写在这里
└─ Grid # 这里可以拿到
└─ StackPanel # 这里也可以拿到
└─ TextBlock #这里还是可以拿到
当然 有完没完啊你
我们不仅可以在xaml代码中声明数据上下文,也可以在C#代码中声明数据上下文
MainWindow.xaml.cs
using System.Text;
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;
namespace DataContext
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new User { Name = "无名氏", Age = 100 };
}
}
public class User
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}
}
MainWindow.xaml
<Window x:Class="DataContext.MainWindow"
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"
xmlns:local="clr-namespace:DataContext"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<StackPanel DataContext="{x:Static SystemFonts.IconFontFamily}">
<TextBlock Text="{Binding Source}"/>
<TextBlock Text="{Binding LineSpacing}"/>
<TextBlock Text="{Binding FamilyTypefaces[0].Style}"/>
<TextBlock Text="{Binding FamilyTypefaces[0].Weight}"/>
</StackPanel>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Age}" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</Window>
非元素对象绑定
│
├─ 1.Source(显式数据源)
│ │
│ ├─ 静态对象
│ │ x:Static
│ │ └─ SystemFonts.IconFontFamily
│ │
│ ├─ 资源对象
│ │ StaticResource
│ │ └─ ResourceDictionary
│ │
│ ├─ 普通对象
│ │ C# 类实例
│ │ └─ User
│ │
│ ├─ ObjectDataProvider
│ │ └─ 调用对象方法 / 属性
│ │
│ └─ x:Reference
│ └─ 引用 XAML 中已有对象实例
│
├─ 2.RelativeSource(相对数据源)
│ │
│ ├─ Self
│ │ └─ 绑定自己
│ │
│ ├─ FindAncestor
│ │ └─ 向 UI 树上查找祖先控件
│ │
│ ├─ TemplatedParent
│ │ └─ 访问模板宿主控件
│ │
│ └─ PreviousData
│ └─ 访问集合中上一条数据
│
└─ 3.DataContext(默认数据源)
│
├─ XAML 中设置
│ Window.DataContext
│ Grid.DataContext
│
├─ C# 中设置
│ this.DataContext = ViewModel
│
└─ 继承机制
Window
└─ Grid
└─ StackPanel
└─ TextBlock
Binding 数据来源
│
├─ ElementName
│ └─ 绑定到指定控件
│
├─ Source
│ └─ 显式指定对象
│
├─ RelativeSource
│ └─ 从 UI 树寻找对象
│
└─ DataContext
└─ 默认数据源(最常用)
随笔参考:
1.61.第8章_绑定到非元素_Source_哔哩哔哩_bilibili
2.62.第8章_绑定到非元素_RelativeSrouce_哔哩哔哩_bilibili
3.63.第8章_绑定到非元素_DataContext_哔哩哔哩_bilibili
哦吼吼吼!终于写完了,要死了要死了,最近这两篇博客,感觉写的实在是太久了,资料是越查越多,越查越多.....
多的让我以为世界快完蛋了,虽然是用来学习的同时,打发一下摸鱼时间
好吧,其实是打法摸鱼的时候顺便学一下架构.......
上一篇:.NET Win32磁盘动态卷触发“函数不正确”问题排查
下一篇:没有了