WPF个人文档(五)------ 绑定
!IMPORTANT
在开始之前,我觉得我们非常有必要要先了解一下
ViewModel
ViewModel:专门给界面(View)使用的数据对象
shell# ViewModel = 专门给界面(View)使用的数据对象 如果只讲绑定,可以简单理解为数据源对象 在这里先留一个简单的印象,后面会详细讲解,在看完本篇随笔之后,你也会对这个东西有一个较为深刻的印象 # 常用于MVVM架构(此架构我们以后再详细讲解) Model → ViewModel → View 数据 UI数据 界面
!NOTE
WPF中,绑定的本质实际上就是在找东西
换句话就是:**WPF的一切绑定,本质都是在找 数据源 **
只不过 ------ 数据源到底是 对象里的数据 ,还是 界面里的控件,这个就得看你的代码了
shell# 根据数据源的位置,WPF绑定通常会被分成两大类 绑定 ├─ 元素绑定(Element Binding) └─ 非元素绑定(Non-Element Binding)
一.元素绑定
-
!NOTE
WPF ------ 绑定
这里,我们来看看官方对于绑定的解释
- WPF 元素绑定:将UI元素属性与数据源对象建立连接的机制,能在数据变化时自动更新界面,或在界面修改时同步数据源
- 它支持
.NET 对象、XML、集合等多种数据源,并可通过Binding对象灵活配置 - 🌱**
Binding= 在UI属性 和 数据源 之间建立连接**
- 它支持
示例:将按钮背景色绑定到数据对象的属性
- 此处
Background是绑定目标属性,ColorName是绑定源属性,通过Path指定
xml<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:
xaml<ListBox ItemsSource="{Binding MyItems}" />- 若需排序、筛选、分组,可用
CollectionViewSource:
xaml<CollectionViewSource x:Key="view" Source="{Binding MyItems}" /> <ListBox ItemsSource="{Binding Source={StaticResource view}}" />数据转换与验证
- 类型不匹配时可实现
IValueConverter转换值 - 可通过
ValidationRule添加验证逻辑,并结合ErrorTemplate提供视觉反馈
注意事项
- 源对象应实现
INotifyPropertyChanged,集合应实现INotifyCollectionChanged以支持动态更新 - 合理选择绑定模式和触发器可优化性能与交互体验
这样,WPF 数据绑定不仅能减少手动更新 UI 的代码量,还能保持业务逻辑与界面的清晰分离
- WPF 元素绑定:将UI元素属性与数据源对象建立连接的机制,能在数据变化时自动更新界面,或在界面修改时同步数据源
-
元素绑定:让一个 UI 控件的属性直接依赖另一个 UI 控件的属性,即:一个 UI 控件属性绑定到另一个 UI 控件属性
- 元素指UI控件,数据源也是控件属性
- 非常很多博主在他们的教程中都说的是,绑定就是控件绑定什么什么数据源,实际上他们说的控件是值控件属性
<!-- 绑定语法:{Binding ElementName=源控件名, Path=源属性, Mode=绑定模式} -->
!CAUTION
当你手动给一个绑定属性赋值时,WPF 会把这个 Binding 直接移除
这里用一个实例代码演示一下,如果你无法看明白,可以当作元素绑定的一个引题,在看完绑定模式后,会看明白的
现在有一张图片,还有两个滑块,还有一个
TextBox用于显示slider.Value的数值
shell# 两个元素绑定 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 (本地值)最后滑块与图片透明度之间的绑定就被移除了
c#slider.Value ❌ img.Opacity # 于是乎,按钮仍然在工作,只是 UI 不再联动会出现这种情况的本质原因:
WPF 的依赖属性内部其实有一个 值优先级系统:
shellAnimation # 优先级最高 LocalValue Binding Style Default # 优先级最低当我们写:
img.Opacity = 0.8就产生了 LocalValue(本地值),而 LocalValue 的优先级 高于 Binding,于是 Binding 就被覆盖了
MainWindow.xaml
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
c#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种手动挡
shellBindingMode ├─ Default # 自动挡 ├─ OneWay # 数据源 → UI ├─ TwoWay # 数据源 ⇿ UI ├─ OneWayToSource # UI → 数据源 └─ OneTime # 只初始化一次 # 这只狐狸🦊还是这么喜欢树状图-
从数据流行为 看:真正的模式只有 4 种,但从API 的角度 看:
BindingMode一共有 5 个枚举值
绑定模式 数据流向 OneWay数据源 → UI TwoWay数据源 ↔ UI OneWayToSourceUI → 数据源 OneTime只初始化一次 Default
-
1.🌱**OneWay --- 单向绑定**
-
数据流向:数据 只从数据源流向 UI
shellViewModel → UI # 如果数据改变 VM.UserName 改变 ↓ TextBlock.Text 自动更新 # 如果 UI 改变 UI 改变 不会写回 VM # 示例 <TextBlock Text="{Binding UserName, Mode=OneWay}" /> -
适用场景:显示数据,状态显示,只读 UI
- 比如:温度显示,设备状态,日志信息
2.🌱**TwoWay --- 双向绑定**
-
数据流向:数据 双向同步(数据源 ⇿ UI)
shellViewModel ⇿ UI # 数据改变 VM → UI # 用户输入(UI改变) UI → VM # 示例 <TextBox Text="{Binding UserName, Mode=TwoWay}" /> -
适用场景:输入框,表单,参数设置
- 比如:设备参数,用户名输入,数值调整
3.🌱**OneWayToSource --- 单向反向绑定**
-
数据流向:数据 只从 UI 写回数据源
shellUI → ViewModel # 数据源改变 不会更新 UI # 用户输入(UI改变) UI → VM # 示例 <TextBlock Tag="{Binding WidthValue, Mode=OneWayToSource}" /> -
适用场景(比较少见):获取 UI 尺寸,获取控件状态,UI 信息回传
4.🌱**OneTime --- 仅进行一次的绑定**
-
数据绑定:只在初始化进行一次绑定
shell# 初始化 Data → UI # 示例 <TextBlock Text="{Binding Version, Mode=OneTime}" /> -
适用场景:版本号,初始化数据,静态信息
- 优点是性能更高,因为不监听变化
5.🌱**Default --- 自动档**
-
本质:延迟决定绑定模式,它实际上是一个占位符
-
当没有显式指定
Mode时,Binding 的默认值就是Default
shell# 你写的代码 <TextBox Text="{Binding UserName}" /> # 实际上等价于 <TextBox Text="{Binding UserName, Mode=Default}" /> -
-
工作方式
-
当
Mode = Default时,WPF 会去查询这个属性的 DependencyProperty 元数据-
你可能会问
DependencyProperty是什么鬼东西,这个鬼东西其实就是依赖属性
shell# DependencyProperty只是一种带规则的属性系统 # 它并不能直接控制绑定模式,而是由它下面的BindsTwoWayByDefault决定的 # 当控件定义一个 依赖属性 时,会注册一段 Metadata(元数据) # 元数据中有很多配置,其中有一个非常关键的标志BindsTwoWayByDefault # 这个标志决定了 Default 的 绑定模式 DependencyProperty(依赖属性) │ └─ Metadata(元数据) │ └─ BindsTwoWayByDefault │ └─ 决定 Default 绑定模式-
看不懂?那我们用代码来表示一下
c#if (BindsTwoWayByDefault == true) Mode = TwoWay else Mode = OneWay -
-
BindingMode.Default并不是一种新的数据流模式- 它只是告诉 WPF:去使用该依赖属性预设的默认绑定模式
-
6.示例代码
-
MainWindow.xamlxaml<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.csc#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 直接移除
c#// 这里的代码干了一件很关键的事:删除了 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 的依赖属性系统会执行一个优先级规则
shellLocal Value(本地值) > Binding > Style > Default -
而这句代码:
c#// 设置的是 Local Value(本地值) // 本地值的优先级高于 Binding img.Opacity = 0.8; // 于是 WPF 做了一个很干脆的动作 // 移除 Binding // 保留 Local Value
-
随笔参考:
1.WPF数据绑定深度解析:告别冗余事件,掌握5种绑定模式的精髓 - blfbuaa - 博客园
三.高级数据绑定:多绑定、绑定更新、延迟绑定
-
很多开发文档将多绑定,绑定更新,延迟绑定统称为高级数据绑定(
Advanced Data Binding),或者绑定行为控制(
Binding Behavior Control),但是不管怎么说,他们都是在做三件事情 -
数据从哪里来?什么时候更新?如何组合?
功能 控制内容 多绑定 MultiBinding控制 数据来源数量 绑定更新 UpdateSourceTrigger控制 更新时机 延迟绑定 Delay控制 更新节奏
shell
高级绑定特性
├─ 多源绑定
│ └─ MultiBinding
│
├─ 绑定更新控制
│ └─ UpdateSourceTrigger
│
└─ 更新节流控制
└─ Delay
======================================================================
# 这只小狐狸🦊永远忘不了他的树状图了
======================================================================
# 逻辑框架理解
# WPF 的 Binding 系统其实像一条 数据管道系统
# 不同机制负责不同控制点:
数据源
↓
Binding
↓
[ 🌱多绑定 MultiBinding ] ← # 控制数据来源数量
↓
Converter
↓
UI
↓
[ 🌱UpdateSourceTrigger ] ← # 控制更新时机
↓
[ 🌱Delay ] ← # 控制更新节奏
↓
ViewModel
1.🌱多绑定(MultiBinding)
-
一般的绑定只有一个数据源,如果我们想要多个数据源便无法实现,于是就有了多绑定
-
多绑定实际上就是将 多个数据源合成一个值
shell# 一般的绑定 => 只有一个数据源 Source → Target # 多绑定 => 多个数据源合成一个值 多个数据 → Converter → 一个UI值 # 多个 Source → 合成一个 Target Source1 Source2 Source3 ↓ Converter ↓ Target -
那么,下面这段代码是多绑定吗?
-
严格意义上来说,这里并不是多绑定,只是2个独立的单绑定的同时存在而已
xamlTextBox.Text ← slider.Value TextBox.FontSize ← sliderSize.Value <!-- 绑定语法:{Binding ElementName=源控件名, Path=源属性, Mode=绑定模式} -->xaml<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> -
-
下面这段代码才是真正意义上的多绑定
shell# 这里的多绑定使用流程 1. 准备数据源 2. 创建转换器 3. 注册转换器 4. 编写 MultiBinding 5. 在 Converter 中处理数据 # 这里的整体结构 Binding1 Binding2 ↓ MultiBinding # 多绑定 ↓ Converter ↓ TextBlock.Text # 翻译一下 sliderValue.Value sliderSize.Value ↓ SliderInfoConverter ↓ TextBlock.Text-
MainWindow.xaml
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进行数据转换
c#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(); } } }shell# 数据流 # 当滑块变化时 sliderValue.Value 改变 sliderSize.Value 改变 ↓ MultiBinding 监听到变化 ↓ 调用 Convert() ↓ 返回字符串 ↓ TextBlock.Text 更新 # 同时 sliderSize.Value ↓ TextBlock.FontSize 更新 -
-
多绑定适用场景
-
MultiBinding 常用于 组合计算 UI 或者UI状态判断
-
MultiBinding 在真实项目里最常见的用途其实是做 UI 状态判断
-
例如:
shell# 组合计算 UI 宽 × 高 → 面积 单价 × 数量 → 总价 名字 + 姓氏 → 全名 多个条件 → 控件是否可用 # UI 状态判断 用户名是否填写 密码是否填写 验证码是否填写 ↓ 全部满足 ↓ 登录按钮 Enable # 本质 多个 Source → 一个 Target -
-
随笔参考:
1.DataBinding:绑定多属性MultiBinding、IMultiValueConverter - 知乎
2.60.第8章_多绑定_绑定更新_绑定延迟_哔哩哔哩_bilibili
3.数据绑定概述 - WPF | Microsoft Learn
2.🌱绑定更新(UpdateSourceTrigger)
-
所谓的绑定更新,实际上就是考虑了一件事情:
- UI 改变后什么时候写回数据源
-
绑定更新(UpdateSourceTrigger)常用枚举值
值 说明 PropertyChanged目标属性一变化就立刻更新 LostFocus当目标属性发生变化,且失去焦点时才更新 Explicit手动触发更新 Default自动档 大多数默认行为是 PropertyChanged但是TextBox.Text属性的默认行为是LostFocus
(1)PropertyChanged ------ 实时更新
xaml
<TextBox Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}" />
-
适用于:实时搜索,实时计算,实时过滤等
```shell
# 行为
输入一个字
↓
立刻写回 ViewModel
```
(2)LostFocus (TextBox 默认) ------ 失去焦点才更新
xaml
<TextBox Text="{Binding UserName, UpdateSourceTrigger=LostFocus}" />
```shell
# 行为
用户输入
↓
离开 TextBox
↓
更新数据
# 优点:减少更新次数
```
(3)Explicit ------ 手动更新
shell
# 数据流
UI改变
↓
什么都不会发生
↓
手动触发 # GetBindingExpression + UpdateSource()
↓
更新 Source
-
代码示例:
shell# 逻辑交互 TextBox → 输入 Button → 提交 TextBlock → 显示 ViewModel 数据 -
MainViewModel.csc#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.xamlxaml<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.csc#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(); } } }
(4)Default ------ 自动档
-
Default本质上不是一种策略,而是一个 占位符(占坑的家伙)-
即:
Default= 让控件自己决定
shell# 下面两个等价 <TextBox Text="{Binding UserName}" /> <TextBox Text="{Binding UserName, UpdateSourceTrigger=Default}" /> -
-
关于工作方式,我们可以查看绑定模式中的Default,非常类似 那我就copy了,我觉得我copy我自己的东西没毛病
-
工作方式
-
当
UpdateSourceTrigger = Default时,WPF 会去查询这个属性的 DependencyProperty 元数据-
你可能会问
DependencyProperty是什么鬼东西,这个鬼东西其实就是依赖属性
shell# DependencyProperty只是一种带规则的属性系统 # 它并不能直接控制绑定模式,而是由它下面的UpdateSourceTrigger决定的 # 当控件定义一个 依赖属性 时,会注册一段 Metadata(元数据) # 元数据中有很多配置,其中有一个非常关键的标志UpdateSourceTrigger # 这个标志决定了 Default 的 绑定模式 DependencyProperty(依赖属性) │ └─ Metadata(元数据) (FrameworkPropertyMetadata) │ └─ UpdateSourceTrigger │ └─ 决定 Default 绑定模式 -
-
UpdateSourceTrigger.Default并不是一种新的数据流模式- 它只是告诉 WPF:去使用该依赖属性预设的默认绑定更新的方式
-
大多数属性的默认行为都是
PropertyChanged,但是也有例外:TextBox.Text的默认更新方式是LostFocus
-
3.🌱延迟绑定 (Delay)
-
有些 UI 更新太频繁,比如搜索框,如果每敲一个字都触发查询,服务器负担可能较大:
shella → 查询 aw → 查询 aws → 查询 awsl → 查询 -
这时候可以使用 Delay 减少负担
shell用户停止输入 100ms ↓ 才更新数据源xaml<TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged, Delay=1000}" />
四.非元素对象绑定
在此之前,我们来重新认识一下**
Binging.elementName** 属性(元素名称)
Binging.elementName属性
- elementName,翻译一下就是:元素名称
- 它的设计目标就是------绑定到"界面元素"
- 如果数据源不是 UI 元素,这个属性基本就该退场了
shell# 再来回顾一下,元素绑定绑定语法: {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可以白嫖.......
1.🌱Source(显式数据源 ------ 直接指定)
-
这种数据源用最直接的方式告诉你,我们使用的是什么数据源
-
!WARNING
❗特别注意
-
Source理论上可以用于 任何对象 和 任何属性- 静态对象,资源字典对象,普通对象,
ObjectDataProvider,x:Reference对象,代码对象 等
- 静态对象,资源字典对象,普通对象,
-
Source更多是 特殊情况的精确绑定工具,而不是主力绑定方式
shell# Source本质结构 Binding │ └─ Source = object # 一切对象的母亲 │ └─ Path = 属性 -
-
-
这里我们简单介绍5种数据源,最后我会将五种数据源的整合代码放出来
(1)静态绑定 & 静态资源绑定
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"
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>
(2)普通对象
-
User.cs我们添加一个额外的类,仅做演示
c#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.xamlxaml<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>
(3)ObjectDataProvider(据说是一种老派 WPF 技术)
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>
<!-- 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不是方法,而是属性
xaml<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"
xaml<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}
xaml<TextBlock Text="{Binding Source={x:Static sys:DateTime.Now}}"/> -
-
(4)x:Reference标记扩展
-
!IMPORTANT
x:Referencex:Reference这个东西其实是 XAML 世界里的"指针"- 作用:拿到某个已经存在的对象实例,然后当作 绑定的资源
- 和常见的
ElementName很像,但机制不一样
- 和常见的
x:Reference属于 XAML 标记扩展 ,来自XAML体系
shell
x:Reference → 找到某个对象实例
Binding → 从这个实例读取属性
# 数据流
对象实例
↓
Binding
↓
目标属性
-
这里我们使用一个滑块,让
TextBlock实时显示它的值
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">
<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的区别xaml<!-- 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的对象
-
(5)整体代码
c#
// 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;
}
}
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>
<!-- 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>
2.🌱RelativeSource(相对资源 ------ 从视觉树查找资源)
-
我们先来翻译一下这个单词
Relative= 相对的,Source= 数据源- 所以直接翻译就是相对资源
-
换句话说:数据源不是外部对象,而是"和当前控件有关系的对象"
-
这种关系通常来自 UI 树(控件层级),WPF 的绑定系统会在控件树里找目标
shell当前控件 │ └─ 向某个方向查找 │ └─ 找到对象 │ └─ 读取属性 # 他还是忘不了他的树状图 -
-
RelativeSource有四种模式,即:枚举体RelativeSourceMode有4种数值shellSelf FindAncestor TemplatedParent PreviousData
(1)Self --- 绑定自己
shell
# 这两个绑一起了,然后永远都是一个正方形了,永远.....永远.....(海绵宝宝口音)
TextBox.Width
↓
TextBox.Height
xaml
<TextBlock Height="50" Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"/>
(2)FindAncestor --- 寻找先祖控件
-
欲先利其器,必然先翻译
Find= 寻找,Ancestor= 祖先- 所以直接翻译就是 寻找祖先,本地化一点就是寻找先祖 我们又不是国服游戏无良翻译,本地化都不搞就上线圈钱了
-
这个感觉是最常用的一种,它会沿着 UI 树往上找指定类型
shell# 语法示例 # Path:表示 要读取祖先控件的哪个属性 # AncestorType:表示 要找哪种类型的祖先控件 # AncestorLevel:表示 第几个祖先(默认 1) # Mode:绑定模式(OneWay / TwoWay 等) {Binding Path=源属性, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=祖先控件类型, AncestorLevel=祖先层级}, Mode=绑定模式} -
例如下面这段代码:
shell# 假设它的UI树是这样的 Window └─ Grid (Background=OrangeRed) ├─ TextBlock │ └─ Width ← Height # 之前的Self │ └─ TextBlock # 现在的FindAncestor └─ Background ← Window.Backgroundxaml<TextBlock Height="100" Width="100" Grid.Row="1" Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor, Path=Background, AncestorType={x:Type Window}}}"/> -
然后,我们再来看另一个例子:
shell# UI树 Grid └─ StackPanel └─ Grid └─ TextBlock # 先祖层级 Grid (第2个) ← 绑定目标 │ StackPanel │ Grid (第1个) │ TextBlockxamlRelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}, AncestorLevel=2}
(3)TemplatedParent --- 模板父控件(绑定到应用模板的元素)
-
先上翻译:
- Templated:模板
- TemplatedParent:模板父母
-
这是控件模板专用模式,当你写
ControlTemplate时,模板里的元素需要访问外部控件属性-
这里我对WPF中的模板不是特别了解,所以前面的内容以后再来探索吧
Button.Content ↓ TextBlock.Textxaml<Button Content="Hello"> <Button.Template> <ControlTemplate TargetType="Button"> <Border Background="LightBlue"> <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" /> </Border> </ControlTemplate> </Button.Template> </Button> -
(4)PreviousData --- 绑定到数据绑定列表中(集合)的前一个数据项(很少使用)
-
集合里的上一个数据项
- 常用于 数据对比,列表差值,趋势计算
xaml
{Binding RelativeSource={RelativeSource PreviousData}}
- 这里偷个懒,就不做过多的解释了,再解释,我感觉我资料查不完了啊
3.DataContext(数据上下文)
-
在讲解数据上下文之前,我们先给出一个示例,来解释这个东西到底是个啥
xaml<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} -
于是,我们就可以使用数据上下文(在上一级控件上声明数据上下文)减少代码量
xaml<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区域中默认绑定对象 - 数据上下文在MVVM架构中使用的非常频繁
- 学术一点就是:
-
为什么数据上下文只要是在上层控件就可以使用呢,因为它可以继承
-
即:
DataContext会从父控件自动传给子控件 -
如图: 树状图也是图!
shellWindow # 假设数据上下文写在这里 └─ Grid # 这里可以拿到 └─ StackPanel # 这里也可以拿到 └─ TextBlock #这里还是可以拿到 -
-
-
当然 有完没完啊你
-
我们不仅可以在xaml代码中声明数据上下文,也可以在C#代码中声明数据上下文
-
MainWindow.xaml.cs
c#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
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> -
4.总结
sell
非元素对象绑定
│
├─ 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
哦吼吼吼!终于写完了,要死了要死了,最近这两篇博客,感觉写的实在是太久了,资料是越查越多,越查越多.....
多的让我以为世界快完蛋了,虽然是用来学习的同时,打发一下摸鱼时间
好吧,其实是打法摸鱼的时候顺便学一下架构.......