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

Visualizing data with the XtraMap control

One important tool in my company’s flagship software offers users the ability to search for trucks and visualize that information on map to see what is available and where it is. It’s easy to display results to a grid control, but looking at tabular data over and over can be a little boring. Mixing in another control, such as the XtraMap control provides us with the ability to visualize these results in an easy-to-understand & interactive manner.

Here’s a basic overview of how it might look in my software:

XtraMap example
XtraMap example

There’s a basic set of editors in which a user can enter some search criteria and then search results are displayed with a GridControl to the left, and an XtraMap to the right. Clicking on a row show zoom us to the selected result marker on the map and vice versa. We should also make use of the tooltip ability for map markers to display some additional information when a user hovers over a marker.

To simplify things, I’ve provided a basic Xml data source with the project file that will have our “search” results. To get started, we’ll first drop an XtraLayoutControl onto the form to help us with arranging our controls.

I’ve created a LayoutGroup at the top of the form to arrange where our search criteria controls would (we’ll just execute the search with a button since our results are hardcoded), and the bottom portion of the form will have another LayoutGroup containing the GridControl and XtraMap control. Drop both of these onto your form via the VS toolbox–both are contained within the Data & Analytics tab. At this point, we have the skeleton of our interface:

form1Out of the box, the XtraMap control offers the ability to connect to 2 different map providers: Bing Maps and OpenStreetMap. I’d advise you to consult both of these providers to learn the terms and conditions of using their services. Bing Maps will require a developer account; OpenStreetMap will work immediately without the need for an account. For this reason, will choose OpenStreetMap as our provider for this example.

One common topic that pops up on the DevExpress Support Center concerns the ability to connect the XtraMap control to Google Maps. While it’s technically possible (after all, the XtraMap control simply issues an HttpWebRequest to retrieve map tiles and then displays them), it does violate Google’s terms of service for their mapping service and is beyond the scope of this example.

To connect to OpenStreetMap, click on the MapControl’s Smart Tag and click on the “Connect to OpenStreetMap Server” link:

connecttoopenmapAt this point, your XtraMap control is fully operational. Start your project and you will see that map tiles are loaded and you have the ability to pan and zoom your map. When you connected to OpenStreetMap, the XtraMap control automatically generated an Image Tiles Layer for your control. This single layer is responsible for arranging the images returned from OpenStreetMap and draws our map.

From here, we’ll handle the SimpleButton’s Click event, load the Xml into a DataSet and assign it to the GridControl’s DataSource property. We now have a grid with search results, but that’s only half of what we want. We’d like to display a little icon in the map that corresponds to each of the results, so how is that accomplished?

First, we need add another layer to our MapControl. For our purposes, we’ll use the VectorItemsLayer type. I’d recommend reviewing the Layers documentation to see a quick overview of the various layer types available and when & how you should use them.

This can be done via the designer or at runtime; we’ll choose the latter just so we can see what is happening:

/// <summary>
/// Creates a Vector Items Layer on which to display equipment icons
/// </summary>
private void CreateVectorLayer()
{
    VectorItemsLayer itemsLayer = new VectorItemsLayer();
    ListSourceDataAdapter dataAdapter = new ListSourceDataAdapter();

    dataAdapter.Mappings.Latitude = "OriginLatitude";
    dataAdapter.Mappings.Longitude = "OriginLongitude";
    itemsLayer.ItemImageIndex = 0;

    //Map the attributes used for the tooltip
    dataAdapter.AttributeMappings.Add(new MapItemAttributeMapping() { Member = "CompanyName", Name = "CompanyName" });
    dataAdapter.AttributeMappings.Add(new MapItemAttributeMapping() { Member = "MCNumber", Name = "MCNumber" });
    dataAdapter.AttributeMappings.Add(new MapItemAttributeMapping() { Member = "MatchID", Name = "MatchID" });
    dataAdapter.AttributeMappings.Add(new MapItemAttributeMapping() { Member = "CompanyContact.ContactMethodValue", Name = "ContactMethod" });

    //Set up the tooltip pattern for the truck icons
    itemsLayer.ToolTipPattern = "<b>{CompanyName}</b>\r\nMC/DOT #{MCNumber}\r\n{ContactMethod}";
    itemsLayer.Data = dataAdapter;
    mapControl1.Layers.Add(itemsLayer);

}   //End the CreateVectorLayer() method

