Delphi and Word Part II

By: Charles Calvert

Abstract: This article shows how to use interfaces to access Word, Excell and the Internet Explorer from Delphi

(d)Using Interfaces with Excel and Word

(d)Using Interfaces with Excel and Word

If you look at the declaration for the Form1 object, you can see that it declares the following variables:

XLApp: Excel_TLB.Application_;

WordApp: Word_TLB.Application_;

Obviously, I am no longer declaring XLApp and WordApp to be simple Variants. Instead, I am declaring them as instances of the Application object from the Excel_TLB and Word_TLB files generated automatically when I selected Project, Import Type Library and imported EXCEL8.OLB. These files are both huge documents. For instance, Excel_TLB contains over 20,000 lines of code. The vast majority of these files are simply declarations for objects that are implemented inside Excel itself. If you contemplate this situation for a moment, you might begin to get a feeling for the huge scope of the resources available to you as a Delphi programmer sitting on the doorstep of the COM resources available in Word and Excel. Indeed, every corner of either of these two huge applications can be accessed using COM.

***Begin Note***

Note

You will notice that some of the objects in Excel_TLB or Word_TLB have underscores appended to their name. This convention helps you avoid name conflicts with internal Delphi objects. For instance, the VCL Application object plays a big role in many Delphi applications. As a result, the compiler avoids a name clash by appending an underscore to the name assigned to Application objects hosted by Excel and Word and listed in the XXX_TLB files. This simple device makes it easy for you to distinguish the Excel Application object from the VCL Application object. It also makes sure that neither one will accidentally obscure the other inside the scope of a particular Delphi object.

***End Note***

To create an instance of the Excel.Application_ object, the Excel4I program executes the following code:

procedure TForm1.RunBtnClick(Sender: TObject);

var

WorkSheet: _Worksheet;

WorkBks: WorkBooks;

ASheets: Sheets;

Workbk: WorkBook;

begin

XLApp := Excel_TLB.CoApplication_.Create;

XLApp.Visible[0] := True;

WorkBks := XLApp.WorkBooks as WorkBooks;

Workbks.Add(XLWBatWorksheet, 0);

WorkBk := WorkBks.Item[1];

ASheets := Workbk.WorkSheets;

WorkSheet := ASheets.Get_Item(1) as _WorkSheet;

Worksheet.Name := 'Delphi Data';

HandleData(WorkSheet);

ChartData(ASheets);

CopyData;

SendMailBtn.Enabled := True;

end;

You should compare this method to the one from the Excel4 program in the preceding chapter that uses Variants:

procedure TForm1.Button1Click(Sender: TObject);

begin

XLApp := CreateOleObject('Excel.Application');

XLApp.Visible := True;

XLApp.Workbooks.Add[XLWBatWorksheet];

XLApp.Workbooks[1].Worksheets[1].Name := 'Delphi Data';

HandleData;

ChartData;

CopyData;

SendMailBtn.Enabled := True;

end;

If you perform a simple line count, it should be immediately evident that using interfaces is more difficult than using Variants. It is, however, often more efficient, and certainly it gives you a more fine-tuned control over your application.

You can create an instance of the object in these two different ways:

XLApp := Excel_TLB.CoApplication_.Create; // Initialize an interface

XLApp := CreateOleObject('Excel.Application'); // Initialize a Variant

In the first case, I am creating an interface; in the second, I create an IDispatch-based Variant.

CoApplication.Create is a method from an object declared automatically when Excel_TLB was created. To view this class, go to the bottom of the Excel_TLB file and look for the following code:

CoApplication = class

class function Create: Application_;

class function CreateRemote(const MachineName: string): Application_;

end;

 

class function CoApplication.Create: Application_;

begin

Result := CreateComObject(Class_Application) as Application_;

end;

 

class function CoApplication.CreateRemote(

const MachineName: string): Application_;

begin

Result := CreateRemoteComObject(MachineName,

Class_Application) as Application_;

end;

As you can see, CoApplication.Create generates an instance of the Application_ class. Here is another way to create an instance of this class:

V := CreateOleObject('Excel.Application') as Application;

