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

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

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

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

Getting started

If you’ve never worked with the DevExpress products before, you’ll want to download the free 30 day trial. The trial allows you full access to all of the DevExpress products and DevExpress graciously extends full support to you for those 30 days via the Support Center. The trial will work with Visual Studio and Visual Studio Express, but VS Express does not support all of the templates/wizards. Additionally, the newest version of DevExpress (14.1) requires .NET 4.0 and VS 2010. You may want to review the .NET Framework Support History documentation and the Visual Studio Support History documentation.

Once you’re up and running, try running the Demo Center to have a look at the various products and controls. There’s a lot going on so it can be a bit daunting, but most of the demos come with full C# and VB.NET source code, which is installed by default to C:\Users\Public\Documents\DevExpress Demos 14.1\Components\WinForms (at least for WinForms demos).

Note: Many of the DevExpress demos tend to be a bit obfuscated and do a lot of things that aren’t documented. While they’re a good example of what CAN be done, I don’t find them to be the best example of HOW to do a task.

Fortunately, DevExpress provides a lot of resources to help you learn. Unfortunately, they provide a lot of resources to help you learn. It can be a bit daunting at first, but you’ll find that you will have the most success if you can learn where to look for help.

Here are some great starting points for learning the ins-and-outs of the DevExpress products:

As a final tip, I recommend that users looking to search the support center/knowledge base use Google instead of the DevExpress search function. The DevExpress search feature leaves a bit to be desired, but searching Google with the -site:devexpress.com option works incredibly well.

Ready to go?

Getting started