Note: This has been rewritten to follow a much better approach that I found shortly after first posting.
I recently had the opportunity to watch The .NET Show's latest installment, all about programming for the Tablet PC on .NET. The ease with which they added Ink support to an application in the show inspired me to try it for myself. I started by checking the internet for samples (as you do :) ), and found the Tablet PC Developer website.
On that website there are a large number of samples available, from serializing Ink to adding Ink support to a scrollable picturebox. One in particular caught my eye: "Smoothing Ink". Basically, the author suggests you use DrawingAttributes.FitToCurve for smoothing and some Win32 interop to get a double buffered effect. This technique seems to be necessary if you are trying to draw to a bitmap object, but is overkill if you just want to draw to a control.
I've decided to take a different approach. I've inherited from Panel and used the .NET Framework's built in support for double buffering (quite easy with InkOverlay's AutoRedraw property set to false). Originally I had a great deal of trouble supporting the Panel's built in scrolling, but soon after posting I found a better way. Every time the control is painted I simply check to see if the scroll position has moved and update the InkOverlay accordingly, which is much better than detecting the scrolling as I was doing originally. Here's the code:
using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.Ink;
/// <summary>
/// Represents a standard <see cref="Panel"/> with Ink
/// functionality.
/// </summary>
/// <remarks>
/// Probably requires the Tablet PC SDK to compile. Almost
/// certainly requires either Windows XP Tablet edition or
/// the redistributable runtime (you have this if you did
/// Windows Update and installed the Journal Viewer) in
/// order to run correctly.
/// </remarks>
public class InkPanel : Panel
{
/// <summary>
/// Initializes a new instance of the <see cref="InkPanel"/>
/// class.
/// </summary>
public InkPanel()
{
// Make sure we are double buffering or ink flickers
SetStyle( ControlStyles.AllPaintingInWmPaint, true );
SetStyle( ControlStyles.DoubleBuffer, true );
SetStyle( ControlStyles.UserPaint, true );
SetStyle( ControlStyles.ResizeRedraw, true );
UpdateStyles();
ink = new InkOverlay( Handle );
ink.Stroke += new InkCollectorStrokeEventHandler( Stroke );
// Setting this property to true enables smoothing
ink.DefaultDrawingAttributes.FitToCurve = true;
// Setting this property to false means we have to do
// all redrawing of the ink ourselves in OnPaint
ink.AutoRedraw = false;
ink.Enabled = true;
}
/// <summary>
/// Overrides <see cref="Control"/>.Dispose.
/// </summary>
protected override void Dispose( bool disposing )
{
// InkOverlay is managed, so only dispose if necessary
if ( disposing )
ink.Dispose();
}
/// <summary>
/// Overrides <see cref="Control"/>.OnPaint.
/// </summary>
protected override void OnPaint( PaintEventArgs e )
{
base.OnPaint( e );
// Adjust for any change in the view position
HandleScroll();
// Doing all our redrawing here enables us to take
// full advantage of the automatic double buffering
ink.Renderer.Draw( e.Graphics, ink.Ink.Strokes );
}
// Deal with any scrolling messages
private void HandleScroll()
{
// We can be called without actually having scrolled
if ( oldScrollPos == AutoScrollPosition )
return;
// These get altered, so take copies
Point oldPos = oldScrollPos;
Point newPos = AutoScrollPosition;
using( Graphics g = CreateGraphics() )
{
// Transform pixels to more useful units
ink.Renderer.PixelToInkSpace( g, ref oldPos );
ink.Renderer.PixelToInkSpace( g, ref newPos );
// Move the ink to match the new scroll position.
// Notice that newPos and oldPos are negative, so
// this order is required to get the right result
ink.Renderer.Move( newPos.X - oldPos.X,
newPos.Y - oldPos.Y );
}
// Must update our old position
oldScrollPos = AutoScrollPosition;
}
// The event that this method handles is raised whenever
// a stroke is completed (the pen leaves the surface)
private void Stroke( object sender,
InkCollectorStrokeEventArgs e )
{
// Redraw the ink so it is smoothed immediately,
// not on the first update as is the default
Invalidate();
}
// This would have been called "inkOverlay" but it takes
// up too much horizontal space to be posted on a weblog
private InkOverlay ink;
// Store our old position to determine when view changes
private Point oldScrollPos;
}
Notice that we no longer need the WndProc or OnMouseWheel overrides, because the new approach is to detect any scrolling directly from OnPaint. This is a much nicer method because it accounts for any change in AutoScrollPosition, not just scrolling by the user.
My next post will add some properties to the InkPanel allowing the editing mode to be changed.
EDIT: Major change in the approach used.