2011. június 17., péntek

Installing BDE from BDEINST.CAB


Problem/Question/Abstract:

How to install BDE from BDEINST.CAB file

Answer:

If you have taken a close look at the listing of the BDE installation directory (usually \Program Files\Borland\Common FIles\BDE), you've noticed there's a file called BDEINST.CAB. If BDEINST.CAB isn't present in the BDE folder, you probably chose not to let it be installed. As this tip requires this file, you might want to run install again and install only BDEINST.CAB. Anyway, let's get back to the tip.

What is BDEINST.CAB?

BDEINST.CAB is a cabinet (Microsoft's compression format) file that contains only one large file: BDEINST.DLL. This DLL contains a simple installation program along with all the necessary files for a basic install of BDE. It will correctly install BDE with the native drivers for Paradox, dBase, MS Access and FoxPro. It won't install drivers for SQL database servers. If all you need is a basic installation of BDE for supporting one of the forementioned databases, then BDEINST.CAB is the best choice for you.

Given the problem InstallShield and Wise have with installing BDE 5, BDEINST.DLL has a great appeal, since it was created by the Borland folks and doesn't suffer from the same problems InstallShield and WISE do.

There is, however, a drawback: BDEINST.DLL is a quite large file, so it's that good if you're deploying on floppy disks. There's a workaround for this problem and we'll get back to it later on.

Using BDEINST.DLL

In order to use BDEINST.DLL, all you have to do is to extract it from BDEINST.CAB. There are several ways this can be done. Two of them are:

Using WinZip or another CAB-compatible archiver. Simply extract BDEINST.DLL from the CAB file.
Using Microsoft's EXTRACT utility that comes with Windows 9x and NT. From a DOS window, issue the command below (path is also shown):

C:\Program Files\Borland\Common Files\BDE>EXTRACT /E BDEINST.CAB

This will extract BDEINST.DLL to the current directory, since no destination dir was specified in the command line.

The task now is to use the DLL. This is as simple as issuing the command line below:

C:\WINDOWS\SYSTEM\REGSVR32.EXE /S CABINST.DLL

If the command above fails, make sure you have REGSVR32.EXE on your machine. Not all machines have it, and, in case of deploying BDEINST.DLL, it's also a good idea to deploy REGSVR32.EXE. This file can be found in \WINDOWS\SYSTEM or \WINNT\SYSTEM32.

A progress dialog box will popup indicating that the installation of BDE is going ok. This is all it takes to install BDE without needing any additional tool such as InstallShield or Wise.

If you do not want to deploy REGSVR32.EXE, you can create a small VCL-less and formless application that simply calls DllRegisterServer from the DLL.

2011. június 16., csütörtök

How to store records in a TList when their number is unknown until runtime


Problem/Question/Abstract:

How to store records in a TList when their number is unknown until runtime

Answer:

To store a number of records ( probably number unknown until runtime ), one would use a Delphi TList object. TList is basically an array of pointers that grows as needed, up to 16K pointers can be stored in a TList. It will accept anything that even remotely looks like a pointer (a pointer is an address, normally of a bit of data that has been allocated from the heap, and needs 4 bytes to store the address). If you work with dynamically allocated data items you need to take care of releasing this memory to the system heap again if it is no longer needed. It is easy to forget this, especially if the data items are kept in a list. It is thus a good idea to derive a custom list class from TList that takes care of freeing the memory for the items it stores automatically.


type
  TRecord = record { the record type }
    { ... }
  end;
  PRecord = ^TRecord; { pointer type for pointers to TRecords }
  TRecordList = class(TList) { a customized version of TList to hold PRecord pointers }
  private
    procedure SetRecord(index: Integer; Ptr: PRecord);
    function GetRecord(index: Integer): PRecord;
  public
    procedure Clear;
    destructor Destroy; override;
    property Records[i: Integer]: PRecord read GetRecord write SetRecord;
  end;

  {Methods of TRecordList}

procedure TRecordList.SetRecord(index: Integer; Ptr: PRecord);
var
  p: PRecord;
begin
  { get the pointer currently in slot index }
  p := Records[index];
  if p <> Ptr then
  begin
    { if it is different from the one we are asked to put into this slot, check if it is <> Nil. If so, dispose of the memory it points at! }
    if p <> nil then
      Dispose(p);
    { store the passed pointer into the slot }
    Items[index] := Ptr;
  end;
end;

function TRecordList.GetRecord(index: Integer): PRecord;
begin
  { return the pointer in slot index, typecast to PRecord }
  Result := PRecord(Items[index]);
end;

procedure TRecordList.Clear;
var
  i: Integer;
  p: PRecord;
begin
  { dispose of the memory pointed to by all pointers in the list that are not Nil }
  for i := 0 to Pred(Count) do
  begin
    p := Records[i];
    if p <> nil then
      Dispose(p);
  end;
  { call the Clear method inherited from TList to set Count to 0 }
  inherited Clear;
end;

destructor TRecordList.Destroy;
begin
  { clear the list to dispose of any pointers still stored first }
  Clear;
  inherited Destroy;
end;



All we did up to here was declaring types, lets put them to use now. First we need an instance of TRecordList to store pointers to dynamically allocated records in. That may be a field in a form, for example. Code to create and destroy the list has to be added to the forms OnCreate and OnDestroy handlers.


{ in a forms public section: }
RecordList: TRecordList;

{ in the forms OnCreate handler }
RecordList := TRecordList.Create;

{ in the forms OnDestroy handler }
RecordList.Free;


To add a record to the list you use code like this:


var
  Ptr: PRecord; { local variable in a method }

  New(Ptr); { allocate a record on the heap }
  with Ptr^ do
  begin { note the caret to dereference the pointer }
    { put data into the fields of the record }
  end;
  recordIndex := RecordList.Add(Ptr);


You do this sequence for each record you need to store. Each record now resides at a specific slot in the list and you can access it via the index of this slot. Indices start at 0 and run to RecordList.Count-1.

2011. június 15., szerda

How to prevent csOpaque child controls from flickering


Problem/Question/Abstract:

I have a TPaintBox that I use to draw a representation of the data the user is entering. It updates whenever the form is repainted or data is changed. This works fine. Using D5, I've noticed that when the user is moving the mouse around causing hints to pop up and down, it causes a tremendous amount of flicker. In part, this is caused by the fact that I clear the canvas before redrawing. Should I draw to an invisible paintbox and then copy to a TImage?

Answer:

{ Overrides the WM_ERASEBKGND message in TWinControl and TForm to prevent flicker of csOpaque child controls.
Unpublished; (c) 1999, David Best, davebest@usa.net
You are free to use this and derived works provided you acknowlege it's source in your code.}

procedure WMEraseBkgndEx(WinControl: TWinControl; var Message: TWmEraseBkgnd);
var
  i, Clip, SaveIndex: Integer;
begin
  { Only erase background if we're not doublebuffering or painting to memory }
  with WinControl do
    if not DoubleBuffered or (TMessage(Message).wParam = TMessage(Message).lParam) then
    begin
      SaveIndex := SaveDC(Message.DC);
      Clip := SimpleRegion;
      if ControlCount > 0 then
      begin
        for i := 0 to ControlCount - 1 do
          if not (Controls[i] is TWinControl) then
            {child windows already excluded}
            with Controls[i] do
            begin
              if (Visible or (csDesigning in ComponentState) and not
                                                                (csNoDesignVisible in ControlStyle))
                 and (csOpaque in ControlStyle) then
              begin
                Clip := ExcludeClipRect(Message.DC, Left, Top, Left +
                                                                 Width, Top + Height);
                if Clip = NullRegion then
                  break;
              end;
            end;
      end;
      if Clip <> NullRegion then
        FillRect(Message.DC, ClientRect, Brush.Handle);
      RestoreDC(Message.DC, SaveIndex);
    end;
  Message.Result := 1;
end;

procedure TNoFlickerForm.WMEraseBkGnd(var msg: TWMEraseBkGnd);
begin
  WMEraseBkgndEx(Self, msg);
end;

2011. június 14., kedd

Delphi Frames


Problem/Question/Abstract:

Understanding Delphi 5's New Visual Container Class

Answer:

Delphi 5 introduces a new, visual container class that represents an important advance in rapid application development (RAD) programming. This class, TFrame, provides you with the ability to visually configure a set of one or more components, and then to easily reuse this configuration throughout your application. This capability is so powerful that Delphi 5's integrated development environment (IDE) was re-designed to make extensive use of frames.

This article begins with a general discussion of what frames are, and what benefits they provide. It continues with a demonstration of how to create frames, and how to modify the properties of objects that appear on frame instances. Next, you'll learn how to create event handlers for frames, and how to override or extend these event handlers in frame instances. This article concludes by showing you how to add frames to the Component palette and the Object Repository, and the benefits of doing so.

Overview of Frames

There are two primary benefits of frames. The first is that, under certain circumstances, frames can dramatically reduce the amount of resources that need to be stored in a project. The second, and generally more important benefit, is that frames permit you to visually create objects that can be duplicated and extended. These happen to be the same two benefits that you enjoy with visual form inheritance (VFI).

VFI permits you to create form objects that can be inherited from easily. The main limit to VFI is that you must use the form in an all-or-nothing fashion. Specifically, when you use VFI you always create an entirely new form. Frames, on the other hand, are more similar to panels in this respect. That is, a single form can contain two or more frames. Importantly, every frame maintains its relationship with the parent TFrame class, meaning that subsequent changes to the parent class are automatically inherited by the instances. Although you could achieve a similar effect using TPanel components, doing so would be a strictly code-based operation. That is, you would have to write the code to define the TPanel descendants manually. Frames, on the other hand, are designed visually, just like forms.

Frames can also be thought of as sharing some similarities with component templates (a group of one or more components that are saved to the Component palette by selecting Component | Create Component Template). However, the similarities are limited to the fact that both component templates and frames are designed visually (unlike traditional component design, which is an exclusively code-based process). The differences between component templates and frames are actually very great. As you've already learned, a frame is an instance of a defining class, and, as such, is changed when the defining class is changed. By comparison, component templates are aggregates of components. A change to a component template has no effect on objects previously created from that template.

Creating a Frame

The following steps demonstrate how to create a frame (the code for this project is available for download; see end of article for details).

Select File | New Application to create a new project.

Select File | New Frame to create a new frame. On this frame, place three labels and three DBEdits. Also place a DBNavigator and a DataSource (as shown in Figure 1). Set the captions of the labels to ID, First Name, and Last Name. Set the DataSource property of each DBEdit and the DBNavigator to DataSource1.

With this frame still selected, set its Name property to NameFrame. (More so than other objects, it's particularly important to give a frame a meaningful name.) Finally, save the frame by selecting File | Save As. In this case, save the frame using the file name NAMEFRAM.PAS.



Figure 1: A simple frame for displaying an ID number, as well as a first and last name.

That's all there is to creating a frame. The following section demonstrates how to put it to use.

Using a Frame

A frame is a component. However, its use typically differs from most other components that appear on the Component palette. The following steps demonstrate how to use a frame:

Select Form1 of the application you created in the preceding steps.

Add two group boxes to the form, one above the other. Set the caption of the first frame to Customers, and the caption of the second to Employees. Your form may look something like that shown in Figure 2.

Now add the frames. With the Standard page of the Component palette selected, click on the Frame component and drop it in the Customers frame. Delphi responds by displaying the Select frame to insert dialog box (see Figure 3).

Select NameFrame. The frame will now appear in the Customers frame. Repeat this process, this time placing the frame within the Employees frame. You may have to select each frame and correct its size, depending on how you placed it originally. When you're done, your form should look similar to that shown in Figure 4.

Continue by placing two Table components onto the form. Set the DatabaseName property of both tables to IBLocal. Set the TableName property of Table1 to CUSTOMER and the TableName property of Table2 to EMPLOYEE. Make both tables active by setting their Active properties to True.

Here's where things get interesting. Select the DataSource in the Customers frame, and set its DataSet property to Table1. Normally you can't directly select objects that appear within a component, but frames are special. You can select any of the objects that appear within a frame, and work with their properties. Next, repeat this operation by selecting the DataSource in the Employees frame and setting its DataSet property to Table2.

Finally, hook up all the DBEdits. Assign the DataField property of the three DBEdits on the Customers frame to CUST_NO, CONTACT_FIRST, and CONTACT_LAST, respectively. For the Employees frame, set the DataField properties of these same DBEdits to EMP_NO, FIRST_NAME, and LAST_NAME.

Save this project and then run it. The running project will look something like that shown in Figure 5.



Figure 2: A form ready for the placement of frames.


Figure 3: The Select frame to insert dialog box.


Figure 4: Two instances of NameFrame appear on this form.


Figure 5: The example frame project at run time.

Frames and Inheritance

Up to this point, there may seem to be little benefit to using frames. However, it's when you use the same frame in a number of different situations, and then want to change all instances, that the power of frames becomes obvious. For example, imagine you've decided to make NameFrame read-only. This can be accomplished easily by simply changing the original frame; each frame instance immediately inherits all changes.

You can demonstrate this by following these steps:

With the project created in the preceding section, press [Shift][F12] and select NameFrame from the displayed list of forms.

Set the AutoEdit property of the DataSource to False.

Next, select the DBNavigator, expand its VisibleButtons property, and set the nbInsert, nbDelete, nbEdit, nbPost, and nbCancel flags to False.

Now look at your main form. Notice that both NameFrame descendants have inherited the changes you made to the frame (see Figure 6).



Figure 6: Updating NameFrame automatically causes all instances to be updated as well.

Overriding Contained Component Properties

One of the advantages of frames (one shared with VFI) is that you can change the properties and event handlers associated with the objects inside the inherited frame. These changes override the inherited values. Specifically, subsequent changes to the overridden property in the original frame don't affect the inherited value. The following steps demonstrate this behavior:

Select the label whose caption is "ID" in the Customers frame. Using the Object Inspector, change its Caption property to Customer No:. Now select the ID label for the Employees frame and change it to Employee ID:.

Press [Shift][F12] and select NameFrame. Change the caption of this ID label to Identifier.

Return to the main form. Notice that the Caption properties of the labels haven't changed to Identifier. They still use their overridden values.

This effect is accomplished through information stored in the DFM file. Figure 7 displays a relevant part of the DFM file for this project.



Figure 7: A DFM file containing property overrides for a frame instance.

Notice that information about all components contained within the frame whose property values have been changed appear in the frame's inline section of the DFM file. However, this section only lists those values that have been changed. All other properties are assigned their values based either on the values set for the original frame (and which are stored in the frame's DFM file), or are designated as default values in the individual component's class declarations.

Contained Object Event Handlers

Objects contained within a frame may also have event handlers. Although events are simply properties of a method pointer type, they're treated differently than other types of properties when it comes to overriding the default behavior defined for the frame.

Let's begin by considering how an event handler is defined for a frame object. Consider the frame shown in Figure 8. (This code is found in the Frame2 project found in the download for this article.) This frame contains two buttons, one labeled Help and the other Done. (Of course, these captions can be overridden in descendant frames). These buttons also have OnClick event handlers, which are shown in Figure 9.


Figure 8: A frame with components that have event handlers.

procedure TTwoButtonFrame.Button1Click(Sender: TObject);
begin
  if (TComponent(Sender).Tag = 0) or
    (Application.HelpFile = '') then
    MessageBox(Application.Handle, 'Help not available',
      'Help', MB_OK)
  else
    Application.HelpContext(TComponent(Sender).Tag);
end;

procedure TTwoButtonFrame.Button2Click(Sender: TObject);
var
  AParent: TComponent;
begin
  AParent := TComponent(Sender).GetParentComponent;
  while not (AParent is TCustomForm) do
    AParent := AParent.GetParentComponent;
  TCustomForm(AParent).Close;
end;
Figure 9: The OnClick event handlers for the Help and Done buttons on our frame.

Just as the event handlers for objects on a form are published methods of that form's class, the event handlers of objects on a frame are published methods of that frame. (The code segment doesn't actually depict the fact that these methods are published. Rather, they're declared in the default visibility section of the frame's class declaration, and the default visibility is published.)

If you inspect the code associated with the Button2Click event handler, which is associated with the Done button, you'll notice that the event handlers associated with the frame introduces an interesting artifact. Specifically, Self is the frame, not the form in which the frame is contained. Consequently, it isn't possible to simply invoke the Close method from within this event handler to close the form. When an unqualified method invocation appears in code, the compiler assumes you want it to apply to Self. Because a TFrame object doesn't have a Close method, the compiler generates an error if you simply use an unqualified call to Close.

Because the frame in this example is designed to be embedded within a form, the event handler uses the GetParentComponent method of the frame to climb the containership hierarchy within which the frame is nested. Once a TCustomForm instance is found (which will either be a TForm descendant or a custom form based upon TCustomForm), that reference is used to invoke the form's Close method.

Overriding Contained Object Event Handlers

If you're familiar with event overriding in VFI, you'll recall that Delphi embeds a call to inherited from within an overridden event handler on a descendant form. You can then alter the generated code to either add additional behavior before, or following, the call to inherited, or conditionally invoke inherited, or you can omit the call altogether.

Frame descendants don't use inherited when invoking the event handler for an object embedded on the parent frame. Instead, the ancestor frame's method is called directly. For example, if you place the TwoButtonFrame frame (shown in Figure 8) onto a form and then double-click it, Delphi will generate the following code:

procedure TForm1.TwoButtonFrame1Button2Click(
  Sender: object);
begin
  TwoButtonFrame1.Button2Click(Sender);
end;

In this generated code, TwoButtonFrame1 is the frame descendant of TTwoButtonFrame (the original frame's class). Button2Click, as you saw in the earlier code segment, is the event handler for the Done button on that frame. As a result, this code invokes the original event handler, passing it the Sender that was passed to the button on the frame instance.

This means that event handling introduces another interesting feature. Specifically, in these situations, Sender is generally not a member of the Self object. Indeed, Sender is usually a member of the form object, and Self is the frame object.

Figure 10 shows an overridden event handler for a TwoButtonFrame descendant that was placed on a form. In this case, the original behavior is "commented out," so the new behavior completely replaces the originally defined behavior for the Done button.

procedure TForm1.TwoButtonFrame1Button2Click(
  Sender: TObject);
begin
  with TForm2.Create(Self) do
  begin
    ShowModal;
    Release;
  end;
  // The following is the original, auto-generated code
  //   TwoButtonFrame1.Button2Click(Sender);
end;
Figure 10: An overridden event handler for a TwoButtonFrame descendant that was placed on a form.

The caption of this button was also overridden, so it displays the text, Start. Figure 11 shows the form on which this TwoButtonFrame descendant appears.


Figure 11: This TwoButtonFrame instance overrides both the caption and the OnClick event handler.

Frames that Save Resources

The form shown in Figure 11 actually contains two frames. We've already discussed the TwoButtonFrame frame. The second frame displays the company logo, and is named LogoFrame.

LogoFrame appears on more than one form in the FramDemo project. The alternative to using a frame to display the logo is to place an Image object on each form upon which you want the logo to appear. However, the use of a frame for this purpose significantly reduces the amount of resources that must be compiled into the .EXE, and, therefore, results in a smaller executable.


The reason for this can be seen if you consider the following segment of the DFM file for the form shown in Figure 11:

inline LogoFrame1: TLogoFrame
        Left = 6
        Top = 6
        Width = 211
        Height = 182
        inherited Image1: TImage
        Width = 211
        Height = 182
        end
end

If, instead, a TImage instance had been placed onto the form, the DFM file for the form would have had to contain the entire binary representation of the logo. Figure 12 shows a segment of LogoFrame's DFM file. (Note that it shows only a tiny portion of the entire hexadecimal representation of the binary resource.) Furthermore, every form containing one of these images would have repeated this resource. When a frame is used, however, that resource is defined only once.

object LogoFrame: TLogoFrame
  Left = 0
    Top = 0
    Width = 239
    Height = 178
    TabOrder = 0
    object Image1: TImage
    Left = 0
      Top = 0
      Width = 239
      Height = 178
      Align = alClient
      Picture.Data = {
    07544269746D6170D6540000424DD654000000000000760000...
Figure 12: A segment of LogoFrame's DFM file.

Simplifying Frame Use

Within a single, small project, it's fairly easy to use the Frame component on the Standard page of the Component palette. For larger projects, however, or for situations where you want to use the same frame in multiple applications, you need something easier. Fortunately, Delphi permits you to place individual frames onto the Component palette, permitting these frames to be used easily and repeatedly without the extra steps required by the Frame component. A frame can also be placed into the Object Repository, permitting it to be copied easily. Both of these techniques are described in the following sections.

Adding a Frame to the Component Palette

By placing a particular frame onto the Component palette, you make its placement as simple as any other. By comparison, using the Frame component on the Standard page of the Component palette requires four steps and limits you to placing frames already defined within your project. To place a particular frame onto the Component palette, follow these steps:

Save your frame to disk. If you want to use this frame in multiple applications, it's highly recommended that you save the frame to a directory that won't be deleted when you update Delphi. For example, create a folder named c:\Program Files\Borland\DelphiFrames and store your frames there.

Select the frame and right-click on it. Select Add to Palette. Delphi displays the Component Template Information dialog box (see Figure 13).

Define the name of the frame component in the Component name field, the page of the Component palette on which you want the frame to appear in the Palette page field, and, if you've created a custom 24 x 24 pixel, 16-color icon for the frame, click the Change button to select this .BMP file. Click OK when you're done.



Figure 13: The Component Template Information dialog box.

Using a Frame from the Component Palette

To use a frame previously placed on the Component palette, select the page of the Component palette onto which you saved the frame, select the frame's icon, and drop it onto the form on which you want a descendant of that frame to appear. This process requires only two steps.

Adding a Frame to the Object Repository

By adding a frame to the Object Repository, you make it easy to copy it into a new project. Especially important is the ability to use the inheritance offered by the Object Repository to place an inherited frame into a new project, thereby maintaining the relationship between the frame and its ancestor. To add a frame to the Object Repository, follow these steps:

Save your frame to disk. In addition to saving this frame to Delphi's OBJREPOS directory or to a shared directory, you can also save it to the same one to which you save frames that you add to the Component palette. Saving the frame to a shared directory is especially nice if you are using a shared object repository. This permits multiple developers to share frames.

Right-click the frame and select Add To Repository. Delphi responds by displaying the Add To Repository dialog box (see Figure 14).

Fill out the Add To Repository dialog box just as you would for any template you're adding to the Object Repository. Click OK when done.



Figure 14: The Add To Repository dialog box.

Using a Frame from the Object Repository

To use a frame from the Object Repository, use the following steps:

Select File | New.

Select the page of the Object Repository to which you saved your frame template (see Figure 15).

Select the icon for the frame; then select the Inherit radio button.

Click OK to add an inherited version of the frame to your project.



Figure 15: The location of your frame template.

If you select the Copy radio button instead of the Inherit radio button, the newly added frame will be a copy of the original frame. This is useful when you want to create a new frame, but don't want to maintain a relationship between it and the original.

Conclusion

Does it make a difference whether you place a frame you want to reuse on the Component palette or the Object Repository? The answer is a strong "Yes!" In most cases, you'll want to place frames you use frequently onto the Component palette. When you place a frame from the Component palette, you're always placing an instance of the frame class. You can then easily change the properties and event handlers of this instance as described earlier in this article. By comparison, placing a frame from the Object Repository creates a new class, not an instance. This new class is either a copy of the original or a descendant, depending on which radio button you select in the Object Repository dialog box. If you want to use a frame in a project, it makes a great deal of sense to place an instance, rather than define a new class for your frame. For this purpose, saving the frame to the Component palette is the best approach.

The one situation where you might want to use the Object Repository is when you're specifically creating hierarchies of frames, where each frame descendant introduces additional objects, methods, or event handlers. Here, the inheritance offered by the Object Repository makes it easier for you to create each new descendant. However, once you've defined the frame descendants you want to use regularly, I would again suggest that you add these to the Component palette to simplify their use.


Component Download: delphi_frames.zip

2011. június 13., hétfő

How to draw buttons on the title bar of a TForm


Problem/Question/Abstract:

How to draw buttons on the title bar of a TForm

Answer:

Solve 1:

Place an icon-sized TImage on a form and add the following code:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls;

type
  TForm1 = class(TForm)
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
  private
    {Private declarations}
    TitleBarCanvas: TCanvas;
    procedure WMNCPaint(var Msg: TWMNCPaint); message WM_NCPAINT;
    procedure WMNCActivate(var Msg: TWMNCActivate); message WM_NCACTIVATE;
    procedure DrawExtraStuff;
  public
    {Public declarations}
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
  NonClientMetrics: TNonClientMetrics;
begin
  TitleBarCanvas := TCanvas.Create;
  TitleBarCanvas.Handle := GetWindowDC(Handle);
  NonClientMetrics.cbSize := SizeOf(NonClientMetrics);
  SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @NonClientMetrics, 0);
  TitleBarCanvas.Font.Handle := CreateFontIndirect(NonClientMetrics.lfCaptionFont);
  TitleBarCanvas.Brush.Style := bsClear;
  Caption := '';
end;

procedure TForm1.WMNCPaint(var Msg: TWMNCPaint);
begin
  inherited;
  DrawExtraStuff;
end;

procedure TForm1.WMNCActivate(var Msg: TWMNCActivate);
begin
  inherited;
  if Msg.Active then
    TitleBarCanvas.Font.Color := clCaptionText
  else
    TitleBarCanvas.Font.Color := clInactiveCaptionText;
  DrawExtraStuff;
end;

procedure TForm1.DrawExtraStuff;
var
  X, Y, TransColor: Integer;
begin
  {set the transparent color to bottom left pixel}
  TransColor := Image1.Canvas.Pixels[0, Image1.Picture.Height - 1];
  with Image1 do
    for x := 0 to Picture.Width - 1 do
      for y := 0 to Picture.Height - 1 do
        if Canvas.Pixels[x, y] <> TransColor then
          TitleBarCanvas.Pixels[22 + x, 5 + y] := Canvas.Pixels[x, y];
  TitleBarCanvas.TextOut(40, 6, '<- Here is the other icon');
end;

end.


Solve 2:

I got my first clue into solving this problem when I wrote a previous tip that covered rolling up the client area of forms so that only the caption bar showed. In my research for that tip, I came across the WMSetText message that is used for drawing on a form's canvas. I wrote a little sample application to test drawing in the caption area. The only problem with my original code was that the button would disappear when I resized or moved the form.

I turned to well-known Delphi/Pascal guru, Neil Rubenking, for help. He pointed me in the direction of his book, "Delphi Programming Problem Solver," which had an example of doing this exact thing. The code you'll see below is an adaptation of the example in his book. The most fundamental difference between our examples is that I wanted to make a speedbutton with a bitmap glyph, and Neil actually drew a shape directly on the canvas. Neil also placed the button created in 16-bit Delphi on the left-hand side of the frame, and Win32 button placement was on the right. I wanted my buttons to be placed on the right for both versions, so I wrote appropriate code to handle that. The deficiency in my code was the lack of handlers for activation and painting in the non-client area of the form.

One thing that I'm continually discovering is that there is a very definitive structure in Windows - a definite hierarchy of functions. I've realized that the thing that makes Windows programming at the API level difficult is the sheer number of functions in the API set. For those who are reluctant to dive into the WinAPI, think in terms of categories first, then narrow your search. You'll find that doing it this way will make your life much easier.

What makes all of this work is Windows messages. The messages that we are interested in here are not the usual Windows messages handled by vanilla Windows apps, but are specific to an area of a window called the non-client area. The client area of a window is the part inside the border which is where most applications present information. The non-client area of a window consists of its borders, caption bar, system menu, and sizing buttons. The Windows messages that pertain to this area have the naming convention of WM_NCMessageType. Taking the name apart, 'WM' stands for Windows Message, 'NC' stands for Non-client area, and MessageType is the message type being trapped. For example, WM_NCPaint is the paint message for the non-client area. Taking into account the hierarchical and categorical nature of the Windows API, nomenclature is a very big part of it; especially with Windows messages. If you look in the help file under messages, peruse through the list of messages and you will see that the order that is followed.

Let's look at a list of things that we need to consider to add a button to the title bar of a form:

We need to have a function to draw the button
We'll have to trap drawing and painting events so that our button stays visible when the form activates, resizes, or moves
Since we're dropping a button on the title bar, we have to have some way of trapping for a mouse click on the button.

I'll now discuss these topics, in the above order.


Drawing a TRect as a Button

As I mentioned above, you can't drop VCL objects onto a non-client area of a window, but you can draw on it and essentially simulate the appearance of a button. In order to perform drawing in the title bar of a window, you have to do three very important things in order:

You have to get the current measurements of the window and the size of the frame bitmaps so you know what area to draw in and how big to draw the rectangle. 2.Then, you have to define a TRect structure with the proper size and position within the title bar. 3.Finally, you have to draw the TRect to appear as a button, then add any glyphs or text you might want to draw to the buttonface.

All this is accomplished in a single call. For this program we make a call to a procedure called DrawTitleButton, which is listed below:

procedure TTitleBtnForm.DrawTitleButton;
var
  bmap: TBitmap; {Bitmap to be drawn - 16 x 16 : 16 Colors}
  XFrame, {X and Y size of Sizeable area of Frame}
  YFrame,
    XTtlBit, {X and Y size of Bitmaps in caption}
  YTtlBit: Integer;
begin
  {Get size of form frame and bitmaps in title bar}
  XFrame := GetSystemMetrics(SM_CXFRAME);
  YFrame := GetSystemMetrics(SM_CYFRAME);
  XTtlBit := GetSystemMetrics(SM_CXSIZE);
  YTtlBit := GetSystemMetrics(SM_CYSIZE);
{$IFNDEF WIN32}
  TitleButton := Bounds(Width - (3 * XTtlBit) - ((XTtlBit div 2) - 2), YFrame - 1,
    XTtlBit + 2, YTtlBit + 2);
{$ELSE} {Delphi 2.0 positioning}
  if (GetVerInfo = VER_PLATFORM_WIN32_NT) then
    TitleButton := Bounds(Width - (3 * XTtlBit) - ((XTtlBit div 2) - 2), YFrame - 1,
      XTtlBit + 2, YTtlBit + 2)
  else
    TitleButton := Bounds(Width - XFrame - 4 * XTtlBit + 2, XFrame + 2, XTtlBit + 2,
      YTtlBit + 2);
{$ENDIF}
  Canvas.Handle := GetWindowDC(Self.Handle); {Get Device context for drawing}
  try
    {Draw a button face on the TRect}
    DrawButtonFace(Canvas, TitleButton, 1, bsAutoDetect, False, False, False);
    bmap := TBitmap.Create;
    bmap.LoadFromFile('help.bmp');
    with TitleButton do
{$IFNDEF WIN32}
      Canvas.Draw(Left + 2, Top + 2, bmap);
{$ELSE}
      if (GetVerInfo = VER_PLATFORM_WIN32_NT) then
        Canvas.Draw(Left + 2, Top + 2, bmap)
      else
        Canvas.StretchDraw(TitleButton, bmap);
{$ENDIF}
  finally
    ReleaseDC(Self.Handle, Canvas.Handle);
    bmap.Free;
    Canvas.Handle := 0;
  end;
end;

Step 1 above is accomplished by making four calls to the WinAPI function, GetSystemMetrics, asking the system for the width and height of the window that can be sized (SM_CXFRAME and SM_CYFRAME), and the size of the bitmaps contained on the title bar (SM_CXSIZE and SM_CYSIZE).

Step 2 is performed with the Bounds function which returns a TRect defined by the size and position parameters which are supplied to it. Notice that I used some conditional compiler directives here. This is because the size of the title bar buttons in Windows 95 and Windows 3.1 are different, so they have to be sized differently. And since I wanted to be able to compile this in either version of Windows, I used a test for the predefined symbol, WIN32, to see what version of Windows the program is compiled under. However, since the Windows NT UI is the same as Windows 3.1, it's necessary to grab further version information under the Win32 conditional to see if the Windows version is Windows NT. If it is, then we define the TRect to be just like the Windows 3.1 TRect.

To perform Step 3, we make a call to the Buttons unit's DrawButtonFace to draw button features within the TRect that we defined. As added treat, I included code to draw a bitmap in the button. Again, you'll see that I used a conditional compiler directive to draw the bitmap under different versions of Windows. I did this purely for personal reasons because the bitmap that I used was 16 X 16 pixels in dimension, which might be too big for Win95 buttons. So I used StretchDraw under Win32 to stretch the bitmap to the size of the button.


Trapping the Drawing and Painting Events

You have to make sure that the button will stay visible every time the form repaints itself. Painting occurs in response to activation and resizing, which fire off paint and text setting messages that will redraw the form. If you don't have a facility to redraw your button, you'll lose it every time a repaint occurs. So what we have to do is write event handlers which will perform their default actions, but also redraw our button when they fire off. The following four procedures handle the paint triggering and painting events:

{Paint triggering events}

procedure TForm1.WMNCActivate(var Msg: TWMNCActivate);
begin
  inherited;
  DrawTitleButton;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  Perform(WM_NCACTIVATE, Word(Active), 0);
end;

{Painting events}

procedure TForm1.WMNCPaint(var Msg: TWMNCPaint);
begin
  inherited;
  DrawTitleButton;
end;

procedure TForm1.WMSetText(var Msg: TWMSetText);
begin
  inherited;
  DrawTitleButton;
end;

Every time one of these events fires off, it makes a call to the DrawTitleButton procedure. This will ensure that our button is always visible on the title bar. Notice that we use the default handler OnResize on the form to force it to perform a WM_NCACTIVATE.


Handling Mouse Clicks

Now that we've got code that draws our button and ensures that it's always visible, we have to handle mouse-clicks on the button. The way we do this is with two procedures. The first procedure tests to see if the mouse-click was in the area of our button, then the second procedure actually performs the code execution associated with our button. Let's look at the code below:

{Mouse-related procedures}

procedure TForm1.WMNCHitTest(var Msg: TWMNCHitTest);
begin
  inherited;
  {Check to see if the mouse was clicked in the area of the button}
  with Msg do
    if PtInRect(TitleButton, Point(XPos - Left, YPos - Top)) then
      Result := htTitleBtn;
end;

procedure TForm1.WMNCLButtonDown(var Msg: TWMNCLButtonDown);
begin
  inherited;
  if (Msg.HitTest = htTitleBtn) then
    ShowMessage('You pressed the new button');

end;

The first procedure WMNCHitTest(var Msg : TWMNCHitTest) is a hit tester message to determine where the mouse was clicked in the non-client area. In this procedure we test if the point defined by the message was within the bounds of our TRect by using the PtInRect function. If the mouse click was performed in the TRect, then the result of our message is set to htTitleBtn, which is a constant that was declared as htSizeLast + 1. htSizeLast is a hit test constant generated by hit test events to test where the last hit occurred.

The second procedure is a custom handler for a left mouse-click on a button in the non-client area. Here we test if the hit test result was equal to htTitleBtn. If it is, we show a message. This was purely for simplicity's sake, but you can make any call you choose to at this point.


Putting it All Together

Let's look at the entire code in the form to see how it all works together:

unit Capbtn;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs,
    Buttons;

type
  TTitleBtnForm = class(TForm)
    procedure FormResize(Sender: TObject);
  private
    TitleButton: TRect;
    procedure DrawTitleButton;
    {Paint-related messages}
    procedure WMSetText(var Msg: TWMSetText); message WM_SETTEXT;
    procedure WMNCPaint(var Msg: TWMNCPaint); message WM_NCPAINT;
    procedure WMNCActivate(var Msg: TWMNCActivate); message WM_NCACTIVATE;
    {Mouse down-related messages}
    procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
    procedure WMNCLButtonDown(var Msg: TWMNCLButtonDown);
      message WM_NCLBUTTONDOWN;
    function GetVerInfo: DWORD;
  end;

var
  TitleBtnForm: TTitleBtnForm;

const
  htTitleBtn = htSizeLast + 1;

implementation

{$R *.DFM}

procedure TTitleBtnForm.DrawTitleButton;
var
  bmap: TBitmap; {Bitmap to be drawn - 16 X 16 : 16 Colors}
  XFrame, {X and Y size of Sizeable area of Frame}
  YFrame,
    XTtlBit, {X and Y size of Bitmaps in caption}
  YTtlBit: Integer;
begin
  {Get size of form frame and bitmaps in title bar}
  XFrame := GetSystemMetrics(SM_CXFRAME);
  YFrame := GetSystemMetrics(SM_CYFRAME);
  XTtlBit := GetSystemMetrics(SM_CXSIZE);
  YTtlBit := GetSystemMetrics(SM_CYSIZE);
{$IFNDEF WIN32}
  TitleButton := Bounds(Width - (3 * XTtlBit) - ((XTtlBit div 2) - 2), YFrame - 1,
    XTtlBit + 2, YTtlBit + 2);
{$ELSE} {Delphi 2.0 positioning}
  if (GetVerInfo = VER_PLATFORM_WIN32_NT) then
    TitleButton := Bounds(Width - (3 * XTtlBit) - ((XTtlBit div 2) - 2), YFrame - 1,
      XTtlBit + 2, YTtlBit + 2)
  else
    TitleButton := Bounds(Width - XFrame - 4 * XTtlBit + 2, XFrame + 2, XTtlBit + 2,
      YTtlBit + 2);
{$ENDIF}
  Canvas.Handle := GetWindowDC(Self.Handle); {Get Device context for drawing}
  try
    {Draw a button face on the TRect}
    DrawButtonFace(Canvas, TitleButton, 1, bsAutoDetect, False, False, False);
    bmap := TBitmap.Create;
    bmap.LoadFromFile('help.bmp');
    with TitleButton do
{$IFNDEF WIN32}
      Canvas.Draw(Left + 2, Top + 2, bmap);
{$ELSE}
      if (GetVerInfo = VER_PLATFORM_WIN32_NT) then
        Canvas.Draw(Left + 2, Top + 2, bmap)
      else
        Canvas.StretchDraw(TitleButton, bmap);
{$ENDIF}
  finally
    ReleaseDC(Self.Handle, Canvas.Handle);
    bmap.Free;
    Canvas.Handle := 0;
  end;
end;

{Paint triggering events}

procedure TTitleBtnForm.WMNCActivate(var Msg: TWMNCActivate);
begin
  inherited;
  DrawTitleButton;
end;

procedure TTitleBtnForm.FormResize(Sender: TObject);
begin
  Perform(WM_NCACTIVATE, Word(Active), 0);
end;

{Painting events}

procedure TTitleBtnForm.WMNCPaint(var Msg: TWMNCPaint);
begin
  inherited;
  DrawTitleButton;
end;

procedure TTitleBtnForm.WMSetText(var Msg: TWMSetText);
begin
  inherited;
  DrawTitleButton;
end;

{Mouse-related procedures}

procedure TTitleBtnForm.WMNCHitTest(var Msg: TWMNCHitTest);
begin
  inherited;
  {Check to see if the mouse was clicked in the area of the button}
  with Msg do
    if PtInRect(TitleButton, Point(XPos - Left, YPos - Top)) then
      Result := htTitleBtn;
end;

procedure TTitleBtnForm.WMNCLButtonDown(var Msg: TWMNCLButtonDown);
begin
  inherited;
  if (Msg.HitTest = htTitleBtn) then
    ShowMessage('You pressed the new button');
end;

function TTitleBtnForm.GetVerInfo: DWORD;
var
  verInfo: TOSVERSIONINFO;
begin
  verInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
  if GetVersionEx(verInfo) then
    Result := verInfo.dwPlatformID;
  {Returns:
  VER_PLATFORM_WIN32s -- Win32s on Windows 3.1
  VER_PLATFORM_WIN32_WINDOWS -- Win32 on Windows 95
  VER_PLATFORM_WIN32_NT -- Windows NT }
end;

end.

You might want to play around with this code a bit to customize it to your own needs. For instance, if you want to add a bigger button, add pixels to the XTtlBit var. You might also want to mess around with creating a floating toolbar that is purely on the title bar. Also, now that you have a means of interrogating what's going on in the non-client area of the form, you might want to play around with the default actions taken with the other buttons like the System Menu button to perhaps display your own custom menu. Take heed though, playing around with Windows messages can be dangerous. Save your work constantly, and be prepared for some system crashes while you mess around with them.


Solve 3:

unit TitleBtn;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Forms, Dialogs,
  Buttons, Controls, StdCtrls, ExtCtrls;

type
  TTitleBtnForm = class(TForm)
    procedure FormResize(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    function GetSystemTitleBtnCount: integer;
    procedure KillHint;
  private
    TitleButton: TRect;
    FActive: boolean;
    FHint: THintWindow;
    Timer2: TTimer;
    procedure DrawTitleButton(i: integer);
    {Paint-related messages}
    procedure WMSetText(var Msg: TWMSetText); message WM_SETTEXT;
    procedure WMNCPaint(var Msg: TWMNCPaint); message WM_NCPAINT;
    procedure WMNCActivate(var Msg: TWMNCActivate); message WM_NCACTIVATE;
    {Mouse-related messages}
    procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHitTest;
    procedure WMNCLButtonDown(var Msg: TWMNCLButtonDown);
      message WM_NCLBUTTONDOWN;
    procedure WMNCLButtonUp(var Msg: TWMNCLButtonUp); message WM_NCLBUTTONUP;
    procedure WMNCMouseMove(var Msg: TWMNCMouseMove); message WM_NCMouseMove;
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    {-}
    function GetVerInfo: DWORD;
    {-}
    procedure ShowHint;
    procedure Timer2Timer(Sender: TObject);
  public
  end;

const
  htTitleBtn = htSizeLast + 1;

implementation

uses
  PauLitaData, About, SpoolMessages;

procedure TTitleBtnForm.FormResize(Sender: TObject);
begin
  Perform(WM_NCACTIVATE, Word(Active), 0);
end;

procedure TTitleBtnForm.DrawTitleButton(i: integer);
var
  bmap: TBitmap; {Bitmap to be drawn - 16x16: 16 Colors}
  XFrame, {X and Y size of Sizeable area of Frame}
  YFrame,
    XTtlBit, {X and Y size of Bitmaps in caption}
  YTtlBit: integer;
  n: integer;
begin
  {Get size of form frame and bitmaps in title bar}
  XFrame := GetSystemMetrics(SM_CXFRAME);
  YFrame := GetSystemMetrics(SM_CYFRAME);
  XTtlBit := GetSystemMetrics(SM_CXSIZE);
  YTtlBit := GetSystemMetrics(SM_CYSIZE);
  n := GetSystemTitleBtnCount;
  if GetVerInfo = VER_PLATFORM_WIN32_NT then
    TitleButton := Bounds(Width - XFrame - (n + 1) * XTtlBit + 1 - 3, YFrame + 1 - 3,
      XTtlBit - 2, YTtlBit - 4)
  else
    TitleButton := Bounds(Width - XFrame - (n + 1) * XTtlBit + 1, YFrame + 1, XTtlBit
      - 2, YTtlBit - 4);
  Canvas.Handle := GetWindowDC(Self.Handle);
  try
    {Draw a button face on the TRect}
    DrawButtonFace(Canvas, TitleButton, 1, bsAutoDetect, FALSE, FALSE, FALSE);
    bmap := TBitmap.Create;
    DataModule1.ImageList1.GetBitmap(i, bmap);
    with TitleButton do
      if GetVerInfo = VER_PLATFORM_WIN32_NT then
        Canvas.Draw(Left + 2, Top + 2, bmap)
      else
        Canvas.StretchDraw(TitleButton, bmap);
  finally
    ReleaseDC(Self.Handle, Canvas.Handle);
    bmap.Free;
    Canvas.Handle := 0;
  end;
end;

procedure TTitleBtnForm.WMSetText(var Msg: TWMSetText);
begin
  inherited;
  DrawTitleButton(0);
end;

procedure TTitleBtnForm.WMNCPaint(var Msg: TWMNCPaint);
begin
  inherited;
  DrawTitleButton(0);
end;

procedure TTitleBtnForm.WMNCActivate(var Msg: TWMNCActivate);
begin
  inherited;
  DrawTitleButton(0);
end;

procedure TTitleBtnForm.WMNCLButtonDown(var Msg: TWMNCLButtonDown);
begin
  inherited;
  if (Msg.HitTest = htTitleBtn) then
    DrawTitleButton(1);
end;

procedure TTitleBtnForm.WMNCLButtonUp(var Msg: TWMNCLButtonUp);
begin
  inherited;
  if (Msg.HitTest = htTitleBtn) then
  begin
    KillHint;
    ShowAboutBox;
  end;
end;

procedure TTitleBtnForm.WMNCMouseMove(var Msg: TWMNCMouseMove);
begin
  inherited;
  if (Msg.HitTest = htTitleBtn) and PtinRect(TitleButton, Point(Msg.XCursor - Left,
    Msg.YCursor - Top)) then
    ShowHint
  else
    KillHint;
end;

function TTitleBtnForm.GetVerInfo: DWORD;
var
  verinfo: TOSVERSIONINFO;
begin
  verinfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
  if GetVersionEx(verinfo) then
    Result := verinfo.dwPlatformID;
end;

procedure TTitleBtnForm.WMNCHitTest(var Msg: TWMNCHitTest);
begin
  inherited;
  with Msg do
  begin
    if PtinRect(TitleButton, Point(XPos - Left, YPos - Top)) then
      Result := htTitleBtn;
  end;
end;

function TTitleBtnForm.GetSystemTitleBtnCount: integer;
var
  Menu: HMenu;
  i, n, m, l: integer;
begin
  l := 0;
  Menu := GetSystemMenu(Handle, FALSE);
  n := GetMenuItemCount(Menu);
  for i := 0 to n - 1 do
  begin
    m := GetMenuItemID(Menu, i);
    if (m = SC_RESTORE) or (m = SC_MAXIMIZE) or (m = SC_CLOSE) then
      Inc(l)
    else if (m = SC_MINIMIZE) then
      Inc(l, 2);
  end;
  Result := l;
end;

procedure TTitleBtnForm.KillHint;
begin
  if Assigned(Timer2) then
  begin
    Timer2.Enabled := FALSE;
    Timer2.Free;
    Timer2 := nil;
  end;
  if Assigned(FHint) then
  begin
    FHint.ReleaseHandle;
    FHint.Free;
    FHint := nil;
  end;
  FActive := FALSE;
end;

procedure TTitleBtnForm.Timer2Timer(Sender: TObject);
var
  thePoint: TPoint;
  theRect: TRect;
  Count: DWORD;
  i: integer;
begin
  Timer2.Enabled := FALSE;
  Timer2.Free;
  Timer2 := nil;
  thePoint.X := TitleButton.Left;
  thePoint.Y := TitleButton.Bottom - 25;
  with theRect do
  begin
    topLeft := ClientToScreen(thePoint);
    Right := Left + Canvas.TextWidth(MsgAbout) + 10;
    Bottom := Top + 14;
  end;
  FHint := THintWindow.Create(Self);
  FHint.Color := clInfoBk;
  FHint.ActivateHint(theRect, MsgAbout);
  for i := 1 to 7 do
  begin
    Count := GetTickCount;
    repeat
      {Application.ProcessMessages;}
    until
      (GetTickCount - Count >= 18);
    with theRect do
    begin
      Inc(Top);
      Inc(Bottom);
      FHint.SetBounds(Left, Top, FHint.Width, FHint.Height);
      FHint.Update;
    end;
  end; { i }
  FActive := TRUE;
end;

procedure TTitleBtnForm.ShowHint;
begin
  if FActive then
    Exit;
  if Assigned(Timer2) then
    Exit;
  Timer2 := TTimer.Create(Self);
  Timer2.Interval := 500;
  Timer2.OnTimer := Timer2Timer;
  Timer2.Enabled := TRUE;
end;

procedure TTitleBtnForm.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y:
  Integer);
begin
  inherited;
  KillHint;
end;

procedure TTitleBtnForm.FormCreate(Sender: TObject);
begin
  OnMouseMove := FormMouseMove;
end;

end.

2011. június 12., vasárnap

Dynamic Arrays


Problem/Question/Abstract:

Dynamic Arrays overview

Answer:

The Long and Winding Way of the Dynamic Array

Dynamic arrays were introduced to Object Pascal in Delphi 4. It was, however, not the first attempt of the Pascal/Delphi team to evolve the static array of Wirth's original Pascal.

Before going any further, let's first clarify some terminology. The terms "static" and "dynamic" are now applied at least four ways:

For arrays whose boundaries may vary (dynamic), versus arrays with constant boundaries (static).
For the methods of assigning memory to the variables: Either their relative addresses are known at compile time (static), or the addresses are assigned by the system at run time (dynamic). Correspondingly, there are two methods of memory allocation: on the stack (static), or on the heap (dynamic).
There are two methods referring to variables: either directly by their names (static), with one-to-one correspondence between a variable and its instance in memory; or indirectly via pointers (dynamic) where a variable and its instance are not the same.
Methods in the class declaration may be either static or virtual/dynamic.

This article follows the evolution of the "dynamic" concept with regard to arrays only. We will analyze its particularities for all array types appearing in Borland's Object Pascal. In addition to the standard arrays (static), there are now three more types in the family of arrays: open, variant, and dynamic. Why so many? Although it's beyond the scope of this article, we should also add to this list all the different types of strings in Delphi 4, which are a family of special character arrays whose diversity is even larger.

The Origin

The idea of dynamic arrays has a long history, beginning as early as ALGOL-60. In ALGOL-60, the syntax of array type looks similar to that of Pascal. For example, a declaration:

Real array A[M1: N1, M2: N2, M3: N3]

defines a 3D array of Real numbers. Only constants (integer numbers) are allowed as the array boundaries in the outermost block - just as in Pascal; in this case, the array is called static. But in the inner blocks of ALGOL-60, unlike Pascal, the boundaries may be variables (see Figure 1).

begin
  Real array A[1: 100, 0..10]; { Static array in ALGOL-60. }
  Integer M1, N1, M2, N2; { Variables. }
  { M1, N1, M2, N2 have to be defined. }
  ...
  begin
    Real array B[M1: N1, M2: N2]; { Dynamic array in ALGOL-60. }
    Real array C[1: 3, 1: 4]; { "Static" array in ALGOL-60. }
    ...
  end;
  ...
end
Figure 1: Variable boundaries in the inner blocks of ALGOL-60.

In the inner blocks, the array boundaries could be any arithmetic expression with the only requirement that numeric values were assigned to all variables in the boundaries before entering the block. Therefore, beginning with ALGOL-60, the concept of a dynamic array means that its boundaries may vary during run time, while for static arrays only explicit numbers are allowed in the boundary expressions.

For arrays that are local variables (like arrays B and C in Figure 1), the memory was allocated when entering the block and released when leaving it. Because declarations of the variables local in a block could appear only in the beginning of that block, no complications with redefining dynamic arrays occurred, and obviously such arrays could not be kept till the next entrance in this block. Nevertheless, in ALGOL-60, one could use the specifier own for any local variable including dynamic arrays, which meant that, although the visibility of the concerned variables obeyed the rule of scope, their values were kept available after re-entering the block. According to the Revised Report on ALGOL-60, this persistency in case of dynamic arrays ought to be followed also for the subset of indexes that are valid for the current and previous versions of the own array, although specific compilers could limit or simplify that behavior.

Note: Regarding compile-time versus run-time assignment of memory to the variables, it was typically run-time assignment in ALGOL-60 versus compile time in Pascal, although both use the stack.

The Long and Winding Road

In Wirth's Pascal, programmers could derive an unlimited number of new types from the basic types, but, for some reasons, the basic array type strictly required only constant boundaries and, therefore, was allowed to deal only with static arrays. Wirth's motivation probably was to have very fast and efficient one-pass compilers, for which all but indirect variables (i.e. except instances of pointer types) are compile-time variables providing the most efficient access. As a drawback, there was no way to overcome the static nature of the arrays, e.g. to develop procedures that deal with vectors, matrixes, and other structures of arbitrary sizes. We had to hard-code all array sizes as the maximum possible numeric values in the const section at least once.

For one-dimensional arrays, with a constant low boundary Low1 and variable boundary High1, we could resort to a trick involving indirect variables (pointers):

type
  TMaxArray = array[Low1..MaxInteger] of AnyType;
  PArray = ^TMaxArray;

and later allocate memory to the instance of PArray according to the actual value of High1:

GetMem(PArray, (High1 - Low1 + 1) * SizeOf(AnyType));

Then, we can use index expressions like PArray^[k]; we can omit ^ because of the undocumented syntax feature of Delphi. Unfortunately, for multi-dimensional arrays, this approach with indirect variables doesn't work as simply with just one pointer. Another drawback is that we have to deal with pointers instead of direct variables (as we are responsible for allocating and de-allocating memory and other possible confusions connected with indirect access).

Open Arrays

The open array, introduced in Delphi 1, was the first extension of Pascal's concept of static arrays, but it wasn't really a type like the others that could be used to declare variables. Instead, it was an intrinsic type, applicable only to formal parameters in procedures and functions. If a formal parameter looks like this:

FormalArr: array of TSomeType;

then the actual parameter may be either a one-dimensional array of type TSomeType, just one variable, or the so-called open array constructor [Expr1, Expr2, ..., ExprN] - all of type TSomeType. The latter is a nice feature not available in ALGOL-60, otherwise the behavior of the open array as a formal parameter suffers two serious drawbacks. First, only one-dimensional arrays as actual parameters are allowed. Second, whatever the low and high boundaries of the actual array are, they're always mapped to the zero-based formal open array. The same is true for the open array constructor. The expressions are numbered starting with 0, which looks a little confusing. This zero-based indexing of the open arrays isn't consistent with the more convenient Pascal array boundaries of low..high type, but, for some reason, Borland still adheres to this principle.

The open arrays enabled us to overcome a serious limitation of static arrays, and allowed us to deal comfortably with one-dimensional arrays. Thus, procedures for simple vectors of any size like scalar product, average, maximum, minimum, etc. were no longer a problem.

The variant open array formal parameter looks like:

FormalVarArr: array of const ;

and is intended only to transfer the open array constructors containing expressions of different types as an actual parameter - an extension of the similar feature for open arrays and the predecessor of the more general idea of the type variant.

Variant Arrays

The Variant type, introduced by Delphi 2, was a very powerful extension of Pascal intended for different purposes. We're going to discuss it here only with regard to the concept of dynamic arrays. A variable declared as Variant may represent a multi-dimensional dynamic array, but you need a special non-Pascal statement to specify the dimensions and the type of elements:

var
  vArr: Variant;
  {...}
begin
  {...}
  vArr := VarArrayCreate([Low1, High1, ..., LowN, HighN],
    ElementType);

where the boundaries may be variables and ElementType belongs to the fixed list of basic Pascal types denoted by the identifiers of the format varXXXX, for example varInteger, varDouble. After that we can consider vArr as an N-dimensional index variable vArr[i1,i2,...,iN]. This implementation is the closest to the notion of dynamic arrays as it appeared in ALGOL-60; it really made possible the multi-dimensional rectangular arrays with the variable boundaries of low..high type. Unfortunately, access to elements of variant arrays is at least 10 times slower than to static arrays; a simple benchmark that transposes a big matrix (e.g. A[i,j]:=A[j,i], i=1,...,N; j = 1,...,i-1) demonstrates it well. Also, in terms of memory consumption, any variant variable requires a 16-byte overhead. Although it doesn't seem too much if one variable represents a big array, it's something to keep in mind in case of many non-array variants.

The re-dimensioning of variant arrays is possible within the same block, but only for the last (right-most) dimension; the special function, varArrayRedim, does the job.

As to the efficiency of access to the elements, it may be improved to the level as fast as that of static arrays via the special procedure varArrayLock. It returns a pointer that is assignment compatible with pointers to any static array, but meaningful only if that static type corresponds exactly to the dimensions specified in the varArrayCreate. For example, for vArr, the corresponding static array type must be:

TvArrStat = array[LoN..HiN, ..., Lo1..Hi1] of ElementType;

with the dimensions specified in the order inverse to that in the varArrayCreate (why?!) and all LoN, HiN, ... Lo1, Hi1 being constants equal to the current values of the corresponding variable boundaries. Then, providing the declaration:

var
  vArrLock: ^TvArrStat;

the statement:

vArrLock := varArrayLock(vArr)

allows us to use the index variable vArrLock^[iN,...,i1] (or simply vArrLock[iN,...,i1]) with the access speed as quick as static arrays. We increased speed, but, to deal with variable boundaries, we must explicitly declare as many different static array types as we are going to have in run time. For example, we may need to prepare in advance several type declarations:

type
  T200x200 = array[1..200; 1..200] of Real;
  T150x150 = array[1..150; 1..150] of Real;
  {...}

and then specify the variable dimensions in the varArrayCreate according to one of these types - not a very convenient technique.

The interesting feature of variant arrays is that the ElementType may be variant, too:

vArr := VarArrayCreate([Low1, High1, varVariant)

In particular, it means that individual elements vArr[k] may be defined as a variant array again with any number of dimensions of any size:

vArr[k] := VarArrayCreate([kLow, kHigh, varDouble)

This creates an illusion as though we can treat vArr as a two-dimensional, non-rectangular array. (The examples of non-rectangular arrays of two dimensions are triangle matrixes, or matrixes with just a few stripes. For three dimensions, it may be an integer grid of points inside a pyramid.) Unfortunately, the variant array of variant arrays doesn't work like the similar construction of the standard Pascal arrays. Providing the declaration of vArr given previously, the code compiles for the index variable like vArr[i,j], but stops with a run-time error (because vArr is created as one-dimensional). Surprisingly, vArr[i][j] - that should be the synonym in Pascal - shows different behavior: It even produces a syntax error if it appears in the left side of the assignment statement; a := vArr[i][j] compiles and runs correctly, while vArr[i][j] := b doesn't, resulting in a syntax error.

So we see that although the variant array type allows some functionality of the ALGOL-60's dynamic arrays, the variant arrays are far more complex, slow, and not consistent with the Pascal's concept of arrays both in syntax and semantics.

Dynamic Arrays of Delphi 4

Finally, here is the latest attempt to implement the dynamic arrays (covered only on two pages of the Object Pascal Language Guide!). The declaration of one-dimensional dynamic arrays looks like this:

type
  TDynArray1 = array of baseType;

boundaries [...] must be omitted, where the baseType may be also a static array type or a dynamic array type again. This allows the declaration of the multi-dimensional "mixture" as well as "purely" dynamic arrays. For example, the declaration for three dimensions takes the following form:

type
  TDynArray3 = array of array of array of baseType;

This syntax allows us to declare a certain number of dimensions, but not their sizes (which require special consideration). Thus, if the baseType is of the non-array type, a variable:

var
  A, B: TDynArray3

may be used with up to three indexes, e.g. A[i,j,k]. Otherwise, if the baseType = array[1..100;1..200] of Double, this variable may appear with up to five indexes A[k1,k2,k3,k4,k5].

After a dynamic array variable is declared, it still cannot be used unless the special statement SetLength specifies the sizes of all dimensions and allocates the required memory. This shows the important difference between the static and dynamic arrays. The latter are - but only partially behave like - hidden pointers, i.e. a dynamic array variable is not strictly associated with its memory image, the instance, but rather separates from it.

Thus, the above-mentioned variable A (without indexes), or A[i], or A[i,j] (with number of indexes less than the declared number) are all hidden pointers. As such, at certain moments they may point nowhere, or more than one hidden pointer may point to the same instance. For example, after the assignment A := B, both A and B point to the same instance of B, so that any change to the elements of A affects B; this contradicts the usual meaning of the assignment statement. While the instance of A (if it exists) seems to be lost because it's pointed to by nothing, it doesn't cause a memory leak, which is prevented by the so-called reference count technique implemented for the dynamic arrays. For that reason, two consecutive statements - SetLength(A, ...) and SetLength(A, ...) - don't cause the loss of the piece of memory allocated in the first statement (leak) - unlike the similar situation, say, for classes. The sequence:

X := TAnyClass.Create;
X := TAnyClass.Create;

is a mistake. Also, the assignment:

A := nil
  
actually signals to the system that the memory (instance) must be freed, which is never the case for classes or pointers.

And even if:

A[i1, i2, i3] = B[i1, i2, i3]

for all indexes, it never means that the conditions:

A = B or A[i1] = B[i1] or A[i1, i2] = B[i1, i2]

are True, because these partially-indexed variables point to different locations.

In terms of persistency, while leaving and re-entering a block, the dynamic array variables behave like all other local (static) variables: leaving the block, the variables and their instances are freed automatically. For local variables of the types class and pointer it's wrong to leave the block without freeing all the instances of all such variables - the reason why such variables must be declared, for example, global. The user should nil a dynamic array only if it's important to free the memory before leaving the block. Hence, dynamic arrays are much safer than classes and pointers.

Thus, both the syntax and semantics of dynamic arrays differ from those of static arrays. Two system procedures, SetLength and Copy, previously intended to deal with strings, are applied now also for dynamic arrays. To define the sizes of dimensions - and the allowed index space - we must use the system procedure SetLength(A, Length1,...) with a non-fixed number of the integer parameters Length1,..., LengthN. At least one of them is always required to specify the size of the left-most dimension. If the number of dimensions is more than 1, the remaining sizes may be specified either in the same SetLength statement, or later in other such statements individually for each sub-array element. The former method defines rectangular arrays, similar to those known in ALGOL-60 or standard Pascal (but with mandatory zero low indexes), while the latter enables the so-called non-rectangular arrays.

For example, providing:

var
  A: TDynArray3

the single statement:

SetLength(A, N1, N2, N3)

defines the rectangular array with the index field [0..N1-1; 0..N2-1; 0..N3-1], while the statement:

SetLength(A, N1)

defines the size for the first dimension as N1 and correspondingly the index field for the first index as [0.. N1-1]. This postpones the definition of the 2 other dimensions for each A[k] individually. Figure 2 defines two types of triangle matrixes.

var
  A, B: array of array of Double;
  N, i: Integer;
begin
  { Defining N. }
  SetLength(A, N);
  SetLength(B, N);
  for i := 0 to N - 1 do
  begin
    { Lower-left triangle matrix; index field 0<=i<=N-1, 0<=j<=i }
    SetLength(A[i], i + 1);
    { Upper-left triangle matrix; index field 0<=i<=N-1, 0<=j<=N-i-1 }
    SetLength(B[i], N - i);
  end;
  { ...}
end;
Figure 2: Two types of triangle matrixes.

Unfortunately, because of the limitation imposed by zero-based indexing, dynamic arrays don't allow us to define the lower- and upper-right triangle matrixes, matrixes with several diagonal stripes this way. Figure 3 shows some examples of three-dimensional dynamic arrays of a 3- and 4-lateral pyramid-type.

var
  C, D: array of array of array of Double;
  N, i, j: Integer;
begin
  { Defining N }
  SetLength(C, N);
  SetLength(D, N);
  for i := 0 to N - 1 do
  begin
    { 4-lateral pyramid; index field  0<=i<=N-1, 0<=j,k<=i }
    SetLength(C[i], i + 1, i + 1);
    { 3-lateral pyramid }
    SetLength(D[i], i + 1);
    for j := 0 to i do
      { index field  0<=i<=N-1, 0<=j<=i, 0<=k <=j }
      SetLength(D[i, j], j + 1)
  end;
  {...}
end;
Figure 3: 3D dynamic arrays of a 3- and 4-lateral pyramid type.

As to the speed of access to elements of dynamic arrays, it's almost as high as for static arrays, at least one- and two-dimensional ones, as the benchmark with matrix transposing proves. For static arrays, the memory location of each element in a multi-dimensional array is known as soon as the index expression computes. Thus, for a static element, such as A[k1,k2,k3], the relative location may look like this:

N2N3 * k1 + N3 * k2 + k3

Instead, for dynamic arrays to access an element of K-dimensional array, the code must sequentially de-reference K pointers to the respective one-dimensional sub-arrays. Both approaches seem compatible.

Language Barrier

The evolution of dynamic arrays in Borland Pascal/Delphi wasn't straightforward. With regard to the functionality, the multi-dimensional rectangular variant arrays are the closest to the concept of dynamic arrays as they first appeared in ALGOL-60, but variant arrays are 10 times slower than static ones, and they differ in syntax. In addition, for the concept of a variant array of variant arrays, both syntax and semantics remain not quite clear.

The dynamic arrays in Delphi 4 exceed the arrays of ALGOL-60, at least in that they can be non-rectangular and still be as fast as static arrays in Pascal. Unfortunately, this notion reveals several language inconsistencies:

Why the mandatory zero-based indexing when the static and variant arrays don't require it? The low..high indexing in standard Pascal is an important feature, and can be very helpful in many applications. Also, the most natural and safe method of numbering in structures like vectors and matrixes is to number the elements in a vector corresponding to the index field: 1..N, not 0..N-1.
Why the special syntax in the declaration that omits the boundaries [ ]? As a result, the separate statement, SetLength, is later always required. True, SetLength allows to re-define the dimensions several times within a block, if it's really needed, but for the more typical case when the dimensions and the sizes are declared once at the beginning of the block, the standard form array[low..high] is better, because it is consistent with the syntax. The compiler knows by itself if the boundary expression is constant or variable, therefore it could implement the static or dynamic models according to the situation.
The assignment statements with incomplete indexes for dynamic array variables such as A := B have a quite unusual meaning: Any change in an element A[k] affects also B[k] because A and B refer to the same instance. This behavior contradicts the standard meaning of the assignment statement and is dangerous, so that incomplete indexes in assignment statements for dynamic arrays shouldn't be allowed.
The behavior of dynamic arrays as hidden pointers is better and safer than the behavior of classes (also hidden pointers) or pointers. Why not improve the behavior of classes and pointers to the level of dynamic arrays so that all indirect reference mechanisms in the language follow uniform rules?
Logically, the Delphi type class must be simply an indirect reference version of the type object introduced in Turbo-Pascal 5.5. The only difference should be in the method of memory allocation: for the class on the heap, and for the object on the stack. This is safer and doesn't involve the dangerous separation of variables from their instances. Delphi 4 and all previous versions support the type object for backward compatibility, but there are still certain differences between syntax and semantics of both types.

Conclusion

The fact that now there are as many as four different array types with quite different and inconsistent syntax and semantics in Borland Pascal/Delphi doesn't seem to be a good thing. Too many - and not always good - new features have been added to standard Pascal, which makes it cluttered, hectic, and less safe. It looks like the language doesn't evolve according to a well-developed fundamental plan; rather, it's trying to cope somehow with all different and inconsistent features of the very complex operating environment.

Delphi is still an unparalleled software development tool, but it's getting more and more complex, while its documentation and Help system lag behind. Even the Object Pascal Language Guide, the fundamental document of the language, is neither complete nor clear, or strict and formal enough as one should expect from a document of this type. This bears no resemblance to that high standard of documentation that Borland was proud of in the era of Borland Turbo Pascal. Back to the future?

I am very thankful to Dr Manfred Mackeben for his patience in reading and improving this text and for many valuable notes.

2011. június 11., szombat

Internet Explorer Automation


Problem/Question/Abstract:

Internet explorer comes with windows, so is available on nearly every client machine of your users. It's many capabilities can be used from your delphi application. This article contains an introduction to this subject.

Answer:

Microsoft sells its windows product with its browser Internet Explorer. This browser, like all MS products, is COM based, so through its interface we can use this component. The component holds all core functionality of the browser, so this functionality is available from Delphi as well. Even better, the explorer can be put into edit mode, so you can use it to edit html pages as well.

Usage of Internet Explorer in your application may enhance its functionality by a considerable degree. Recently I encountered the wish to use IE automation on two separate occasions. The first occasion was at the office, where an email application needed to be enhanced with html display as an increasing amount of mails had no plain text, just html. The second occasion was when some of the senior members of our church had difficulty maintaining the church website: used to just using MS word, and never even having bothered with things like directories, learning download with ftp, editing html, and uploading again just was too much to master in a short time. An integrated ftp / html-editing program seemed like the ideal solution.

Development environment.

The first thing we will have to do is install our development environment. This article is written with Delphi 5 enterprise, and tested with Delphi 7 personal edition.

Start Delphi, select "Component - Import ActiveX Control". In the list, select "Microsoft Internet Controls (version 1.1)" and add it to a new or existing package. Delphi will generate a ShDocVw_TLB.pas file. In some instances,  the file will be called ShDocVw.pas, for reasosn which are not entirely clear. Use Windows explorer to locate this file on your hard disk. Installing this component will also add the WebBrowser component to the component palette's Internet tab. (Some friends reported it on their ActiveX tab). If you don't have the 'Microsoft Internet controls' in your list of active X controls, import it from ShDocVw.dll.

Another thing you will need is the mshtml type library. Search your pc for files named mshtml.pas or mshtml_tbl.pas. If you don't have them, import the type library (Project - Import type library - Microsoft html object library). If you don't see this one, search your pc for mshtml.tlb, and add this file to your project. If you can not find it - and Delphi 5 Enterprise edition seems to come with the IE component already installed, go again to "Component - Import ActiveX Control", select "Microsoft Html Object Library" and click 'create unit'. This type library is fairly large so its generation may take a while if you have an old CPU.

Loading a page

The first thing you will probably do is load an html page. Nothing is simpler than that. Create a new application, go to the Internet tab of your component palette. Create a button, and in the on click event create the code:
  
WebBrowser1.Navigate('c:\webdemo\demo1.html');

Now create this demo1.html with something like:

hi


Just to show it supports more than plain text, create a page demo2 like:

  hi

Underlinedbolditalic

This is truly MS internet explorer. So you can not just load a page from your hard disk, you can also load a page from the web. As an illustration, drop a TEdit component on the form and name it 'edtWebAddress'. Create a econd button, label it 'Load web page', and in the onclick event enter:

WebBrowser1.Navigate(edtWebAddress.text);
  
Run your app, and enter 'www.google.com' in the edit field. You will notice that you don't need to enter the http: before the url, inserting this before the name is part of the behaviour of the component, not of the shell app you know as Internet Explorer. All pages will be loaded and displayed as pages would be in Internet Explorer itself. This includes forms and javascript.

Navigating to a page is just one way of loading a page. You can also load it from stream. Add a new button to your form, label it 'Load from stream', and add:

var
  ms: TMemoryStream;
begin
  ms := TMemoryStream.Create;
  Tekst.SaveToStream(ms);
  ms.seek(0, 0);
  if WebBrowser1.Document <> nil then
    Result := (WebBrowser1.Document as
      IPersistStreamInit).Load(TStreamAdapter.Create(ms));
  ms.free;
end;

You will have to add the ActiveX file to your uses clause for the IPersistStreamInit declaration. The first time you start your app, no document will have been loaded. So Webbrowser1.document will be nil. Load a page or site page first, then run this code. This is exactly the reason for the if statement. There is a slight problem: under some circumstances (especially when you load a double byte coded page), loading from stream will show the html source instead of the intended layout. So generally you will want to navigate to a page instead.

Before we leave the Navigate2 command, lets allow ourselves a small digression. You may have heard that Microsoft integrated Internet Explorer and Windows explorer. Try for yourself the next command:
      
WebBrowser1.Navigate('c:\temp\');

Forward and back

IE, like every browser, has buttons for moving forward and back through the list of visited pages. The commands for these actions are so simple, that we hardly need to comment on them:

begin

  WebBrowser1.Back;

  WebBrowser1.Forward;

end;    

IE keeps track of the pages you have visited, you don't have to keep track of them yourself.

Printing a page

To print a page, once it has loaded, we can send a message OLECMDID_PRINT to the control interface. Add another button, declare two variables of type olevariant, and type the following code:
      
var
  vaIn, vaOut: OleVariant;
begin
  WebBrowser1.ControlInterface.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER,
    vaIn, vaOut);
end;
    
Note that a document needs to have been loaded, else an access violation will occur.

Discovering busy

Some actions like loading a webpage or printing might take a while. Using the interface, you can see the moving graphics in the upper right corner as an indication that the browser is still busy. But how do you find if it's still busy in your program? The answer is provided by the ReadyState property. Add a label to your form, and add the following code to one of the previous buttons.
      
while (WebBrowser1.ReadyState <> ReadyState_Complete) do

begin

  Label1.caption := 'busy ..';

  Application.ProcessMessages;

end;

Label1.Caption := 'Ready';
    
Retrieving and setting the html code

Once we have loaded an html page, we might want to inspect the html code. One purpose might be to save it to file. Another purpose is if we want to build a dedicated html editor. The html resides in the IHtmldocuments, which is derived from IDispatch. We have to define a variable of the type IHTMLDocument2. This one is defined in the type library mentioned in the development environment paragraph above, and you have to include it in your uses clause.
  
var
  Doc: IHTMLDocument2;
  Html: string;
begin
  Doc := WebBrowser1.Document as IHTMLDocument2;
  Html := Doc.body.InnerHTML;
  ShowMessage('Innerhtml =' + Html);
  Html := Doc.body.OuterHTML;
  ShowMessage('Outerhtml =' + Html);
end;

The InnerHtml property can also be used to set the  contents of the page. Simply assign a new value to the Doc.Body.InnerHtml.

Another action you might be interested in is the retrieval of text selected by the user.

Clipboard activation

To use Ctrl-C and Ctrl-v, we need to use initialize and un-initialize Olehandling. Windows provides two apis, which we can call in the intialization and finalization sections:

initialization

  OleInitialize(nil);

finalization

  OleUninitialize;

Note that you will need to include the ActiveX unit in your uses clause.

Retrieving Head section

You may have noticed that when we retrieved the InnerHtml property, we did not get everything. All lines from the head section were missing. This also applied to the OuterHtml property, though according to many sources this property contains all the html. One way to obtain them would be to write the document to a file and read the file. But there is a faster and more direct way.

The document has a property all of the type IHtmlCollection. This property contains all the html elements, and we can simply loop through the collection.

var
  Doc: IHTMLDocument2;
  EllColl: IHTMLElementCollection;
  i: integer;
  Item: OleVariant;
begin
  Doc := WebBrowser1.Document as IHTMLDocument2;
  EllColl := Doc.all;
  for i := 0 to EllCOll.Length - 1 do
  begin
    Item := EllColl.item(i, varEmpty);
    ShowMessage(Item.tagname + '*contains*' + Item.InnerHtml);
  end;
end;      
  
The elements in this collection can also be manipulated. You could, for instance, loop through the collection, check for a certain type, and then replace the contents.

Editing

The previous paragraph introduced us to some possibilities to replace part or all of the html code with new content. But you may not always be interested in changing everything by hand. It may be more interested in letting your user do the job. The good news is that your users will be able to change content directly, without your interference. Simple set the design property of the document:
      
var
  Doc: IHTMLDocument2;
begin
  Doc := WebBrowser1.Document as IHTMLDocument2;
  Doc.designMode := 'On';
end;      
    
Another way to achieve the same result:
    
var
  Doc: IHTMLDocument2;
begin
  Doc := WebBrowser1.Document as IHTMLDocument2;
  Doc.body.setAttribute('contentEditable', 'true', 0);
end;      

After setting this property, your user will be able to edit the contents of the file directly. The user is even able to apply formats by pressing ctrl-b, ctrl-i and ctrl-u. So in effect, you have much of the functionality of MS Frontpage at your disposal. Of course you will have to write your own interface around it for loading and saving files.

Let's have a look at some of the stuff you might wish to use when writing your own html-editor.

We already remarked that your user can use ctrl-b to make the selected text bold, italic or underlined:. A nice feature, but you will probably want to provide your user with a menu option and a speedbutton to provide the same  functionality. The Document2 interface provides an 'execCommand' method, which enables us to do just that:

var
  Doc: IHTMLDocument2;
begin
  Doc := WebBrowser1.Document as IHTMLDocument2;
  Doc.execCommand('Underline', False, 0);
end;    

The second parameter, False in the above example, will prompt IE to present the user with a dialog if one is applicable (with the noticable exception of the saveAs command, which will always show a dialog!). The third parameter is an optional variant. It's possible values depend on the selected command.

Here is a list of supported commands:

2D-Position: Allows absolutely positioned elements to be moved by dragging.

AbsolutePosition : Sets an element's position   property to "absolute."

BackColor : Sets or retrieves the background color of the current selection.

Bold : Toggles the current selection between bold and nonbold.

ClearAuthenticationCache : Clears all authentication credentials from the  cache.

Copy : Copies the current selection to the clipboard.

CreateBookmark : Creates a bookmark anchor or retrieves the name of a bookmark anchor for the current selection or insertion point.

CreateLink : Inserts a hyperlink on the current selection, or displays a dialog box enabling the user to specify a URL to insert as a  hyperlink on the current selection.

Cut : Copies the current selection to the clipboard and then deletes it.

Delete : Deletes the current selection.

FontName : Sets or retrieves the font for the current selection.

FontSize : ets or retrieves the font size for the current selection.

ForeColor : Sets or retrieves the foreground (text) color of the current selection.

FormatBlock : Sets the current block format tag.

Indent : Increases the indent of the selected text by one indentation increment.

InsertButton : Overwrites a button control on the text selection.

InsertFieldset : Overwrites a box on the text selection.

InsertHorizontalRule : Overwrites a horizontal line on the text selection.

InsertIFrame : Overwrites an inline frame on the text selection.

InsertImage : Overwrites an image on the text selection.

InsertInputButton : Overwrites a button control on the text selection.

InsertInputCheckbox : Overwrites a check box control on the text selection.

InsertInputFileUpload : Overwrites a file upload control on the text selection.

InsertInputHidden : Inserts a hidden control on the text selection.

InsertInputImage : Overwrites an image control on the text selection.

InsertInputPassword : Overwrites a password control on the text selection.

InsertInputRadio : Overwrites a radio control on the text selection.

InsertInputReset : Overwrites a reset control on the text selection.

InsertInputSubmit : Overwrites a submit control on the text selection.

InsertInputText : Overwrites a text control on the text selection.

InsertMarquee : Overwrites an empty marquee on the text selection.

InsertOrderedList : Toggles the text selection between an ordered list and a   normal format block.

InsertParagraph : Overwrites a line break on the text selection.

InsertSelectDropdown : Overwrites a drop-down selection control on the text selection.

InsertSelectListbox : Overwrites a list box selection control on the text selection.

InsertTextArea : Overwrites a multiline text input control on the text selection.

InsertUnorderedList : Toggles the text selection between an ordered list and a  normal format block.

Italic : Toggles the current selection between italic and nonitalic.

JustifyCenter : Centers the format block in which the current selection is located.

JustifyLeft : Left-justifies the format block in which the current selection is located.

JustifyRight : Right-justifies the format block in which the current selection is located.

LiveResize : Causes the MSHTML Editor to update an element's appearance continuously during a resizing or moving operation, rather than updating only at the completion of the move or resize.

MultipleSelection : Allows for the selection of more than one element at a time when the user holds down the SHIFT or CTRL keys.

Outdent : Decreases by one increment the indentation of the format block in which the current selection is located.

OverWrite : Toggles the text-entry mode between insert and overwrite.

Paste : Overwrites the contents of the clipboard on the current selection.

Print : Opens the print dialog box so the user can print the current page.

Refresh : Refreshes the current document.

RemoveFormat : Removes the formatting tags from the current selection.

SaveAs : Saves the current Web page to a file.

SelectAll : Selects the entire document.

UnBookmark : Removes any bookmark from the current selection.

Underline : Toggles the current selection between underlined and not underlined.

Unlink : Removes any hyperlink from the current selection.

Unselect : Clears the current selection.

The Document2 interface not only provides us with a method execCommand to change the document, but also with the queryCommandState method which can tell us in what state the document is.
        
if Doc.queryCommandState('JustifyLeft') then
  ShowMessage('left');
  
will tell us of the text is left justified. Note that this function only results in true if the text has been justified left explicitly, if it has been justified left by default the result is false.

Every command has its own pecularities. This article would become too long to list them all, and most of them you will easily discover yourself.

Sources

Here are some sources for further study:
  
http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/editing/editdesignerovw.asp#Tutorials

tells a lot about the way Microsoft designed the built in editor of IE. Note that Microsoft has the habit of a-periodically but frequently redesigning their msdn site. So the link may have moved by the time you read this.
  
http://bdn.borland.com/article/0,1410,26574,00.html

Borland introduction to Internet Explorer automation
  
http://groups.yahoo.com/group/delphi-webbrowser/

is a newsgroup with lots of info.
  
Delphi 5 Enterprise edition comes with a small demo program. You can find it in the Demoes\Coolstuf directory.