Blogs

Paul Kimmel's Blog

Creating a Custom Repository Editor with Validation

     

A customer asked about a Custom Repository Editor for “pilots”. Being a pilot it seems reasonable that one might enter flight times in a variety of formats—90, 1:30, or 1.5 each representing 90 minutes}, perhaps for a digital log book. One might want to enter a variety of formatted data for any individual field. A good general solution might be support for multiple formatting masks. Of course, this solution (in this blog) could also be implemented by just defining a Validating event for the control (XtraGrid) in this example, but creating a custom editor is a more interesting challenge.

A repository editor exists as an internal editor for container controls like the XtraGrid or XtraTreeList, and there is an external repository (PersistentRepository) component. You can define multiple editors in either place. A repository let’s you predefine edit controls for column data. When the container is placed in edit mode the editor associated through the column’s ColumnEdit property—if defined—is displayed and the settings defined are used. You have the option of using the default editor determined by the data type, one or more editor’s in an internal repository, an external repository, or defining a custom editor and registering that with the repository.

The example in this blog borrows from the help document at--Custom Editors—and extends that solution, internalizing the validation as a series of regular expressions. The input can be entered for the custom editor as a decimal value, a (very) short time value, or an integer. All non-integer values are converted into an integer representing the time as an integer. Thus, 1:30 is treated as an hour and thirty minutes, converted to 90 minutes, 1.5 is an hour and a half and again converted to 90 (minutes), and integer entries are left as is. (The basic code for the OnValidating method could be used in the Validating event handler for the container control if you don’t want to create a custom editor.)

The solution is in two parts: a class that inherits from an existing repository editor with the UserRepositoryItem attribute used for editing in the repository and a class that represents the editor when it is invoked and provides the additional validation/conversion behavior. Listing 1 shows the new repository editor, Listing 2 shows the runtime editor with the additional behaviors, in this instance validation. Listing 3 is a Form with an XtraGrid and a single List with a collection of “FlightTime” objects.

Listing 1: The RepositoryItemFlightTimeEdit class.

using System;
using DevExpress.XtraEditors.Repository;
using DevExpress.XtraEditors.Registrator;
using DevExpress.XtraEditors.ViewInfo;
using System.Drawing;
using DevExpress.XtraEditors.Drawing;
using System.ComponentModel;
using DevExpress.XtraEditors;

namespace AnyTimeEditor
{
  // see Custom Editors
  [UserRepositoryItem("RegisterCustomEdit")]
  public class RepositoryItemFlightTimeEdit : RepositoryItemTextEdit
  {
    static RepositoryItemFlightTimeEdit() { RegisterCustomEdit(); }
    public RepositoryItemFlightTimeEdit()
    {
      useDefaultMode = true;
    }

    public const string CustomEditName = "CustomEdit";
    public override string EditorTypeName
    {
      get
      {
         return CustomEditName;
      }
    }

    public static void RegisterCustomEdit()
    {
      Image img = null;
      // load image
      EditorRegistrationInfo.Default.Editors.Add(
        new EditorClassInfo(CustomEditName,
        typeof(CustomEdit),
        typeof(RepositoryItemFlightTimeEdit),
        typeof(TextEditViewInfo),
        new TextEditPainter(),
        true, img));
    }

    private bool useDefaultMode;
    public bool UseDefaultMode
    {
      get { return useDefaultMode; }
      set
      {
        if (useDefaultMode != value)
        {
          useDefaultMode = value;
          OnPropertiesChanged();
        }
      }
    }

    public override void  Assign(RepositoryItem item)
    {
      BeginUpdate();
      try
      {
        base.Assign(item);
        RepositoryItemFlightTimeEdit source = item as RepositoryItemFlightTimeEdit;
        if (source == null) return;
        useDefaultMode = source.UseDefaultMode;
      }
      finally
      {
        EndUpdate();
      }
    }
  }
}

In the repository the type of the repository editor is RepositoryItemFlightTimeEdit. UserRepositoryItem is used to define the registrator method name. The static constructor invokes the registrator method. The constructor sets the default values for properties (in this example.) The EditorTypeName provides a name for the editor. The Registrator is a helper that calls EditorRegistrationInfo.Default.Editors.Add to provide type information for the repository editor and the editor to create, providing information like a bitmap (if you have one handy). The code in Listing 1 also shows how you can define custom properties repository editors and store new property values (see the Assign method).

