首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

WPF C# 代碼動態建立 ControlTemplate:FrameworkElementFactory 實戰指南

     編輯:關於C語言

前言:為什麼用 C# 代碼建 ControlTemplate?

雖然 XAML 是 WPF 定義樣式/樣板的標準方式(設計師友好、關注點分離、熱重載),但在以下場景**代碼動態建構**更具彈性:

  • 樣板結構需**根據資料/邏輯動態決定**(如不同角色顯示不同按鈕組合)。
  • **程式碼產生器**輸出樣板(避免龐大 XAML 字串拼接)。
  • **單元測試**驗證視覺樹結構,無需載入 XAML 解析器。
  • **效能極致場景**避免 XAML 解析開銷(極少見)。

核心類別:**`FrameworkElementFactory`**(已標記過時但仍可用)、**`ControlTemplate`**、視覺樹組合。

核心步驟

  1. 建立子元素 Factory(`new FrameworkElementFactory(typeof(ElementType), "name")`)。
  2. 設定屬性(`factory.SetValue(Property, value)`)。
  3. 建立容器 Factory(如 `Grid`, `StackPanel`),`AppendChild` 組合視覺樹。
  4. 實例化 `ControlTemplate(targetType)`,指派 `VisualTree = rootFactory`。
  5. 套用至控制項:`control.Template = template`。

完整範例:按鈕樣板 = 圖片 + 文字輸入框

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`,代碼僅負責結構組合。

避坑指南

  • ❌ **`FrameworkElementFactory` 已標記 `[Obsolete]`** — 官方推薦用 `FrameworkElementFactory` 替代方案:
      **`XamlReader.Parse(xamlString)`** 動態載入 XAML 字串(支援完整 XAML 功能、觸發器、動畫、綁定語法),且效能可接受。
      範例:`var template = (ControlTemplate)XamlReader.Parse(@"...");`
  • ❌ **共用 Factory 實例** — 每個 `ControlTemplate` 需**獨立的 Factory 樹**,不可跨 Template/控制項重用同一 Factory 實例,否則視覺樹衝突導致渲染異常。
  • ❌ **屬性值型別不符** — `SetValue` 需傳入正確的 `DependencyProperty` 型別(如 `GridLength`、`Thickness`、`Brush`),不可直接傳 `double`/`string` 給非字串屬性。
  • ✅ **`NameScope` 註冊** — 若樣板內元素需透過 `Template.FindName` 取得,務必在 Factory 設定 `x:Name`(第二個建構函數參數),並確保 `ControlTemplate` 建立後註冊名稱範圍(通常自動處理)。

現代替代方案:`XamlReader.Parse` 動態 XAML(推薦)

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。掌握兩種方式,按需選擇。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved