Extending the PictureEdit control with custom drawing, part deux

There’s been a bit of additional interest in the PictureEditCanvas control, which allows a user to annotate an image with some shapes and freehand drawing. To improve on it a little further, I’ve added the ability to “stamp” some text onto the canvas.

Before the last post, all of the polygon shapes were descendants of a Shape class. When I decided to add the freehand drawing functionality, this required a little bit of refactoring in which all drawing tools now implement the IDrawShapes interface. This interface contains the common properties and methods that all drawing elements use at a minimum. The abstract Shape and FillableShape classes extend this further for the elements that support them (Line, Rectangle and Ellipse).

I guess it could be argued that at the end of the day, text is just a series of shapes as well, so it certainly makes sense to make our TextStamp implement the IDrawShapes interface just like its toolbox brethren. Let’s have a look at how the TextStamp class ultimately turned out:

 sealed public class TextStamp : IDrawShapes
    {

        #region Public properties

        /// <summary>
        /// Gets or sets the Fore Color of this Text Stamp
        /// </summary>
        public Color ForeColor
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Fill Color of this Text Stamp
        /// </summary>
        public Color FillColor
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Border Width of this Text Stamp
        /// </summary>
        public float BorderWidth
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the starting X co-ordinate for the Text Stamp
        /// </summary>
        public int StartX
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the starting Y co-ordinate for the Text Stamp
        /// </summary>
        public int StartY
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Text string of this Text Stamp
        /// </summary>
        public string Text
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Text Font to be used for this Text Stamp
        /// </summary>
        public Font TextFont
        {
            get;
            set;
        }

        #endregion


        /// <summary>
        /// Initializes a new instance of the TextStamp class
        /// </summary>
        public TextStamp()
        {

        }



        /// <summary>
        /// Starts this Text Stamp by registering the start location coordinates
        /// </summary>
        /// <param name="Location">Start location</param>
        public void StartDrawing(Point Location)
        {
            StartX = Location.X;
            StartY = Location.Y;

        }   //End the StartDrawing() method



        /// <summary>
        /// Ends this Text Stamp by registering the end location coordinates
        /// </summary>
        /// <param name="Location">End location</param>
        public void EndDrawing(Point Location)
        {
            StartX = Location.X;
            StartY = Location.Y;

        }   //End the EndDrawing() method



        /// <summary>
        /// Draws this Text Stamp
        /// </summary>
        /// <param name="graphics">Current Graphics context</param>
        public void Draw(Graphics graphics)
        {
            using (Pen pen = new Pen(ForeColor))
            {
                pen.Alignment = PenAlignment.Inset;
                graphics.SmoothingMode = SmoothingMode.HighQuality;

                using (GraphicsPath path = new GraphicsPath())
                {
                    path.AddString(Text, TextFont.FontFamily, (int)TextFont.Style, TextFont.Size, new Point(StartX, StartY), StringFormat.GenericDefault);
                    graphics.DrawPath(pen, path);

                    using (SolidBrush brush = new SolidBrush(FillColor))
                        graphics.FillPath(brush, path);

                }   //End the using() statement

            }   //End the using() statement

        }   //End the Draw() method



        /// <summary>
        /// Returns True if the provided text can fit on within the given image width, based on the text settings
        /// </summary>
        /// <param name="ImageWidth">Width of the Image to be test the text against</param>
        /// <param name="TextString">Text string to check</param>
        /// <param name="TextFont">Text font settings</param>
        /// <param name="graphics">Current Graphics context</param>
        /// <returns>True if the text will fit within the provided Image width</returns>
        static public bool CheckTextSizing(int ImageWidth, string TextString, Font TextFont, Graphics graphics)
        {
            int TextWidth = 0;

            TextWidth = Convert.ToInt32(graphics.MeasureString(TextString, TextFont).Width);

            return (TextWidth < ImageWidth);

        }   //End the CheckTextSizing() method


    }   //End the TextStamp class

Our class makes use of those standard IDrawShapes members and has a few additional properties responsible for storing the text string and text Font object.

Unlike the other Draw methods, I’m building a GraphicsPath object in the Draw method in order to render text that can be filled with another color (I may adopt this method in the FillableShape types as well). The text is anti-aliased, because honestly it would look pretty awful if it wasn’t.

