Advanced properties
This chapter describes custom properties of an automation object. These
custom properties can be automation objects themselves. Automation objects which
will be created internally by the server itself and cannot be created externally
by the client. As a follow up I will present a technique to construct an indexed
property. On the other side are constants, the simplest types.
Typelibraries can hold constant enumerations which can be used by client and by server code.
The properties of an automation object and the parameters of its methods
cannot just have any type. Using Delphi types is of course out of the question.
A VBA client of an automation server created with Delphi has no idea what a
tComponent is. The list of types recognized by automation has been officially defined. This table lists them and maps them to the names in the Delphi
typelibrary editor, the internal type in Delphi and the corresponding type in
VBA :
| Automation specs |
Type library editor |
Delphi |
VBA |
| boolean |
VARIANT_BOOL |
WordBool |
Boolean |
| unsigned char |
unsigned char |
Byte |
Byte |
| double |
double |
Double |
Double |
| float |
float |
Single |
Single |
| int |
int |
SYSINT |
Long |
| long |
long |
Integer |
Long |
| short |
short |
SmallInt |
Integer |
| BSTR |
BSTR |
WideString |
String |
| CURRENCY |
CURRENCY |
Currency |
Currency |
| DATE |
DATE |
TDateTime |
Date |
| Idispatch |
IDispatch * |
Idispatch |
Object |
| Iunknown |
IUnknown * |
IUnknown |
Unknown |
| CoClass TypeName |
(struct tag )TypeName * |
TypeName |
TypeName |
| SafeArray(TypeName) |
SafeArray(TypeName) |
PSafeArray |
TypeName() |
|
VARIANT |
OleVariant |
Variant |
The most interesting type is Idispatch. As any automation object can be
represented as an Idispatch interface (see objects and
interfaces) this provides a way to create object properties.
The Filemanager automationserver has just the FileZapper class in its
typelibrary. Its interface IfileZapper has a method Select to select a file. It would be
nice to know a little more about that selected file. What is it's size and what
are it's attributes ? I will create a class to describe the file and will use an
interface to an object of this class as type for a new Selected property.
Using the wizard again (see the first chapter on
creating an automation server) I will create the SelectedFile class and
give it an integer size and an integer attributes property. These are both
read-only properties.

Now that I have declared a new IselectedFile type, I can use this type
for a property. I will give IfileManger a new read-only property Selected
and can pick IselectedFile as type for the property
Who will create the Selectedfile object ?
In automation it is expected that the client will create the automation
object. But in this case this would not really make that much sense. A SelectedFile
object on itself has no real meaning, it needs the form which is part of the IfileZapper
object.
An automation client creates an object through its objectfactory. The first
thing I will do is delete this object factory. Which is found in
the initialization section of the implementing unit
initialization
TAutoObjectFactory.Create(ComServer, TSelectedFile, Class_SelectedFile,
ciMultiInstance, tmApartment);
When I delete this line no objectfactory will be created and no external
client will be able to create SelectedFile objects.
SelectedFile objects will be created by filezapper, as this class
resides in the same DLL I can directly use a VCL constructor to create the
(object) value of the selectedfile property.
First I will change the ancestor type of tSelectedFile from
tAutoObject to tAutoIntfObject. This is a VCL class which does implement
Idispatch but does not use a class factory. The FileZapper class itself will create the
autointfobject. As it will be based on a filename I will create a handy
constructor in the tSelectedFile class which takes this filename as argument.
constructor TSelectedFile.CreateEx(FileName: string);
begin
Create(ComServer.TypeLib, ISelectedFile);
fFileName:= FileName;
end;
CreateEx first calls the default constructor of tAutoIntfObject. Comserver is a VCL
public variable housing in the ComServ unit which bundles the properties of the
automation server. One of these is the typelib and is passed to the
tAutoIntfObject.Create. The second parameter is the interface ID of Iselectedfile.
I will store the passed filename in a string. So it can be used by the size
and attributes property.
function TSelectedFile.Get_Size: Integer;
var F : tSearchRec;
begin
if FindFirst(fFileName, faAnyFile, F) = 0 then
result:= F.Size
else
result:= 0;
FindClose(F);
end;
Thus the value of size is not computed until the moment the
property is actually used.
I will include the Selectedfile unit in the uses clause of the filezapper
unit. The tSelectedFile object can now be created in the Get_selected
method of the tFileZapper class.
function TFileZapper.Get_Selected: ISelectedFile;
var I : integer;
begin
for i:= 0 to Form1.FileListBox1.items.count - 1 do
if Form1.FileListBox1.Selected[i] then
result:= tSelectedFile.CreateEx(Form1.FileListBox1.Items[i]);
end;
This code iterates through the items in the listbox which contain the
filenames. When it finds a selected item it will create the object and pass it
back as the result of the property getter. So an object instance of
tAutoIntobject can be used as an Iselectedfile interface result. For a background
on this see the chapter objects and interfaces.
The client will get a full IselectedFile interface and can use all its
properties. Here is a Delphi client working with the selected property
fFM:= CoFileZapper.Create; ... if fFM.Selected <> nil then
Label1.Caption:= Format('%d bytes', [fFM.Selected.Size]);
If no item in the listbox is selected the selected property will be nil. If I
would not test on this, I could get an access violation as I would ask for the
size property of a nil (interface) pointer.
tAutoIntfObjects are interfaced objects. Which mean that they will get
destroyed as soon as all their references go out of scope. Check the objects
and interfaces chapter for details on this.
In the current implementation of the filemanager there is a maximum of one
selected file. The user selects files in the filelistbox component. This
component has a multiselect property, when this is set the user could select
multiple files. Which will imply that the selected property now has to be able
to point to multiple items as well.
The solution is to index the selected property. Via a parameter each
of the individual selected files can be addressed. To do this I will add a new
parameter index to the property.
As type for the parameter is take a BSTR so the client can ask for the item
by name.
The implementation of get_selected will now have to check the filename
function TFileZapper.Get_Selected(const Index: WideString): ISelectedFile;
var I : integer;
begin
for i:= 0 to Form1.FileListBox1.items.count - 1 do
if Form1.FileListBox1.Selected[i] and
(CompareText(Form1.FileListBox1.Items[i], Index) = 0) then
result:= tSelectedFile.CreateEx(Form1.FileListBox1.Items[i]);
end;
The only difference with the former implementation is the usage of the comparetext
function.
And here is the Delphi client again
if fFM.Selected[Edit1.Text] <> nil then
Label1.Caption:= Format('%d bytes', [fFM.Selected[Edit1.Text].Size]);
The syntax for the property is the same as an array, using square brackets.
The contents of an editbox is used as index.
All of this code is not the most efficient, it is primarely intended as a
demo. There is a lot more to indexed properties . What if the user wanted to
know the name of first selected file ? Automation has a built in support for
collections via the IenumVariant interface. This interface is used by VBA and
VBscript clients in the for each language construction. This will be
covered in a separate chapter on automation collections.
Named constants are a nice and easy way to represent standard values. In a
typelibrary constants can be grouped in enumerations. Both server and any
client can use a constant by name, the type library will assure that everybody
is talking about the same thing. Internally the constants are stored as an int
which means they can be used as a parameter or as property type in automation.
I will create an attributes enumeration
in the filemanager typelibarary. To create an enumeration click the
button

To add constants to this enumeration you have to use the right mouse button

Besides the name of the constant I can enter its value. I will mimic the VCL
values. The good thing about these is that they can be combined.

These constants can now be used by in the implementation of the selectedfile
class
function TSelectedFile.Get_Attributes: Integer;
var F : tSearchRec;
begin
result:= None;
if FindFirst(fFileName, faAnyFile, F) = 0 then
begin
if F.Attr and faReadOnly <> 0 then
result:= Result or ReadOnly;
if F.Attr and faHidden <> 0 then
result:= Result or Hidden;
if F.Attr and faSysFile <> 0 then
result:= Result or Sys;
end;
FindClose(F);
end;
Thanks to the chosen values I can combine them in one integer. The client can
use the same flags:
if fFM.Selected[Edit1.Text] <> nil then
if (fFM.Selected[Edit1.Text].Attributes and ReadOnly) <> 0 then
ShowMessage('File is readonly');
Where are we ?
We have made a survey of the different types which can be used in automation.
As Idispatch is among them, an automation object can have another automation
object as property. In this way object trees of an arbitrary complexity can be
build.
Properties can be indexed. The value of the index can be of any automatable
type, the translation of the index to the item passed back is entirely up to the
implementation of the property getter.
Typelibraries can be used to store named constants, they will be organized in
enumerations.
What's next ?
Automation collections
Automation arrays
Demo application
|