博客 / 詳情

返回

WPF新手村教程(五)— 附魔教學(綁定)

WPF個人文檔(五)—— 綁定


[!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等,通過 PathXPath 指定
    • 數據上下文(DataContext):未顯式指定源時,從父元素繼承
    • 模式(Mode)
      • OneWay:源 → 目標
      • TwoWay:雙向同步
      • OneWayToSource:目標 → 源
      • OneTime:初始化一次
    • 觸發器(UpdateSourceTrigger):如 PropertyChangedLostFocus 控制何時更新源

    集合綁定與視圖

    • 綁定到集合時使用 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 控件屬性

    • 元素指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

1.🌱OneWay — 單向綁定

  • 數據流向:數據 只從數據源流向 UI

    • ViewModel  →  UI
      
      # 如果數據改變
      VM.UserName 改變
              ↓
      TextBlock.Text 自動更新
      
      # 如果 UI 改變
      UI 改變
      不會寫回 VM
      
      # 示例
      <TextBlock Text="{Binding UserName, Mode=OneWay}" />
      
  • 適用場景:顯示數據,狀態顯示,只讀 UI

    • 比如:温度顯示,設備狀態,日誌信息

2.🌱TwoWay — 雙向綁定

  • 數據流向:數據 雙向同步(數據源 ⇿ UI)

    • ViewModel  ⇿  UI
      
      # 數據改變
      VM → UI
      
      # 用户輸入(UI改變)
      UI → VM
      
      # 示例
      <TextBox Text="{Binding UserName, Mode=TwoWay}" />
      
  • 適用場景:輸入框,表單,參數設置

    • 比如:設備參數,用户名輸入,數值調整

3.🌱OneWayToSource — 單向反向綁定

  • 數據流向:數據 只從 UI 寫回數據源

    • UI  →  ViewModel
      
      # 數據源改變
      不會更新 UI
      
      # 用户輸入(UI改變)
      UI → VM
      
      # 示例
      <TextBlock Tag="{Binding WidthValue, Mode=OneWayToSource}" />
      
  • 適用場景(比較少見):獲取 UI 尺寸,獲取控件狀態,UI 信息回傳


4.🌱OneTime — 僅進行一次的綁定

  • 數據綁定:只在初始化進行一次綁定

    • # 初始化
      Data → UI
      
      # 示例
      <TextBlock Text="{Binding Version, Mode=OneTime}" />
      
  • 適用場景:版本號,初始化數據,靜態信息

    • 優點是性能更高,因為不監聽變化

5.🌱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 並不是一種新的數據流模式

      • 它只是告訴 WPF:去使用該依賴屬性預設的默認綁定模式

6.示例代碼

  • 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
    

1.🌱多綁定(MultiBinding)

  • 一般的綁定只有一個數據源,如果我們想要多個數據源便無法實現,於是就有了多綁定

  • 多綁定實際上就是將 多個數據源合成一個值

    • # 一般的綁定 => 只有一個數據源
      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


2.🌱綁定更新(UpdateSourceTrigger)

  • 所謂的綁定更新,實際上就是考慮了一件事情:

    • UI 改變後什麼時候寫回數據源
  • 綁定更新(UpdateSourceTrigger)常用枚舉值

    • 説明
      PropertyChanged 目標屬性一變化就立刻更新
      LostFocus 當目標屬性發生變化,且失去焦點時才更新
      Explicit 手動觸發更新
      Default 自動檔
      大多數默認行為是PropertyChanged
      但是TextBox.Text屬性的默認行為是LostFocus

(1)PropertyChanged —— 實時更新

  • <TextBox Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}" />
    
    • 適用於:實時搜索,實時計算,實時過濾等

    • # 行為
      
      輸入一個字
      	↓
      立刻寫回 ViewModel
      

(2)LostFocus (TextBox 默認) —— 失去焦點才更新

  • <TextBox Text="{Binding UserName, UpdateSourceTrigger=LostFocus}" />
    
    • # 行為
      
      用户輸入
        ↓
      離開 TextBox
        ↓
      更新數據
      
      # 優點:減少更新次數
      

(3)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();
              }
          }
      }
      