One additional point of interest in the TextStamp class is the CheckTextSizing method. The idea behind this is perform a check in which we determine if the text being placed on the image will actually fit, given the font & length and the size of the image itself. Keep in mind though that the width of the image isn’t necessarily the width of the control (and by extension, your canvas) itself given the various methods of clipping/stretching/zooming an image in the PictureEdit control.

The Text tool was then added to the Drawing Tools window:

Extended Drawing Tools window
Extended Drawing Tools window

The Text Stamp tool is handled a little differently than the other tools by the PictureEditCanvas. Specifically, it doesn’t require the user to hold down the left mouse button while placing the text. Instead, a preview of the text simply follows the mouse cursor until the user left-clicks in the control to place it.

This mostly seems to satisfy my initial requirements for a control. It allows for simple shapes, freehand drawing and text placement. It may make sense to extend this functionality to a Panel control to create a stand-alone drawing surface control that has nothing to do with an Image (or the PictureEdit), which can easily be done by moving the majority of the PictureEditCanvas control into a Panel control descendant.

If I do decide to continue work on this control (or Panel control analog), some features I’d like to see are:

  • Ability to select shapes already placed onto the canvas to modify/remove them
  • Ability to make perfect squares & rectangles by holding SHIFT or CTRL while dragging the mouse. This is a common behavior of painting programs.
  • Ability to rotate shapes
  • Ability to enter text in-place
  • Ability to place an image onto the canvas

If there is interest in these features, I may migrate my work onto CodePlex and continue development on it. Until then, here is the updated source code for what I discussed today: PictureEditDrawing2

Extending the PictureEdit control with custom drawing, part deux

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

Using a custom tile provider with the XtraMap control

I saw this post a few weeks ago in the DevExpress Support Center inquiring about how to connect the XtraMap control to the Nokia HERE Maps system. Nokia’s HERE system provides geocoding, routing, and for our purposes, a map tile provider.

HERE Maps in an XtraMap control
HERE Maps in an XtraMap control

Out of the box, the XtraMap control can be paired with the OpenMaps and Bing Map providers. If these don’t suit your needs, you do have the ability to supply your own map provider by writing a little bit of code.

For our example, we’re going to connect to the HERE map provider. If you want to do your own testing–or use it in a production environment–you will want to sign up for a developer license. HERE maps allows for a limited free developer account, so go ahead and register for that for access to the mapping, geocoding and routing functionality.

As I’ve mentioned in a previous blog, the #1 request I see with regards to the XtraMap control is for the ability to connect it to Google Maps. The Google Maps terms of service does not allow for it to be used in a desktop environment. That being said, you could probably use a little bit of creativity and adapt the techniques shown here to achieve it…

Luckily for us, most of what we need to do really involves some boilerplate code. The best starting place is this example in the DevExpress Support Center: How to load image tiles from another source by creating custom data provider. This example illustrates using a local tile store but we can easily adapt it to fetch map tiles from HERE.

The first step is creating a class that derives from the MapDataProviderClass. We really don’t need to change anything from the example code except for some class/member names to suit our needs.

    sealed public class HereMapTileProvider : MapDataProviderBase
    {

        #region Private members

        /// <summary>
        /// Map project
        /// </summary>
        private readonly SphericalMercatorProjection _Projection = new SphericalMercatorProjection();

        #endregion

        #region Public accessors

        /// <summary>
        /// Gets the Projection for this tile provider
        /// </summary>
        public override ProjectionBase Projection
        {
            get { return _Projection; }
        }

        /// <summary>
        /// Returns a size struct representing the base size of this provider, in pixels
        /// </summary>
        protected override Size BaseSizeInPixels
        {
            get { return new Size(Convert.ToInt32(HereMapTileSource._TileSize * 2), Convert.ToInt32(HereMapTileSource._TileSize * 2)); }
        }

        #endregion


        /// <summary>
        /// Initializes a new instance of the HereMapTileProvider class
        /// </summary>
        public HereMapTileProvider()
        {
            TileSource = new HereMapTileSource(this);
        }


        /// <summary>
        /// Returns the pixel size of this map for the provided zoom level
        /// </summary>
        /// <param name="zoomLevel">Current zoom level</param>
        /// <returns></returns>
        public override MapSize GetMapSizeInPixels(double zoomLevel)
        {
            double imageSize;
            imageSize = HereMapTileSource.CalculateTotalImageSize(zoomLevel);
            
            return new MapSize(imageSize, imageSize);

        }   //End the GetMapSizeInPixels() method




    }   //End the HereMapTileProvider class

