雖然 XAML 是 WPF 定義樣式/樣板的標準方式(設計師友好、關注點分離、熱重載),但在以下場景**代碼動態建構**更具彈性:
核心類別:**`FrameworkElementFactory`**(已標記過時但仍可用)、**`ControlTemplate`**、視覺樹組合。
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
public class DynamicTemplateDemo
{
public static Button CreateCustomButton()
{
// 1. 子元素:Image
var imgFactory = new FrameworkElementFactory(typeof(Image), "PART_Image");
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri("pack://application:,,,/Resources/Background.png", UriKind.Absolute);
bitmap.EndInit();
imgFactory.SetValue(Image.SourceProperty, bitmap);
imgFactory.SetValue(Image.StretchProperty, Stretch.UniformToFill);
// 2. 子元素:TextBox
var txtFactory = new FrameworkElementFactory(typeof(TextBox), "PART_TextBox");
txtFactory.SetValue(TextBox.WidthProperty, 200.0);
txtFactory.SetValue(TextBox.HeightProperty, 30.0);
txtFactory.SetValue(TextBox.VerticalContentAlignmentProperty, VerticalAlignment.Center);
txtFactory.SetValue(TextBox.MarginProperty, new Thickness(10, 0, 0, 0));
// 3. 容器:Grid 佈局
var gridFactory = new FrameworkElementFactory(typeof(Grid), "RootGrid");
// 定義欄位
var col1 = new FrameworkElementFactory(typeof(ColumnDefinition));
col1.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto));
var col2 = new FrameworkElementFactory(typeof(ColumnDefinition));
col2.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Star));
gridFactory.AppendChild(col1);
gridFactory.AppendChild(col2);
// 將子元素放入 Grid 對應欄位
imgFactory.SetValue(Grid.ColumnProperty, 0);
txtFactory.SetValue(Grid.ColumnProperty, 1);
gridFactory.AppendChild(imgFactory);
gridFactory.AppendChild(txtFactory);
// 4. 建立 ControlTemplate 目標型別為 Button
var template = new ControlTemplate(typeof(Button))
{
VisualTree = gridFactory
};
// 5. 可選:加入 Trigger(滑鼠進入變色等)——代碼較繁瑣,建議仍用 XAML 定義觸發器
// template.Triggers.Add(...);
// 6. 套用
var btn = new Button
{
Template = template,
Width = 320,
Height = 60
};
return btn;
}
}
| 需求 | 代碼實現方式 |
|---|---|
| **綁定** (`{Binding Prop}`) | `factory.SetValue(TextBox.TextProperty, new Binding("Prop") { Mode = BindingMode.TwoWay });` |
| **動態資源** (`{DynamicResource Key}`) | `factory.SetValue(Control.BackgroundProperty, new DynamicResourceExtension("MyBrushKey"));` |
| **靜態資源** (`{StaticResource Key}`) | `factory.SetValue(Control.BackgroundProperty, Application.Current.FindResource("MyBrushKey"));` (需在資源載入後) |
| **觸發器** (`Trigger`/`DataTrigger`/`EventTrigger`) | `var trigger = new Trigger { Property = UIElement.IsMouseOverProperty, Value = true };` `trigger.Setters.Add(new Setter(Control.BackgroundProperty, Brushes.LightBlue));` `template.Triggers.Add(trigger);` |
| **視覺狀態** (`VisualStateManager`) | 代碼建構極其複雜,**強烈建議用 XAML** 定義 `VisualStateGroups`,代碼僅負責結構組合。 |
public ControlTemplate LoadTemplateFromString(string xaml)
{
// 必須包含命名空間宣告
string fullXaml = $@"
{xaml}
";
return (ControlTemplate)System.Windows.Markup.XamlReader.Parse(fullXaml);
}
// 使用
string dynamicXaml = @"
";
var template = LoadTemplateFromString(dynamicXaml);
button.Template = template;
優點:支援完整 XAML 功能(觸發器、動畫、綁定、VisualState)、字串易維護/產生、無過時警告。缺點:運行時解析開銷(可快取)。
`FrameworkElementFactory` 是理解 WPF 視覺樹組合原理的好工具,但**實務開發首選 `XamlReader.Parse` 動態載入 XAML 字串**——功能完整、語法熟悉、維護性高。僅在極簡結構、極度追求無 XAML 解析依賴的特殊場景才手寫 Factory。掌握兩種方式,按需選擇。