Monday, February 9, 2009

How to use the controls from NavigationWindow in your own Window

I've made a control containing back and forward buttons and a history menu in the style of IE 7. The XAML has been lifted directly from the WPF NavigationWindow template and converted to a control so it can be used in the normal way.

Here's a screenshot of the controls I am talking about (magnified 2x here):

These are the step I went through to do this:

  • A new Custom Control was created named NavigationControl. This was changed to derive from System.Windows.Controls.Selector as the control will contain a list of items of which one can be selected.
  • The BAML Viewer plugin for Reflector was used to extract the default style for the Navigation Window. The template XAML was copied and pasted to the template for NavigationControl.
  • The various components were extracted out into separate resource dictionaries for the different components to aid readability.
  • The button XAML was repeated for the two buttons. This was converted into one and a trigger was added to switch between the two.
  • A class called ThemedResourceDictionarySelector was created to assist in defining the themed parts of the template. This meant that the entire template did not need to be repeated for each theme.
  • Code behind was added so that the menu was correctly populated when the items of NavigationControl changed.

Download source code

Friday, January 30, 2009

How to recreate the Microsoft Office Access welcome page in XAML

I've recreated the Microsoft Office Access welcome/home page using XAML.

This is the kind of thing that WPF is so great for...and is a complete nightmare to do in WinForms. You can download the source using the link at the bottom of the post.

Click here for the full screen shot.

Download source code

Wednesday, January 28, 2009

How to add custom data to an AutomationPeer

In a recent project, we needed to provide extra data through Automation in order to achieve more rigorous testing. We wanted some of our own properties and data types unrelated to the available control patterns and properties.

After spending some time searching for a mechanism to add new properties we came to the conclusion that there wasn't one - the only choice is to hijack an existing property. As far as I can tell the only automation property you're allowed to put whatever you want in is ItemStatus.

The MSDN page for ItemStatus says:
  • Gets or sets a description of the status of an item within an element.
  • This property enables a client to ascertain whether an element is conveying status about an item. For example, an item associated with a contact in a messaging application might be "Busy" or "Connected".
That sounds pretty hijackable to me.

ItemStatus is hidden by default in UISpy but you can show it by going to:
View -> Configure Properties -> AutomationElement -> Misc

You set it in your automation peer by overriding GetItemStatusCore(). You can then serialize any type and send it through:
public class Window1AutomationPeer : WindowAutomationPeer
    public Window1AutomationPeer(Window1 owner)
        : base(owner)

    protected override string GetItemStatusCore()
        // use XmlSerializer to serialize custom data
        XmlSerializer serializer = new XmlSerializer(typeof(CustomData));
        CustomData data = new CustomData(...);
        StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
        serializer.Serialize(stringWriter, data);
        return stringWriter.ToString();

The example contains a working example complete with app, automation-client and XML serialization.

Tuesday, January 27, 2009

How to find out what that XamlParseException really means using MarkupSource tracing

It's sometimes pretty difficult to work out what the XamlParseException thrown when your app starts really means. WPF Markup tracing logs everything in the output window as the XAML is loaded - this nearly always gets you to the source of the problem.

You can turn it on like this:
public Window1()
    PresentationTraceSources.MarkupSource.Switch.Level = SourceLevels.All;
    PresentationTraceSources.MarkupSource.Listeners.Add(new DefaultTraceListener());