The important stuff is in our tile source class, which must derive from the MapTileSourceBase class. Again, we can keep most everything standard with regards to the DevExpress example. The only real work we have to do is in the GetTileByZoomLevel method. This method is responsible for taking the map’s current position and zoom level and translating that into a tile source location from the provider. Internally, the MapControl will make a request to to that tile source, get the tiles and render them on the map.

The HERE tile api gives us the following prototype for a tile request:

http://{1-4}.base.maps.cit.api.here.com/maptile/2.1/maptile/{map id}/{scheme}/{zoom}/{column}/{row}/{size}/{format}
?app_id=DemoAppId01082013GAL
&app_code=AJKnXv84fjrb0KIHawS0Tg
&{param}={value}

We’ll fill in some of those parameters with values from the XtraMap control and others will be static values from the api documentation. You’re free to play around with the different options for  the {scheme} or {format} parameters, or even modify the Url to show a satellite or terrain view. For our purposes, I’ve implemented the GetTileByZoomLevel in the following manner:

/// <summary>
/// Returns a Uri used to fetch tiles for the provided zoom level and co-ordinates
/// </summary>
/// <param name="zoomLevel">Current zoom level</param>
/// <param name="tilePositionX">Current tile x co-ordinate</param>
/// <param name="tilePositionY">Current tile y co-ordinate</param>
/// <returns></returns>
public override Uri GetTileByZoomLevel(int zoomLevel, int tilePositionX, int tilePositionY)
{

    string Url = String.Format(@"http://1.base.maps.api.here.com/maptile/2.1/maptile/newest/normal.day/{0}/{1}/{2}/256/png?app_id={3}&app_code={4}",
                zoomLevel,              //{0}                            
                tilePositionX,          //{1}
                tilePositionY,          //{2}
                _ApplicationID,         //{3}
                _ApplicationCode);      //{4}

    if (zoomLevel <= _MaxZoomLevel)
        return new Uri(Url);
    else
        return null;

}   //End the GetTileByZoomLevel() method

And really, that’s all there is to it! Like I said above, the example provided by the DevExpress Support Center really does contain all of the boilerplate code you need to get started. Feel free to adapt that, or sign up for a HERE developer account, download the project I’ve built and modify it suit your own needs: CustomTileProvider.zip

Using a custom tile provider with the XtraMap control

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

Adding a thumbnail view & navigation to the PdfViewer control

If you’ve used the PdfViewer control you’ll know that it provides an easy way to display PDF documents within  your WinForms application. Currently, you can select text, manipulate a file (via the PdfDocumentProccessor non-visual component) and even fill out PDF forms. The 14.2.5 release even allows the ability to programmatically fill in PDF forms–pretty useful stuff for those of you processing electronic documents and working towards the dream of a paperless office.

One feature that I find lacking in the PdfViewer control is a good thumbnail navigation system. Adobe Reader offers this ability with a simple thumbnail navigation system; each page of the document is displayed as a small image which users can quickly scroll through. Double-clicking on a thumbnail opens the corresponding page and end users can even change the size of the thumbnail images.

Screenshot of Adobe Reader's thumbnail navigation
Screenshot of Adobe Reader’s thumbnail navigation

The DevExpress PdfViewer will display thumbnails for pages if you zoom out to about the 10-20% zoom level, but the functionality ends there. You can’t double-click one of the pages and have the viewer navigate to that particular page. How can we implement a system similar to Adobe Reader ourselves?

DevExpress's non-functional thumbnail mode.
DevExpress’s non-functional thumbnail mode.

