Bill Foust

Nearly every developer has faced a situation where the standard field types are simply not enough because they do not allow you to develop the application in the way the user needs. Even simple User Interface compromises can contribute to an application which some users will find confusing or non-intuitive. When it comes to application development, I subscribe to the theory of least surprises - do what the user will find the least surprising.

I know I'm not the only developer to ask “Is there any way that I can make the edit field do this in a slightly different way?” The Edit field is a tremendously powerful tool supplied as part of the base SDK. The WIN32 API doesn't have anything as powerful and it's for a desktop! Still, it can't be used to fit every need. Fortunately we can implement our own custom fields based on the Edit field through sub-classing and overriding, an important undocumented function.

The Edit field has many undocumented functions that we won't need to discuss at this point. The important one for this technique is the HandleInput function. As its name implies, this function is responsible for handling all user input. Its one parameter is a MESSAGE structure, so its pretty clear that the UIEngine object somehow passes input events such as key down events to this control using this HandleInput function. Therefore, if we override this function and intercept these incoming events, it is possible to exert considerable control over how this field behaves.

Let's use a realistic example to start with. Let's say you want to input a serial number for some production facility. Assume that this serial number follows a pattern of three upper case letters followed by some numbers. The numeric portion of the serial number may be variable length, but there are always three letters in the beginning and then only numbers.

Obviously, the best you can do with a standard Edit field is to apply the ALPHANUMERIC flag to restrict input to only letters and numbers. However, it is still very easy for someone to enter an invalid serial number. Only by checking the value after input can you inform the user they have made a mistake.

If you think about how the Edit field HandleInput function is likely to be implemented, it probably looks something like this:

if(CharacterIsAllowedBasedOnProperties())
{
AddCharacterToField();
}

Since we don't have any control over the properties, we will simply have to do the same basic logic in our special HandleInput function. If you want to allow the character, then pass the message onto the standard Edit field HandleInput function. If not, then do nothing!

if (CharacterIsAllowed())
{
Edit::HandleInput();
}

So now, lets go back to the serial number example we started with and create a new field called SerialNumber.

class SerialNumber : public Edit
{
SerialNumber(){} // Constructor
RESULT HandleInput(MESSAGE const &);
};

RESULT SerialNumber::HandleInput
(MESSAGE const & msg)
{
return Edit::HandleInput(msg);
}

This code implements a custom control that behaved exactly like the standard Edit field, after all, it simply calls the default HandleInput function. Now, however, we can begin to implement the custom logic needed for this field. Let's begin with forcing any letters to be upper case.

We can't simply modify the parameter that is passed in. It is a const, which means that it will not be changed by this function. Instead, we have to create a local copy of the MESSAGE struct, and then modify and use it when calling the base class HandleInput function.

RESULT SerialNumber::HandleInput(MESSAGE const & msg)
{
// Make a local copy first
MESSAGE NewMsg = msg;
// We are only interested in KEYPAD events
if (InputMessage.Device == DEVICE_KEYPAD)
{
// Particularly, key down events
if ( InputMessage.Event == KEY_DOWN ||
InputMessage.Event == KEY_REPEAT)
{
// If the key pressed was lower case
if (NewMsg.SubMsg >= 'a' &&
NewMsg.SubMsg <= 'z')
{
// Change it to upper.
NewMsg.SubMsg -= ('a'-'A');
}
// Now call HandleInput with the
// new value!
return Edit::HandleInput(NewMsg);
}
}
return Edit::HandleInput(msg);
}

So now we have a custom field that turns all of the input into upper case letters. This by itself may be useful on its own, but we aren't done with it yet. The important part though is demonstrating how you can intercept an incoming event and alter it before letting the Edit field handle it.

In order to fully implement the SerialNumber field, we need to make a decision about whether the incoming event can be allowed or not by examining the data that is already in the control.

RESULT SerialNumber::HandleInput(MESSAGE const & msg)
{
// Make a local copy first
MESSAGE NewMsg = msg;
// We are only interested in KEYPAD events
if (InputMessage.Device == DEVICE_KEYPAD)
{
// Particularly, key down events
if ( InputMessage.Event == KEY_DOWN ||
InputMessage.Event == KEY_REPEAT)
{
// If less then 3 characters
// in the buffer, this new character
// must be a letter
if (GetBufferLength() < 3)
{
// If the key pressed was
// lower case
if (NewMsg.SubMsg >= 'a'&&
NewMsg.SubMsg >= 'z')
{
// Change it to upper.
NewMsg.SubMsg -= ('a'-'A');
}

if (NewMsg.SubMsg <= 'A' &&
NewMsg.SubMsg >= 'Z')
{
// Now call HandleInput with
// the new value!
Edit::HandleInput(NewMsg);
}
}
else
{
if (NewMsg.SubMsg >= '0' &&
NewMsg.SubMsg <= '9')
{
Edit::HandleInput(NewMsg);
}
}

return CONTINUE;
}
}
return Edit::HandleInput(msg);
}

By checking the length of the data in the buffer we can determine whether the field should accept letters or numbers. If the field is only accepting letters, then only events with characters between the range of 'A' and 'Z' are passed to the base class HandleInput function. Any other characters are simply ignored. The same is also if the field is only accepting numbers. Only events with characters between the range of '0' and '9' are passed to the base class HandleInput function.

Note: Calls to HandleInput no longer check the return value. Instead the function always returns the constant CONTINUE. CONTINUE is a value defined in result.h and indicates that the event was processed successfully and that the field is ready for the next event.

That's it! The SerialNumber class is complete. As you can see, the logic in the HandleInput function can be as simple or complex as necessary. Of course, this cannot be used in every situation, but I've found that any situation where an Edit field is just OK can be made better by defining a custom field.

Want to make the SerialNumber class even better? Alter the function so that it should only be accepting numbers, but when a letter is pressed that shares a key with a number, the corresponding number is displayed. For instance, pressing 'q' will automatically be changed to a '1' thus making it even easier for the user by not having to press the ALT key repeatedly.

Bill Foust is an independent consultant specializing in BlackBerry development. He has written several applications for BlackBerry including ExpenseMinder and EzToDo, two of the best-selling applications for BlackBerry at http://www.handango.com.