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.Refresh();
    PresentationTraceSources.MarkupSource.Switch.Level = SourceLevels.All;
    PresentationTraceSources.MarkupSource.Listeners.Add(new DefaultTraceListener());
    ...
}




How to add a binding to the CommandParameter of a KeyBinding or MouseBinding

You can't add a binding to the command parameter of an InputBinding. Although InputBinding does derive from DependencyObject, the CommandParameter property is a CLR property. It was implemented in this way as InputBindings don't sit in an inheritance context and so normal bindings are not supported.

Fortunately, this can be worked around in the same way as in my previous post:
<Window.Resources>
    <my:DataResource x:Key="cp" BindingTarget="{Binding MyCommandParameter}"/>
</Window.Resources>
<TextBox>
    <TextBox.InputBindings>
        <MouseBinding 
            Command="ApplicationCommands.Open" 
            MouseAction="LeftDoubleClick">
            <MouseBinding.CommandParameter>
                <my:DataResourceBinding DataResource="{StaticResource cp}"/>
            </MouseBinding.CommandParameter>
        </MouseBinding>
    </TextBox.InputBindings>
</TextBox>



DataResource.cs
Download sample app with source code



How to add a binding to a property on a ValidationRule

You can't add bindings to properties on classes that derive from ValidationRule in the normal way. This is because validation rules don't sit in an inheritance context and don't derive from DependencyObject.

Fortunately, this can easily be worked around using the DataResource technique from a previous post. Here's how you would use this technique for max/min integer validation.

First create the rule class:
public class IntValidationRule : ValidationRule
{
    public int Max { get; set; }
    public int Min { get; set; }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        try
        {
            int i = Convert.ToInt32(value);
            return (i < Min || i > Max) ?
                new ValidationResult(false, "int out of range") :
                new ValidationResult(true, null);
        }
        catch (FormatException fe)
        {
            return new ValidationResult(false, fe.Message);
        }
    }
}
...the DataResources and the associated bindings should then simply be set up like this:
<Window.Resources>
    <my:DataResource x:Key="max" BindingTarget="{Binding Max}"/>
    <my:DataResource x:Key="min" BindingTarget="{Binding Min}"/>
</Window.Resources>

<TextBox>
    <TextBox.Text>
        <Binding UpdateSourceTrigger="PropertyChanged" Path="Value">
            <Binding.ValidationRules>
                <my:IntValidationRule 
                    ValidatesOnTargetUpdated="True" 
                    Min="{my:DataResourceBinding DataResource={StaticResource min}}"
                    Max="{my:DataResourceBinding DataResource={StaticResource max}}">
                </my:IntValidationRule>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>



DataResource.cs
Download sample app with source code



Friday, January 23, 2009

How to get the HWND and hook into the WndProc of a WPF application

I've written a quick sample which hooks into the WM_ messages and displays them real-time. It looks like this:



This is the code that does the work:

private void Window1_Loaded(object sender, RoutedEventArgs e)
{
    // do this to get the HWND and set up the window hook
    WindowInteropHelper helper = new WindowInteropHelper(this);
    HwndSource.FromHwnd(helper.Handle).AddHook(HwndSourceHookHandler);
}

private IntPtr HwndSourceHookHandler(IntPtr hwnd, int msg, IntPtr wParam, 
    IntPtr lParam, ref bool handled)
{
    // this is the window hook handler
}

Download sample app with source code

Thursday, January 22, 2009

How to debug triggers using Trigger-Tracing

Debugging triggers is a painful process: they work behind the scenes, there's nowhere to put a breakpoint and no call-stack to help you. The usual approach taken is trial and error based and it nearly always takes longer than it should to work out what's going wrong.

This post describes a new technique for debugging triggers allowing you to log all trigger actions along with the elements being acted upon:


It's good because it:
  • helps you fix all manner of problems :)
  • works on all types of trigger: Trigger, DataTrigger, MultiTrigger etc.
  • allows you to add breakpoints when any trigger is entered and/or exited
  • is easy to set up: just drop one source file (TriggerTracing.cs) into your app and set these attached properties to the trigger to be traced:
    <Trigger my:TriggerTracing.TriggerName="BoldWhenMouseIsOver"
             my:TriggerTracing.TraceEnabled="True"
             Property="IsMouseOver"
             Value="True">
        <Setter Property="FontWeight" Value="Bold"/>
    </Trigger>
    

It works by:
  • using attached properties to add dummy animation storyboards to the trigger
  • activating WPF animation tracing and filtering the results to only the entries with the dummy storyboards

Download sample app with source code



Friday, January 16, 2009

How to transfer rich text from a FlowDocument to a FormattedText object

The FormattedText class provides low-level control for drawing text. It gives you high performance multi-line text rendering in which each character in the text can be individually formatted.

Other aspects of text in WPF handle text in the context of controls that are dedicated to text (TextBlock, TextBox, RichTextBox), implement the FlowDocument model. As far as I can tell, there is no direct way to transfer text from a FlowDocument to a FormattedText object. This post outlines how it can be done with a bit of jiggery poker.

The tricky part of this process is traversing through the runs and paragraphs in the FlowDocument:
private IEnumerable<TextElement> GetRunsAndParagraphs(FlowDocument doc)
{
    // use the GetNextContextPosition method to iterate through the
    // FlowDocument
    
    for (TextPointer position = doc.ContentStart;
        position != null && position.CompareTo(doc.ContentEnd) <= 0;
        position = position.GetNextContextPosition(LogicalDirection.Forward))
    {
        if (position.GetPointerContext(LogicalDirection.Forward) == 
            TextPointerContext.ElementEnd)
        {
            // return solely the Runs and Paragraphs. all other elements are 
            // ignored since they aren't supported by FormattedText.
            
            Run run = position.Parent as Run;

            if (run != null)
            {
                yield return run;
            }
            else
            {
                Paragraph para = position.Parent as Paragraph;

                if (para != null)
                {
                    yield return para;
                }
            }
        }
    }
}
The rest of process is fairly straightforward. Use the GetRunsAndParagraphs method and build up the formatting on a FormattedText object:
public FormattedText GetFormattedText(FlowDocument doc)
{
    if (doc == null)
    {
        throw new ArgumentNullException("doc");
    }

    FormattedText output = new FormattedText(
        GetText(doc),
        CultureInfo.CurrentCulture,
        doc.FlowDirection,
        new Typeface(doc.FontFamily, doc.FontStyle, doc.FontWeight, doc.FontStretch),
        doc.FontSize,
        doc.Foreground);

    int offset = 0;

    foreach (TextElement el in GetRunsAndParagraphs(doc))
    {
        Run run = el as Run;

        if (run != null)
        {
            int count = run.Text.Length;

            output.SetFontFamily(run.FontFamily, offset, count);
            output.SetFontStyle(run.FontStyle, offset, count);
            output.SetFontWeight(run.FontWeight, offset, count);
            output.SetFontSize(run.FontSize, offset, count);
            output.SetForegroundBrush(run.Foreground, offset, count);
            output.SetFontStretch(run.FontStretch, offset, count);
            output.SetTextDecorations(run.TextDecorations, offset, count);

            offset += count;
        }
        else
        {
            offset += Environment.NewLine.Length;
        }
    }

    return output;
}

private string GetText(FlowDocument doc)
{
    StringBuilder sb = new StringBuilder();

    foreach (TextElement el in GetRunsAndParagraphs(doc))
    {
        Run run = el as Run;
        sb.Append(run == null ? Environment.NewLine : run.Text);
    }
    return sb.ToString();
}
You can download the source code and demo here.



Thursday, January 15, 2009

How to ensure a new instance of a resource is returned every time it is requested

There's a little known attribute named x:Shared for this purpose. It's little known since it doesn't appear in Intellisense for some reason. Add the attribute it to any item in a resource dictionary and set its value to False to ensure each request returns a new instance.

