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: { HandledEventArgs e = new HandledEventArgs(false);
OnPaste(e);
if (e.Handled)
return;
}
break;
case 0x0300: case 0x0303: 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.