Virtual Developer Workshop: Containerized Development with Docker
The demo program, shown below, illustrates the capabilities of these classes.
Reflections on DesignI had a difficult time deciding just what would be the best way to redesign the Latitude (CGCLatitudeCtrl) and Longitude (CGCLongitudeCtrl) classes. I had developed my original CGCLatitudeCtrl and CGCLongitudeCtrl classes in a hurry and they duplicated most of the code in the classes. I wanted to eliminate that duplication and make them more "object oriented." If I were to be a fanatical OOP purist, I should put the code specific to the latitude control in the CGCLatitudeCtrl class and the code specific to the longitude control in the CGCLongitudeCtrl class. However, the differences in implementation between the two are only a few lines of code. The maximum value of the degree field and the way in which you represent the hemisphere for certain styles are the only differences. And, since it is extremely unlikely that you would ever have a need for JUST a latitude or JUST a longitude control, you should make sure that both controls always use consistent formats and behavior. In order to emphasize the need for consistency across both control classes, and due to the small difference between their implementations, I opted to develop an abstract (pure virtual) class, CGCLatLongAbstractBase, that would pull the implementation for both controls together into a single class. I would then provide two control classes, CGCLatitudeCtrl and CGCLongitudeCtrl derived from the new abstract class, that would provide only specialized input validation for their methods and maybe a few methods to make better use of the abstract class' methods. This arrangement allowed me to provide the tight relationship needed in their implementation (to ensure consistency) while emphasizing the fact that they are still logically different, albeit related, entities.
The implementation file for CGCLatLongAbstractBase is long. For better manageability, it should be broken up into smaller files. However, I wanted to make incorporating it into a new project as easy as possible so I put all the code in one file.
I had a lot of positive feedback on the last design and I hope that you will view this one in a positive manner as well.
The class hierarchy for the Latitude and Longitude classes is illustrated in the following figure:
The figure above also shows that I indirectly derived the latitude and longitude controls from my CGCColorEdit class to provide a means for controlling the colors and fonts used in the controls. Since there are already articles available on how to control color and fonts in edit controls, I will not dwell on the use of that class. However, you may find my implementation of CGCColorEdit to be very versatile.
The CGCLatLongAbstractBase class also makes use of a new class that I created to ease the implementation of the context menus for the controls. This new class, CGCContextMenu, provides some wrappers for convenience. Feel free to add to this class as you wish.
Preprocessor Flags and Compiler SwitchesThe latitude and longitude controls now support several styles to be described below. Among these is a style that supports the display and entry of a decimal degrees value. The implementation makes use of a preprocessor constant "GCDD" to conditionally compile support for the decimal degrees style. If you will not need to support the display/entry of decimal degrees, do not define GCDD. That code will then be left out upon compilation. However, if you need support for decimal degrees, be sure to define GCDD in your compile options. There is no way to compile only decimal degrees support. You will always get support for degrees-minutes-seconds regardless of compiler options.
An alternative to this approach would have been to derive extended CGCLatitudeCtrl and CGCLongitudeCtrl classes from the existing ones that would have added the decimal degrees functionality. Using a preprocessor flag was just the choice I went with so I wouldn't have to keep track of as many classes.
Because of the use of logically different field support classes in the implementation of the controls, some dynamic casting was used to access the appropriate methods for the different field support objects. This requires that the project using these classes be compiled with the /GR switch to enable Run-Time Type Information (RTTI).
BehaviourThe CGCLatitudeCtrl and CGCLongitudeCtrl classes are very simple to use. Each control divides its display into different fields. The user can navigate between fields within the control using the left and right arrow keys. Navigation to the right may also be achieved using the space bar. When the user navigates to a particular field within the control, that field is selected and highlighted. Actions taken by the user will affect that field. The up and down arrow keys may be used to increment or decrement, respectively, the currently selected field. In addition, the user may enter values for that field directly using the number keys on the keyboard. The user may also select a specific field within the control by clicking on it with the left mouse button. The user can reset the currently-selected field by pressing the backspace key.
A context menu is even provided. When the user clicks over the control that has focus using the right mouse button, a context menu is displayed. It allows the user to reset the entire control setting or to reset only the currently-selected field. There are even "hot keys" defined for the controls.
StylesThe new implementation provide several styles that may be applied to allow the developer to tailor these controls to most needs. The controls provide the following styles:
This style specifies that colons are to be used to separate the fields in both controls.
This style specifies that a single white space is to be used to separate the fields in both controls.
This style specifies that both controls are to use a contemporary format in the representation of a latitude or longitude. In this style, the raised "degree symbol" is used after the degree field, a single quote is used after the minutes field and a double quote is used after the seconds field. A single white space is used to separate the hemisphere field from the latitude or longitude value.
This style allows the client to specify that strings obtained from the string resource table are to be used after each field. The string resources used are identified as IDS_CUSTOM_HEM_DELIM, IDS_CUSTOM_DEG_DELIM, IDS_CUSTOM_MIN_DELIM and IDS_CUSTOM_SEC_DELIM. For further details, see "STRING RESOURCES" below.
This syle states that the string resources IDS_NHEM_TXT and IDS_SHEM_TXT are to be used to indicate the hemisphere in the Latitude Control and the string resources IDS_EHEM_TXT and IDS_WHEM_TXT are to be used to indicate the hemisphere in the Longitude Control. For further details regarding these resources, see "STRING RESOURCES" below.
This style states that the string resources IDS_POSHEM_TXT and IDS_NEGHEM_TXT are to be used to indicate the hemisphere in both controls. For further details on these resources, see "STRING RESOURCES" below.
Use a floating point seconds field in both controls. The seconds field represents seconds to the thousandths.
Use a simple integer seconds field for both controls.
Specifies that leading zeros be used in all numeric fields in both controls.
Specifies that no leading zeros be used in any field in both controls.
Specifies that the hemisphere indicator is to be displayed on the left of the control.
Specifies that the hemisphere indicator is to be displayed on the right.
Specifies that the control display the latitude/longitude as separate hemisphere, degrees, minutes and seconds values.
Specifies that the control display the latitude/longitude using the hemisphere and the decimal degrees value as two separate fields. This style is not defined if the code is not compiled with "GCDD" defined.
Equivalent to OR'ing the GC_COLON_DELIM_STYLE, GC_HEM_INDICATOR_STYLE, GC_FLOAT_SECONDS_STYLE, GC_LEADING_ZEROS_STYLE, GC_FULL_STYLE and GC_LEADING_HEM_STYLE styles.
Equivalent to OR'ing the GC_COLON_DELIM_STYLE, GC_HEM_INDICATOR_STYLE, GC_LEADING_ZEROS_STYLE GC_DECIMAL_DEGREES_STYLE and GC_LEADING_HEM_STYLE styles. This style is not defined if the code is not compiled with "GCDD" defined.
In this demo, the client sets the background and text colors, as well as selecting a non- proportional (fixed pitch) font using methods provided by the CGCColorEdit class. I find that Multi-Field Edit Controls, such as CGCLatitudeCtrl and CGCLongitudeCtrl look better when using a non-proportional font.
MethodsThere are several methods defined for each class in addition to those inherited from their ancestor classes. These are:
BOOL SetStyle(const STYLE_TYPE Style);
The SetStyle() method is used to set the controls to the style(s) defined above. The method allows the client to specify only the style to be set without the client needing to OR all desired styles together.
UINT GetStyle();Returns the current style of the control.
BOOL Set(const CString& hsph, const UINT Degrees, const UINT Minutes, const float Seconds);
The method Set() allows the application to initialize the control to any setting valid for that type of control (latitude or longitude). With this method, the client may specify the latitude or longitude using separate hemisphere, degrees, minutes and seconds values. The method returns TRUE if successful and FALSE if any parameter is outside the valid range for that parameter. This method may be for controls with either the GC_FULL_STYLE or the GC_DECIMAL_DEGREES_STYLE.
void Get(CString& hsph, UINT& Degrees, UINT& Minutes, float& Seconds) const;
The method Get() allows the application to obtain the current setting as separate values. This is usually called from within an EN_CHANGE handler. This method may be called for controls with either the GC_FULL_STYLE or the GC_DECIMAL_DEGREES_STYLE.
double GetDecimalDegrees() const;
The method GetDecimalDegrees() allows the client to obtain the current setting in decimal degrees. Like method Get(), this method would typically be called from an EN_CHANGE handler. This method may be called for controls with either the GC_FULL_STYLE or the GC_DECIMAL_DEGREES_STYLE.
BOOL SetDecimalDegrees(const double Degrees);
The SetDecimalDegrees() method allows the client to initialize the control by specifying a decimal degree value. Returns TRUE if if the call succeeds or FALSE if the call fails due to an invalid input parameter. This method may be called for controls with either the GC_FULL_STYLE or the GC_DECIMAL_DEGREES_STYLE.
For further details on these methods and others that the control classes inherit, please refer to the extensive comments supplied in the source code of the demo project.
String ResourcesThe CGCLatLongAbstractBase class makes use of string resources to facilitate modifications for different languages and for custom needs.
There are 6 string resources used to support two different styles controlling the use of the hemisphere field. These are:
|RESOURCE ID||VALUE||USED BY STYLE|
They are defined to the typical characters one would expect to use when indicating/specifying a hemisphere. However, you are not restricted to using single-character strings. You could, for example, specify "NORTH" for IDS_NHEM_TXT and "SOUTH" for IDS_SHEM_TXT. However, the design of the CGCMultiFieldEdit class imposes a field width constraint on the contents of a given field via parameters specified in the CGCMultiFieldEdit::Format() method call. As the field changes value, its width stays the same. Therefore, you should ensure that the strings you specify for IDS_NHEM_TXT and IDS_SHEM_TXT are the same length. Further, you should ensure that the strings for IDS_EHEM_TXT and IDS_WHEM_TXT are the same size and the same is true for IDS_POSHEM_TXT and IDS_NEGHEM_TXT. For example, if you wished to do something silly like use "UP" to represent the Northern hemisphere and "DOWN" to represent the Southern hemisphere, then you should define IDS_NHEM_TXT as "UP " and IDS_NHEM_TXT as "DOWN". Otherwise, "DOWN" will likely be displayed as "DO"! For further details on the use of the CGCMultiFieldEdit class, please see my article "Multi-Field Edit Controls - A Whole New Class of Edit Controls".
There are 6 string resources that are defined for use in the control context menus. These are:
These allow the developer to change the strings used for these menu options if needed. A private method of CGCLatLongAbstractBase, LoadStringResource() provides a convenient location for modifications should you need to change how the menu options are defined/obtained.
There are 4 string resources defined to support the GC_CUSTOM_DELIM_STYLE style. These are labeled as follows:
|RESOURCE ID||VALUE||RESOURCE USE|
|IDS_CUSTOM_HEM_DELIM||" h "||Displayed following the|
|IDS_CUSTOM_DEG_DELIM||" d "||Displayed following degress field|
|IDS_CUSTOM_MIN_DELIM||" m "||Displayed following minutes field|
|IDS_CUSTOM_SEC_DELIM||" s "||Displayed following seconds field|
The strings provided in the resource table for these resource IDs are examples only. If you wish to use the GC_CUSTOM_DELIM_STYLE, you may change the definitions of these resources to whatever you wish. These string resources are provide to allow you to customize a style for the controls.
The DemoThe demo supplied provides you with controls to apply the different styles to the Latitude and Longitude controls. You can combine the different styles and see how they interact. The demo also illustrates a possible use for the CGCColorEdit class functionality for your latitude and longitude controls. In this demo, the background color changes according to the hemisphere being displayed! You probably will not want this but it illustrates one use of the CGCColorEdit class. The primary reason for using CGCColorEdit is to select a non-proportional (fixed pitch) font for the controls. Such a font makes the controls look better when changing the field values because the fields do not shift left and right as different width characters are displayed.
How to UseHere are the steps to place a latitude control and a longitude control in your user interface:
- Add CGCColorEdit class to your project.
- Add CGCMultiFieldEdit class to your project.
- Add CGCLatLongAbstractBase class to your project.
- Add CGCLatitudeCtrl class to your project.
- Add CGCLongitudeCtrl class to your project.
- Add CGCContextMenu class to your project.
- Place an edit box using the Resource Editor to be used for your latitude control.
- Place an edit box using the Resource Editor to be used for your longitude control.
- Use ClassWizard, create a member variable of type CGCLatitudeCtrl for one of the new edit boxes.
- Use ClassWizard to create a member variable of type CGCLongitudeCtrl for the other new edit box.
- Add an EN_CHANGE handler for each of the new edit boxes to your parent window to handle when the controls change as you would any other control class.
Also see my related article "UTM Coordinate Control" on a control for entering and displaying Universal Transverse Mercator coordinates.
I hope that these classes will continue to prove useful to someone on future projects.