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