The value of the attribute is True by default. This means that any given resource request always returns the same instance by default.

This code snippet demonstrates its usage:
<Window.Resources>
    <ContextMenu x:Shared="False" x:Key="menu">
        <ContextMenu.Items>
            <MenuItem Header="A Menu Item"/>
        </ContextMenu.Items>
    </ContextMenu>
</Window.Resources>

<StackPanel>
    <Button Content="Button1" ContextMenu="{StaticResource menu}" Click="Btn_Click"/>
    <Button Content="Button2" ContextMenu="{StaticResource menu}" Click="Btn_Click"/>
</StackPanel>
private void Btn_Click(object sender, RoutedEventArgs e)
{
    Button b = (Button)sender;
    MenuItem item = (MenuItem)b.ContextMenu.Items[0];
    item.IsChecked = !item.IsChecked;
}

Visit the MSDN page for more info about this attribute.



Tuesday, January 13, 2009

How to intercept a copy or paste operation

I've just found these static methods on the System.Windows.DataObject class.
DataObject.AddPastingHandler(dependencyObject, handler);
DataObject.RemovePastingHandler(dependencyObject, handler);

DataObject.AddCopyingHandler(dependencyObject, handler);
DataObject.RemovePastingHandler(dependencyObject, handler);

DataObject.AddSettingDataHandler(dependencyObject, handler);
DataObject.RemoveSettingDataHandler(dependencyObject, handler);
Very useful!!

You can use these to inspect an object before it is pasted/copied/drag-dropped and change the course of the action by doing stuff like Cancel or Change Format.



Monday, January 12, 2009

How to assign a URL to one or more CLR namespaces

Look at the way the XML namespaces are defined in the snippet below. The 'local' definition is mapped directly to a CLR namespace in the normal way but the first two definitions are just URLs. What do thes URLs represent and where are they defined?
<Window x:Class="WpfLibrary.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfLibrary">
    
    <Window.Resources>
        <SolidColorBrush Color="AliceBlue"/>
    </Window.Resources>
    
    <TextBox/>
    
    
    
</Window>

The URLs have been setup to represent multiple CLR namespaces in the AssemblyInfo of the assembly in which they are defined. Notice that both the TextBox and SolidColorBrush classes are referenced using the default XML namespace even though the classes are actually defined in different CLR namespaces.

To find out which CLR namespaces are assigned to a particular URL then you can look at the assembly definition in Reflector.

Here's the syntax to set this up in the AssemblyInfo of your assembly:
[assembly:XmlnsDefinition("http://myorganization.com", "WpfLibrary")]
[assembly:XmlnsDefinition("http://myorganization.com", "WpfLibrary.Pens")]
[assembly:XmlnsDefinition("http://myorganization.com", "WpfLibrary.Brushes")]
Consumers of your assembly can then use the XML namespace to reference all of the associated CLR namespaces in one swoop:
<Window x:Class="WpfApplication1.Window1"
 xmlns:myorg="http://myorganization.com"
</Window>




Saturday, January 10, 2009

How to make the text of a CheckBox wrap across multiple lines

Just a quick one about how to make CheckBox text wrap across multiple lines while showing text accelerators correctly.

You might have noticed that a CheckBox doesn't have properties like TextWrapping and TextTrimming. This is because CheckBox derives from ContentControl and can therefore take any element as its content - it doesn't have to be a string like in the bad old WinForms days.

In order to add text wrapping, you could add a TextBlock to the Content of the CheckBox like this:
<CheckBox>
    <TextBlock TextWrapping="Wrap"
        Text="_This is a long piece of text attached to a checkbox."/>
</CheckBox>
There is a problem regarding accelerator keys here though. Look at the output from the last example:




...the accelerator isn't display correctly. TextBlocks don't support accelerator keys. In order to do this properly you should use an AccessText element to display your text:
<CheckBox>
    <AccessText TextWrapping="Wrap"
        Text="_This is a long piece of text attached to a checkbox."/>
</CheckBox>
This will ensure that the underscore is correctly displayed.