Virtual Developer Workshop: Containerized Development with Docker
OverviewThis article addresses a new implementation of a popular class of Edit Controls I've created that I call Multi-Field Edit Controls. A Multi-Field Edit Control is an Edit Control in which the edit box contains multiple fields, each of which may behave as though it were an individual control. I had the idea to create this class recently after I developed a couple of classes for the entry and display of geographic Latitude and Longitude values. These classes, CGCLatitudeCtrl and CGCLongitudeCtrl, incorporated functionality that I recognized to be potentially useful in many other types of controls. I identified this functionality and extracted it, refined it, and added to it to create a new, abstract (pure virtual) class, CGCMultiFieldEdit. The demo is illustrated in the following figure:
The CGCMultiFieldEdit class provides a building-block upon which you may easily create your own Multi-Field Edit Control of any kind. Specifically, the CGCMultiFieldEdit class provides the following capabilities:
- To define as many fields within an edit control as desired.
- To control the widths of each field.
- To specify any field separators desired of any length desired.
- To navigate to any field using the left/right cursor control (arrow)
- keys or the Space Bar.
- To select a field using the left mouse button.
- To programmatically select a field.
- To programmatically determine which field is currently selected.
- When the operator tabs input focus to the control, it should select the field selected when the control last had input focus during the session.
The abstract class CGCMultiFieldEdit is derived from another of my classes, CGCColorEdit. That class provides the functionality to select the font and background and text colors used in your Multi-Field Edit control. I find that proportional (fixed pitch) fonts work best with Multi-Field Edit controls. They prevent the fields from shifting left and right as characters of different widths are displayed.
I will provide an overview of the methods provided by the CGCMultiFieldEdit class. After this overview, I will provide some insight into the design for the class. Then I will introduce you to some classes I created to support you when you derive your own control class from CGCMultiFieldEdit. I will then wrap up with a look at the demo project included with this article.
Overview of the Public MethodsWhen deriving a new control class from the CGCMultiFieldEdit class, the constructor for the derived class should call the following public method:
BOOL Format(const vector<CString> FieldContents, const vector<CString> FieldSeparators, const CString Prefix = "", const CString Postfix = "");
Calling this method is the mechanism by which the number of fields, the width of the fields and the field separators to be used by the control are established.
The method accepts two parameters of type vector<CString>. The "vector" is a template class provided by the Standard Template Library (STL) and behaves somewhat like an array. The statement "vector<CString>" declares a vector of CString objects. The first parameter, "FieldContents", contains the initial contents of each field. The CGCMultiFieldEdit class uses this vector to determine how many fields the control is to contain. If the "FieldContents" vector contains 4 CString objects, the Multi-Field Edit Control will contain 4 fields. Further, the CGCMultiFieldEdit class utilizes this vector to determine the widths of each field. The length of each string in "FieldContents" is the length that will be set for that field. The fields are ordered in the order of the items in the "FieldContents" vector. That is, the first item in the vector (index 0) will be the left-most field in the Multi-Field Edit Control.
The second CString vector parameter, "FieldSeparators", provides for the specification of the field separators. The field separators are not limited to a single character. Nor is the developer restricted to using the same field separator to separate all fields. If the "FieldSeparators" vector contains a single CString object, then that CString will be used for all field separators. Otherwise, the "FieldSeparators" vector must contain at least one less as many CString objects as there are in the "FieldContents" vector.
Note that the Format() method checks to make sure that each element in the FieldContents parameter specifies a string of greater than or equal to one character in length. If any entry has a length of zero, or the FieldContents vector contains no elements, the Format() call fails and returns FALSE.
Two additional parameters are provided that allow you to specify a Prefix and a Postfix for the control. Ordinarily, when implementing a Multi-Field Edit Control, these are not used. For example, if you were defining a control with two fields, the control would contain the first field on the left, then a single Field Separator followed by the last field on the right. If you wish, you can specify a Prefix which will be a string displayed to the left of the first field. The Postfix parameter allows you to specify a string to be displayed to the right of the last field in the control.
The Format() method is intended to be called once by the constructor of the derived class. When the derived class must update the contents of the fields only, a more efficient public method is provided:
BOOL CGCMultiFieldEdit::Update(const vector<CString>& FieldContents);
The Update() method allows the derived class to update the contents of the fields only. It accepts a single CString vector containing the new contents to display in each field. It does NOT allow the field widths to change. If the "FieldContents" vector contains a CString object for a field that is longer than that field's width, the new field content will be truncated on the right to fit in the previously-specified field width.
However, if you wish to change the number of fields in the control, the width of either field, or the field separator(s) in use, you are free to call the Format() method to do so. The Format() method of the CGCMultiFieldEdit class provides for changing the format of the Multi-Field Edit Control at any time.
A public method is provided to allow for programmatically selecting a field in the control:
BOOL CGCMultiFieldEdit::SelectField(const int FieldNumber);
This method accepts a single integer parameter that is a zero-based index of the field to select. The left-most field is field 0. The method returns TRUE if the specified field was selected.
An additional method is provided to allow for programmatically determining which field is currently selected:
int CGCMultiFieldEdit::GetSelectedField() const;
This method returns the zero-based index of the currently selected field.
The operator is prevented from selecting a Field Separator using the mouse. The operator can only select a field. Like the Field Separators, the Prefix and Postfix can not be selected.
Design of the CGMultiFieldEdit ClassLike any programming problem, there are typically many ways in which to implement it. This part of the article looks at the way I chose to implement the CGCMultiFieldEdit class.
The CGCMultiFieldEdit class is designed to provide just the functionality required to assist you in creating your own Multi-Field Edit Control. It makes no assumption as to what information you want to display in any field. Since the CGCMultiFieldEdit class is designed to provide a building block on which to construct any kind of new control class, CStrings were selected as the data type for specifying the field contents. This allows virtually any kind and format of data to be displayed in the fields, keeping the CGCMultiFieldEdit class as "generic" as possible. The class that is derived from CGCMultiFieldEdit will convert the data to be displayed in the fields into CString objects and pass the CStrings to the CGCMultiFieldEdit class for display in the fields. You can display numeric data, text data, or a combination. By converting the information that you want to display within each field to CString variables, you are free to decide what you want to display.
When you examine the code, you will notice that I have overridden the SetWindowText() method inherited from the MFC CEdit class. Since Multi-Field Edit Controls require that a rigid format be maintained in the information displayed and entered, allowing an application free use of the SetWindowText() method would defeat the CGCMultiFieldEdit class' ability to control the format of the display. I provide a "do nothing" override to remind you that this should not be used. You should make use of the Format() and Update() methods only when programmatically setting the control to display information.
Other "do nothing" overrides are also implemented for WM_CHAR, WM_LBUTTONDBLCLK, WM_MOUSEMOVE and WM_RBUTTONUP. The handler for WM_CHAR is overridden to prevent the handler from CEdit from being inadvertently called. If this were allowed to happen, the displayed data and format could be corrupted.
The CGCMultiFieldEdit class must allow for the selection of individual fields within the control. I did not want the operator to be able to select the entire contents for the control. Therefore, I provided a "do nothing" override for the WM_LBUTTONDBLCLK message. Should the operator double-click with the left mouse within the control, the control will not select the entire contents.
Also, with the standard CEdit control, the operator could single-click with the left-mouse button and drag the cursor to select part or all of the contents of the control. Again, to ensure that only specific fields are selected, I provided a "do nothing" override for the WM_MOUSEMOVE message to prevent this from happening.
Since the standard context menu provided by the MFC CEdit class would not logically apply to most Multi-Field Edit Controls, I did not want that context menu to ever be displayed. When deriving a specific control from CGCMultiFieldEdit, you should provide your own context menu if one is required. The "do nothing" override provided by the CGCMultiFieldEdit class ensures that the CEdit context menu isn't inadvertently displayed.
I do allow you to call the GetWindowText() inherited from CWnd if you wish to obtain the entire contents of the control as a string.
A more ideal solution would be to privately inherit from CEdit but that would cause more work than is necessary. So it is up to the developer to properly use the Multi-Field Edit Control and not circumvent the interfaces provided.
When you call CGCMultiFieldEdit::Format(), the class looks at the contents of the FieldContents vector and calculates the starting and ending character positions for each field within the control. The class stores these values in a private array "m_FieldSpecs" with elements of type "FIELD_SPEC_TYPE". It stores a copy of the FieldContents vector in an internal vector, m_FieldContents for later use. It also stores the field separators in the private vector "m_FieldSeparators".
With each call to the Format() method, CGCMultiFieldEdit throws away any previous contents of the m_FieldContents and m_FieldSeparators private vectors and recreates them using the new specifications passed into the Format() method. The private function, FormatAndDisplay() is then called by the Format() method to update the display. When you call the CGCMultiFieldEdit::Update() public method, the m_FieldContents vector is updated and FormatAndDisplay() is again called to update the display.
Also, in most cases with Multi-Field Edit Controls, the caret would typically be an annoyance. Therefore I call the HideCaret() method inherited from CWnd where necessary to ensure that the caret isn't displayed.
The CGCMultiFieldEdit class overrides the WM_KEYDOWN handler to provide for selection of the fields by using the cursor-control (arrow) keys and the space bar.
I provide no methods in the CGCMultiFieldEdit class for setting the fields individually. I consider that to be a specialization that belongs in the control class you derive from the CGCMultiFieldEdit class. If you need such a capability, provide it within your derived class, then call the Update() method (or Format() if needed) to update all fields at once.
Support ClassesThe file GCMultiFieldEdit.h (and its associated cpp file) provides a set of support classes to assist you when developing your on Multi-Field Edit Control derived from CGCMultiFieldEdit. Objects of these classes are not passed into the CGCMultiFieldEdit class by any means. They are to be used within your derived class as needed to help you implement your class' functionality.
In each case, objects of the support classes are typically manipulated in the OnChar() and OnKeyDown() overrides of the class you derive from CGCMultiFieldEdit. The comments in the example control classes in the supplied demo project will give further details. I tried to supply sufficient comments in the source to make the code as easy to understand as possible.
The class hierarchy for the field support classes is shown in the following illustration:
Once you study the code for these support classes you may determine that some of it isn't needed. Indeed, I do not always use all of the code presented. I simply provide a framework that you may alter as needed.
You will not that I have provided macros that you can undefine to eliminate unwanted code if you wish.
Support Class CGCIntegerFieldThe first support class, CGCIntegerField, will aid you in adding an integer (or long integer) field to your Multi-Field Edit Control. For every integer (or long) field in your derived Multi-Field Edit Control, you will want to create a CGCIntegerField class object to support it.
Support Class CGCDoubleFieldThe next support class, CGCDoubleField, will aid you in adding a floating point (or double) field to your Multi-Field Edit Control. For every float (or double) field in your derived Multi-Field Edit Control, you will want to create a CGCDoubleField class object to support it.
Support Class CGCTextFieldThe next support class, CGCTextField, assists you in creating a simple text field to allow the entry and display of free text. For every simple text field in your derived Multi-Field Edit Control, you will want to create a CGCTextField class object to support it.
Support Class CGCStringListFieldThe final support class provided, CGCStringListField, allows you to create a field in your derived Multi-Field Edit Control that can be used to display any one entry from a predefined list of values.
A CGCStringListField field operates somewhat like a combo box without the drop down list. You define the list of items (CStrings) that may be displayed and selected in the field using an overloaded "+" operator.
The Demonstration ProgramThe demo project includes several Multi-Field Edit controls to demonstrate the use of the CGCMultiFieldEdit class and the support classes. The project was developed using Visual C++ Pro V6.0 on Windows 98 and should run on NT 4.0 Workstation as well.
I provide one complete class for the entry and display of Social Security Numbers (SSN). This class illustrates a basic Multi-Field Edit Control with 3 integer fields and the field separators. This class is complete and could be used as is in any application where you need to enter an SSN. This class (as well as some of the others described below) also illustrates the use of the CGCIntegerField support class.
I also provide some INCOMPLETE control classes to illustrate the use of other field types (and their support classes) and the use of the Prefix and Postfix (see comments in GCMultiFieldEdit.h). Remember, the following control classes are INCOMPLETE classes provided as EXAMPLES of the use of the MultiFieldEdit class.
The control class CGCSimpleLongDistance primarily illustrates the use of the Prefix parameter in the call to the Format() method of CGCMultiFieldEdit. It also utilizes the CGCIntegerField support class.
The control class CGCSAEWeight is provided to illustrate the use of the Postfix in the call to the Format() method of the CGCMultiFieldEdit class. It too uses the CGCIntegerField support class.
The control class CGCInventoryItem is provided to illustrate the use of simple text fields and the CGCTextField support class. It too uses the CGCIntegerField support class.
The control class CGCMonthDay is provided to illustrate the use of String List Fields and the CGCStringListField support class. This one also uses the CGCIntegerField support class.
And finally, the control class CGCXYCoordinate illustrates the use of floating point fields and the CGCDoubleField support class. It also illustrates the use of negatives numbers.
Start with the complete Multi-Field Edit Control class CGCSocialSecurityNumberCtrl. Study the code and the comments. Play with the Social Security Number Control in the demo. Step through the code in the debugger. After you understand how the Social Security Number Control works, then look at the source for the other example controls. In all cases, pay close attention to the class constructors for the example control classes derived from the CGCMultiFieldEdit class as well as their OnChar() and OnKeyDown() overrides and their Display() methods. These contain the statements of interest in the use of the CGCMultiFieldEdit class and its support classes.
This may seem complicated, but after a careful review of the Multi-Field Edit class and the example control classes derived from it, you should understand how to use it and soon be developing your own Multi-Field Edit Controls.
What other controls could you use the CGCMultiFieldEdit class for? How about deriving a control for entering insurance policy numbers that typically contain multiple groups of numbers? Bank account numbers? Rectangular or polar coordinate entry controls? Measurement controls that allow entry/display in feet and inches or meters and centimeters? Or cost controls that display costs in dollars and cents? There could be many instances where Multi-Field Edit Controls would be useful. I hope that you will find the CGCMultiFieldEdit class and its support classes useful too.