Skip to main content
A newer version of this page is available. .
All docs
V22.1

How to: Manually Process User Input in Editors

  • 10 minutes to read

If you require a mask that is not included in the standard set of Input Masks, you can do one of the following:

  • Create and register a custom DevExpress.Data.Mask.MaskManager descendant.

  • Create an API-based mask that allows you to inspect and modify every edit attempted by a user.

This article describes the second technique.

Limitations

API-based masks can only be created for the following editors:

Implementation Techniques

You need to declare a method that processes a user action (edit). This method accepts information about this edit, and responds to it (accepts/rejects the change, or otherwise modifies the editor value). Implementation details depend on how you want to apply a custom mask to editors.

Specify an Action for an individual editor.
Use this techique when you need an API-based mask for one specific editor. If your users are able to invoke the “Mask Settings” dialog (see the EditMaskSettings method), masks implemented in this manner are not visible in this dialog.
Register a mask globally.
Create and register a separate class that encapsulates change processing logic. This technique adds your API-based mask to the set of masks available in the “Mask Settings” dialog. A registered mask can be reapplied to a large number of supported editors.

Unregistered Mask for an Individual Editor

This section demonstrates how to create a sample custom mask for a standalone TextEdit editor. The mask ensures the editor’s text always uses the title case.

Key Takeaways

  • You need to assign a custom Action to the editor’s EnableCustomMaskTextInput method. This Action is performed each time a user attempts to edit the editor value.

  • Your Action must be of the DevExpress.Data.Mask.CustomTextMaskInputArgs type. This class exposes public properties that allow you to check current and projected editor values, and identify what edit a user attempts to perform.

  • Call the SetResult method to manually set the final editor value.

  • Masks created in this manner are not available in the “Mask Settings” dialog.

Implementation Details

Whenever a user makes a change (types or erases a character, inserts a string, and so on), text editors invoke a callback passed to the EnableCustomMaskTextInput method. You can assign a custom Action to this method to override the default text processing logic. In other words, you can review and modify the editor’s new value.

textEdit1.Properties.EnableCustomMaskTextInput(args => {
    // Process user input
});

An assigned Action must be of the DevExpress.Data.Mask.CustomTextMaskInputArgs type. Objects of this type expose the following properties that allow you to obtain the current editor state:

textEdit1.Properties.EnableCustomMaskTextInput(args => {
    if (args.ActionType == CustomTextMaskInputAction.Delete) // Do something
});

Once you’ve checked the user operation type, and how this operation will affect the editor text, call one of the SetResult method overloads to assign a new editor value. For example, the following sample applies title case to the editor string.

textEdit1.Properties.EnableCustomMaskTextInput(args => {
    if(args.IsCanceled)
        return;
    var textInfo = CultureInfo.InvariantCulture.TextInfo;
    args.SetResult(textInfo.ToTitleCase(args.ResultEditText),
        args.ResultCursorPosition, args.ResultSelectionAnchor);
});

Global Mask Registration

This section demonstrates a “Letter Case Style” custom mask implementation. The mask includes a parameter that allows users to choose between upper case, lower case, and title case. After registration, the “Letter Case Style” mask becomes available for all TextEdit and ButtonEdit editors in the registered namespace.

Key Takeaways

  • Create a descendant of the DevExpress.Data.Mask.CustomTextMaskManager class and override its ProcessCustomTextMaskInput method. This method accepts objects of the same CustomTextMaskInputArgs type as Actions passed to individual editors (see the Unregistered Mask for an Individual Editor section). As a result, you should check the same properties and call the same SetResult method, as described in the previous section.

  • Masks created in this manner are available in the “Mask Settings” dialog (call the EditMaskSettings() method to display this dialog at runtime). For example, the following mask allows users to select a letter case style:

custom text mask available at runtime

  • After registration, masks become available for supported editors, but remain inactive (editors do not automatically apply these masks). To apply a registered mask in code, create a custom MaskSettings descendant and call the editor’s Configure method.

Implementation Details

Create a Custom Mask Manager

Create a custom DevExpress.Data.Mask.CustomTextMaskManager class descendant.

public class LetterCaseMaskManager : CustomTextMaskManager {
    // ...
}

A class constructor method should have parameters that match mask settings available to users. For example, the sample “Letter Case Style” mask has a selector that allows users to choose between three case styles (title case, all uppercase, and all lowercase). This mode is passed to the constructor as a single @case parameter.

public enum LetterCase {
    // Use the System.ComponentModel.Description attribute to alter public names of enum values
    Title,
    Upper,
    Lower
}

public class LetterCaseMaskManager : CustomTextMaskManager {
    readonly LetterCase @case;
    // Class constructor
    public LetterCaseMaskManager(
        LetterCase @case = LetterCase.Title) {
        this.@case = @case;
    }
}

Decorate your class constructor with the DevExpress.Data.Mask.MaskManager.Parameters attribute to specify a namespace whose editors should be able to use this custom mask. Additionally, this attribute allows you to set a public mask name (the name users can see in the “Mask Settings” dialog).

Public mask name

namespace DXApplication1 {