To mimic Adobe Reader’s thumbnail function, I immediately though of a few controls that might make our job easier:

  • GalleryControl – This could easily organize and display our thumbnail images. We don’t really need any grouping functionality but that’s easy to ignore.
  • GridControl – This tends to be my default go-to for displaying a list of items/records. But that’s only the first half of the equation–after that, we’d have to decide which view type to use. The LayoutView in a single column mode would work well, but honestly, I hate the layout editor.

In the end, I decided to use the GridControl, but with the WinExplorerView instead of the LayoutView. The WinExplorerView mimics the various ways you might view a folder in Windows Explorer–details, large icons, small icons etc… This is all controlled via the OptionsViewStyles property, so we can even allow the user to toggle between styles at runtime.

At the end of this exercise, we’ll end up with something like this:

Our semi-finished product: a PdfViewer control with a sidebar of page thumbnails.
Our semi-finished product: a PdfViewer control with a sidebar of page thumbnails.

To being, we’ll create a form and place a LayoutControl onto the form. Dock the LayoutControl to fill the form, and then drop a GridControl onto the LayoutControl, followed by a PdfViewer control to the right. The GridControl can be placed in a LayoutControlGroup and the PdfViewer can just be contained within a standard LayoutControlItem. I also changed the view type of the GridControl from the default GridView to the WinExplorerView type.

The thumbnail logic is all going to be contained within a class I’ve called PdfPage. Let’s have a look at it:

namespace PdfThumbnails
{
    /// &amp;lt;summary&amp;gt;
    /// Encapsulates the properties and methods of a PdfPage
    /// &amp;lt;/summary&amp;gt;
    sealed public class PdfPage
    {

        #region Public properties

        /// &amp;lt;summary&amp;gt;
        /// Gets or sets the number of this Pdf Page
        /// &amp;lt;/summary&amp;gt;
        public int PageNumber
        {
            get;
            set;
        }

        /// &amp;lt;summary&amp;gt;
        /// Gets the thumbnail image for this Pdf Page
        /// &amp;lt;/summary&amp;gt;
        public Image Thumbnail
        {
            get;
            private set;
        }

        #endregion


        /// &amp;lt;summary&amp;gt;
        /// Initializes a new instance of the PdfPage class
        /// &amp;lt;/summary&amp;gt;
        public PdfPage()
        {

        }



        /// &amp;lt;summary&amp;gt;
        /// Sets the thumbnail image for the current PdfPage
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&amp;quot;DocumentViewer&amp;quot;&amp;gt;PdfViewer control responsible for displaying the PdfPage&amp;lt;/param&amp;gt;
        public void SetThumbnailImage(PdfViewer DocumentViewer)
        {
            int PageWidth = 0;
            int PageHeight = 0;
            int PagePixelWidth = 0;
            int PagePixelHeight = 0;

            using (Graphics graphics = DocumentViewer.CreateGraphics())
            {
                //Get the page dimensions
                PageWidth = (int)DocumentViewer.GetPageSize(PageNumber).Width;
                PageHeight = (int)DocumentViewer.GetPageSize(PageNumber).Height;

                //Convert the page dimensions into screen pixels
                PagePixelWidth = (PageWidth * (int)graphics.DpiX);
                PagePixelHeight = (PageHeight * (int)graphics.DpiY);

                Thumbnail = DocumentViewer.CreateBitmap(PageNumber, Math.Max(PagePixelWidth, PagePixelHeight));

            }   //End the using() statement

        }   //End the SetThumbnailImage class



    }   //End the PdfPage class
}   //End the PdfThumbnails namespace

The class is pretty sparse in and of itself–just two properties. One to keep track of the page number within the document and another to hold the thumbnail image. The important part is the SetThumbnailImage method, which is in charge of creating a thumbnail for this page.

The method requires an instance of the PdfViewer control because unfortunately, the control doesn’t offer a way to get an instance of a single visual page. Luckily, having an instance of the PdfViewer control allows to use its CreateGraphics method to get a reference to the drawing surface which will be useful for generating that thumbnail. And it is useful because the PdfViewer’s GetPageSize method returns the page’s dimensions in inches, not in pixels.

I think this was a poor decision on the part of DevExpress, because it’s not mentioned in the documentation that SizeF struct returned from the method is measured in inches and I have no clue how useful that information would be in metric-based countries. Our demonstration PDF will return 8 & 11 for the page’s width and height, respectively. Since we don’t want to create an 8px X 11px Bitmap, we need to convert these dimensions into pixels.

