Pete's Weblog

The Blog Formerly Known as Fun with WinForms

  Home  |   Contact  |   Syndication    |   Login
  14 Posts | 0 Stories | 37 Comments | 100 Trackbacks

News

Archives

Post Categories

.NET Programming

Software

Weblogs

In a comment on the original post of this series, Kollen Glynn points out that I could specify a bounding rectangle to the Invalidate() call in the Stroke method. Here's the code for that alteration:

    // 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 )
    {
        // Make sure we only invalidate the necessary region
        Rectangle bounds = e.Stroke.GetBoundingBox();
       
        // GetBoundingBox uses Ink Space coords, so convert
        using( Graphics g = CreateGraphics() )
            InkSpaceToPixel( g, ref bounds );
       
        // The false here means that we don't refresh the
        // control's children. You might want to use true
        // if you are using InkOverlayAttachMode.InFront
        Invalidate( bounds, false );
       
        // For some reason, this must come after the call
        // to Invalidate. Otherwise the stroke is not redrawn
        if ( inkMode == InkMode.Highlight )
            e.Stroke.ExtendedProperties.Add( HighlightGuid, inkMode );
    }

I also wrote a helper method to deal with converting a Rectangle from Ink space to pixel space:

    // Convert an Ink Space Rectangle to Pixel Space
    private void InkSpaceToPixel( Graphics g, ref Rectangle rect )
    {
        // Cannot pass a reference of a property,
        // so we need to make copies of each
        Point topLeft = rect.Location;
        Point bottomRight = rect.Location + rect.Size;
       
        // Convert both temporaries to pixel space
        ink.Renderer.InkSpaceToPixel( g, ref topLeft );
        ink.Renderer.InkSpaceToPixel( g, ref bottomRight );
       
        // Repackage our new values as a Rectangle
        rect.Location = topLeft;
        rect.Size = ( Size )bottomRight - ( Size )topLeft;
    }

At this point it occurred to me that it would probably be better to only draw those strokes that fall within the clipping rectangle. In order to do this I've changed the painting code quite a lot, moving the actual drawing into two separate methods:

    /// <summary>
    /// Overrides <see cref="Control"/>.OnPaint.
    /// </summary>
    protected override void OnPaint( PaintEventArgs e )
    {      
        // Doing all our redrawing here enables us to take
        // full advantage of the automatic double buffering
        base.OnPaint( e );
       
        // Adjust for any change in the view position
        HandleScroll();
       
        // Draw all the strokes, clipped appropriately
        DrawInk( e.Graphics, e.ClipRectangle );
               
        // If you aren't selecting you can go home now :)
        if ( inkMode != InkMode.Select )
            return;
       
        // Draw all selected ink, regardless of clipping
        DrawSelectedInk( e.Graphics );
    }

The method to draw selected ink strokes uses the same approach as before, except that I no longer bother with the second drawing operation if the stroke is a highlight. This is because the white centre is never visible for selected highlights anyway, so drawing it would be a waste of time.

    // Draw all the selected ink. Does not bother with
    // testing to see if strokes are in the clipping region
    private void DrawSelectedInk( Graphics g )
    {
        Strokes strokes = ink.Selection;
        Renderer render = ink.Renderer;
       
        // Get the amount to increase size of selected lines
        Point strokeExtra = new Point( 4, 4 );
        render.PixelToInkSpace( g, ref strokeExtra );
       
        // Redraw the selection strokes as they should look
        for( int i = 0; i < strokes.Count; ++i )
        {
            Stroke stroke = strokes[i];
            DrawingAttributes da = stroke.DrawingAttributes.Clone();
           
            // Draw the extra thick stroke
            da.IgnorePressure = true;
            da.Width += strokeExtra.X;
            da.Height += strokeExtra.Y;
            render.Draw( g, stroke, da );
           
            // The second draw doesn't show for highlights
            // so there's no need to bother with it
            ExtendedProperties ep = stroke.ExtendedProperties;
            if ( ep.DoesPropertyExist( HighlightGuid ) )
                continue;
           
            // Draw the white center
            da.Width -= strokeExtra.X;
            da.Height -= strokeExtra.Y;
            da.Color = Color.White;
            render.Draw( g, stroke, da );
        }
    }

There are quite a few changes to the way I'm drawing normal ink strokes. Firstly, I'm not drawing selected strokes here anymore (since they get drawn afterwards anyway). Secondly, each stroke's bounding box is tested against the clipping rectangle to determine if it should be drawn:

    // Draw all the non-selected ink. Tests each
    // stroke to ensure it should be drawn
    private void DrawInk( Graphics g, Rectangle clip )
    {
        Strokes strokes = ink.Ink.Strokes;
        Strokes selection = ink.Selection;
        Renderer render = ink.Renderer;
        Rectangle bounds;
       
        // Draw all the non-selected strokes
        for( int i = 0; i < strokes.Count; ++i )
        {
            Stroke stroke = strokes[i];
           
            // Don't bother drawing the selection here
            if ( selection.Contains( stroke ) )
                continue;
           
            // Don't bother drawing if it's not needed
            bounds = stroke.GetBoundingBox();
            InkSpaceToPixel( g, ref bounds );
            if ( !clip.IntersectsWith( bounds ) )
                continue;
           
            // Just draw the damn thing already!
            render.Draw( g, stroke );
        }
    }

I have no idea if this testing against the clipping rectangle actually produces a performance increase or not. It certainly doesn't seem to hurt performance on my machine (which is a laptop, not a tablet), but you should probably try benchmarking or profiling to see if you need this yourself. I don't bother doing this for the selected strokes because there are generally fewer of them, and the testing is much harder (you have to compensate for the extra size).

As always, enjoy and let me know if there are problems with it.

EDIT: Corrected a nasty little error that got the size wrong in InkSpaceToPixel.

posted on Saturday, October 18, 2003 8:15 PM