Converting Data

Converting Data

As mentioned earlier in this chapter, Delphi includes a new conversion engine, defined in the Conv Utils unit. The engine by itself doesn't include any definition of actual measurement units; instead, it has a series of core functions for end users.

The key function is the conversion call, the Convert function. You simply provide the amount, the units it is expressed in, and the units you want it converted into. The following converts a temperature of 31 degrees Celsius to Fahrenheit:

Convert (31, tuCelsius, tuFahrenheit)

An overloaded version of the Convert function lets you convert values that have two units, such as speed (which has both a length unit and a time unit). For example, you can convert miles per hour to meters per second with this call:

Convert (20, duMiles, tuHours, duMeters, tuSeconds)

Other functions in the unit allow you to convert the result of an addition or a difference, check if conversions are applicable, and even list the available conversion families and units.

A predefined set of measurement units is provided in the StdConvs unit. This unit has conversion families and an impressive number of values, as shown in the following reduced excerpt:

// Distance Conversion Units
// basic unit of measurement is meters
cbDistance: TConvFamily;
duAngstroms: TConvType;
duMicrons: TConvType;
duMillimeters: TConvType;
duMeters: TConvType;
duKilometers: TConvType;
duInches: TConvType;
duMiles: TConvType;
duLightYears: TConvType;
duFurlongs: TConvType;
duHands: TConvType;
duPicas: TConvType;

This family and the various units are registered in the conversion engine in the initialization section of the unit, providing the conversion ratios (saved in a series of constants, such as MetersPerInch in the following code):

cbDistance := RegisterConversionFamily('Distance');
duAngstroms := RegisterConversionType(cbDistance, 'Angstroms', 1E-10);
duMillimeters := RegisterConversionType(cbDistance, 'Millimeters', 0.001);
duInches := RegisterConversionType(cbDistance, 'Inches', MetersPerInch);

To test the conversion engine, I built a generic example (ConvDemo) that allows you to work with the entire set of available conversions. The program fills a combo box with the available conversion families and a list box with the available units of the active family. This is the code:

procedure TForm1.FormCreate(Sender: TObject);
  i: Integer;
  GetConvFamilies (aFamilies);
  for i := Low(aFamilies) to High(aFamilies) do
    ComboFamilies.Items.Add (ConvFamilyToDescription (aFamilies[i]));
  // get the first and fire event
  ComboFamilies.ItemIndex := 0;
  ChangeFamily (self);
procedure TForm1.ChangeFamily(Sender: TObject);
  aTypes: TConvTypeArray;
  i: Integer;
  CurrFamily := aFamilies [ComboFamilies.ItemIndex];
  GetConvTypes (CurrFamily, aTypes);
  for i := Low(aTypes) to High(aTypes) do
    ListTypes.Items.Add (ConvTypeToDescription (aTypes[i]));

The aFamilies and CurrFamily variables are declared in the private section of the form as follows:

aFamilies: TConvFamilyArray;
CurrFamily: TConvFamily;

At this point, a user can enter two measurement units and an amount in the corresponding edit boxes on the form, as you can see in Figure 3.3. To make the operation faster, the user can select a value in the list and drag it to one of the two Type edit boxes. The dragging support is described in the following sidebar "Simple Dragging in Delphi."

Click To expand Figure 3.3: The ConvDemo example at run time

The units must match those available in the current family. In case of error, the text in the Type edit boxes is shown in red. This is the effect of the first part of the form's DoConvert method, which is activated as soon as the value of one of the edit boxes for the units or the amount changes. After checking the types in the edit boxes, the DoConvert method performs the conversion, displaying the result in the fourth, grayed edit box. In case of errors, you'll get an appropriate message in the same box. Here is the code:

procedure TForm1.DoConvert(Sender: TObject);
  BaseType, DestType: TConvType;
  // get and check base type
  if not DescriptionToConvType(CurrFamily, EditType.Text, BaseType) then
    EditType.Font.Color := clRed
    EditType.Font.Color := clBlack;
  // get and check destination type
  if not DescriptionToConvType(CurrFamily, EditDestination.Text,
      DestType) then
    EditDestination.Font.Color := clRed
    EditDestination.Font.Color := clBlack;
  if (DestType = 0) or (BaseType = 0) then
    EditConverted.Text := 'Invalid type'
    EditConverted.Text := FloatToStr (Convert (
      StrToFloat (EditAmount.Text), BaseType, DestType));

If all this is not interesting enough for you, consider that the conversion types provided serve only as a demo: You can fully customize the engine by providing the measurement units you are interested in, as described in the next section.

Part I: Foundations