Introduction
Usually, when we want to change the appearance of a control, we will change the control’s
template. However, most of the time, we will not create a template from scratch.
We get the control’s default template then edit it bit by bit until we obtain
the appearance that we want. To get the template, we need to use a tool like
Expression Blend. There are also some free standalone applications that can get
a control’s default style or template. With Visual Studio 2010, we can extend
the WPF designer so we can get the default style or template of a control right
within the IDE. Unfortunately, I wasn’t able to find a way how to do the same
thing with Visual Studio 2008.
Getting Started
Below is the link to the main WPF designer extensibility reference. I would recommend
reading this first for an overview and basic examples. You can see here what
other functionalities you can add to the WPF designer.
http://msdn.microsoft.com/en-us/library/bb546938(v=VS.100).aspx
Creating a Context Menu Item
First, we have to add a new item to the context menu of a control. Fist, create a
class library project. This automatically creates a class called Class1. Rename
this class to Metadata.
using System.Windows.Controls;
using Microsoft.Windows.Design.Features;
using Microsoft.Windows.Design.Metadata;
namespace GetTemplateExtension.VisualStudio.Design
{
// Container for any general design-time metadata to initialize.
// Designers look for a type in the design-time assembly that
// implements IProvideAttributeTable. If found, designers instantiate
// this class and access its AttributeTable property automatically.
internal class Metadata : IProvideAttributeTable
{
// Accessed by the designer to register any design-time metadata.
public AttributeTable AttributeTable
{
get
{
AttributeTableBuilder builder = new AttributeTableBuilder();
// Add the menu provider to the design-time metadata.
builder.AddCustomAttributes(typeof(Control),
new FeatureAttribute(typeof(GetTemplateContextMenuProvider)));
return builder.CreateTable();
}
}
}
}
Listing 1. Class for Registering Design-time Metadata
I got most of the codes here from the WPF designer extensibility reference. You can
read the inline comments to better understand what the Metadata class does. Basically,
we added a custom attribute for the Control type. This custom attribute is of
type FeatureAttribute, which is used for adding a design-time feature for the
specified type. The FeatureAttribute constructor takes in as an argument a type
that inherits from FeatureProvider. In our case, we created a new class called
GetTemplateContextMenuProvider, which is shown in the following code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using GetTemplateExtension.VisualStudio.Design.Dialogs;
using Microsoft.Windows.Design.Interaction;
using Microsoft.Windows.Design.Model;
namespace GetTemplateExtension.VisualStudio.Design
{
class GetTemplateContextMenuProvider : PrimarySelectionContextMenuProvider
{
private MenuAction getDefaultStyleMenuAction;
private MenuGroup getTemplateMenuGroup;
private ModelItem currentModelItem;
// The provider's constructor sets up the MenuAction objects
// and the the MenuGroup which holds them.
public GetTemplateContextMenuProvider()
{
getDefaultStyleMenuAction = new MenuAction("Get Default Style");
getDefaultStyleMenuAction.Execute += GetDefaultStyle_Execute;
getTemplateMenuGroup = new MenuGroup("GetTemplateGroup", "Get Template");
getTemplateMenuGroup.HasDropDown = true;
this.Items.Add(getDefaultStyleMenuAction);
this.Items.Add(getTemplateMenuGroup);
// The UpdateItemStatus event is raised immediately before
// this provider shows its tabs, which provides the opportunity
// to set states.
UpdateItemStatus += new EventHandler<MenuActionEventArgs>(CustomContextMenuProvider_UpdateItemStatus);
}
// The following method handles the UpdateItemStatus event.
void CustomContextMenuProvider_UpdateItemStatus(object sender, MenuActionEventArgs e)
{
ModelItem controlModelItem = e.Selection.PrimarySelection;
// Checking to prevent duplicate menu items, which happens when user
// switch to other window and back.
if (controlModelItem == currentModelItem)
return;
currentModelItem = controlModelItem;
// Get properties that are templates.
IEnumerable<PropertyInfo> templatePropertyInfos = controlModelItem.ItemType.GetProperties(
BindingFlags.Instance | BindingFlags.Public).Where(p => p.PropertyType.IsSubclassOf(typeof(FrameworkTemplate)));
foreach (PropertyInfo templatePropertyInfo in templatePropertyInfos)
{
MenuAction menuAction = new MenuAction(templatePropertyInfo.Name);
menuAction.Execute += GetTemplate_Execute;
getTemplateMenuGroup.Items.Add(menuAction);
}
}
// The following method handles the Execute event.
// It sets the Background property to its default value.
void GetDefaultStyle_Execute(object sender, MenuActionEventArgs e)
{
ModelItem controlModelItem = e.Selection.PrimarySelection;
Control control = (Control)controlModelItem.GetCurrentValue();
StyleWindow window = new StyleWindow(control);
window.ShowDialog();
}
// The following method handles the Execute event.
// It sets the Background property to its default value.
void GetTemplate_Execute(object sender, MenuActionEventArgs e)
{
ModelItem controlModelItem = e.Selection.PrimarySelection;
Control control = (Control)controlModelItem.GetCurrentValue();
MenuAction menuAction = (MenuAction)sender;
TemplateWindow window = new TemplateWindow(control, menuAction.DisplayName);
window.ShowDialog();
}
}
}
Listing 2. The Menu Provider
Our class inherits from PrimarySelectionContextMenuProvider. This base class provides
us a way to show a group of context menu items for the current selection. The
Items property, inherited from ContextMenuProvider, contains the MenuBase objects
that will be shown in the context menu. As you can see in the constructor in
Listing 2, we added to the Items collection one MenuAction object and one MenuGroup
object: “Get Default Style” and “Get Template.”
The “Get Default Style” MenuAction shows a window containing the selected control’s
default style. Meanwhile, the “Get Template” MenuGroup does not contain any MenuAction
object yet. We will fill up the MenuGroup with the selected control’s properties
that are templates. Since the selected control changes, we can only do this in
the UpdateItemStatus event handler. The UpdateItemStatus event is raised before
the context menu is shown. In the event handler, we can identify which properties
of the control derive from FrameworkTemplate.
Loading Design-Time Metadata
We now have our assembly that contains additional context menu items for WPF controls.
Our problem now is how to load the metadata in Visual Studio. Karl Shifflett
has great posts on this topic. These are found on the following links.
http://blogs.msdn.com/wpfsldesigner/archive/2010/01/13/wpf-silverlight-design-time-code-sharing-part-i.aspx
http://blogs.msdn.com/wpfsldesigner/archive/2010/01/13/loading-metadata-for-microsoft-controls.aspx
The first link details how to load design-time metadata for custom controls you authored.
Basically, there is a naming convention for an assembly containing the metadata,
and the assembly should be on the same folder (or Design sub-folder) as that
of the assembly containing the custom control. In our example, the assembly is
named GetTemplateExtension.VisualStudio.Design. Using the naming convention,
you can specify whether your metadata is available for Visual Studio, Expression
Blend, or both.
As you might remember, we did not create a custom control. We only added a custom
attribute for the Control type. Thus, we are more interested in the second link.
The link details how to load the metadata for existing controls like Button,
CheckBox, and ComboBox. This involves adding registry entries. The following
figure shows the registry entries used in our example.