Listing 2: The CustomEdit Helper class.

using System;
using DevExpress.XtraEditors.Repository;
using DevExpress.XtraEditors.Registrator;
using DevExpress.XtraEditors.ViewInfo;
using System.Drawing;
using DevExpress.XtraEditors.Drawing;
using System.ComponentModel;
using DevExpress.XtraEditors;
using System.Text.RegularExpressions;

namespace AnyTimeEditor
{
  public class CustomEdit : TextEdit
  {

    //Initialize the new instance
    public CustomEdit()
    {
      //...
    }

    //Return the unique name
    public override string EditorTypeName
    {
      get
      {
        return
          RepositoryItemFlightTimeEdit.CustomEditName;
      }
    }

    //Override the Properties property
    //Simply type-cast the object to the custom repository item type
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public new RepositoryItemFlightTimeEdit Properties
    {
      get { return base.Properties as RepositoryItemFlightTimeEdit; }
    }
    protected override void OnValidating(CancelEventArgs e)
    {
      TextEdit edit = ((TextEdit)this);
      string text = edit.Text;

      int toMinutes = 0;

      // hh:mm
      bool hourMinute = Regex.IsMatch(text, @"^\d+:[0-5][0-9]$");

      e.Cancel = false;
      if (hourMinute)
      {
        toMinutes = ChangeTimeToMinutes(text);
        edit.Text = toMinutes.ToString();
        return;
      }
      // minutes
      bool decimalFormat = Regex.IsMatch(text, @"^\d*\.\d+$");
      if (decimalFormat)
      {
        toMinutes = ConvertDecimalToMinutes(text);
        edit.Text = toMinutes.ToString();
        return;
      }

      //decimal
      bool minutes = Regex.IsMatch(text, @"^\d+$");
      if (minutes)
        return;
      e.Cancel = true;
    }

    private int ConvertDecimalToMinutes(string text)
    {
      return ConvertDecimalToMinutes(Convert.ToDecimal(text));
    }

    private int ConvertDecimalToMinutes(decimal dec)
    {
      return Convert.ToInt32(dec * 60);
    }

    private int ChangeTimeToMinutes(string text)
    {
      return ChangeTimeToMinutes(DateTime.Parse(text));
    }

    private int ChangeTimeToMinutes(DateTime dateTime)
    {
      return dateTime.Hour * 60 + dateTime.Minute;
    }
  }
}

The RepositoryItemFlightTextEdit stores properties for repository editors and the registered class is the type of editor created. At runtime when the XtraGrid column associated with the listed editor is created a CustomEdit control will be created. EditorTypeName is used to generate a unique name for the editor in the code-behind. The public property Properties surfaces the constituent properties of RepositoryItemFlightTimeEdit, and the remaining code provides the validation/conversion behavior.

When the user changes the value of the editor OnValidating is called. Regular expressions are used to look for ##:## formats, #.## formats, or ## formats. The first two represent a short time and a decimal number. These are converted using basic arithmetic and stored back in the editor’s text field and ultimately the underlying data store.

To create the basic form to test the editor, compile the library containing the classes in Listings 1 and 2. Define a WinForms project. Place an XtraGrid on the form. Add one column and bind it to a field Named FlightTime. Using the XtraGrid Designer switch to the Repository tab and using the Add button add an instance of CustomEdit to the Repository—see Figure 1. Click the Main tab, Columns button, and associate the CustomEdit object with the (only) column’s ColumnEdit property—see Figure 2. Finally, define a class with a FlightTime field—to match the column’s FieldName property (see Listing 3)—and run and test the sample.

image
Figure 1: Add an instance of CustomEdit to the In-place Editor Repository.

image
Figure 2: Associate the editor with the column or columns that will use the new custom editor.

Listing 3: Test form with XtraGridControl.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using DevExpress.XtraEditors;

namespace CustomRepositoryEditor
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();

      List<FlightTime> times = new List<FlightTime>(){
        new FlightTime(10),
        new FlightTime(10),
        new FlightTime(10) };

      gridControl1.DataSource = times;

    }
  }

  public class FlightTime
  {
    /// <summary>
    /// Initializes a new instance of the FlightTime class.
    /// </summary>
    /// <param name="time"></param>
    public FlightTime(int time)
    {
      this.time = time;
    }

    private int time;
    public int Time
    {
      get { return time; }
      set { time = value; }
    }
  }

}