This code returns an instance of IDispatch and then uses the as operator to call a function called QueryInterface that returns the underlying Application_ object. CreateComObject, shown in the excerpts from Excel_TLB, does more or less the same thing, but it starts with a CLSID rather than a ProgID. If you wanted to get even more technical, you could call CoCreateInstance, which would also return an instance of this class. In fact, both CreateComObject and CreateOleObject ultimately end up calling CoCreateInstance.

Regardless of how you create it, you wind up with an instance of the Application object. In most cases, doing so via the predeclared objects such as CoApplication is simplest.

(d)Comparing Variant and Interface Technologies

In the next few paragraphs, I am going to step you through the rest of the Variant and interface code shown in the RunBtnClick and Button1Click methods from the preceding section. I am going to compare the way things are done in each instance. I'm taking this approach because all the documentation for these objects always shows you how to do things using Variants. Knowing how to translate Variant-based code into interface-based code is therefore useful. By showing you the two techniques side by side, I'm hoping I'll give you enough information so that you can learn the general principles behind each technology and can start writing your own code based on the many Visual Basic-centered documents you will find in the Microsoft documentation.

Here are the next two lines in the two routines used to start Excel4 and Excel4I applications:

XLApp.Visible[0] := True; // Uses an interface

XLApp.Visible := True; // Uses a Variant

As you can see, the interface call uses array syntax, whereas the Variant call does not. The following is the declaration for the interface property:

property Visible[lcid: Integer]: WordBool read Get_Visible write Set_Visible;

An lcid is a language ID, specifying whether the current language used by a particular copy of Windows is English, German, Japanese, and so on. You can always pass in 0 for this ID to get the default language. You will find that this parameter needs to be passed to a number of methods found in Word_TLB and Excel_TLB.

The following are the declarations for the getters and setters for these methods:

function Get_Visible(lcid: Integer): WordBool; safecall;

procedure Set_Visible(lcid: Integer; RHS: WordBool); safecall;

lcid here is the language ID, and RHS indicates whether you want to set Visible to True or to False.

You use the following lines to add a worksheet to the default workbook object using an interface:

var

WorkSheet: _Worksheet;

WorkBks: WorkBooks;

ASheets: Sheets;

Workbk: WorkBook;

begin

WorkBks := XLApp.WorkBooks;

Workbk := Workbks.Add(XLWBatWorksheet, 0);

ASheets := Workbk.WorkSheets;

WorkSheet := ASheets.Get_Item(1) as _WorkSheet;

Worksheet.Name := 'Delphi Data';

You can do the same thing this way by using Variants:

XLApp.Workbooks.Add[XLWBatWorksheet];

XLApp.Workbooks[1].Worksheets[1].Name := 'Delphi Data';

When you're writing the interface code shown here, the goal is to never end up calling one object off another object. For instance, when I write XLApp.Workbooks.Add in the Variant code, I am asking the Excel application object to retrieve an instance of the Workbooks object and call its add method. In other words, there has to be a place where code like this is executed:

WorkBks := XLApp.WorkBooks;

Workbks.Add(XLWBatWorksheet, 0);

As you can see, the preceding is the same code that I wrote in the interface example. The point is that you end up having to perform the same actions whether you do things with interfaces or with Variants, but with Variants a lot of the tricky stuff is handled in the background for you. In other words, the Variant code is not faster just because it is shorter.

***Begin Note***

Note

The 0 that I have passed in the second parameter on the call to Workbks.Add is again an lcid. As I mentioned earlier in this section, the lcid parameter pops up in a number of places.

***End Note***

The next line of code in the Variant version again calls instances of one object off another object:

XLApp.Workbooks[1].Worksheets[1].Name := 'Delphi Data';

This code is quite easy to write, but somewhere in the background the same kind of thing you see in the interface code has to occur:

ASheets := Workbk.WorkSheets;

WorkSheet := ASheets.Get_Item(1) as _WorkSheet;

Worksheet.Name := 'Delphi Data';

As you can see, the interface code explicitly goes and retrieves each interface that you need to use and then calls methods off those interfaces.

