Skip to main content

Referring to nested types in XAML

Anyone who has programmed using the WPF framework for a while eventually runs into its limitations. In my opinion, the framework is great overall; it offers many advantages over WinForms, and for little extra overhead. However, the limitation of {x:Type} to only reference classes that are direct descendants of a namespace was bothering me, so I made a markup extension to overcome the issue.

At the thousand-foot view, the WPF framework has many conveniences for the programmer. In particular, the ability to create arbitrary events and properties on objects really makes it easy for the interaction logic to closely match the implementation logic. Plus, forcing the MVVM pattern makes things a bit more consistent between projects and developers. But not allowing nested types to be referenced from XAML is the sort of nitty-gritty implementation detail that gets on my nerves.

I was able to create a new markup extension that provides this functionality:

using System;
using System.Windows.Markup;

namespace SomeNameSpace
{
     public class NestedTypeExtension : TypeExtension
     {
         public NestedTypeExtension() { }

         public NestedTypeExtension(string typeName) : base(typeName) { }

         public override object ProvideValue(IServiceProvider serviceProvider)
         {
             string[] types = TypeName.Split('.');
             IXamlTypeResolver resolver = (IXamlTypeResolver)serviceProvider.GetService(typeof(IXamlTypeResolver));
             if (resolver != null && types.Length > 0)
             {
                 Type t = resolver.Resolve(types[0]);
                 for (int i = 1; i < types.Length; i++)
                 {
                     t = t.GetNestedType(types[i]);
                 }
                 Type = t;
                 return t;
             }
             return null;
         }
     }
}

Then in XAML you can use the extension by adding the namespace to a parent element

<Element
    xmlns:sns="clr-namespace:SomeNameSpace;assembly=SomeAssembly"
    >
    ...

and referencing the markup extension with

<DataTemplate DataType="{sns:NestedType sns:MyClass.MyNestedClass}">
...
</DataTemplate>

Intellisense will still complain that nested types are not supported, but it will compile and run just fine. Unfortunately even with this, it is not possible to set x:Key of a ResourceDictionary, since that is only allowed to be exactly a string, TypeExtension, or StaticExtension (in XAML). So if you want to use a nested type as a key, I recommend giving the resource a fixed name and adding some code to the type's static constructor to assign the key:

namespace SomeNameSpace {
    class MyClass {
        class MyNestedClass {
            static MyNestedClass() {
                MyDictionary.Add(typeof(MyNestedClass), MyDictionary["MyNestedClassKeyName"]);
            }
        }
    }
}

For the purposes of organization, I set the key name to be the string representation of the nested class. For instance, the key name I would use for the example above would be "MyClass.MyNestedClass".

Remember that if you're trying to insert a datatemplate to be used for instances of the nested class, the key should be new DataTemplateKey(typeof(MyNestedClass)). This had me scratching my head for a little while until I figured it out.

Comments