Published Mar 19 2010, 09:15 PM by Paul Kimmel (DevExpress)
Bookmark and Share

Comments

 

Neal said:

Outstanding Paul!  What a great demonstration of using a custom repository item!  If possible, can you make the source available?  It would be neat to see in action.

Another great topic that has been invaluable to me is creating an XtraGrid descendant.  I use this in my apps and it's so neat to see how to extend and customize the XtraGrid, or any other control for that matter.  I think more people should learn and implement descendant classes of the DXperience controls as it really empowers them to further tailor to their needs.

I could not be happier with my choice for using nothing but DevExpress controls!  I admit I feel sorry for other component vendors! :)

If I get Logbook Pro 2010 done within this decade, let me know and I'll give you a free copy! :)

Thanks again - nice work!

March 20, 2010 5:55 PM
 

Paul Kimmel (DevExpress) said:

Thanks Neal. I am glad you find it instructive. A couple of great people internally helped (Thanks Mike.)

March 20, 2010 10:53 PM
 

Sigurd Decroos said:

Very nice indeed!

You should add these extra editors in your arsenal (like treelistlookupedit, multicheck multicolumn dropdown, ...). You have the solutions more or less already, just make some editors out of them and add them to the XtraEditors. A lot of people would be very happy, including me :).

March 20, 2010 11:50 PM
 

Neal said:

Sigurd:

Did you make suggestions for these controls in the support center?  If so, post the links and I'll track them to add my vote.  My editor suggestion is also in the SC somewhere, maybe Paul & Co. now that the labor is done can move this work into a new editor possibly rename it "RepositoryItemTimeTracker" so it can be used for employee time sheets, source code control apps, i.e. anything that logs time spent on a task.  Another idea:  RepositoryItemTaskTimer?

Again Paul, please post the source, and for that matter, spread the word that on all of these blog tutorials a source link would be great.  Maybe link it to that demo runner system you all created so we can choose the language, etc.

Thanks!

March 21, 2010 9:24 AM
 

Paul Kimmel (DevExpress) said:

Neal:

I am in SoCal and the code is on a workstation in Michigan. When I get back I will post the project. Sigurd asked for a demo too.

March 22, 2010 12:19 PM
 

Sigurd Decroos said:

www.devexpress.com/.../Q104177.aspx

Here is my link about it (at the end), but I refer to 2 other links (haven't found those yet) in 2006 and 2008.

March 22, 2010 4:54 PM
 

Johnny Cunningham said:

Need more f# programing help, linear parellel programing

March 26, 2010 7:43 PM
 

Johnny Cunningham said:

need more f# releases, linear parellel programing is the only way to go to get full beta, and have use of the intire capasity of a digital gateway, the new fiber optic infrastucture will easily handle the load requierments, everone needs to stop thinking and imposing limitations on what can be accomplise, don't start thinking that our progress is anymore than a technology that is still in it's infancy

March 26, 2010 7:51 PM
 

Neal said:

Paul,

Review your RegEx, it looks like you have them backwards, i.e. minutes is using a decimal regex check.

Let me know when you have the source available or shoot me an e-mail please.  I assume you can find my e-mail address from this system?

Thx!

Neal

March 30, 2010 4:48 PM
 

Paul Kimmel's Blog said:

I don’t do the full-on tourism thing that much. If I am out and about and end up at a popular tourist

March 30, 2010 5:37 PM
 

Paul Kimmel (DevExpress) said:

Neal:

I think I posted with the regex in the wrong placess, but reposted right away. Any chance you have  cached page?!

March 30, 2010 6:05 PM
 

Neal said:

Paul,

No, not cached.  Here is the line I'm talking about:

bool minutes = Regex.IsMatch(text, @"^\d*\.\d+$");

minutes should not have a \. check in it, I think you have it mixed up with:

bool decimalFormat = Regex.IsMatch(text, @"^\d+$");

March 30, 2010 6:27 PM
 

Paul Kimmel (DevExpress) said:

Neal:

I missed that. When I switchhed the Regex I missed switching the variable names. The code works, but I switched the var name usage too.

March 30, 2010 7:21 PM
More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.