Geeks With Blogs
Pete's Weblog The Blog Formerly Known as Fun with WinForms

This time I'll be adding a few more properties to the InkPanel, including a way to change the mode of the Ink and some support for changing colours. As an added bonus, I'll add support for separate highlighting including a way to separate the highlights from the ordinary ink.

Changing the Mode

The InkOverlay object has support for three basic editing modes: Ink, Select and Delete. I also want two more of my own, specifically Highlight and Disabled. In order to achieve this we can add a new enumeration that will be used to specify the mode to set:

/// <summary>
/// Specifies the mode that the <see cref="InkPanel"/>
/// should be set to.
/// </summary>
public enum InkMode
{
    /// <summary>Standard drawing mode.</summary>
    Ink = InkOverlayEditingMode.Ink,
    /// <summary>Deletion mode.</summary>
    Delete = InkOverlayEditingMode.Delete,
    /// <summary>Selection mode.</summary>
    Select = InkOverlayEditingMode.Select,
    /// <summary>Highlighter mode.</summary>
    Highlight,
    /// <summary>
    /// Disable ink support so we can access the
    /// <see cref="InkPanel"/> directly.
    /// </summary>
    Disabled
}

Now that we have a way of representing the mode, we'll be needing a property to control it. The set method has a bit of work to do in order to correctly clean up from the previous mode and set up the new one, but it isn't too complicated. We helped ourselves quite a bit by making the enumeration map directly to InkOverlayEditingMode, so we don't need to mess around converting between the two. Notice that we also include the underlying private variable for the property. You can add this code to the appropriate place in the class yourself, I won't waste space with complete code here.

    /// <summary>
    /// Gets or sets the Ink mode for the <see cref="InkPanel"/>
    /// </summary>
    /// <remarks>
    /// The Ink mode can be set to any value of the
    /// <see cref="InkMode"/> enumeration.
    /// </remarks>
    public InkMode InkMode
    {
        get { return inkMode; }
        set
        {
            // Simplify things later by ensuring it changes
            if ( value == inkMode )
                return;
           
            ink.Enabled = false;
           
            // Turn off highlighting
            if ( inkMode == InkMode.Highlight )
                SetHighlightMode( false );
            // Remove the old selection
            else if ( inkMode == InkMode.Select )
                ink.Selection = ink.Ink.CreateStrokes();
           
            inkMode = value;
           
            // Deal with the two special cases
            if ( inkMode == InkMode.Disabled )
                return;
            else if ( inkMode == InkMode.Highlight )
            {
                SetHighlightMode( true );
                ink.Enabled = true;
                return;
            }
           
            // InkMode should map directly onto the EditingMode
            ink.EditingMode = ( InkOverlayEditingMode )inkMode;
           
            ink.Enabled = true;
        }
   
}

    private InkMode inkMode = InkMode.Ink;

That's it for the simple stuff, now on to something more fun.

Changing the Pen Colour

Before we can look into changing the pen colour, we need to decide on a highlighting strategy. Since I wanted to keep highlights separate from normal ink, I have chosen to add an ExtendedProperty to each highlight and test for it when changing colours.

It turns out that changing the pen colour is even easier than setting the mode. The only hassle here is to make sure we don't change the colour of any highlights by mistake. To do this we can loop through all the strokes in the selection and test each one for the property that we set on highlights (which I'll detail in the next section):

    /// <summary>
    /// Gets or sets the color to use for normal inking.
    /// </summary>
    /// <remarks>
    /// If we are in selection mode and this property is set,
    /// then any normal ink strokes that are selected will be
    /// changed to this color.
    /// </remarks>
    public Color InkColor
    {
        get { return inkColor; }
        set
        {
            inkColor = value;
           
            // Don't bother with highlights, they are separate
            if ( inkMode == InkMode.Highlight )
                return;
           
            ink.Enabled = false;
            ink.DefaultDrawingAttributes.Color = inkColor;
           
            // Change the color for every selected ink stroke
            if ( inkMode == InkMode.Select )
            {
                foreach( Stroke stroke in ink.Selection )
                {
                    ExtendedProperties ep;
                    ep = stroke.ExtendedProperties;
                   
                    // Don't change highlights
                    if ( !ep.DoesPropertyExist( HighlightGuid ) )
                        stroke.DrawingAttributes.Color = inkColor;
                }
            }
            ink.Enabled = true;
        }
   
}

    private Color inkColor = Color.Black;