Because of space considerations, I'm not going to be able to analyze all the code in the Excel4I application. However, if you study it, you will find that the same general pattern shown here is continued in multiple settings. You should note in particular places where I retrieve an interface once and then reuse it over and over. In the contrasting Variant code, I often end up repeatedly retrieving that same interface. For instance, in the following code from the ButtonClick method, I retrieve the Workbooks object twice:

XLApp.Workbooks.Add[XLWBatWorksheet];

XLApp.Workbooks[1].Worksheets[1].Name := 'Delphi Data';

This code looks very efficient, but behind the scenes the following code gets executed twice:

WorkBks := XLApp.WorkBooks;

In the corresponding interface code, I retrieve the Workbooks object once and then hang on to it so that I can use it a second time. This example can help to illustrate how you can increase performance by using interfaces or by intelligent use of Variants. The moral here is that the capability to call objects off other objects using dot notation is very slick and easy to use, but it is not always as efficient as digging in and working with interfaces.

(c)Working with IE and TWebBrowser

Microsoft's Internet Explorer versions 3.x and 4.x are based on ActiveX controls. You can import these ActiveX controls into Delphi and use them in your own applications. For instance, if you have IE installed on your system, then you need only choose Components, Import ActiveX Control from the Delphi menu to add the full functionality of the Explorer to the Component Palette and hence to your applications. The act of importing the control is shown in Figure 17.1. After you have placed the control on the Component Palette, you can just drop it into your applications as you would any other component.

***Insert Figure 17.1 19FIG01 PC X

Figure 17.1

When you import the Internet Controls from IE 4.X into Delphi, you find three components called TWebBrowser_V1, TWebBrowser, and TShellFolderViewOC.

You are free to distribute the Microsoft WebBrowser control as widely as you want, as long as you bring the entirety of Internet Explorer along with you. This decision is driven in part by marketing and in part by the fact that the WebBrowser control relies on a large number of DLLs and other controls that are distributed with IE. Of course, if your target machine already has IE installed, then you need do nothing to run your application. In Windows 98, God and the Justice department willing, you will find copies of IE on each and every properly installed system.

When you drop an instance of the Internet Explorer browser control on one of your forms, you get the complete set of tools that come with IE. You have support for VBScript, JScript, Java, HTML, FTP, VRML, and who knows what else directly inside your application.

You can find out details about the objects in the WebBrowser object by getting hold of the Internet Client SDK from Microsoft. You can find these files at www.microsoft.com, in the MSDN, and in the standard Microsoft CD that contains IE4.

(d)Getting Started: Accessing the Elements in a Web Page

The DocumentObject program from the CD that accompanies this book shows how to use the TWebBrowser (IE) control in a Delphi application. It also shows how to retrieve an interface from the object and use it to explore an HTML file in some depth.

Listing 17.3 shows the main form for the program, and Figure 17.2 shows the browser displaying a famous Web site. This form provides code for accessing the most important features of the TWebBrowser control, such as opening a Web site and browsing through recently displayed files. Listing 17.4 shows a form from the program that is used to report detailed information about the particular HTML file currently displayed in the TWebBrowser control.

***Insert Figure 17.2 19FIG02 PC

Figure 17.2

Using the DocumentObject program to browse to the centrally important http://www.eldermage.com site.

The program allows you to browse to a site of your choice on the Internet and to examine in some depth the HTML file you find there. One of the program's dialogs, shown in Figure 17.3, reports on the date an HTML file was created, the character set it uses, and other detailed bits of information.

***Insert Figure 17.3 19FIG03 PC X

Figure 17.3

The DocumentDetails dialog shows some of the information you can retrieve about an HTML file displayed in the TWebBrowser control.

Listing 17.3[em]The Main Form for the DocumentDetails Program

unit Main;

 

interface

 

uses

Windows, Messages, SysUtils,

Classes, Graphics, Controls,

Forms, Dialogs, OleCtrls,

SHDocVw_TLB, ExtCtrls, StdCtrls;

 

type

TForm1 = class(TForm)

WebBrowser1: TWebBrowser;

Panel1: TPanel;

Edit1: TEdit;

ConnectBtn: TButton;

