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|
|unsigned char||unsigned char||Byte||Byte|
|CoClass TypeName||(struct tag )TypeName *||TypeName||TypeName|
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
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
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');
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.