A UserControl inheriting RepositoryItemTextEditor in a DevExpress Grid – Including Events

Having to get a RepositoryItemTextEditor behave as you would expect it to, sometimes can be cumbersome. I wanted a number format with a thousand separator plus two digits – AND I wanted the whole value to be selected once the editor was entered, not matter if this was by tabbing, mouse or programmatically.

After reading through some of the documentation and support issues I found out that a total of three events need to be handled to get the desired behaviour. Faced with six such editors in my grid I almost immediately decided to wrap wthings up in a UserControl editor.

My solution is based on this DevExpress documentation and my own experiments. Also, some googling was involved, but I can’t reproduce the details anymore.

So how do you extend a RepositoryItemTextEditor? First thing you need to know it is based on a TextEditor. When activated, the underlying TextEditor gets created. Therefore you need to inherit both of them – note the two classes in this definition: UniversalRepositoryItemTextEditMoney inheriting from RepositoryItemTextEdit and UniversalTextEditMoney inheriting from TextEdit:

    /**
    *     The RepositoryItemEditor
    */
    //The attribute that points to the registration method
    [UserRepositoryItem("Register")]
    public class UniversalRepositoryItemTextEditMoney : RepositoryItemTextEdit    // extend the type that best suites your needs
    {
        //The unique name for the custom editor
        public const string CustomEditorName = "UniversalRepositoryItemTextEditMoney";

        // a static constructor (I'm not sure what for)
        static UniversalRepositoryItemTextEditMoney()
        {
            Register();     // call the registration method on construction
        }

        public UniversalRepositoryItemTextEditMoney()
        {
            // some settings that are common, right here in the constructor
            base.Mask.MaskType = MaskType.Numeric;
            base.Mask.UseMaskAsDisplayFormat = true;
            base.Mask.EditMask = "n2";
        }
        
        //Return the unique type name
        public override string EditorTypeName { get { return CustomEditorName; } }

        //Register the editor
        public static void Register()
        {
            var ci = new EditorClassInfo(CustomEditorName,
                                         typeof (UniversalTextEditMoney),                         // the underlying editor, definition see below
                                         typeof (UniversalRepositoryItemTextEditMoney),           // the repository item itself
                                         typeof (TextEditViewInfo),                               // each of the editor types has its own view info, use the right one!
                                         new TextEditPainter(),                                   // use standard painter for TextEdit
                                         true,                                                    // design time visiblilty
                                         null,                                                    // an image for design time
                                         typeof (DevExpress.Accessibility.TextEditAccessible));   // accessibility information, be sure to use the right one
                                                                                                  // some more basic accessibility objects may be sealed 
                                                                                                  // and that results in a null pointer exception hard to find
            EditorRegistrationInfo.Default.Editors.Add(ci);
        }  
    }

    /**
    *  The Base Editor: required for the VS Toolbox
    */
    [ToolboxItem(true)]
    public class UniversalTextEditMoney : TextEdit
    {
        //The static constructor that calls the registration method
        static UniversalTextEditMoney() { UniversalRepositoryItemTextEditMoney.Register(); }

        //Initialize the new instance
        public UniversalTextEditMoney() { }

        //Return the unique name
        public override string EditorTypeName { get { return UniversalRepositoryItemTextEditMoney.CustomEditorName; } }

        //Override the Properties property
        //Simply type-cast the object to the custom repository item type
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public new UniversalRepositoryItemTextEditMoney Properties
        {
            get { return base.Properties as UniversalRepositoryItemTextEditMoney; }
        }
    }

Make sure to use your subclass type in all relevant places. The Base Editor class is only used once in the EditorClassInfo in the registration method. IN this case, I did not want entering the field by mouse to position the caret at any specific position within the number, instead all text should be selected for the user to start editing. I find this behaviour more according to non-specialist users expectations. Some tab, some click… besides, using the right and left keys you can move the caret to any position within the number.

In case there’s need for setting some properties in the context of the individual row and values at the time of creation, overriding the Assign method is the place to put that code.