DetailBtn: TButton;

GoBackBtn: TButton;

GoForwardBtn: TButton;

procedure ConnectBtnClick(Sender: TObject);

procedure DetailBtnClick(Sender: TObject);

procedure Edit1KeyDown(Sender: TObject; var Key: Word;

Shift: TShiftState);

procedure GoBackBtnClick(Sender: TObject);

procedure GoForwardBtnClick(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

 

var

Form1: TForm1;

 

implementation

 

uses

MSHTML_TLB, DocumentDetails1;

 

{$R *.DFM}

 

procedure TForm1.ConnectBtnClick(Sender: TObject);

var

A, B, C, D: OleVariant;

begin

WebBrowser1.Navigate(Edit1.Text, A, B, C, D);

end;

 

procedure TForm1.DetailBtnClick(Sender: TObject);

var

Doc: IHTMLDocument2;

begin

Doc := WebBrowser1.Document as IHTMLDocument2;

DocumentDetails.ShowDoc(Doc);

end;

 

procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;

Shift: TShiftState);

begin

if Key = 13 then

ConnectBtnClick(nil);

end;

 

procedure TForm1.GoBackBtnClick(Sender: TObject);

begin

WebBrowser1.GoBack;

end;

 

procedure TForm1.GoForwardBtnClick(Sender: TObject);

begin

WebBrowser1.GoForward;

end;

 

end.

Listing 17.4[em]The DocumentDetails Unit Uses the HTMLDocument2 Object to Describe an HTML File

unit DocumentDetails1;

 

interface

 

uses

Windows, Messages, SysUtils,

Classes, Graphics, Controls,

Forms, Dialogs, StdCtrls,

MSHTML_TLB, Buttons, ExtCtrls;

 

type

TDocumentDetails = class(TForm)

GroupBox1: TGroupBox;

Edit1: TEdit;

Edit2: TEdit;

Label1: TLabel;

Label2: TLabel;

Edit3: TEdit;

Edit4: TEdit;

Label3: TLabel;

Label4: TLabel;

GroupBox2: TGroupBox;

Edit5: TEdit;

Edit6: TEdit;

Label5: TLabel;

Label6: TLabel;

Edit7: TEdit;

Edit8: TEdit;

Label7: TLabel;

Label8: TLabel;

Edit9: TEdit;

Label9: TLabel;

Label10: TLabel;

Edit10: TEdit;

Edit11: TEdit;

Edit12: TEdit;

Label11: TLabel;

Label12: TLabel;

Edit13: TEdit;

Label13: TLabel;

Label14: TLabel;

Edit14: TEdit;

GroupBox3: TGroupBox;

Label15: TLabel;

Label16: TLabel;

Label17: TLabel;

Label18: TLabel;

Edit15: TEdit;

Edit16: TEdit;

Edit17: TEdit;

Edit18: TEdit;

Panel1: TPanel;

BitBtn1: TBitBtn;

procedure BitBtn1Click(Sender: TObject);

private

FDoc: IHTMLDocument2;

procedure ShowColors;

procedure ShowCoreSettings;

procedure ShowFileInfo;

public

procedure ShowDoc(Doc: IHTMLDocument2);

{ Public declarations }

end;

 

var

DocumentDetails: TDocumentDetails;

 

implementation

 

{$R *.DFM}

 

procedure TDocumentDetails.ShowColors;

begin

Edit1.Text := FDoc.bgColor;

Edit2.Text := FDoc.fgColor;

Edit3.Text := FDoc.LinkColor;

Edit4.Text := FDoc.VLinkColor;

end;

 

procedure TDocumentDetails.ShowCoreSettings;

begin

Edit5.Text := FDoc.Title;

Edit6.Text := FDoc.URL;

Edit7.Text := FDoc.Domain;

Edit8.Text := FDoc.Protocol;

Edit9.Text := FDoc.Cookie;

Edit10.Text := FDoc.Security;

Edit11.Text := FDoc.CharSet;

Edit12.Text := FDoc.DefaultCharSet;

Edit13.Text := FDoc.MimeType;

Edit14.Text := FDoc.NameProp;

end;

 

