Manipulating Text


This chapter presents three final C++ programs then skips into C# to present a fourth. After that, the book is entirely C#.

The programs of this section illustrate how to scroll text in a window. Doing so is quite a complex task in C++; although, the Form class simplifies this task considerably. The final C# program of this chapter uses the Form class. The first three C++ programs are used to give the reader the flavour of C++ windows programs.

The program of this section displays a body of text defined in a header file. Each line contains:

Each line to be displayed contains three columns. The first and second columns are left justified; whereas, the third column is right justified. The output of the program is shown below.

As can be seen, not all of the text is visible. The first program of this chapter displays the text without any scrolling facilities. The second program of this chapter scrolls vertically and the third example builds upon the first and second and scrolls both vertically and horizontally. The fourth C# example also scrolls vertically and horizontally.

The window procedure contains several static variables relating to rendering the text, which are shown below.

Handle standard Client(Handle Window,
                       unsigned Message,
                       Handle Parameter1,
                       Handle Parameter2)
{
 static int WidthOfCharacter,
            HeightOfCharacter,
            WidthOfCapitals;
 ...
}

Static variables retain their values between successive invocations of the containing function - the window procedure in this case. If they contain initial values, that initialization is performed but once - the first time the function is entered. In the case at hand, these variables are initialized upon receipt of the message Message::Create.

There are a number of ways of creating a device context. Perhaps the most commonly used method is the function BeginPaint. When a device context is required in a circumstance other than when processing the message Message::Paint, other functions may be used to create a device context; in particular, the function GetDeviceContext. Such a device context may be used to acquire information about the size of font characters. This is the approach used in the program at hand to obtain metrics of the font - as shown below.

case Message::Create:
 {
  ....
  Handle DeviceContext = GetDeviceContext(WindowHandle);

  TextMetrics^ TextMetricsGet = GetTextMetrics(DeviceContext);

  WidthOfCharacter = TextMetricsGet->AverageCharacterWidth;

  WidthOfCapitals = (TextMetricsGet->PitchAndFamily & 1 ? 3 : 2) * WidthOfCharacter/2;

  HeightOfCharacter = TextMetricsGet->Height + TextMetricsGet->ExternalLeading;

  ReleaseDeviceContext(WindowHandle,DeviceContext);
 }
 break;

The code fragment above obtains the average character width and the height of characters - including the external leading of characters. When a variable pitch font is used, the width of capitals is estimated as 150% of the average character width; otherwise, it is set to be the average character width.

To render the three columns of text contained in the header file, the function TextOut is used. This is the most commonly used function to perform textual output. The code used to paint the window (and hence draw the text) is shown below. A for loop is used to display multiple text lines using the function TextOut - as shown below.

   case Message::Paint:
    {
     Paint^ PaintStructure = gcnew Paint();

     Handle DeviceContext = Gdi::BeginPaint(WindowHandle,PaintStructure);

     enum {Column1=30, Column2=40};

     for (int i=0; i<Lines; i++)
      {
       Gdi::TextOut(DeviceContext,
                    WidthOfCharacter,
                    HeightOfCharacter * (i+1),
                    gcnew String("SystemMetric::" + (Metrics[i].Index).ToString());

       Gdi::TextOut(DeviceContext,
                    WidthOfCharacter + Column1 * WidthOfCapitals,
                    HeightOfCharacter * (i+1),
                    gcnew String(Metrics[i].Description));

       Gdi::SetTextAlignment(DeviceContext,(int)TextAlignment::Right | (int)TextAlignment::Top);

       Gdi::TextOut(DeviceContext,
                    WidthOfCharacter + Column1 * WidthOfCapitals + Column2 * WidthOfCharacter,
                    HeightOfCharacter * (i+1),
                    Win::GetSystemMetrics((int)Metrics[i].Index).ToString());

       Gdi::SetTextAlignment(DeviceContext,(int)TextAlignment::Left | (int)TextAlignment::Top);
     }

     Gdi::EndPaint(WindowHandle,PaintStructure);
    }
    break;

The number of lines is a fixed value calculated in the header file with identifier Lines. The first column is positioned at x-offset:

WidthOfCharacter

whereas, the second column is positioned at x-offset:

WidthOfCharacter + Column1 * WidthOfCapitals

and the third column is positioned at x-offset:

WidthOfCharacter + Column1 * WidthOfCapitals + Column2 * WidthOfCharacter

The vertical positioning is the same for each string. It is the line number plus one multiplied by the line height (the line height being calculated when processing the message Message::Create).