I’m not a graphics programmer, but I do know that DPI settings will influence the conversion between inches and pixels–after all, its name implies how many dots-per-inch there are on our display! Because of this, we can’t just multiply the page dimensions by some magic number. We have to take this DPI into account and multiply by that instead.

Once we have the page and screen dimensions, it’s a matter of using the PdfViewer’s CreateBitmap method to generate a Bitmap image of current page.

I then create a BindingSource component on the Form and set its DataSource property the PdfThumb class. After that, I set the GridControl’s DataSource to this BindingSource component. The GridControl will automatically read the scheme from this data source, but because we’re using the GridControl with a WinExplorerView there aren’t any columns. Instead, there’s a ColumnSet, which maps properties of the data source to the predefined properties of the WinExplorerView.

Since a future version of this application may allow a user to toggle between thumbnail sizes, we’ll set the values for SmallImageColumn, MediumImageColumnn, LargeImageColumn and ExtraLargeImageColumn members of that ColumnSet property. These are all set to the PdfPage’s ThumbnailImage property, and we’ll set the ColumnSet’s Description andTextColumn properties to the PageNumber property.

At this point, we have a PdfViewer control capable of displaying our PDF, a GridControl capable of displaying page thumbnails and a class capable of generating those thumbnails. The only thing we need to do is actually tell our application to generate those thumbnails at runtime when a PDF is loaded into the viewer.

Create a handler for the the PdfViewer’s DocumentChanged event as this will be fired every time a document is loaded into the PdfViewer control. After that, it’s just a matter of looping through the pages of the PdfViewer and creating an instance of the PdfPage class for each:

/// &amp;lt;summary&amp;gt;
/// Document changed event handler for the PdfViewer control
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;sender&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;e&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
private void pdfViewer1_DocumentChanged(object sender, DevExpress.XtraPdfViewer.PdfDocumentChangedEventArgs e)
{
    IList&amp;lt;PdfPage&amp;gt; documentPages = new List&amp;lt;PdfPage&amp;gt;(pdfViewer1.PageCount);

    for (int i = 1; i &amp;lt;= pdfViewer1.PageCount; i++)
    {
        PdfPage documentPage = new PdfPage();
        documentPage.PageNumber = i;
        documentPage.SetThumbnailImage(pdfViewer1);

        documentPages.Add(documentPage);

    }   //End the for() loop

    bindingSource1.DataSource = documentPages;

}   //End the pdfViewer1_DocumentChanged() method

Once that’s done, our GridControl will automatically show those page thumbnails when the application is run and a Pdf is loaded into the viewer.

The final step is to handle the WinExplorerView’s DoubleClick event:

/// &amp;lt;summary&amp;gt;
/// Double click event handler for the Thumbnails WinExplorerView
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&amp;quot;sender&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&amp;quot;e&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;
private void wvThumbnails_DoubleClick(object sender, EventArgs e)
{
    PdfPage currentPage = (wvThumbnails.GetFocusedRow() as PdfPage);

    pdfViewer1.CurrentPageNumber = currentPage.PageNumber;

}   //End the wvThumbnails_DoubleClick() method

This code simply takes the current row (thumbnail) from the thumbnail grid and casts it to an instance of our PdfPage class. Since the class is nice enough to keep track of its page number on our behalf, we can tell the PdfViewer to navigate to corresponding page.

And that’s all there is to it! Once small class, a couple of event handlers and we’re done. In an upcoming post, we’ll look at adding a few additional features to this application such as:

  • Allowing the user to change the thumbnail size
  • Synchronizing the thumbnail sidebar scroll position to the PdfViewer scroll position
  • Showing a magnified image of the thumbnail when the user hovers over a thumbnail image

And don’t worry, we’ll also get back to the DxContactList Xpo tutorial soon! In the meantime, here is the full source code for this tutorial: PdfThumbnails.

Adding a thumbnail view & navigation to the PdfViewer control

Creating a sample WinForms application, part three: abstract Xpo business class objects