And now for the Events part – there’s three of them required to implement the “select the whole number when starting to edit” behavior:

    [UserRepositoryItem("Register")]
    public class UniversalRepositoryItemTextEditMoney : RepositoryItemTextEdit
    {
        public const string CustomEditorName = "UniversalRepositoryItemTextEditMoney";

        private bool _enter;            // we need a flag indicating if we're in entering mode
        private bool _needSelect;       // and a flag to indicate if we need to select editor content

        static UniversalRepositoryItemTextEditMoney()
        {
            Register();
        }

        public UniversalRepositoryItemTextEditMoney()
        {
            base.Mask.MaskType = MaskType.Numeric;
            base.Mask.UseMaskAsDisplayFormat = true;
            base.Mask.EditMask = "n2";
            
            // register our events
            base.Enter += this_Enter;
            base.MouseUp += this_MouseUp;
            base.MouseDown += this_MouseDown;
        }

        // the event handlers
        public new event EventHandler Enter;
        public new event MouseEventHandler MouseUp;
        public new event MouseEventHandler MouseDown;
        
        public override string EditorTypeName
        {
            get { return CustomEditorName; }
        }
        
        public static void Register()
        {
            var ci = new EditorClassInfo(CustomEditorName,
                                         typeof (UniversalTextEditMoney),
                                         typeof (UniversalRepositoryItemTextEditMoney),
                                         typeof (TextEditViewInfo),
                                         new TextEditPainter(), true, null,
                                         typeof (DevExpress.Accessibility.TextEditAccessible));
            EditorRegistrationInfo.Default.Editors.Add(ci);
        } 
 
        // after we've handled our events we should forward these to everybody else in the chain, so we will need these
        private void OnEnter(EventArgs e)
        {
            if (Enter != null) Enter(this, e);
        }
        private void OnMouseUp(MouseEventArgs e)
        {
            if (MouseUp != null) MouseUp(this, e);
        }
        private void OnMouseDown(MouseEventArgs e)
        {
            if (MouseDown != null) MouseDown(this, e);
        }

        /**
        *   Handling the desired behaviour whether from mouse, tab or else.
        */
        private void ResetEnterFlag()
        {
            _enter = false;
        }

        // handling the this_Enter event
        private void this_Enter(object sender, EventArgs e)
        {
            _enter = true;
            var snd = sender as TextEdit;
                           // BeginInvoke with delegate insures the value is both set in EditorValue and displayed !
            if (snd != null) snd.BeginInvoke(new MethodInvoker(ResetEnterFlag)); 
            OnEnter(e);     // forward Event
        }

        // handling the this_MouseUp event
        private void this_MouseUp(object sender, MouseEventArgs e)
        {
            if (_needSelect)     // only when we come from a situation that requests selection
            {
                var snd = sender as TextEdit;
                if (snd != null) snd.SelectAll();
                OnMouseUp(e);       // forward Event
            }
        }

        // handling the this_MouseDown event
        private void this_MouseDown(object sender, MouseEventArgs e)
        {
            _needSelect = _enter;     // set the flag that we want selection when Mouse Button is going up again
            OnMouseDown(e);           // forward Event
        }
    }

    [ToolboxItem(true)]
    public class UniversalTextEditMoney : TextEdit
    {
        static UniversalTextEditMoney() { UniversalRepositoryItemTextEditMoney.Register(); }
        
        public UniversalTextEditMoney()
        {
        }
        
        public override string EditorTypeName { get { return UniversalRepositoryItemTextEditMoney.CustomEditorName; } }
        
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public new UniversalRepositoryItemTextEditMoney Properties
        {
            get { return base.Properties as UniversalRepositoryItemTextEditMoney; }
        }
    }

So now if we place this user control like any standard RepositoryItemTextEditor we find all the behaviour we want implemented: it displays bioth a thousand separator and two digits, plus it selects the whole number when entering edit mode.

Print Friendly, PDF & Email

Ein Gedanke zu “A UserControl inheriting RepositoryItemTextEditor in a DevExpress Grid – Including Events”

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Time limit is exhausted. Please reload CAPTCHA.