(4)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 並不是一種新的數據流模式

      • 它只是告訴 WPF:去使用該依賴屬性預設的默認綁定更新的方式
    • 大多數屬性的默認行為都是PropertyChanged,但是也有例外:TextBox.Text 的默認更新方式是LostFocus


3.🌱延遲綁定 (Delay)

  • 有些 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可以白嫖.......


1.🌱Source(顯式數據源 —— 直接指定)

  • 這種數據源用最直接的方式告訴你,我們使用的是什麼數據源

    • [!WARNING]

      ❗特別注意

      • Source理論上可以用於 任何對象 和 任何屬性

        • 靜態對象,資源字典對象,普通對象,ObjectDataProviderx:Reference對象,代碼對象 等
      • Source 更多是 特殊情況的精確綁定工具,而不是主力綁定方式

      • # Source本質結構
        Binding
           │
           └─ Source = object 		# 一切對象的母親
                 │
                 └─ Path = 屬性
        
  • 這裏我們簡單介紹5種數據源,最後我會將五種數據源的整合代碼放出來

(1)靜態綁定 & 靜態資源綁定

  • <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

    我們添加一個額外的類,僅做演示

    • 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>
      

(3)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}}"/>
        

(4)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]

    ElementNamex:Reference的區別

    • <!-- ElementName -->
      <TextBlock Text="{Binding ElementName=slider, Path=Value}"/>
      <!-- x:Reference -->
      <TextBlock Text="{Binding Source={x:Reference slider}, Path=Value}"/>
      
    • 主要是底層機制不同

      • ElementNameWPF Binding 自帶功能,依賴 元素名字表(NameScope
      • x:ReferenceXAML 級別機制,它可以引用 任何帶 x:Key / x:Name 的對象

(5)整體代碼

  • // 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>
    

2.🌱RelativeSource(相對資源 —— 從視覺樹查找資源)

  • 我們先來翻譯一下這個單詞

    • Relative = 相對的,Source = 數據源
    • 所以直接翻譯就是相對資源
  • 換句話説:數據源不是外部對象,而是“和當前控件有關係的對象”

    • 這種關係通常來自 UI 樹(控件層級),WPF 的綁定系統會在控件樹裏找目標

    • 當前控件
         │
         └─ 向某個方向查找
              │
              └─ 找到對象
                   │
                   └─ 讀取屬性
                   
      # 他還是忘不了他的樹狀圖
      
  • RelativeSource 有四種模式,即:枚舉體RelativeSourceMode有4種數值

    Self
    FindAncestor
    TemplatedParent
    PreviousData
    

(1)Self — 綁定自己

  • # 這兩個綁一起了,然後永遠都是一個正方形了,永遠.....永遠.....(海綿寶寶口音)
    TextBox.Width
          ↓
    TextBox.Height
    
  • <TextBlock Height="50" Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"/>
    

(2)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}
      

(3)TemplatedParent — 模板父控件(綁定到應用模板的元素)

  • 先上翻譯:

    • Templated:模板
    • TemplatedParent:模板父母
  • 這是控件模板專用模式,當你寫 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>
      

(4)PreviousData — 綁定到數據綁定列表中(集合)的前一個數據項(很少使用)

  • 集合裏的上一個數據項

    • 常用於 數據對比,列表差值,趨勢計算
  • {Binding RelativeSource={RelativeSource PreviousData}}
    
  • 這裏偷個懶,就不做過多的解釋了,再解釋,我感覺我資料查不完了啊


3.DataContext(數據上下文)

  • 在講解數據上下文之前,我們先給出一個示例,來解釋這個東西到底是個啥

    • <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區域中默認綁定對象
      • 數據上下文在MVVM架構中使用的非常頻繁
    • 為什麼數據上下文只要是在上層控件就可以使用呢,因為它可以繼承

      • 即: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>
      

4.總結

非元素對象綁定
│
├─ 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


哦吼吼吼!終於寫完了,要死了要死了,最近這兩篇博客,感覺寫的實在是太久了,資料是越查越多,越查越多.....
多的讓我以為世界快完蛋了,雖然是用來學習的同時,打發一下摸魚時間
好吧,其實是打法摸魚的時候順便學一下架構.......

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.