Setting the colour of the highlights is almost exactly the same. The only real difference is that we test for the presence of the ExtendedProperty, not it's absence:

    /// <summary>
    /// Gets or sets the color to use for highlighting.
    /// </summary>
    /// <remarks>
    /// If we are in selection mode and this property is set,
    /// then any highlights that are selected will be changed
    /// to this color.
    /// </remarks>
    public Color HighlightColor
    {
        get { return highlightColor; }
        set
        {
            highlightColor = value;
           
            ink.Enabled = false;
            if ( inkMode == InkMode.Select )
            {
                // Change the color of any selected highlights
                foreach( Stroke stroke in ink.Selection )
                {
                    ExtendedProperties ep;
                    ep = stroke.ExtendedProperties;
                   
                    if ( ep.DoesPropertyExist( HighlightGuid ) )
                        stroke.DrawingAttributes.Color = value;
                }
            }
           
            // Don't go any further if we aren't highlighting
            if ( inkMode != InkMode.Highlight )
            {
                ink.Enabled = true;
                return;
            }
           
            SetHighlightMode( false );
            SetHighlightMode( true );
            ink.Enabled = true;
        }
   
}

    private Color highlightColor = Color.Yellow;

Highlighting

In order to make highlighting work properly, we'll be needing to implement the SetHighlightMode method used earlier. There are a number of different options that this method must set, all of them based around the DefaultDrawingAttributes class. You can look them up in the SDK documentation if you want a better idea of what they all do. I borrowed the approach for this highlighting from here.

    // Setup the highlighting mode to mimic Journal
    private void SetHighlightMode( bool highlighting )
    {
        DrawingAttributes da = ink.DefaultDrawingAttributes;
       
        if ( highlighting )
        {
            // Ensure we aren't erasing or selecting
            ink.EditingMode = InkOverlayEditingMode.Ink;

            // Save the old values to go back to later
            oldPenTip = da.PenTip;
            oldWidth = da.Width;
            oldHeight = da.Height;
            oldRast = da.RasterOperation;
           
            // Set our highlighting values
            da.PenTip = PenTip.Rectangle;
            da.Width = 250;
            da.Height = 400;
            da.Color = highlightColor;
            da.RasterOperation = RasterOperation.MaskPen;
        }
        else
        {
            // Reset the old values, or defaults if
            // there aren't any values stored
            da.Color = inkColor;
            da.RasterOperation = oldRast;
            da.Height = oldHeight;
            da.Width = oldWidth;
            da.PenTip = oldPenTip;
        }
   
}

    private RasterOperation oldRast = RasterOperation.CopyPen;
    private float oldWidth = 53;
    private float oldHeight = 1;
    private PenTip oldPenTip = PenTip.Ball;

Now all we need to do is apply the ExtendedProperty to highlight strokes in the Stroke method. For some reason, this property must be changed after we have called Invalidate or the stroke does not get redrawn (on my system):

    // 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();
       
        // 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 );
    }

We'll be needing a GUID for the ExtendedProperty. You can generate your own with the uuidgen command line utility.

    private Guid HighlightGuid = new Guid( "your-GUID-here" );

Conclusion

Well, that's it. Hopefully I've not forgotten anything and I hope you enjoy it. Next time I'll post about getting the selection to display properly (it doesn't when you draw the strokes yourself).

Posted on Friday, October 17, 2003 5:36 PM Windows Forms | Back to top


Comments on this post: Supporting the Tablet PC from Panel - Part II

# re: Supporting the Tablet PC from Panel - Part II
Requesting Gravatar...
Instead of using an extended property for indicating a highlighter stroke, why not just test for RasterOperation.MaskPen?
Left by Kollen Glynn on Oct 18, 2003 4:44 PM

# re: Supporting the Tablet PC from Panel - Part II
Requesting Gravatar...
Thanks for the feedback!

I'll admit that I never thought of that, thanks! I'm not sure that would be adequate though, as I'm thinking of adding other modes (a separate annotation mode, for example) that might also use RasterOperation.MaskPen. That's something that would break my code and I probably wouldn't notice ;)

I'm also trying to demo as much as possible in a small space, which is tricky when none of my code has been thoroughly tested. I'm planning to expand it into a custom layer system at some point, but that's a pretty low priority right now (any suggestions are appreciated).

Thanks again.
Left by Pete Vidler on Oct 18, 2003 4:52 PM

Your comment:
 (will show your gravatar)


Copyright © Pete Vidler | Powered by: GeeksWithBlogs.net