Let’s break it down:

As expected, we create an instance of the VectorItemsLayer class and additionally an instance of the ListSourceDataAdapter class. The ListSourceDataAdapter is what is responsible for providing data to the XtraMap and shares the GridControl’s data source. Before we provide all that, we’ll see at basic properties to tell it what to do with that data that it will be receiving:

  • Mappings.Latitude – We set this the property in our data source (remember that Xml file?) which contains the latitude co-ordinates.
  • Mappings.Longitude – Similar to above, we set this to the property in the data source containing the longitude co-ordinates.
  • ItemImageIndex – I’ve created an ImageCollection component containing any images that I’d like to draw onto the map. Right now this is just a single png image and I’ve set the XtraMap control’s ImageList property to this ImageCollection. I then tell the VectorItemsLayer which image within the collection is going to correspond to our data source items.
  • I create a collection of MapItemAttributeMapping objects which maps items in the data source to attributes of a MapItem. It’s a little confusingly named since we’re dealing with Maps and mappings. We can create as many attributes as we’d like and connect them to a property in the data source. The Member property needs to match a property/field in the data source, and the Name property is what the VectorItemsLayer will use to identify it.
  • We create a tooltip pattern to be used when our users hover over one of these map items. Some limited HTML is supported (you’ll need to use a ToolTipController component for this) and we place our MapItemAttribute names within { } brackets as placeholders to be populated at runtime when the tooltip is created.
  • Finally, we set the data source for the VectorItemsLayer to this ListSourceDataAdapter and add it all to the MapControl.

We’ll invoke this method from the SimpleButton’s click handler after we set the GridControl’s data source. If we run the application now we’d expect both the GridControl and the XtraMap control to show the search results. This doesn’t happen though, because we never actually set the data source for the ListSourceDataAdapter! We told it WHAT to display and we told the VectorImageLayer HOW to display it, but we’re missing that one last link:

(itemsLayer.Data as ListSourceDataAdapter).DataSource = (grdResults.DataSource);

The GridView’s DataSourceChanged event is a good place to use that code to provide data for the layer’s data adapter.

Now when we run the application, our truck image appears for each of the results and the tooltips are nicely formatted to match the ToolTipPattern we created.

Let’s add in that synchronization between the map and grid that we talked about in the beginning. The first thing we want to handle is when a user clicks on an item in the MapControl. The MapControl exposes a MapItemClick event that will be fired when a user clicks on the truck icons. I’ll handle this event, find the item in the GridControl that matches what the user clicks on and focus that.

/// <summary>
/// Map Item click event handler
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void mapControl1_MapItemClick(object sender, MapItemClickEventArgs e)
{
    if (IsItemSelecting == true)
        return;

    string MatchID = e.Item.Attributes["MatchID"].Value.ToString();
    int RowHandle = gvResults.LocateByDisplayText(0, gvResults.Columns["MatchID"], MatchID);

    if (gvResults.IsValidRowHandle(RowHandle))
    {
        IsItemSelecting = true;
        gvResults.ClearSelection();
        gvResults.FocusedRowHandle = RowHandle;
        gvResults.SelectRow(RowHandle);
        gvResults.MakeRowVisible(RowHandle);
        gvResults.Focus();
        IsItemSelecting = false;
    }

}   //End the mapControl1_MapItemClick() method

The IsItemSelecting property is a simple Boolean flag that I created and its use will be apparent in just a bit. The important take-away from the above snippet is that we want to have some sort of unique ID for each search result/map item so that we can find it within the GridControl and focus it. The flip-side to this is focusing a map item when a user clicks on a grid row. First, wire up a FocusedRowChanged event handler for the results GridView:

/// <summary>
/// Focused Row Changed event handler for the results GridView
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void gvResults_FocusedRowChanged(object sender, DevExpress.XtraGrid.Views.Base.FocusedRowChangedEventArgs e)
{
    if (IsItemSelecting == true)
        return;

    DataRow selectedResult = gvResults.GetFocusedDataRow();

    IsItemSelecting = true;

    SelectEquipmentIcon(selectedResult);

    IsItemSelecting = false;

}   //End the gvResults_FocusedRowChanged() method

In here we first check that IsItemSelecting flag. If a user clicks on an item from the map control, this event would be fired and vice versa resulting in an infinite loop. We want to make sure that don’t keep going back and forth like this, thus the need for that Boolean flag.