    public class LetterCaseMaskManager : CustomTextMaskManager {
        // Parameter #1: Project namespace and custom mask type
        // Parameter #2: Public mask name
        [Parameters("DXApplication1, DXApplication1.LetterCaseMaskManager", "Letter Case Style")]
        public LetterCaseMaskManager(
           // Class constructor parameters
        ) {
            // ...
        }
    }
}

Decorate constructor parameters with the DevExpress.Data.Mask.MaskManager.Parameter attribute to set up the visibility and public name of mask options.

Public mask parameter name

public class LetterCaseMaskManager : CustomTextMaskManager {
    readonly LetterCase @case;

    [Parameters("DXApplication1, DXApplication1.LetterCaseMaskManager", "Letter Case Style")]
    public LetterCaseMaskManager(
        [Parameter("case", "Case Style")]
        // Parameter #1: Option's name
        // Parameter #2: Option's public name
        // Parameter #3: Visibility (not set in this sample)
        LetterCase @case = LetterCase.Title) {
        this.@case = @case;
    }
}

Override Text Processing Logic

Override the ProcessCustomTextMaskInput method. This method uses a parameter of the same CustomTextMaskInputArgs type as the EnableCustomMaskTextInput method. See the Unregistered Mask for an Individual Editor section for the description of the type’s public properties.

protected override void ProcessCustomTextMaskInput(CustomTextMaskInputArgs args) {
    if (args.IsCanceled)
        return;
    var textInfo = CultureInfo.InvariantCulture.TextInfo;
    switch (@case) {
        case LetterCase.Title:
            args.SetResult(textInfo.ToTitleCase(args.ResultEditText),
                args.ResultCursorPosition, args.ResultSelectionAnchor);
            break;
        case LetterCase.Upper:
            args.SetResult(textInfo.ToUpper(args.ResultEditText),
                args.ResultCursorPosition, args.ResultSelectionAnchor);
            break;
        case LetterCase.Lower:
            args.SetResult(textInfo.ToLower(args.ResultEditText),
                args.ResultCursorPosition, args.ResultSelectionAnchor);
            break;
    }
}

Apply a Globally Registered Mask in Code

Create a custom MaskSettings descendant.

public abstract class LetterCaseMaskSettings : MaskSettings.User {
    public class CaseStyle : MaskSettingsWithCulture {
        protected override Type GetMaskManagerType() {
            return typeof(LetterCaseMaskManager);
        }
        public LetterCase Case {
            get { return GetValue<LetterCase>("case", LetterCase.Title); }
            set { SetValue("case", value); }
        }
    }
}

Use custom settings to set an active mask with the Configure method. See this article for more information: Input Masks.

var settings = textEdit1.Properties.MaskSettings.Configure<LetterCaseMaskSettings.CaseStyle>();
settings.Case = LetterCase.Title;

Complete Code

The complete code of the sample “Letter Case Style” mask registered for all supported editors is as follows:

using DevExpress.Data.Mask;
using DevExpress.XtraEditors.Mask;
using System;
using System.ComponentModel;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace DXApplication1 {
    public partial class Form1 : DevExpress.XtraEditors.XtraForm {
        public Form1() {
            InitializeComponent();
            // Apply a custom mask
            var settings = textEdit1.Properties.MaskSettings.Configure<LetterCaseMaskSettings.CaseStyle>();
            settings.Case = LetterCase.Title;
        }
    }


    public enum LetterCase {
        [Description("Title case")]
        Title,
        [Description("All caps")]
        Upper,
        [Description("All lowercase")]
        Lower
    }

    // Custom mask manager
    public class LetterCaseMaskManager : CustomTextMaskManager {
        readonly LetterCase @case;
        [Parameters("DXApplication1, DXApplication1.LetterCaseMaskManager", "Letter Case Style")]
        public LetterCaseMaskManager(
            [Parameter("case", "Case Style")]
            LetterCase @case = LetterCase.Upper) {
            this.@case = @case;
        }
        // Editor text processing logic
        protected override void ProcessCustomTextMaskInput(CustomTextMaskInputArgs args) {
            if (args.IsCanceled)
                return;
            var textInfo = CultureInfo.InvariantCulture.TextInfo;
            switch (@case) {
                case LetterCase.Title:
                    args.SetResult(textInfo.ToTitleCase(args.ResultEditText),
                        args.ResultCursorPosition, args.ResultSelectionAnchor);
                    break;
                case LetterCase.Upper:
                    args.SetResult(textInfo.ToUpper(args.ResultEditText),
                        args.ResultCursorPosition, args.ResultSelectionAnchor);
                    break;
                case LetterCase.Lower:
                    args.SetResult(textInfo.ToLower(args.ResultEditText),
                        args.ResultCursorPosition, args.ResultSelectionAnchor);
                    break;
            }
        }
    }

    // Custom mask settings (required for the Configure method)
    public abstract class LetterCaseMaskSettings : MaskSettings.User {
        public class CaseStyle : MaskSettingsWithCulture {
            protected override Type GetMaskManagerType() {
                return typeof(LetterCaseMaskManager);
            }
            public LetterCase Case {
                get { return GetValue<LetterCase>("case", LetterCase.Title); }
                set { SetValue("case", value); }
            }
        }
    }
}