Figure 1. Registry Entries Used in Loading Design-Time Metadata
These registry entries are located in HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\DesignerPlatforms\.NetFramework\Metadata\XamlDesigner\EditTemplateExtension.VisualStudio.Design,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null. To test if this works,
I added a window containing different controls in one of the projects in the
solution.

Figure 2. Loaded Context Menu Items
As you can see in the figure, we now have our custom menu items added to the WPF
designer context menu. The currently selected control is a ComboBox. When we
click on the “Get Template” menu, we will see that it has four public properties
that derive from FrameworkTemplate.
Showing the Default Style
When we click on the “Get Default Style” menu item, an instance of a StyleWindow
class will be shown. Its constructor accepts a parameter of type Control. When
the window is shown, it displays the default style of the control. Since we can’t
select all types of controls in the WPF designer, I decided to add a TreeView
containing all the types that derive from Control, and are found in the current
control’s assembly. Selecting another control type in the tree changes the displayed
style on the right.

Figure 3. Default Style Window
The control that I used to display the style is called AvalonEdit, a WPF Text Editor
by Daniel Grunwald. You can check his CodeProject article regarding AvalonEdit
here http://www.codeproject.com/KB/edit/AvalonEdit.aspx. The reason I used this
is because of its automatic support for XML syntax highlighting and code folding.
The following code listing shows the StyleWindow code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Highlighting;
namespace GetTemplateExtension.VisualStudio.Design.Dialogs
{
/// <summary>
/// Interaction logic for StyleWindow.xaml
/// </summary>
public partial class StyleWindow : Window
{
private Control control;
private FoldingManager foldingManager;
public StyleWindow(Control control)
{
InitializeComponent();
this.control = control;
// Set text editor hightlighting and code folding
textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML");
BuildTree();
}
private void BuildTree()
{
Assembly assembly = control.GetType().Assembly;
// Get public controls and have default constructors
IEnumerable<Type> controlTypes = assembly.GetTypes().Where(
t => t.IsSubclassOf(typeof(Control)) && t.IsPublic && t.GetConstructor(Type.EmptyTypes) != null);
// Get namespaces so we can build a tree like MSDN Library
List<string> namespaces = new List<string>();
foreach (Type controlType in controlTypes)
{
if (!namespaces.Contains(controlType.Namespace))
namespaces.Add(controlType.Namespace);
}
namespaces.Sort();
TreeViewItem prevTreeViewItem = null;
foreach (string controlNamespace in namespaces)
{
// Make the namespaces TreeViewItem controls.
TreeViewItem treeViewItem = new TreeViewItem();
treeViewItem.Header = controlNamespace;
// Find the place where to insert the TreeViewItem
TreeViewItem parentTreeViewItem = prevTreeViewItem;
while (parentTreeViewItem != null &&
!treeViewItem.Header.ToString().StartsWith(parentTreeViewItem.Header.ToString()))
{
parentTreeViewItem = parentTreeViewItem.Parent as TreeViewItem;
}
if (parentTreeViewItem != null)
parentTreeViewItem.Items.Add(treeViewItem);
else
controlsTreeView.Items.Add(treeViewItem);
prevTreeViewItem = treeViewItem;
// Get the controls that are in the current namespace and add them to the TreeViewItem
foreach (Type type in controlTypes.Where(ct => ct.Namespace == controlNamespace).OrderBy(t => t.Name))
{
TreeViewItem controlTreeViewItem = new TreeViewItem();
controlTreeViewItem.Header = type.Name;
controlTreeViewItem.Selected += ControlTreeViewItem_Selected;
treeViewItem.Items.Add(controlTreeViewItem);
if (control.GetType() == type)
{
controlTreeViewItem.IsSelected = true;
treeViewItem.ExpandSubtree();
}
}
}
}
private void ControlTreeViewItem_Selected(object sender, RoutedEventArgs e)
{
Assembly assembly = control.GetType().Assembly;
// make an instance of the type
TreeViewItem leaf = (TreeViewItem)sender;
TreeViewItem parent = (TreeViewItem)leaf.Parent;
Type type = assembly.GetType(parent.Header + "." + leaf.Header);
Title = type.AssemblyQualifiedName;
// get the type's default style key
FrameworkElement element = (FrameworkElement)Activator.CreateInstance(type);
PropertyInfo defaultStyleKeyPropertyInfo = element.GetType().GetProperty(
"DefaultStyleKey", BindingFlags.Instance | BindingFlags.NonPublic);
object defaultStyleKey = defaultStyleKeyPropertyInfo.GetValue(element, null);
// get the default style using the key
Style style = Application.Current.TryFindResource(defaultStyleKey) as Style;
try
{
textEditor.Text = XamlHelper.GetFormattedXaml(style);
}
catch (Exception ex)
{
textEditor.Text = ex.ToString();
}
UpdateTextEditorFolding();
}
private void UpdateTextEditorFolding()
{
if (foldingManager != null)
FoldingManager.Uninstall(foldingManager);
foldingManager = FoldingManager.Install(textEditor.TextArea);
XmlFoldingStrategy foldingStrategy = new XmlFoldingStrategy();
foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document);
}
}
}
Listing 3. StyleWindow Class
In the ControlTreeViewItem_Selected method, we create an instance of the type that
is currently selected in the tree. We then obtain the default style key using
reflection, as it is not a public member. To find the style, we call the TryFindResource
method of the Application object. After this, we can now convert the style into
XAML using the static GetFormattedXaml helper method. This method formats the
XAML by adding indentation and new lines on attributes.
using System.IO;
using System.Linq;
using System.Windows.Markup;
using System.Xml;
using System.Xml.Linq;
namespace GetTemplateExtension.VisualStudio.Design.Dialogs
{
static class XamlHelper
{
internal static string GetFormattedXaml(object obj)
{
string objXaml = XamlWriter.Save(obj);
XElement rootElement = XElement.Parse(objXaml);
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.NewLineOnAttributes = true;
settings.Indent = true;
settings.IndentChars = " ";
using (MemoryStream stream = new MemoryStream())
{
using (XmlWriter xmlWriter = XmlWriter.Create(stream, settings))
{
rootElement.WriteTo(xmlWriter);
}
stream.Flush();
stream.Position = 0;
StreamReader reader = new StreamReader(stream);
string formattedXaml = reader.ReadToEnd();
// attributes that are XML namespaces are not put on next line by default
foreach (XAttribute attribute in rootElement.Attributes().Where(a => a.IsNamespaceDeclaration))
{
int index = formattedXaml.IndexOf(attribute.ToString());
formattedXaml = formattedXaml.Insert(index, "\n ");
}
return formattedXaml;
}
}
}
}
Listing 4. XAML Formatting
I put this method in a separate helper class because this is also used in the TemplateWindow,
the window responsible for showing a control’s template.
Showing a Template
There’s not much difference between the StyleWindow and TemplateWindow. There’s also
a tree on the left side, but it displays the current control’s public properties
that derive from FrameworkTemplate. Clicking on an item shows the template on
the right side.

