Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

GuidTextBox

0.00/5 (No votes)
26 Oct 2016 3  
An edit control for the masked input of GUIDs

Introduction

This is a TextBox control especially overloaded for the input/editing of GUIDs.

Background

With my current project, I require a UITypeEditor for the generation and input of a GUID in one of my fields. I started building the editor, and was looking at the TextBox entry. It is basically a masked edit, so I thought I might use the MaskedEditControl. Does anyone actually use that? What a PITA that control is! Not only is there not an option for hexadecimal character input (which, I suppose, is kind of reasonable), when I tried to filter the input by processing the KeyDown event, I find that the control has already processed the input before it gets to the event, so trying to filter at that stage simply did not work. The bottom line is that I wrote my own, using the standard TextBox as a base.

What Was Needed

The control had a few basic requirements:

  • The "Value" field is a Guid structure - not a string.
  • It is a fixed-width string, so input to the control was always in overwrite mode. Insertion and deletion of characters is prohibited.
  • All character input is hexadecimal.
  • The input characters have to bypass the separator ('-') characters in the text. They are at fixed positions, so cannot be moved, deleted, or overwritten.
  • Handle clipboard operations is as logical a way as possible.

The GUID Property

Requirement 1 is simple enough. Create a public property with a Nullable<Guid> type, and populate the Text with its string representation. The property is Nullable to indicate if there is no value. I then proceed to hide the normal Text property from the designer so that the GUID property becomes the only obvious way to set it.

Fixed width Input

The input text of the GUID must be exactly 36 characters, that is 32 hexadecimal digits and 4 separators, in the format "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx". This means that if there is no value for the GUID, the field must be filled up with appropriate placeholders for each digit. The input key processing then ensures that the character at the current cursor position is overwritten on a valid keyboard input, while avoiding the separators. The default PlaceHolder is a zero ('0') character, meaning that if the TextBox is entered with a null value, the text will appear as "00000000-0000-0000-000000000000".

Process Keyboard Input

All of the hard work is done in the OnKeyDown override. The keystrokes are filtered such that any keys that would cause text to be deleted are blocked, cursor movement keys (left, right, home, end) are processed so that they take into account the non-editable separators, and the only printable characters allowed through are hexadecimal digits ([0-9A-Fa-f]). Any invalid keystrokes are thrown away, the valid characters are processed by the OnKeyDown override, and anything left over is allowed to pass through to the default handler.

The Clipboard

Clipboard operations are a bit dodgy due to the formatted nature of the GUID. Obviously, as the text is of a fixed width, and deletions are not allowed, the Cut and Clear(delete) operations have been prohibited. Copy works as per normal, and is handled by the default TextBox processing. Paste is where it became tricky, so I made up a few basic rules for it:

  • If the clipboard contains a full Guid (i.e., anything that can be parsed into a Guid structure) AND the GUID property is null, or the whole field is selected, or nothing is selected and the cursor is at the start of the field, then the clipboard is pasted as a suitably formatted Guid, overwriting any existing value.
  • If the clipboard contains a partially formatted Guid with heaxdecimal digits and separators, then the positions of the digits and separators in the clipboard must line up exactly with the selected text. If that happens, the selected text is replaced by the clipboard text.
  • If the clipboard only contains hexadecimal digits, then any digits in the selected text are replaced by the clipboard text, skipping over separators as required.
  • If any of the above scenarios are not covered, the paste operation fails.

As a TextBox does not normally have any clipboard event handlers, this control had to add its own. To do this, the WndProc message handler is overwritten, and the WM_PASTE, WM_CUT, and WM_CLEAR messages are processed. WM_COPY did not have to be processed, as there is nothing special about the copying of the text to the clipboard.

protected override void WndProc(ref Message m) {
    switch (m.Msg) {
    case 0x0302: {  // WM_PASTE
            HandledEventArgs e = new HandledEventArgs(false);
            OnPaste(e);
            if (e.Handled)
                return;
        }
        break;
        case 0x0300:    // WM_CUT
        case 0x0303:    // WM_CLEAR
            return;
    }
    base.WndProc(ref m);
}

WM_CUT and WM_CLEAR messages are simply thrown away, as it is not permissible to delete text from the GuidTextBox.

To handle the pasting, a Paste event was created, and a corresponding protected virtual OnPaste method was created. This method is called by the WndProc on a WM_PASTE message.

The first thing the OnPaste method does is to call any registered Paste events. This gives the end-user first shot at pasting the clipboard contents themselves, if they don't like the way the automatic processing works. If the event handler does process the paste, they can set the e.Handled flag to true, and the OnPaste method will not do any of its normal processing.

Using the Code

Using the control is very simple - just like any other control. Just drag it onto your form, set a couple of properties, and Bob's your uncle! For this control, I have added only 4 public properties and one public method, as detailed below:

public Guid? GUID { get; set; } The value of the Guid to be edited.
public char PlaceHolder { get; set; } The placeholder character to use in the displayed text to replace any un-entered characters. The default value is the zero ('0') character, but this can be anything except the minus ('-' character, as it has a special meaning, being the separator in a Guid.
public bool InitNewGuid { get; set; } Setting this flag will force the control to automatically generate a new Guid if one does not already exist when it is initialized. Setting this flag at design time will cause the GUID property to be set to null. Setting the GUID property as design time will cause this flag to be set to false.
public bool IsValid { get; } This read-only property simply determines if the text in the control is a valid Guid.
public void NewGuid(); Causes a new Guid to be generated as the value of the GUID property.

The CharacterCasing property has also been refactored so that its default value is CharacterCasing.Upper. Just a personal taste thing, but I think there is nothing uglier than a Guid that has the casing of its hexadecimal digits all over the place.

There are also a number of standard TextBox properties that I have refactored so that they are hidden from the designer, as they are not really applicable to a GuidTextBox. The Text property is probably the most significant of these, as it is replaced by the GUID property in the control. The other properties hidden are AcceptsReturn, AcceptsTab, AutoCompleteCustomSource, AutoCompleteMode, AutoCompleteSource, AutoScrollOffset, Lines, MaxLength, Multiline, PasswordChar, ScrollBars, UseSystemPasswordChar, and WordWrap. The MaxLength property probably also warrants special mention. It is preset to 36 by the control, and then hidden from the designer and the editor to prevent changing it. It is still possible to change it by casting the GuidTextBox to a TextBox, but one would have to really want to, and then know what they're doing to fix all the problems the change has caused.

History

  • 2016-10-13 16:00 AEST - Initial post.
  • 2016-10-13 19:30 AEST - Fixed bug that prevented the initial null GUID from being entered.
  • 2016-10-27 15:30 AEST - Reformatted the table with a few non-breaking spaces to make the property definitions more readable.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here