tags:

views:

1648

answers:

2

I'd like a textbox that allows for certain text within to be "constant" and uneditable while the rest of the text is editable. For instance, I'd like to define a template like this:

<Name:>[]
<Address:>[] <City>:[]

So that the user could later enter:

<Name:>[Stefan]
<Address:>[Nowhere] <City>:[Alaska]

But not:

<I'm typing here lol:>[Stefan]
<Address:>[Nowhere] <State>:[Alaska]

Ideally, they wouldn't even be able to put their cursor in between the <>, similar to Microsoft Word templates.

Any ideas? The masked textbox control seems to be along the right path, but isn't multiline and doesn't allow you to enter a variable number of characters in between the braces, for instance.

Thanks in advance.

+3  A: 

I don't know of any ready-made components. But you could try this simple method.

  1. Create a normal multi-line text box control
  2. Create a regex-based template that has (.*) or ([a-z]*) or whatever wherever you want the user to add text.
  3. Whenever the text is changed, checked if it still matches the regex. If it does, accept the chagne. If it doesn't, reject the change.

You can then use the same regex to extract your data from the text box.

You could even use a RichTextBox and use the regex to perform formatting.

Update Here's what I wrote. It seems to work:

[DefaultProperty("Regex")]
public partial class MaskedEdit : UserControl
{
    private Regex regex = new Regex("");
    private bool myChange = false;

    private string goodText;
    private Font dataFont;

    public MaskedEdit()
    {
        myChange = true;
        InitializeComponent();
        myChange = false;
        dataFont = new Font(Font, FontStyle.Bold);
        goodText = Text;
    }

    [Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design", typeof(UITypeEditor)), Localizable(true)]
    [DefaultValue("")]
    public String Regex
    {
        get { return regex.ToString(); }
        set
        {
            if (value != null)
            {
                regex = new Regex(value);
            }
        }
    }

    [EditorBrowsable(EditorBrowsableState.Always)]
    [Browsable(true)] 
    [Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design", typeof(UITypeEditor)), Localizable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public override string Text
    {
        get { return rtf.Text; }
        set {
            int selSt = rtf.SelectionStart;
            int selLen = rtf.SelectionLength;

            rtf.Text = value;

            rtf.SelectionStart = selSt;
            rtf.SelectionLength = selLen;
        }
    }

    private void rtf_TextChanged(object sender, EventArgs e)
    {
        if (myChange) return;
        Match m = regex.Match(Text);
        if (m.Success)
        {
            goodText = Text;
            Colorize(m);
        }
        else
        {
            myChange = true;
            Text = goodText;
            myChange = false;
            m = regex.Match(Text);
            if (m.Success)
            {
                Colorize(m);
            }
        }
    }

    public IEnumerable<string> Data
    {
        get
        {
            Match m = regex.Match(Text);
            bool first = true;
            foreach (Group g in m.Groups)
            {
                if (first) { first = false; continue; }
                yield return Text.Substring(g.Index, g.Length);
            }
        }
    }

    private void Colorize(Match m)
    {
        int selSt = rtf.SelectionStart;
        int selLen = rtf.SelectionLength;

        rtf.SelectionStart = 0;
        rtf.SelectionLength = rtf.TextLength;
        rtf.SelectionFont = Font;
        bool first = true;
        foreach (Group g in m.Groups)
        {
            if (first) { first = false; continue; }
            rtf.SelectionStart = g.Index;
            rtf.SelectionLength = g.Length;
            rtf.SelectionFont = dataFont;
        }

        rtf.SelectionStart = selSt;
        rtf.SelectionLength = selLen;
    }

    private void MaskedEdit_FontChanged(object sender, EventArgs e)
    {
        dataFont = new Font(Font, FontStyle.Bold);
    }
}
Frank Krueger
A variation on your answer seems to be the ticket. We thought of regex before, but not to accept or reject changes. Thanks!
Stefan Mai
And code?! Beautiful.
Stefan Mai
+1  A: 

I would suggest using multiple controls instead of just one. A single text box would require the user to cursor over to the next field, whereas multiple controls would allow the user to tab between fields, which is expected behavior. If you want to make it look like one control (perhaps to make it look like an address label, or something), you can use borderless textboxes and put the labels and textboxes all within a panel that has a border and a SystemColors.Window background. For an extra effect, it may be nice to put lines underneath each textbox, which is easily done with a 1-pixel high label with a BorderStyle.FixedSingle border.

P Daddy