Pete's Weblog

The Blog Formerly Known as Fun with WinForms

  Home  |   Contact  |   Syndication    |   Login
  15 Posts | 0 Stories | 50 Comments | 98 Trackbacks

News

Tag Cloud


Archives

Post Categories

.NET Programming

Software

Weblogs

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.

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati
posted on Thursday, October 16, 2003 4:35 PM

Feedback

# re: Supporting the Tablet PC from Panel 10/17/2003 3:07 PM Fritz Switzer
Pete,

Thanks for the posting, I'm looking forward to trying it out and viewing the new properties for panel1.

Fritz

# re: Supporting the Tablet PC from Panel 10/17/2003 4:57 PM Pete Vidler
Fritz,

Thanks for the feedback! I'm about to post two more parts to this series (! this was going to be a one off!).

Enjoy.

# re: Supporting the Tablet PC from Panel 10/18/2003 4:34 PM Kollen Glynn
You're right, the only reason you need to use the smoothing ink sample code is to render to a bitmap, otherwise Renderer.Draw is all that's needed. (The problem with rendereing to a bitmap is actually a bug in GDI+ not the ink Renderer for anyone that's curious.) Also a small perf win could be gained by just invalidating the stroke's bounding box rather than the whole panel.

There was a thread on this here [1].

[1]
http://tabletpcdeveloper.com/Community/MessageBoard/Thread.aspx?id=176412&PageNumber=1

# re: Supporting the Tablet PC from Panel 10/18/2003 4:55 PM Pete Vidler
Interesting, presumably the performance benefit would only be if you checked the bounding rectangles in the paint override as well.. I'll take a look and post it when I'm done.

Thanks.

# re: Supporting the Tablet PC from Panel 10/18/2003 5:22 PM Kollen Glynn
I'm not sure I follow, the paint handler automatically clips itself so you don't need to worry about checking for rectangles in there. I may be missing something.

# re: Supporting the Tablet PC from Panel 10/18/2003 7:45 PM Pete Vidler
It clips itself, but I don't think it will stop you from drawing every single stroke. I think a speed improvement could be had by testing each stroke against the clipping rectangle (otherwise we attempt to draw even those strokes that aren't even on the screen).

Basically I think the ink renderer will draw the to off-screen buffer (supplied through the graphics object) regardless of e.ClipRectangle (which doesn't get passed to the renderer at all).

I could be wrong though.

# re: Supporting the Tablet PC from Panel 10/18/2003 8:25 PM Kollen Glynn
Clipping is handled by the OS automatically, definitely don't try and clip anything yourself, you'll only decrease performance. The whole idea of Windows painting is to draw it all at once and let the OS do the rest.



# re: Supporting the Tablet PC from Panel 10/18/2003 8:36 PM Pete Vidler
I don't mean full clipping, just not drawing those strokes that don't intersect (or aren't contained by) the clipping rectangle. See above for how I'm doing it.

Surely redrawing a stroke is a fairly expensive operation (especially when it could be an antialiased bezier curve on tablet pc hardward). Imagine the overhead of the OS (or GDI+) clipping a stroke to the ClientRectangle that never needed to be redrawn in the first place.

Once again, I haven't profiled or bechmarked and I don't own a Tablet PC so I can't say for certain. Although there's an article (http://windowsforms.net/articles/windowsformspainting.aspx) which seems to recommend this approach (under "Intelligent Invalidation, i.e. Think Before You Paint").

# re: Supporting the Tablet PC from Panel 10/18/2003 9:41 PM Kollen Glynn
The author of that article isn't doing any manual clipping, in fact he's taking advantage of the OS's automatic clipping by making the background clip region as small as possible. He's making sure the clip region for the background paint handler excludes where the foreground will be painted. Don't confuse the fact that he is using FillRegion to render the foregound, he's still rendering the whole string and letting the OS do the clipping (remember the clip region might only be a small rectangle covering a few pixels from an overlapping window).

The equivalent would be calculating the stroke's region (quite hard to do) and exluding it from the background paint handler's clip region but since you specified ControlStyles.AllPaintingInWmPaint (which means there's isn't a separate background paint event) you can't do that.

You are doing the next best thing already by only adding the stroke's bounding rectangle to the clip region.

# re: Supporting the Tablet PC from Panel 10/19/2003 11:16 AM Pete Vidler
There's a chapter of a book online (http://www.codeproject.com/books/1861004990.asp) that appears to agree with testing against e.ClipRectangle (under "Using the Clipping Region").

I have no idea how reliable this info is though, as I didn't read the entire chapter/book and have not heard of any of the authors.

I also put the question to the microsoft.public.dotnet.framework.drawing newsgroup to try and settle the matter.

# re: Supporting the Tablet PC from Panel 10/21/2003 5:41 PM Kollen Glynn
From an internal discussion at Microsoft:

"The Renderer does not validate the Stroke regions against the HDC. While it's a worthy notion, practically speaking, the OS is going to perform its own clipping anyway, and if there is complex region support, the Renderer would not likely be as performant - without a great deal of Renderer algorithmic work.

If you have a very controlled scenario (such as no regions), then HitTesting the bounding box against DC might be useful.
However, also keep in mind the Renderer uses transforms (object and view), which you should also take into account.

I would suggest profiling your app before making this decision."

# re: Supporting the Tablet PC from Panel 3/3/2008 11:59 AM Silvia
Hi, I¡m student of engineering science computer, and I have to implement a paint application for tablet pc (draw lines, rectangles ellipse), and I would like to see some examples and all kind of information, because I don't understand very well developing in tablet pc about the strokes and renderer, etc.... If you can help me, i'm very pleased.
My e-mail: slopez.utrilla@gmail.com

Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: