WPF 的數據綁定不只是屬性同步,更是一套完整的視覺化管線:資料源 → 綁定引擎 → 模板擴充 → 視覺樹 → 渲染。掌握模板、集合視圖、分層綁定、驗證機制,才能構建專業級企業應用介面。
雖然 XAML 聲明式綁定優雅,但動態構建 UI、執行時決定綁定源、條件式綁定等場景需程式碼介入:
// 綁定 TextBox.Text 至資料物件屬性
static void BindText(TextBox textBox, string propertyPath)
{
var binding = new Binding(propertyPath);
BindingOperations.SetBinding(textBox, TextBox.TextProperty, binding);
}
// 解除綁定
static void UnbindText(TextBox textBox)
{
BindingOperations.ClearBinding(textBox, TextBox.TextProperty);
}
// 檢查驗證規則
static bool HasValidationRules(TextBox textBox)
{
var expr = textBox.GetBindingExpression(TextBox.TextProperty);
return expr?.ParentBinding.ValidationRules.Any() ?? false;
}
關鍵洞見:BindingExpression 維護源目標連結,Binding 僅為共享配置範本。
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Text="{Binding LastName}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding MiddleInitial}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
模板為每個資料項建構視覺樹,支援任意複雜 UI 組合(含 3D、動畫、互動控制項)。
DataContextCurrentItem 即可實現主從聯動// 取得預設視圖
var view = CollectionViewSource.GetDefaultView(customers);
// 同步選取:ListBox 需設定 IsSynchronizedWithCurrentItem="True"
view.MoveCurrentTo(selectedCustomer); // 程式碼控制選取
var current = view.CurrentItem as Customer;
// 排序
view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
// 篩選
view.Filter = obj => obj is Customer c && c.IsActive;
// 分組
view.GroupDescriptions.Add(new PropertyGroupDescription("City"));
<Grid DataContext="{Binding Source={StaticResource xmlData}, XPath=customers/customer}">
<!-- 客戶下拉選單 -->
<ComboBox IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate><TextBlock Text="{Binding XPath=@name}"/></DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- 訂單列表:綁定至當前客戶 -->
<ListBox x:Name="orders" DataContext="{Binding Path=CurrentItem}"
IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding XPath=order}">
<ListBox.ItemTemplate><DataTemplate><TextBlock Text="{Binding XPath=@desc}"/></DataTemplate></ListBox.ItemTemplate>
</ListBox>
<!-- 訂單明細:綁定至當前訂單 -->
<ItemsControl DataContext="{Binding ElementName=orders, Path=SelectedItem}"
ItemsSource="{Binding XPath=orderDetail}">
...
</ItemsControl>
</Grid>
利用 CurrentItem 巢狀路徑,無需 ElementName 綁定:
<ComboBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"/> <!-- 客戶 -->
<ListBox ItemsSource="{Binding CurrentItem.Orders}" IsSynchronizedWithCurrentItem="True"/> <!-- 訂單 -->
<ItemsControl ItemsSource="{Binding CurrentItem.Orders.CurrentItem.OrderDetails}"/> <!-- 明細 -->
業務物件覆寫 ToString() 可簡化顯示模板。
<Grid.Resources>
<!-- 明細模板(葉節點) -->
<DataTemplate x:Key="DetailTemplate">
<TextBlock><Run Text="Product:"/><TextBlock Text="{Binding Product}"/><Run Text="("/><TextBlock Text="{Binding Quantity}"/><Run Text=")"/></TextBlock>
</DataTemplate>
<!-- 訂單模板(中間節點) -->
<HierarchicalDataTemplate x:Key="OrderTemplate"
ItemsSource="{Binding OrderDetails}" ItemTemplate="{StaticResource DetailTemplate}">
<TextBlock Text="{Binding Desc}"/>
</HierarchicalDataTemplate>
<!-- 客戶模板(根節點) -->
<HierarchicalDataTemplate x:Key="CustomerTemplate"
ItemsSource="{Binding Orders}" ItemTemplate="{StaticResource OrderTemplate}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</Grid.Resources>
<TreeView ItemsSource="{Binding}" ItemTemplate="{StaticResource CustomerTemplate}"/>
宣告順序重要:葉節點模板 → 中間節點 → 根節點,使用 StaticResource 避免運行時開銷。
<TextBox.Text>
<Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule/> <!-- 捕獲轉換例外 -->
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
public class SmartEra : IDataErrorInfo
{
public DateTime StartDate { get; set; }
public TimeSpan Duration { get; set; }
public string Error => null; // 整體錯誤,較少用
public string this[string propertyName]
{
get
{
switch (propertyName)
{
case "StartDate":
if (StartDate > DateTime.Now) return "開始日期不能是未來";
break;
case "Duration":
if (Duration == TimeSpan.Zero) return "持續時間必須大於零";
break;
}
return null; // 無錯誤
}
}
}
<TextBox Text="{Binding Path=StartDate, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"/>
<!-- 或明確加入 DataErrorValidationRule -->
<Style TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Ellipse DockPanel.Dock="Right" Width="10" Height="10" Fill="Red"
ToolTip="資料無效" Margin="2,0"/>
<AdornedElementPlaceholder/> <!-- 佔位原控制項 -->
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip">
<Setter.Value>
<Binding Path="(Validation.Errors)[0].ErrorContent"
RelativeSource="{x:Static RelativeSource.Self}"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
WPF 數據綁定的強大在於其可組合性:綁定、模板、視圖、驗證、樣式各司其職,透過標準介面無縫協作。深入理解這些機制,能讓開發者用最少代碼表達最豐富的資料視覺化與交互邏輯,真正實現「關注點分離」與「聲明式 UI」的承諾。