Figure 4. Template Window
When a template is currently not set, then the text editor will display the string
“Template is not set.” The following code shows the TemplateWindow class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Highlighting;
namespace GetTemplateExtension.VisualStudio.Design.Dialogs
{
/// <summary>
/// Interaction logic for TemplateWindow.xaml
/// </summary>
public partial class TemplateWindow : Window
{
private Control control;
private string templateName;
private FoldingManager foldingManager;
public TemplateWindow(Control control, string templateName)
{
InitializeComponent();
this.control = control;
this.templateName = templateName;
textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML");
BuildTree();
}
private void BuildTree()
{
// Get properties that are templates.
IEnumerable<PropertyInfo> templatePropertyInfos = control.GetType().GetProperties(
BindingFlags.Instance | BindingFlags.Public).Where(p => p.PropertyType.IsSubclassOf(typeof(FrameworkTemplate)));
foreach (PropertyInfo templatePropertyInfo in templatePropertyInfos)
{
TreeViewItem templateTreeViewItem = new TreeViewItem();
templateTreeViewItem.Header = templatePropertyInfo.Name;
templateTreeViewItem.Selected += TemplateTreeViewItem_Selected;
templatesTreeView.Items.Add(templateTreeViewItem);
if (templatePropertyInfo.Name == templateName)
templateTreeViewItem.IsSelected = true;
}
}
private void TemplateTreeViewItem_Selected(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItem = (TreeViewItem)sender;
string templateName = (string)treeViewItem.Header;
Title = control.GetType().AssemblyQualifiedName + " [" + templateName + "]";
PropertyInfo templatePropertyInfo = control.GetType().GetProperty(
templateName, BindingFlags.Instance | BindingFlags.Public);
object template = templatePropertyInfo.GetValue(control, null);
if (template != null)
{
try
{
textEditor.Text = XamlHelper.GetFormattedXaml(template);
}
catch (Exception ex)
{
textEditor.Text = ex.ToString();
}
}
else
{
textEditor.Text = "Template is not set.";
}
UpdateTextEditorFolding();
}
private void UpdateTextEditorFolding()
{
if (foldingManager != null)
FoldingManager.Uninstall(foldingManager);
foldingManager = FoldingManager.Install(textEditor.TextArea);
XmlFoldingStrategy foldingStrategy = new XmlFoldingStrategy();
foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document);
}
}
}
Listing 5. TemplateWindow Class
You can download the solution here. Don’t forget to set the registry values needed to load the metadata.