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等,通過
Path或XPath指定 - 數據上下文(DataContext):未顯式指定源時,從父元素繼承
- 模式(Mode):
OneWay:源 → 目標TwoWay:雙向同步OneWayToSource:目標 → 源OneTime:初始化一次
- 觸發器(UpdateSourceTrigger):如
PropertyChanged、LostFocus控制何時更新源
集合綁定與視圖
- 綁定到集合時使用
ItemsSource:
<ListBox ItemsSource="{Binding MyItems}" />- 若需排序、篩選、分組,可用
CollectionViewSource:
<CollectionViewSource x:Key="view" Source="{Binding MyItems}" /> <ListBox ItemsSource="{Binding Source={StaticResource view}}" />數據轉換與驗證
- 類型不匹配時可實現
IValueConverter轉換值 - 可通過
ValidationRule添加驗證邏輯,並結合ErrorTemplate提供視覺反饋
注意事項
- 源對象應實現
INotifyPropertyChanged,集合應實現INotifyCollectionChanged以支持動態更新 - 合理選擇綁定模式和觸發器可優化性能與交互體驗
這樣,WPF 數據綁定不僅能減少手動更新 UI 的代碼量,還能保持業務邏輯與界面的清晰分離
- 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 OneWayToSourceUI → 數據源 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理論上可以用於 任何對象 和 任何屬性- 靜態對象,資源字典對象,普通對象,
ObjectDataProvider,x: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:Referencex:Reference這個東西其實是 XAML 世界裏的“指針”- 作用:拿到某個已經存在的對象實例,然後當作 綁定的資源
- 和常見的
ElementName很像,但機制不一樣
- 和常見的
x:Reference屬於 XAML 標記擴展,來自XAML體系
-
x:Reference → 找到某個對象實例 Binding → 從這個實例讀取屬性 # 數據流 對象實例 ↓ Binding ↓ 目標屬性 -
這裏我們使用一個滑塊,讓
TextBlock實時顯示它的值 -
<Window x:Class="Non_element_binding.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Non_element_binding" xmlns:sys="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <StackPanel> <!-- 5.標記擴展 --> <Slider x:Name="slider" Value="50" Minimum="0" Maximum="100"/> <TextBlock Text="{Binding Source={x:Reference slider}, Path=Value}" FontSize="30" HorizontalAlignment="Center"/> </StackPanel> </Grid> </Window> -
[!IMPORTANT]
ElementName和x:Reference的區別-
<!-- ElementName --> <TextBlock Text="{Binding ElementName=slider, Path=Value}"/> <!-- x:Reference --> <TextBlock Text="{Binding Source={x:Reference slider}, Path=Value}"/> -
主要是底層機制不同
ElementName是WPF Binding自帶功能,依賴 元素名字表(NameScope)x:Reference是XAML級別機制,它可以引用 任何帶x:Key / x:Name的對象
-
(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
哦吼吼吼!終於寫完了,要死了要死了,最近這兩篇博客,感覺寫的實在是太久了,資料是越查越多,越查越多.....
多的讓我以為世界快完蛋了,雖然是用來學習的同時,打發一下摸魚時間
好吧,其實是打法摸魚的時候順便學一下架構.......