Extending the PictureEdit control to allow for custom drawing onto the image

I came across this request from DevExpress customer Christopher Todd on the support center requesting that a component be created that allowed for basic drawing/shapes in a WinForms app. While there certainly are a multitude of graphic/imaging libraries available for .NET, many of them are expensive (LeadTools comes to mind) or simply overkill for a simple WinForms business application (DirectX). Specifically, Christopher requested:

It would be nice to have a control that allows basic drawing functionality on the PictureEdit. Basically, a color picker, lines with optional arrow ends, drawing with mouse cursor, basic shapes like square and circle and maybe a highlighter.

I can see the usefulness of such a control. Users could load an image into the PictureEdit control and draw some shapes or lines onto the image for whatever reason. My idea was to create a PictureEdit descendant control which could be dragged onto a form at design time and which internally handled drawing those shapes onto itself at runtime. The developer shouldn’t have to do any customization–it should just work.

My initial creation offers 4 methods of drawing onto the “canvas”. There are three built-in shapes (ellipse, rectangle and line) and a freehand drawing mode which uses the mouse cursor as a pencil. To activate them, I added two DXEditMenu items to the PictureEdit’s built-in context menu:

/// <summary>
/// Gets the Picture Menu for this PictureEditCanvas control
/// </summary>
protected override PictureMenu Menu
{
    get { return dxPopupMenu; }
}
/// <summary>
/// On create control event handler
/// </summary>
protected override void OnCreateControl()
{
    base.OnCreateControl();
    dxPopupMenu = CreatePictureMenu();

    //Add in our custom DXMenuItems
    DXMenuItem drawingTools = new DXMenuItem("Drawing Tools", new EventHandler(OnDrawingToolsClick));
    drawingTools.BeginGroup = true;
    drawingTools.Image = PictureEditDrawing.Properties.Resources.pictureshapeoutlinecolor_16x16;
    dxPopupMenu.Items.Add(drawingTools);

    DXMenuItem saveImage = new DXMenuItem("Save Image", new EventHandler(OnSaveImageClick));
    saveImage.Image = Menu.Items[6].Image;
    dxPopupMenu.Items.Add(saveImage);

}   //End the OnCreateControl() method

Nothing special going on here. This gives us something like this:

PictureEdit custom context menu
PictureEdit custom context menu

Clicking on the Drawing Tools menu item gives us our toolset, which perhaps we can extend in a future version:

Drawing tools dialog
Drawing tools dialog

Here is where your end user will pick which tool they want to use on the PictureEdit canvas. Different tools have slightly different options (start & end caps only apply to lines, and only the rectangle and ellipse can be filled). Once the user clicks OK, the PictureEdit turns into “Drawing Mode” where it tracks the mouse buttons and movement and paints the shape/drawing onto the canvas.

The final part of the control involves saving your new masterpiece to the harddrive. Using the PictureEdit’s built-in save method won’t cut it for this. The PictureEdit itself has no concept of the shapes you’ve drawn on top of it–it would only save the Image object stored internally. To overcome this, we take a snapshot of the control and save that:

/// <summary>
/// Saves the modified PictureEditCanvas image
/// </summary>
/// <param name="FileName">File name for the saved image</param>
private void SaveModifiedImage(string FileName)
{
    using (Bitmap imageBMP = new Bitmap(Width, Height))
    {
        DrawToBitmap(imageBMP, Bounds);
        imageBMP.Save(FileName);

    }   //End the using() statement

}   //End the SaveModifiedImage() method

This is really a quick overview of how it works, but the internals of the whole project are just basic GDI drawing commands which can be studied anywhere. Some important points to note are that the control should force itself to redraw when the mouse is moved (and the user is actually drawing something) via the Invalidate method. We also want to take care to dispose of GDI objects wherever possible, because Paint events can be fired very often which increases the result of a memory leak or an exception as GDI handles increase.

As always, I’ve provided the full sample code and a basic test project showing the control in action. Download here: PictureEditDrawing

PS. Forgive the code formatting in this post–WordPress doesn’t seem to be very friendly to formatted C# code anymore.

Extending the PictureEdit control to allow for custom drawing onto the image

Previewing images in a SuperToolTip

In my previous post I mentioned the possibility of displaying a PDF page thumbnail in a tooltip to show a larger preview when the user hovers over it. Here’s what our end goal looks like:

Pdf Page SuperToolTip
The thumbnail displays a larger preview of itself when the mouse hovers above a page.

To accomplish this, we’ll add upon the PdfThumbnails solution from the last post to create this new functionality.

First, drop a ToolTipController onto the frmMain form. This component can be used to provide tooltips for DevExpress which implement the IToolTipControlClient interface necessary for displaying tooltips. Don’t worry about the nitty-gritty: DevExpress controls all seem to implement this interface allowing you to customize and create tooltips.

Once you drop the ToolTipController onto your form, make sure that you then set the grdThumbnails GridControl’s ToolTipController property to this new ToolTipController instance.

