如何在后台代码中嵌入并解析XAML样式至自定义控件?
Question
I have a C++ program that uses Roslyn to parse C# code. I need to convert custom controls and their corresponding XAML styles into pure code-behind. Here's an example:
XAML Style
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CUSTOM_LIBRARY_PARSE"> <Style TargetType="{x:Type local:CustomControl1}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CustomControl1}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Button Background="#FF487DF0" > <Label VerticalContentAlignment="Center" HorizontalContentAlignment="Center" OpacityMask="#FFC3C3C3" Content="{Binding text_of_button_Value, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomControl1}}}" /> </Button> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
Control Code-Behind
using System; using System.Windows; using System.Windows.Controls; namespace CUSTOM_LIBRARY_PARSE { public class CustomControl1 : Control { public static readonly DependencyProperty text_of_button = DependencyProperty.Register( "text_of_button_Value", typeof(string), typeof(CustomControl1), new PropertyMetadata(Environment.UserName) ); public string text_of_button_Value { get { return (string)GetValue(text_of_button); } set { SetValue(text_of_button, value); } } static CustomControl1() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1))); } } }
I want to embed the above XAML code as a string into the code-behind, then parse this string with XamlParser and load it into CustomControl1. Is this approach feasible?
Answer
Absolutely, this approach is feasible—WPF's XamlReader (the standard API for runtime XAML parsing, which I assume you're referencing) fully supports parsing XAML from string inputs, making it straightforward to embed your style directly in code-behind. Here's how to implement it, plus key tips to avoid common pitfalls:
Step 1: Prepare the XAML string for C#
First, convert your raw XAML into a valid C# string. This requires:
- Replacing single double quotes (
") with escaped double quotes ("") - Using a verbatim string prefix (
@) to simplify handling line breaks and avoid extra escape characters for backslashes
Your embedded XAML string will look like this:
private const string CustomControl1StyleXaml = @" <ResourceDictionary xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:local=""clr-namespace:CUSTOM_LIBRARY_PARSE""> <Style TargetType=""{x:Type local:CustomControl1}""> <Setter Property=""Template""> <Setter.Value> <ControlTemplate TargetType=""{x:Type local:CustomControl1}""> <Border Background=""{TemplateBinding Background}"" BorderBrush=""{TemplateBinding BorderBrush}"" BorderThickness=""{TemplateBinding BorderThickness}""> <Button Background=""#FF487DF0"" > <Label VerticalContentAlignment=""Center"" HorizontalContentAlignment=""Center"" OpacityMask=""#FFC3C3C3"" Content=""{Binding text_of_button_Value, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomControl1}}}"" /> </Button> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>";
Step 2: Parse and load the XAML in your control
You can parse the string with XamlReader.Parse() and integrate the resulting resources into your application. For a custom control, loading the style in the static constructor ensures it's available when the control is initialized:
using System.Windows.Markup; static CustomControl1() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1))); try { // Parse the embedded XAML string into a ResourceDictionary var resourceDict = (ResourceDictionary)XamlReader.Parse(CustomControl1StyleXaml); // Merge the dictionary into app-level resources so it's accessible to all control instances Application.Current.Resources.MergedDictionaries.Add(resourceDict); } catch (XamlParseException ex) { // Handle invalid XAML gracefully (log errors, fall back to default style) System.Diagnostics.Debug.WriteLine($"Failed to parse control style: {ex.Message}"); } }
If you prefer to apply the style only to specific instances instead of globally, you can do this in the control's constructor:
public CustomControl1() { var resourceDict = (ResourceDictionary)XamlReader.Parse(CustomControl1StyleXaml); var controlStyle = (Style)resourceDict[typeof(CustomControl1)]; this.Style = controlStyle; }
Key Considerations
- Namespace Accuracy: Double-check that the
xmlns:localvalue in your embedded XAML exactly matches the namespace ofCustomControl1—mismatches will break the style binding. - XAML Feature Support: Most standard WPF XAML features (like template bindings, relative source bindings, and basic controls) work with
XamlReader.Parse(), but some compile-time features (likex:Codeor custom markup extensions with complex dependencies) may fail. Your example uses fully compatible features. - Runtime Overhead: Parsing XAML at runtime has a small performance cost compared to compiled BAML. For a handful of controls, this is unnoticeable, but for large-scale apps, you may want to balance this against generating pure code-behind UI elements via Roslyn.
- Error Handling: Always wrap
XamlReader.Parse()in a try-catch block to catch typos, invalid property names, or namespace errors—these will throwXamlParseExceptionat runtime.
Alternative: Generate Pure Code-Behind UI
Since you're already using Roslyn, another option is to generate C# code that creates the ControlTemplate, Border, Button, and other elements programmatically, instead of embedding a XAML string. This eliminates runtime parsing entirely but requires more complex code generation logic to replicate the XAML's structure and bindings.
内容的提问来源于stack exchange,提问作者Samuel Hardson

