Search code examples
c#winformsuser-controlswindows-forms-designermaskedtextbox

How to use built-in editors for a exposed properties in User Controls - Mask Property Editor Issue


I think there is a simple solution for my stupid question but I just can't solve it today.

I have a User Control that has a MaskedTextBox Control in itself. I have also exposed a few of its properties for the user to modify.

One of these properties is Mask property which I want to expose with the ability of starting an editor with predefined values like in a normal MaskedTextBox control.

So I created a public property InputMask and set up everything so that it can work but after showing the editor, I get an error dialog which contains this error:

Object reference not set to an instance of an object

If i don't use editor and copy a mask or set it trough code works without problems.

Here is a code sample:

...
MaskedTextBox maskedtextbox;
myUserControl()
{
    ...
    maskedtextbox = new MaskedTextBox(){
        some stuff...
    };
}

[DefaultValue("")]
[Editor("System.Windows.Forms.Design.MaskPropertyEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
[Localizable(true)]
[MergableProperty(false)]
[RefreshProperties(RefreshProperties.Repaint)]
public string InputMask
{
    get { return this.maskedtextbox.Mask; }
    set { this.maskedtextbox.Mask = value; }
}

Solution

  • In normal cases it's enough to register UI type editor and you don't need to do anything extra. But in MaskPropertyEditor case, when editing the property, the editor expect the the property belong to a MaskedTextBox and converts ITypeDescriptorContext.Instance to MaskedTextBox and since our editing Mask property belongs to our UserControl which is not a masked text box, a null reference exception will throw.

    To solve the problem, you need to create a custom UITypeEditor and override EditValue and edit Mask property of the private MaskedTextBox field. To do so, we need to create an instance of ITypeDescriptorContext containing the MaskedTextBox and pass it to EditValue method of the editor.

    Here is the implementations.

    UserControl

    public partial class UserControl1 : UserControl
    {
        MaskedTextBox maskedTextBox;
        public UserControl1()
        {
            InitializeComponent();
            maskedTextBox = new MaskedTextBox();
        }
    
        [Editor(typeof(MaskEditor), typeof(UITypeEditor))]
        public string Mask
        {
            get { return maskedTextBox.Mask; }
            set { maskedTextBox.Mask = value; }
        }
    }
    

    Editor

    public class MaskEditor : UITypeEditor
    {
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.Modal;
        }
        public override object EditValue(ITypeDescriptorContext context, 
                                         IServiceProvider provider, object value)
        {
            var field = context.Instance.GetType().GetField("maskedTextBox",
                           System.Reflection.BindingFlags.NonPublic | 
                           System.Reflection.BindingFlags.Instance);
            var maskedTextBox = (MaskedTextBox)field.GetValue(context.Instance);
            var maskProperty = TypeDescriptor.GetProperties(maskedTextBox)["Mask"];
            var tdc = new TypeDescriptionContext(maskedTextBox, maskProperty);
            var editor = (UITypeEditor)maskProperty.GetEditor(typeof(UITypeEditor));
            return editor.EditValue(tdc, provider, value);
        }
    }
    

    ITypeDescriptionContext Implementation

    public class TypeDescriptionContext : ITypeDescriptorContext
    {
        private Control editingObject;
        private PropertyDescriptor editingProperty;
        public TypeDescriptionContext(Control obj, PropertyDescriptor property)
        {
            editingObject = obj;
            editingProperty = property;
        }
        public IContainer Container
        {
            get { return editingObject.Container; }
        }
        public object Instance
        {
            get { return editingObject; }
        }
        public void OnComponentChanged()
        {
        }
        public bool OnComponentChanging()
        {
            return true;
        }
        public PropertyDescriptor PropertyDescriptor
        {
            get { return editingProperty; }
        }
        public object GetService(Type serviceType)
        {
            return editingObject.Site.GetService(serviceType);
        }
    }
    

    The project may need to be reloaded before Visual Studio can recognize the new UITypeEditor