In part two of this series we learned how to create a data layer which tells Xpo how to connect to our data store (in this case, a Sql Express server located on the same machine). Now we have to actually create some classes which map to our data store; that is, Xpo classes which directly relate to a table in the Sql Server database.

Xpo does offer a visual tool allowing you to generate classes from an existing database schema or visually design a class and its properties & relations. However, we’ll be doing a code-first approach and letting Xpo generate the database schema at runtime.

To begin, I’ve added a new project to the DxContactList solution named DxContactList.BusinessLogic. I like to segregate my Xpo classes to a separate assembly to promote code re-use and just to keep things nicely organized.

Within this project, I’ve added a folder named BaseClasses, in which I will place some abstract base classes which will be extended by the application’s business class objects. Xpo does allow you to use abstract classes as a base for a persistent class, but you will want to decorate it with the [NonPersistent] attribute. This may seem a little counter-intuitive, but this really just tells Xpo to not create a table from the base class–its properties will still be persisted when they are extended in a persistent class.

Let’s take a look at the entire PersonBase class:

/// <summary>
    /// Serves as a base class for objects that represent a person. This class cannot be instantiated.
    /// </summary>
    [NonPersistent]
    abstract public class PersonBase : XPCustomObject
    {

        #region Protected members

        /// <summary>
        /// Person first name
        /// </summary>
        protected string _FirstName;

        /// <summary>
        /// Person last name
        /// </summary>
        protected string _LastName;

        /// <summary>
        /// Person date of birth
        /// </summary>
        protected DateTime _DateOfBirth;

        /// <summary>
        /// Person picture
        /// </summary>
        protected Image _Picture;

        #endregion

        #region Public accessors

        /// <summary>
        /// Gets or sets this Person's first name
        /// </summary>
        [Size(50)]
        public string FirstName
        {
            get { return _FirstName; }
            set { SetPropertyValue<string>("FirstName", ref _FirstName, value); }
        }

        /// <summary>
        /// Gets or sets this Person's last name
        /// </summary>
        [Size(50)]
        public string LastName
        {
            get { return _LastName; }
            set { SetPropertyValue<string>("LastName", ref _LastName, value); }
        }

        /// <summary>
        /// Gets or sets this Person's date of birth
        /// </summary>
        public DateTime DateOfBirth
        {
            get { return _DateOfBirth; }
            set { SetPropertyValue<DateTime>("DateOfBirth", ref _DateOfBirth, value); }
        }

        /// <summary>
        /// Gets or sets a picture of this Person
        /// </summary>
        [ValueConverter(typeof(ImageValueConverter))]
        public Image Picture
        {
            get { return _Picture; }
            set { SetPropertyValue<Image>("Picture", ref _Picture, value); }
        }

        /// <summary>
        /// Gets the full name of this Person
        /// </summary>
        [NonPersistent]
        public string FullName
        {
            get
            {
                return String.Format("{0} {1}",
                    _FirstName,
                    _LastName);
            }
        }

        #endregion

        /// <summary>
        /// Initializes a new instance of the PersonBase class
        /// </summary>
        /// <param name="session">Current Xpo Session</param>
        public PersonBase(Session session)
            : base(session)
        {

        }

        /// <summary>
        /// Returns a string representation of this Person
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return String.Format("{0} {1}",
                _FirstName,
                _LastName);

        }   //End the ToString() method

    }   //End the PersonBase class

Our persistent classes will inherit from XPCustomObject. Xpo offers a number of base classes that will tell that data layer that a class is to be persisted to the data store. Your requirements will ultimately dictate which of these base classes you derive from, but because we will be letting Sql Server manage the unique identifier for each object (that is, an identity field), we will use the XPCustomObject class for all of our objects.

Because this is an abstract class and will not be mapped directly to a table in the database, we will not have any sort of primary key property in the class.

Note that an XPCustomObject class must either provide a property with the [Key] attribute (stating that it is the primary key/identity field) or the class must be marked as [NonPersistent]. Xpo will throw an exception at runtime if you neglect to do this!

The class itself will have a few simple members with the protected modifier. Xpo does not persist protected/private members; only public properties are persisted. To ensure our members are persisted I’ve created public accessors for each protected member.

