WPF 將控件邏輯與視覺外觀徹底分離,通過 ControlTemplate 實現無代碼修改外觀。本文由淺入深,講解元素與控件差異、默認模板在 DLL 中的打包規範、Control 派生要點、代碼與模板交互協定 (TemplateBinding、Trigger、Command、Part)、以及模板替換實戰。
MeasureOverride/ArrangeOverride/OnRender 構建。Foreground/Background/Font* 等外觀屬性、IsTabStop/TabIndex (支援 Tab 導航)、核心屬性 Template (ControlTemplate)。視覺樹完全由 Template 定義,無需重寫渲染方法。控件用於「交互」。Content 中,默認模板固定 (Border + ContentPresenter),不建議替換模板,代碼與視覺樹緊耦合。複雜可替換控件請繼承 Control。ResourceDictionary)。generic.xaml 定義針對控件類型的 Style,Setter Property="Template" 設置 ControlTemplate。CalendarMonthStyle.xaml),通過 ResourceDictionary.MergedDictionaries 在 generic.xaml 中引用 (Pack URI: AssemblyName;Component/Themes/xxx.xaml)。DefaultStyleKey 元數據:DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]FrameworkElement.WidthProperty.AddOwner(typeof(MyControl)),避免重複定義。OverrideMetadata (低優先級) 或默認模板 Style 中的 Setter,禁止在構造函數賦值 (本地值優先級最高,阻斷樣式/繼承)。OverrideMetadata 註冊 PropertyChangedCallback。Background={TemplateBinding Background}),雙向同步。IsToday=true 觸發高亮)。RoutedCommand 字段,模板按鈕 Command="ns:ControlClass.CmdName" 綁定,構造函數 CommandBindings.Add 掛接執行邏輯,支援鍵勢 (InputGestures) 自動觸發。[TemplatePart(Name="PART_Panel", Type=typeof(Panel))],OnApplyTemplate 中 Template.FindName("PART_Panel", this) 獲取模板內關鍵元素 (面板、按鈕等),類型盡量寬泛 (Panel 而非 UniformGrid),找不到優雅降級。Style TargetType="cc:CalendarMonth" 完全重寫視覺樹 (如垂直列表 12 個月並排)。CalendarMonth.DayType 屬性指定日生成類型 (預設 CalendarDay),替換為 CalendarDayNotes (含備忘錄 TextBox) 或 MoonDisk (3D Viewport3D 模擬月相),無需修改月曆邏輯。CalendarDay 定義隱式 Style (TargetType="cc:CalendarDay") 設置邊框、對齊、字體,無需替換整個模板。完整示例代碼參考《Applications = Code + Markup》第 25 章及配套 DumpControlTemplate 工具提取標準控件默認模板學習。