Search code examples
c#wpfxamlxamlparseexceptionxamlreader

How to use inline XAML in C# to easily make custom objects in code-behind


I'm trying to reference a LinearGradientBrush with the XAML parser, but it can't identify the object and I get the exception:

"Cannot create unknown type 'LinearGradientBrush'"

Is it possible to make this type recognized at runtime?

Here is the code I'm using:

public static class CustomBrushes
{
    public static Brush LinGrad_Bevel()
    {
        
        
        StringReader sr = new StringReader(@"<LinearGradientBrush EndPoint='0.5,1' MappingMode='RelativeToBoundingBox' StartPoint='0.5,0'>
                <GradientStop Color='#00F7F7F7' Offset='0'/>
                <GradientStop Offset='1'/>
                <GradientStop Color='Black' Offset='0.741'/>
                <GradientStop Color='Black' Offset='0.75'/>
                <GradientStop Color='White' Offset='0.25'/>
            </LinearGradientBrush>");
        XmlReader xr = XmlReader.Create(sr);


        return (Brush)XamlReader.Load(xr);
    }
}

I really don't like programing in XAML (especially because most of what I do is design dependent on runtime program flow, but some objects are just way easier to prototype in it than C# and I'd prefer to be able to employ this method...

I've read I'm supposed to include a line like this somewhere, but honestly I don't understand why and it doesn't seem to work if I stick it under all the "usings"

[assembly: XmlnsDefinition("http://schemas.microsoft.com/netfx/2007/xaml/presentation" , "System.Windows.Media")]

Anyway, any help with runtime parsing of XAML in C# code files would be appreciated.


Solution

  • To answer your specific question, the namespace declarations need to be provided when the XAML is being compiled. The easiest way to do that is to just insert them in your string. For example:

    StringReader sr = new StringReader(@"
        <LinearGradientBrush
            xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
            xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
            EndPoint='0.5,1' MappingMode='RelativeToBoundingBox' StartPoint='0.5,0'>
        <GradientStop Color='#00F7F7F7' Offset='0'/>
        <GradientStop Offset='1'/>
        <GradientStop Color='Black' Offset='0.741'/>
        <GradientStop Color='Black' Offset='0.75'/>
        <GradientStop Color='White' Offset='0.25'/>
    </LinearGradientBrush>");
    XmlReader xr = XmlReader.Create(sr);
    
    return (Brush)XamlReader.Load(xr);
    

    Now, that said, I would say that the above seems to me to be the least preferable way to go about this. If you want a reusable Brush object and you want to use XAML to declare it instead of going through the actual WPF programmatic API, I think a better way to do it is to put the declaration in one of the related Resources collections in your program.

    For example, if you want to be able to use it throughout your program, in a number of different windows, you might make it a global resource by putting it into your App.xaml file:

    <Application x:Class="TestSO28999367XamlReader.App"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 StartupUri="MainWindow.xaml">
      <Application.Resources>
        <LinearGradientBrush x:Key="beveledLinearGradientBrush"
                        EndPoint='0.5,1' MappingMode='RelativeToBoundingBox' StartPoint='0.5,0'>
          <GradientStop Color='#00F7F7F7' Offset='0'/>
          <GradientStop Offset='1'/>
          <GradientStop Color='Black' Offset='0.741'/>
          <GradientStop Color='Black' Offset='0.75'/>
          <GradientStop Color='White' Offset='0.25'/>
        </LinearGradientBrush>
      </Application.Resources>
    </Application>
    

    If your use would be limited to just one Window object or even some sub-element of that, you can add the declaration in the Resources collection for that object instead of putting it in App.xaml.

    Regardless, then you can access it with this expression:

    (Brush)FindResource("beveledLinearGradientBrush")
    

    Of course, you can make the key for the resource anything you want…it doesn't have to be beveledLinearGradientBrush.

    You can still use that to initialize some code-behind resource, by using the above C# expression to retrieve the object. However, note that when you do it the above way, the brush also becomes available for direct use in any XAML, via the expression "{StaticResource beveledLinearGradientBrush}".

    For example:

    <Border Background="{StaticResource beveledLinearGradientBrush}"/>