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.
Figure 1: Add an instance of CustomEdit to the In-place Editor Repository.
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; }
}
}
}