TabletPC Development Gotchas Part1: Handling exceptions in Winforms Ink events

Over the past few years of developing apps using the TabletPC platform, I came across a couple of gotchas that are not exactly obvious to solve or workaround (at least they were not obvious to me). I figured I should blog about those and the respective solutions or workarounds to save others some head-scratching.

Here is the first one - it applies to the Microsoft.Ink assembly for developing inking apps using Winforms. Consider the following trivial app:

using System;

using System.Drawing;

using System.Windows.Forms;

using Microsoft.Ink;

class Program : Form

{

    private InkOverlay _inkOverlay;

    public Program()

    {

        _inkOverlay = new InkOverlay(this);

        _inkOverlay.Enabled = true;

        _inkOverlay.Stroke += new InkCollectorStrokeEventHandler(_inkOverlay_Stroke);

    }

    void _inkOverlay_Stroke(object sender, InkCollectorStrokeEventArgs e)

    {

        int foo = 2;

        int bar = 5 / (2 - foo);

        e.Stroke.DrawingAttributes.Color = Color.Red;

        this.Invalidate();

    }

    static void Main(string[] args)

    {

       Application.Run(new Program());

    }

}

The goal of the app is that each new stroke turns red once the stroke has been completed. However, when you run the app, this doesn't actually happen. The app works, but the ink remains in the default color (black). If you set a breakpoint on the line that sets the stroke's color to red, the breakpoint never gets hit. If you set a breakpoint at the top of the Stroke handler and step through the code, you will notice that the code execution returns to the app just before the color would be set.

Why is that? Well, in this simple example it's easy to see that there is a bug (division by zero). But why isn't the exception reported to us as an unhandled exception? The answer is not that obvious. It is due to the fact that the Microsoft.Ink assembly is really just a managed wrapper around a COM library (InkObj.dll). Take a look at the callstack when the event handler gets hit and you will see that your handler is being called from native code. So the unhandled, managed DivideByZero exception bubbles up the stack and eventually gets eaten by the native code that sits upstream from your event handler.

Now this behavior can result in some serious headscratching for the application developer. In most real-world cases, the bug is not as easy to spot as the one in my simplistic example. To avoid getting into these kind of issues, I recommend that you put your ink event handling code in try/catch blocks, so you can respond to exceptions thrown by your code, before they get eaten by the native/managed interop. Something along the lines of:

void _inkOverlay_Stroke(object sender, InkCollectorStrokeEventArgs e)

{

    try

    {

        int foo = 2;

        int bar = 5 / (2 - foo);

        e.Stroke.DrawingAttributes.Color = Color.Red;

        this.Invalidate();

    }

    catch (Exception exc)

    {

        MessageBox.Show(exc.ToString());

    }

}

 

With this, the MessageBox will now tell you that there was a DivideByZero exception, and it will tell you where it happened.

Next post in this series: TabletPC Development Gotchas Part2: Deploying InkPicture applications to Windows XP desktops

Comments

  • Anonymous
    November 02, 2007
    If you are developing applications that use an InkPicture control and you plan to deploy your product

  • Anonymous
    November 06, 2007
    I work in Outlook a lot and this issue comes up every now and then. I have a helper class called TraceEx that I use to do all my trace writing. So whenever I handle an event that comes from some external source such as an Outlook event or an ink event, I use the following template. Now I do this because some unhandled exceptions break things in Outlook. This might not apply to the ink stuff, but anyway: void SomeExternalObject_SomeEvent(object sender, EventArgs e) { try {   TraceEx.MethodEnter(sender, e);   // Event handler code goes here }  // try catch ( Exception ex ) {   TraceEx.TraceError(ex); }  // catch finally {   TraceEx.MethodExit(sender, e); }  // finally } This way, in my trace output, I can not only see that the exception happened, but also see where it happened. When dealing with COM interop, stack frames are often lost due to the native/managed transitions and the above code snippet helps me see where in a nested event scenario I might be.

  • Anonymous
    November 07, 2007
    Awesome - Thanks Josh!

  • Anonymous
    April 04, 2008
    Hi, When I try the above code sample, I keep getting a message "the type or namespace name 'Ink' does not exist in the namespace 'Microsoft' (are you missing an assembly reference?). Ready made project samples I've downloaded from MS handle 'using Microsoft.Ink' with no problem, but it just doesn't build when I create the project from scratch. Any help would be much appreciated. Thanks