The first thing of note are some attributes applied to a few of these properties. The string properties have a [Size] attribute which will limit the size of the field in the database table. By default, Xpo will create each string property as an nvarchar(256) in Sql Server, but that may be more (or less) than what is actually necessary.

You may want to review the table of data types supported by Xpo and how it maps .NET data types to data store types. These default mappings can all be changed by decorating a property with the DbType attribute.

The Picture property is a .NET Image object, but we can’t guarantee that our database system will automatically know how to deal with a binary field. To accommodate this, we decorate the property with the ValueConverter attribute which tells Xpo that we (or Xpo itself) will be performing some sort of value conversion on the property when it is saved to or retrieved from the database. You can create your own value converters to perform this data conversion or use one of the built-in converters offered by Xpo. In this case, we are using Xpo’s built-in ImageValueConverter, which can convert our property between a .NET Image object and a byte array.

The last property in the class is a [NonPersistent] property called “FullName”, which simply returns the name of a person by combining the FirstName and LastName members. NonPersistent properties are useful for simple cases like this, or when we want to have a property calculated from existing persistent properties but not have it persisted in the database.

The only other thing of note in this base class is the constructor, which takes a Session parameter and extends the XPCustomObject base class constructor. Xpo objects require a Session (or UnitOfWork) and you may notice that if you use the DevExpress ORM Persistent Object template in Visual Studio a second constructor is added. This constructor does not require the Session parameter, but because we will always be using a Session in conjunction with our object, there’s no need to keep this constructor around.

We’ve taken a brief look at some of the attributes provided by Xpo as well as discussed some class structure and best-practices. This will be continued in part four, as we build some actual persistent classes.

Creating a sample WinForms application, part three: abstract Xpo business class objects

Creating a sample WinForms application, part two: the data access layer

In part one of this series I provided you with an overview of what I hope to accomplish. We’ll be starting our sample application with an Xpo back-end that will reside as its own project without our overall solution. The nice thing about Xpo is that your classes, data access and UI don’t all need to reside within the same project or namespace. Because of this flexibility, I like to segregate my data access layer (or DAL for short) to its own project so that it could be changed easily to take advantage of the the different ways Xpo can connect to your data store (or even what data store Xpo connects to).

Xpo has a default data layer, known as XpoDefault.Session. If you choose to use this, all newly created Xpo objects will automatically use and share this default data layer. Of course, this method isn’t recommended by DevExpress and won’t really work well in multi-threaded or ASP.NET applications.

Instead of relying on XpoDefault.Session, we’ll create an XpoDefault.DataLayer instance and actually set the XpoDefault.Session to null to ensure that it’s never used. It’s safe to use the XpoDefault.DataLayer static instance, but you could choose to provide a reference to your data layer when instantiating a Session or UnitOfWork object if you wanted.

We have two options to choose from when it comes to creating this XpoDefault.DataLayer:

  • SimpleDataLayer – Used by default.Use this DAL if you need to modify the object metadata at runtime.
  • ThreadSafeDataLayerUse this DAL if multiple threads are allowed to access data in a data store.

Source: IDataLayer documentation

We’ll choose to use the ThreadSafeDataLayer in the event that we want to use threading in our application.

Beyond that, we need to provide a Data Store–namely our database server. We can provide a connection string to the ThreadSafeDataLayer constructor so that it knows where it’s getting and saving our data to/from:

/// <summary>
/// Sets the application's default data layer
/// </summary>
internal override void SetDataLayer()
{
    string ConnectionString = "Data Source=MySqlServer\\sqlexpress;integrated security=SSPI;initial catalog=DxContactList";

    XPDictionary dictionary = MetaDataDictionary.GetMetaDataDictionary();
    XpoDefault.DataLayer = new ThreadSafeDataLayer(dictionary, XpoDefault.GetConnectionProvider(ConnectionString, AutoCreateOption.DatabaseAndSchema));
    XpoDefault.Session = null;

}   //End the SetDataLayer() method

We’ll invoke that method from the Program class when the application starts up, and we’re essentially done at this point. Any new Session or UnitOfWork component used within our application will automatically connect via this data layer.

In part three of this series, we’ll begin building our Xpo business class objects.

Creating a sample WinForms application, part two: the data access layer