We’ll retrieve the DataRow object that corresponds to the selected row and pass this to the SelectEquipmentIcon method to zoom in on the truck that goes with the selected row:

/// <summary>
/// Selects the provided truck on the map
/// </summary>
/// <param name="selectedResult">A DataRow corresponding to the selected Grid row</param>
public void SelectEquipmentIcon(DataRow selectedResult)
{
    double SelectedLatitude = 0.0d;
    double SelectedLongitude = 0.0d;

    SelectedLatitude = Convert.ToDouble(selectedResult["OriginLatitude"]);
    SelectedLongitude = Convert.ToDouble(selectedResult["OriginLongitude"]);

    mapControl1.Zoom(1, true);
    mapControl1.CenterPoint = new GeoPoint(SelectedLatitude, SelectedLongitude);
    mapControl1.Zoom(10, true);

    VectorItemsLayer itemsLayer = mapControl1.Layers[1] as VectorItemsLayer;

    itemsLayer.SelectedItem = itemsLayer.GetMapItemBySourceObject(selectedResult);

}   //End the SelectEquipmentIcon class

And with that, we have a finished product! As always, the source code is provided with this post: XtraMapSearchResults

Visualizing data with the XtraMap control

Extending XtraEditors for fun and profit

One question that I see pop up every now and then in the DevExpress Support Center deals with changing the default behavior of the various controls offered in the XtraEditors suite. Many people are looking for a static property that can override the default settings of the controls so that they don’t need to constantly set the same properties for controls that are used over and over again. While there are some static properties and methods that can control behavior, for the most part you’re on your own when it comes to changing how the editors work on an application-wide basis.

To make things easier, I often find myself extending the DevExpress controls to suit my own requirements. The various controls offered in the XtraEditors suite are for the most part descendants of the standard .NET controls offered in System.Windows.Forms namespace; for instance, the XtraEditors TextEdit is just a descendant of the System.Windows.Forms.TextBox control, albeit with a lot of additional functionality and styling added.

The simplest method of building our own extended control library would be to just create a class that derives from one of the DevExpress controls:

public class MyTextEdit : DevExpress.XtraEditors.TextEdit
{
    public MyTextEdit() : base() { }
}

So with that, we have the beginnings of a basic extension of the TextEdit.

I’ve come to embrace the idea of changing user-input into upper case for the sake of consistency. To handle this, the TextEdit control offers a nifty feature in the form of the CharacterCasing property. You can set this property to CharacterCasing.Upper for each instance of the TextEdit control to automatically change the user’s input to upper case regardless of their CAPS LOCK setting.

Doing this is easy enough, but it kind of violates the principle we’re trying to establish here. If you have a dozen forms, each containing 10 TextEdit controls, do you really want to repetitively change the same property over and over? Instead, let’s build a basic TextEdit descendant that automatically defaults to upper case!

    /// <summary>
    /// A TextEdit control which defaults to upper case text editing
    /// </summary>
    [ToolboxItem(true)]
    [Description("Creates a TextEdit control which defaults to upper case text editing")]
    public class UpperCaseTextEdit : TextEdit
    {

        /// <summary>
        /// Initializes a new instance of the UpperCaseTextEdit class
        /// </summary>
        public UpperCaseTextEdit()
            : base()
        {
        }



        /// <summary>
        /// OnCreateControl event handler
        /// </summary>
        protected override void OnCreateControl()
        {
            base.OnCreateControl();

            Properties.CharacterCasing = System.Windows.Forms.CharacterCasing.Upper;

        }   //End the OnCreateControl() method


    }   //End the UppercaseTextEdit class

Let’s have a look at what’s going on here.

First, we’ve decorated our class with the [ToolboxItem(true)] attribute. This tells Visual Studio that the control should be made available in the toolbox window. Visual Studio is also nice enough to automatically look through your solution for any controls which have this attribute and place them in the toolbox for immediate use.

Aside from that, the most important thing to do is to place your default behavior within the control’s OnCreateControl event handler. If you don’t, you may find that your settings are just overwritten at runtime.