To provide our tooltips we’ll need to handle an event that is fired when the user hovers over a control (or event an element within a control, such as a single grid cell). This event comes in the form of the ToolTipController’s GetActiveObjectInfo event. Let’s create a handler for this event:


///

/// Get active object info event handler for the ToolTipController
/// &lt;/summary&gt;
/// &lt;param name=&quot;sender&quot;&gt;&lt;/param&gt;
/// &lt;param name=&quot;e&quot;&gt;&lt;/param&gt;
private void toolTipController1_GetActiveObjectInfo(object sender, DevExpress.Utils.ToolTipControllerGetActiveObjectInfoEventArgs e)
{
WinExplorerViewHitInfo hitInfo = wvThumbnails.CalcHitInfo(e.ControlMousePosition);

if (hitInfo.IsValid == false || hitInfo.InItem == false)
return;

ToolTipControlInfo toolTipInfo = null;
SuperToolTip toolTip = new SuperToolTip();

toolTipInfo = new ToolTipControlInfo(hitInfo.RowHandle.ToString(), &quot;Page Preview&quot;);
ToolTipItem item1 = new ToolTipItem();
item1.Image = PdfPage.ScaleThumbnailImage(hitInfo.ItemInfo.Image, 640, 480);
toolTip.Items.Add(item1);

toolTipInfo.SuperTip = toolTip;
e.Info = toolTipInfo;

} //End the toolTipController1_GetActiveObjectInfo() method

Our tooltip will be pretty simple–it’s just an image with no text, hyperlinks etc… We construct a SuperToolTip object, and more importantly, a ToolTipControlInfo object. The ToolTipControlInfo object is what is responsible for holding the information we’d like to show in a tooltip. That is, we can set a Title, Text or Image property, all of which are the basis of any tooltip, be it a standard tooltip or SuperToolTip.

One important thing to note is that the ToolTipControlInfo object itself has a property called “Object” (of datatype Object… a little confusing, I agree) which needs to be set to a unique value to identify what the tooltip is associated with. I do this by simply setting the property to the row handle of the WinExplorerView, since it will be unique.

The last bit of code isn’t really DevExpress-centric, but we need a way to scale the image to a manageable size because we don’t want to actually show the full size page image:

/// &amp;lt;summary&amp;gt;
/// Returns a thumbnail scaled to fit within the provided width and height ratio
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;ImageToScale&amp;quot;&amp;gt;Image to be scaled&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;MaxWidth&amp;quot;&amp;gt;New image maximum width&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;MaxHeight&amp;quot;&amp;gt;New image maximum height&amp;lt;/param&amp;gt;
/// &amp;lt;returns&amp;gt;Scaled image&amp;lt;/returns&amp;gt;
static internal Image ScaleThumbnailImage(Image ImageToScale, int MaxWidth, int MaxHeight)
{
    double ratioX = (double)MaxWidth / ImageToScale.Width;
    double ratioY = (double)MaxHeight / ImageToScale.Height;
    double ratio = Math.Min(ratioX, ratioY);

    int newWidth = (int)(ImageToScale.Width * ratio);
    int newHeight = (int)(ImageToScale.Height * ratio);

    Image newImage = new Bitmap(newWidth, newHeight);
    Graphics.FromImage(newImage).DrawImage(ImageToScale, 0, 0, newWidth, newHeight);

    return newImage;

}   //End the ScaleThumbnailImage() method

This method just takes the thumbnail image (which itself is actually a full-sized Bitmap of the page–the WinExplorerView scales it internally to a smaller graphic) and scales it to a new Bitmap of the dimensions requested in the MaxWidth & MaxHeight parameters.

In the future, we’ll get back to our Xpo Contact List project and use this image tooltip functionality to show preview images of our contacts in the program grids. This functionality can be helpful anywhere in your application where you display a small image and want to provide the user with a quick way to view a larger version of that image.

Download the PdfThumbnails_NEW source for our project and see it in action for yourself!

Previewing images in a SuperToolTip

Creating a sample WinForms application, part one

I thought that perhaps a good way to expose new DevExpress developers to the various products and controls might be to create a small, simple application. Borne from this idea is DxContactList, a basic application designed to be a digital address book. I came up with some simple functionality that help you become more familiar with some different controls & products:

  • XtraEditors as well as creating some custom controls
  • XtraGrid
  • XtraMap
  • XtraReports
  • eXpress Persistent Objects (Xpo), DevExpress’ ORM product that will handle our data-access and mapping.
  • XtraRichEdit control

I’ve already built the application and I hope to present it to you over the next few weeks in a series of posts. We’ll take a look at each portion of the application, deconstruct how I’ve built it and point out some tips and tricks to make your life easier if you’re trying to develop something similar.

I’ll be starting with the back-end & business logic which is chiefly comprised of our persistent classes. You way want to review some of the Xpo documentation to familiarize yourself with it first. Additionally, I find that the Xpo Best Practices knowledge base article is valuable in helping you avoid some common mistakes as you begin to learn.

Creating a sample WinForms application, part one