procedure TDocumentDetails.ShowFileInfo;

begin

Edit15.Text := FDoc.FileSize;

Edit16.Text := FDoc.FileCreatedDate;

Edit17.Text := FDoc.FileModifiedDate;

Edit18.Text := FDoc.FileUpdatedDate;

end;

 

procedure TDocumentDetails.ShowDoc(Doc: IHTMLDocument2);

begin

FDoc := Doc;

Show;

ShowColors;

ShowCoreSettings;

ShowFileInfo

end;

 

procedure TDocumentDetails.BitBtn1Click(Sender: TObject);

begin

Close;

end;

 

end.

When you want to connect to a site using the TWebBrowser control, just pass the URL of the site to the Navigate method:

procedure TForm1.ConnectBtnClick(Sender: TObject);

var

A, B, C, D: OleVariant;

begin

WebBrowser1.Navigate(Edit1.Text, A, B, C, D);

end;

The last four parameters passed to Navigate are rarely used and can be filled in with Variants. Variants and OleVariants are always automatically set to the value UnAssigned, so you do not need to initialize them.

If you want to move forward or backward in the files displayed in the browser, then you can call the GoForward or GoBackward methods of TWebBrowser:

procedure TForm1.GoBackBtnClick(Sender: TObject);

begin

WebBrowser1.GoBack;

end;

 

procedure TForm1.GoForwardBtnClick(Sender: TObject);

begin

WebBrowser1.GoForward;

end;

All this code is fairly prosaic. A somewhat more exotic feature of the TWebBrowser control is its support for many detailed interfaces, just as Excel and Word support many detailed interfaces. These interfaces are found in ShDocVw_TLB.pas and in MSHTML.pas. These two huge, labyrinthine files are key guide books for those willing to embrace some portion of Bill Gate's vision of the future.

***Begin Note***

Note

I hope, though without much confidence, that readers of this chapter will understand that I am promoting COM technology, and not necessarily particular products such as IE or Word. My point in this chapter is not to say, "Hey, look at IE, look at Word, aren't these cool apps?" Instead, I'm trying to say, "Hey, look at this COM technology, isn't it wonderful?"

***End Note***

The obvious place to start exploring the TWebBrowser interfaces is the Document object:

procedure TForm1.DetailBtnClick(Sender: TObject);

var

Doc: IHTMLDocument2;

begin

Doc := WebBrowser1.Document as IHTMLDocument2;

DocumentDetails.ShowDoc(Doc);

end;

By definition, TWebBrowser.Document is a variant containing an instance of IDispatch. You can use this object directly if you wish. However, you can get better control over the object by getting a real interface rather than just using a Variant. The first line of this method retrieves the IHTMLDocument2 document by calling QueryInterface on the instance of IDispatch wrapped in the Document Variant.

IHTMLDocument2 is found in MSHTML_TLB.pas. As you may recall from the beginning of the chapter, MSHTML is a DLL found in the System or System32 directory. You can import the type library from that DLL by choosing Project, Import Type Library.

After the program has the interface it wants to use, it passes the interface to the TDocumentDetails form where it can be queried at some length. Understanding all the things you can do with a Document object is not really so terribly important. However, the following method should give you some faint clue as to the expansive list of possibilities:

procedure TDocumentDetails.ShowCoreSettings;

begin

Edit5.Text := FDoc.Title;

Edit6.Text := FDoc.URL;

Edit7.Text := FDoc.Domain;

Edit8.Text := FDoc.Protocol;

Edit9.Text := FDoc.Cookie;

Edit10.Text := FDoc.Security;

Edit11.Text := FDoc.CharSet;

Edit12.Text := FDoc.DefaultCharSet;

Edit13.Text := FDoc.MimeType;

Edit14.Text := FDoc.NameProp;

end;

Here, the code accesses various properties of the Document object such as the Title, URL, Domain, and Protocol. This is such a tiny and humble sliver of the options available to you in the TWebBrowser object that it's hardly worth mentioning in comparison to the riches piled up before you. Nevertheless, it should help introduce you to the subject and might serve to pique your interest.


Server Response from: ETNASC03