This is all and good, but many times we edit our data within a GridControl. If we want our new UpperCaseTextEdit control to be usable as a GridControl repository item, we’ll need to define our own repository item type.


    /// <summary>
    /// 
    /// </summary>
    [UserRepositoryItem("Register")]
    public class RepositoryItemUpperCaseTextEdit : RepositoryItemTextEdit
    {

        #region Internal members

        /// <summary>
        /// Editor name
        /// </summary>
        internal const string EditorName = "UpperCaseTextEdit";

        #endregion

        #region Public accessors

        /// <summary>
        /// Gets the EditorTypeName of this RepositoryItemTextEdit editor
        /// </summary>
        public override string EditorTypeName
        {
            get { return EditorName; }
        }

        #endregion


        /// <summary>
        /// Static constructor
        /// </summary>
        static RepositoryItemUpperCaseTextEdit()
        {
            Register();
        }


        /// <summary>
        /// Initializes a new instance of the RepositoryItemUpperCaseTextEdit class
        /// </summary>
        public RepositoryItemUpperCaseTextEdit()
            : base()
        {

        }


        public override System.Windows.Forms.CharacterCasing CharacterCasing
        {
            get
            {
                return System.Windows.Forms.CharacterCasing.Upper;
            }
            set
            {
                base.CharacterCasing = value;
            }
        }


        /// <summary>
        /// Registers this editor with the designer
        /// </summary>
        public static void Register()
        {
            EditorRegistrationInfo.Default.Editors.Add
                (
                    new EditorClassInfo(EditorName, typeof(UpperCaseTextEdit),
                    typeof(RepositoryItemUpperCaseTextEdit), typeof(TextEditViewInfo),
                    new TextEditPainter(), true, null)
                );

        }   //End the Register() method

    }   //End the RepositoryItemUpperCaseTextEdit class

Most of this code is going to be pretty boiler-plate as you build your own extended control library. You must provide an EditorTypeName, register it in a static constructor and provide an implementation of the Register method. The Register method is going need to know the name of the editor, the type (this is your control class) and what DevExpress ViewInfo and Painter to use. How do I know which ViewInfo or Painter to use? For the most part, it’s pretty self-explanatory, but there are some caveats. For instance, a LookUpEdit descendant needs to use the ButtonEditPainter. Lucky for us, DevExpress provides a handy chart viewable in the Custom Editors documentation.

One more thing of note: you’ll notice that I’ve overrode the CharacterCasing property in the RepositoryItemUpperCaseTextEdit class to have it return CharacterCasing.Upper by default. This is a better solution than setting the property in the OnCreateControl event.

What if we’d like to create a control with some additional properties? When my users need to enter a location address, I present them with a drop-down list containing the abbreviations of the US and Mexican states as well as Canadian provinces. After all, who can remember that “MI” is Michigan and not Missouri? To simply this data entry, let’s create a ComboBoxEdit descendant which is pre-filled with these abbreviations and has some additional properties to determine if we want to show Mexican states or Canadian provinces.

We’ll start by creating a control that derives from the ComboBoxEdit class and we’ll add in a few new members:


        /// <summary>
        /// Include USA flag
        /// </summary>
        private bool _IncludeUSA;

        /// <summary>
        /// Include Canada flag
        /// </summary>
        private bool _IncludeCanada;

        /// <summary>
        /// Include Mexico flag
        /// </summary>
        private bool _IncludeMexico;

That helps us within the class, but we are going to want those properties to be exposed at design-time via the Visual Studio properties window. Let’s add some public accessors to expose the private members:


        /// <summary>
        /// Gets or sets a value indicating if US states should be included in the ComboBoxEdit
        /// </summary>
        [Browsable(true)]
        [PropertyTab("Geography", PropertyTabScope.Component)]
        [Description("Gets or sets a value indicating if US states should be included in the ComboBoxEdit")]
        public bool IncludeUSA
        {
            get { return _IncludeUSA; }
            set { _IncludeUSA = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating if Canadian provinces should be included in the ComboBoxEdit
        /// </summary>
        [Browsable(true)]
        [PropertyTab("Geography", PropertyTabScope.Component)]
        [Description("Gets or sets a value indicating if Canadian provinces should be included in the ComboBoxEdit")]
        public bool IncludeCanada
        {
            get { return _IncludeCanada; }
            set { _IncludeCanada = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating if Mexican states should be included in the ComboBoxEdit
        /// </summary>
        [Browsable(true)]
        [PropertyTab("Geography", PropertyTabScope.Component)]
        [Description("Gets or sets a value indicating if Mexican states should be included in the ComboBoxEdit")]
        public bool IncludeMexico
        {
            get { return _IncludeMexico; }
            set { _IncludeMexico = value; }
        }

The important take-away from the above snippet is to decorate your properties with the [Browsable(true)] attribute. Without this, Visual Studio will not display these properties in the designer.

If you want to pre-populate a list editor, do so within the OnLoaded event:

        /// <summary>
        /// On loaded event handler
        /// </summary>
        protected override void OnLoaded()
        {
            Properties.TextEditStyle = DevExpress.XtraEditors.Controls.TextEditStyles.DisableTextEditor;
            LoadStates(_IncludeUSA, _IncludeCanada, _IncludeMexico);

            base.OnLoaded();

        }   //End the OnLoaded() method

Here you’ll see that we’re disabling the ability for end-users to enter their own text within the drop-down by default. We certainly don’t want them inventing their own state/province abbreviations! From there, we invoke the LoadStates method and pass in our Boolean flags that indicate what should be contained within the drop-down. I chose to NOT populate the list within this event because I’d like to be able to reload the list at runtime; for instance, if we have another drop down called “Country”, it doesn’t make sense to show Canadian provinces if the user selects “United States”. At runtime, I can call the StateComboBoxEdit’s LoadStates method to change what is visible in the list.

The LoadStates method just builds a basic object array to be assigned to the control’s Items collection:

        /// <summary>
        /// Populates the State ComboBoxEdit with states/provinces
        /// </summary>
        /// <param name="includeCanada">True to include Canadian provinces</param>
        public void LoadStates(bool includeUSA, bool includeCanada, bool includeMexico)
        {
            //Clear existing items
            if (DesignMode == true || Properties.Items.Count > 0)
                Properties.Items.Clear();

            //Add states/provinces
            if (includeUSA == true)
            {
                Properties.Items.AddRange(new object[] 
                { 
                    "AK",
                    "AL",
                    "AP",
                    "AR",
                    "AS",
                    "AZ",
                    "CA",
                    "CO",
                    "CT",
                    "DC",
                    "DE",
                    "FL",
                    "FM",
                    "GA",
                    "GU",
                    "HI",
                    "IA",
                    "ID",
                    "IL",
                    "IN",
                    "KS",
                    "KY",
                    "LA",
                    "MA",
                    "MD",
                    "ME",
                    "MH",
                    "MI",
                    "MN",
                    "MO",
                    "MP",
                    "MS",
                    "MT",
                    "NB",
                    "NC",
                    "ND",
                    "NE",
                    "NF",
                    "NH",
                    "NJ",
                    "NM",
                    "NT",
                    "NU",
                    "NV",
                    "NY",
                    "OH",
                    "OK",
                    "OR",
                    "PA",
                    "PR",
                    "PW",                
                    "RI",
                    "SC",
                    "SD",                
                    "TN",
                    "TX",
                    "UT",
                    "VA",
                    "VI",
                    "VT",
                    "WA",
                    "WI",
                    "WV",
                    "WY"                
                });
            }

            if (includeCanada == true)
            {
                Properties.Items.AddRange(new object[]
                {
                    "AA",
                    "AB",
                    "AE",
                    "BC",
                    "MB",
                    "NS",
                    "ON",
                    "PE",
                    "PQ",
                    "QC",
                    "SK",
                    "YT"
                });
            }

        }   //End the LoadStates() method

The only thing of note in this method is that I first check if we’re in design-mode or if the list already has items in it. If you don’t do this you’ll find that every time the list is opened (or created as you switch from design view to code view and back-and-forth) your items will be added again. This is obviously not ideal, so take care to always perform this check with list editors!

Take some time and look at your application to see what editors are duplicated to perform common tasks and you may find that it’s worthwhile to create your own control assembly. Personally, I’ve built some useful editors such as:

  • CountryComboBoxEdit – a basic drop down with a list of countries
  • DataLayoutControlEx – an extension of the DataLayoutControl which disables end-user manipulation by default, is automatically set to DockStyle.Fill, focuses an editor when its corresponding caption is clicked (similar to a <label> tag in Html) and overrides the default behavior to use my UpperCaseTextEdit in lieu of the TextEdit when an item is bound to a string type.
  • EmailAddressEdit and UrlEdit :  ButtonEdit descendants which have a default button that opens the user’s email editor with the control’s text set as the mail to, or opens a browser with the provided Url.
  • PhoneNumberEdit: a TextEdit control which has a default RegEx mask set for North American phone numbers.

Download the full source code for our examples here and experiment! CustomEditors

Extending XtraEditors for fun and profit

Overriding XtraReport or print preview toolbar commands

I happened upon a cool question on the Support Center the other day, asking how to override the default functionality in the Print Preview window to email a document via Gmail. If you look at the default Print Preview (in either ribbon or standard toolbar configuration), you’ll see that you’re given the option to email the document in various formats (Pdf, Xls, RTF etc…): Print Preview window Doing so will typically open a dialog window which prompts you to save the file to your computer before your default email program is opened and the file is attached. This is all done via MAPI, which may not be the method you want to take. It opens the user’s default email client and constructs a blank message with the file attached. Historically, DevExpress used the 32-bit version of MAPI which causes issues if you were to build your application to 64 bit. Additionally, you may not want to save the file to your computer before sending it because then you’ll need to delete it afterwards. And if your end user doesn’t have a default email program set up on their PC, they’ll be prompted with the Windows email wizard which attempts to walk them through the process of setting up an email account. So what if you’d prefer the document to be emailed through your server or a third-party server like Gmail? At first glance you have a couple of options:

  • Create a form, place a DocumentViewer component on the form and then create your own toolbar. You’ll have to provide your own implementation for each of the various commands that are offered by the built-in Document Viewer toolbar.
  • Do the same as above, but create the default Document Viewer toolbar (or ribbon control) via the smart tag. Then, assign an ItemClick event handler to the functions you’d like to control. The problem is that your code will run first and then the default button command will run afterwards. Hardly ideal.
  • Again, create a form with a Document Viewer but remove the BarButtomItems that you want to override. Insert your own BarButtonItems for the commands that you’d like to control. There’s nothing inherently wrong with this approach unless you want to use the built-in print-preview methods offered by controls like the XtraGrid or the XtraTreeList. In this case, they’ll use the default print preview window rather than your custom one.

So it can be done, but I’m not sure if any of these options are really optimal. Fortunately for us, all of the BarButtomItems in the Document Viewer window implement an ICommandHandler interface which, when implemented, tells the Document Viewer window two important things:

  1. Can I handle this command? (done by implementing the CanHandleCommand method)
  2. How should I handle this command? (done by implementing the HandleCommand method)

All of this is detailed in the documentation topic for How to: Execute and Modify Commands in a Print Preview. So how can we apply this to the topic that was originally presented?

Assuming we have a simple project containing an XtraReport and we want to send a Pdf copy via Gmail through the standard toolbar, we’ll need to first create our own CommandHandler.

To create your own CommandHandler, create a new class in Visual Studio and name it “MyCommandHandler”. Your class will need to implement ICommandHandler. You can then tell Visual Studio to implement the interface, which will automatically insert a CanHandleCommand and a HandleCommand method.

Let’s start with the CanHandleCommand method:

public virtual bool CanHandleCommand(PrintingSystemCommand command, IPrintControl control)
{
    //This handler overrides the Send Pdf command.
    return command == PrintingSystemCommand.SendPdf;
}

This crux of this method is to tell the Document Viewer (or printing system) if this handler can process the provided command. The PrintingSystemCommand enum contains all of the commands that are available by the default toolbar/ribbon control. In this case, we’re telling the printing system that we can handle the SendPdf command ourselves and no default action is necessary.

That being said, we now have to do the heavy lifting ourselves and actually do something when the button is clicked. To do that, we provide an implementation for the HandleCommand method.

public virtual void HandleCommand(PrintingSystemCommand command, object[] args, IPrintControl control, ref bool handled)
{

    const string Username = &quot;YOUR_GMAIL_USERNAME_HERE&quot;;   //your username here
    const string Password = &quot;YOUR_GMAIL_PASSWORD_HERE&quot;; ///your password here

    if (CanHandleCommand(command, control) == false)
        return;

    using (MailMessage mailMessage = new MailMessage())
    {
        //Set to/from
        mailMessage.From = new MailAddress(&quot;email address here&quot;);
        mailMessage.To.Add(new MailAddress(&quot;email address here&quot;));
        mailMessage.Subject = &quot;Test message&quot;;
        mailMessage.Body = &quot;This is my test email!&quot;;

        //Create an attachment
        using (MemoryStream ms = new MemoryStream())
        {
            control.PrintingSystem.ExportToPdf(ms);
            ms.Position = 0;
            mailMessage.Attachments.Add(new Attachment(ms, &quot;MyFile.pdf&quot;, &quot;application/pdf&quot;));

            using (SmtpClient client = new SmtpClient())
            {
                client.Host = &quot;smtp.gmail.com&quot;;
                client.Port = 587;
                client.EnableSsl = true;
                client.DeliveryMethod = SmtpDeliveryMethod.Network;
                client.UseDefaultCredentials = false;
                client.Credentials = new NetworkCredential(Username, Password);

                client.Send(mailMessage);
            }
        }
    }

    //Ensure the default action isn't fired by setting Handled to True
    handled = true;
}

So here we’ve created a nice implementation that sends a Pdf copy of the document via email.

Note that I’ve decided to user a MemoryStream here instead of saving the Pdf file to the user’s PC first. This alleviates the need to save a file to the disk, but it does remove the ability to customize the Pdf. If you want to change some of the Pdf properties upon export, you can use the PdfExportOptions class offered by the ExportToPdf overload.

The only thing left to do is tell our PrintPreview window to actually use this CommandHandler. We do that via the PrintingSystem.AddCommandHandler method:

// Create a report instance, assigned to a Print Tool.
ReportPrintTool pt = new ReportPrintTool(new XtraReport1());

// Generate the report's document. This step is required
// to activate its PrintingSystem and access it later.
pt.Report.CreateDocument(false);

// Override the Send Pdf command.
pt.PrintingSystem.AddCommandHandler(new SendToGmailCommandHandler());

// Show the report's print preview.
pt.ShowPreview();

And just like that, we’ve overrided the default behavior and created our own implementation.

Going forward, we can also use this CommandHandler for print previewing other DevExpress controls such as the GridControl. By handling the GridView’s PrintInitialize event, we can insert our CommandHandler and reuse the same implementation:

private void gridView1_PrintInitialize(object sender, DevExpress.XtraGrid.Views.Base.PrintInitializeEventArgs e)
{
    (e.PrintingSystem as PrintingSystemBase).AddCommandHandler(new MyCommandHandler());
}

The full source code is available here: CustomCommandHandler

Overriding XtraReport or print preview toolbar commands

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

Let’s explore together!

Welcome to my new blog, DX-Developers.com! I started this blog to share some tips, tricks and cool sample applications with developers who are using the DevExpress suite of products..

First, a little about me:

I’m the Director of Information Technology for Logistic Dynamics, Inc. (LDi), a logistics brokerage firm headquartered in Buffalo, NY. I started with LDi in September of 2005 as the senior developer and have loved coming to work every day since. I’m primarily responsible for development work but I have my hand in a little bit of everything from accounting to marketing to golfing.

I’m also a DevExpress MVP (since 2009) and I spend quite a bit of time answering questions and providing support on the DevExpress Support Center.

I’ve used many of the various DevExpress products, but I’m most skilled with the WinForms, XtraReports, eXpress Persistent Objects (XPO) and ASP.NET products and this blog will primarily target those products.

With that out of the way, here’s what you can expect from my blog:

  1. How to perform commonly-requested tasks with DevExpress products
  2. How to extend the DevExpress products to provide new functionality
  3. Some cool tricks that you might not know you can do with the DevExpress products

The aim of this blog is not to be a general programming site. I’m assuming you already have intermediate knowledge in .NET, WinForms, HTML, ASP.NET and Sql. I’m not here to teach design patterns and best practices–I certainly don’t know them and even if I do, I probably don’t follow them. My samples will be provided in C# only; please do not ask me to convert it to VB.NET for you. I promise you that if you were to really look at a the sample it will make sense to you. The syntax isn’t THAT different or hard to discern and there are plenty of online converters available.

I’m also not going to respond to comments that are not related to the topic posted. Please don’t hijack a topic to ask how to do your homework. If you have a question and you think it would make a good topic for discussion, contact me!

I’m a bit new to blogging & WordPress, so bear with me as I get over these initial learning curves.

With all of that out of the way, let’s go going!

Let’s explore together!