3 The subcontrol Object

OLE objects that can be used as servers by the IDM through the IDM object control often have a rather complex structure and can consist of several further child objects. For example, the OLE control Word.Application has a collection of Documents, which manages the open documents. In turn, these documents have Words, Sentences, Ranges, etc. as their own children. To be able to access these sub-objects from the Rule Language, i.e. to call methods or query attributes, the subcontrol object can be used. The subcontrol represents an OLE child object which can be directly or indirectly managed by an OLE server. Therefore the starting point for accessing such an object is always the control.

Definition

{ export | reexport } subcontrol { <Identifier> }
{
  <hierarchy attributes>
  <object-specific attributes>
}

Events

None

Children

document

record

subcontrol

transformer

Parents

control

subcontrol

Menu

None

3.1 Attributes

.connect

.control

.dialog

.document[integer]

.firstrecord

.firstsubcontrol

.groupbox

.label

.lastrecord

.lastsubcontrol

.layoutbox

.model

.module

.notepage

.parent

.record[integer]

.recordcount

.scope

.subcontrol[integer]

.subcontrolcount

.toolbar

.transformer[integer]

.userdata

.window

3.2 Implicit Creation

One notable feature of subcontrols is that these objects can be implicitly created without being statically defined as object in a dialog or being dynamically created with create() before. But please pay attention to chapter “Dynamic OLE Properties and Subcontrols” when you work with dynamic OLE properties which create subcontrols.

Probably the implicit use is the most common use of these objects in practice. For example, when using Microsoft Word as an OLE server the individual can be accessed like this:

Let controlCo be defined somewhere in the dialog.

child control Co

{

  .mode mode_client;

  .name "Word.Application";

  .visible true;

  .connect true;

}

Then control Co can be used in a rule like this:

rule OLETest ()

{

  variable object Doc;

  Doc := Co.Documents:Add();

  // Word has a collection that is entitled "Documents".

  // Co.Documents is used to access this OLE object. In order to

  // address the object in IDM, a subcontrol with the identifier

  // "Documents" is implicitly created and registered as a child

  // of control Co. The :Add() method is a member of the

  // "Documents" collection. Therefor, the method can be applied

  // to the just created subcontrol. It creates a new document

  // in Word and returns a pointer to its IDispatch interface.

  // As a counterpart on the IDM side another subcontrol is

  // created as child of the first subcontrol and connected

  // with the Word document. Finally this object is assigned

  // to the variable "Doc".

 

  // The subcontrol in "Doc" now can be used to call methods of

  // the OLE document and to query or set its properties.

  print Doc.FullName;

}

In general, if a method or a property returns a pointer to an IDispatch interface, or if this is passed to the IDM as a parameter, the IDM creates a subcontrol that is connected to the IDispatch interface. This means that the .connect attribute of such a subcontrol is already set to true.

Normally the identifier (label) of such a created object is not set so that something similar to subcontrol Co.SUBCONTROL[2] appears in the trace file. One exception is the access of properties that return OLE objects. Since these are usually collections whose names can be known to the IDM, an implicitly created subcontrol receives the name of the property as identifier.

Example

Doc1 := Co.Documents:Add();

Doc2 := Co.Documents:Item(1); // Assumption: No other Word documents

                              // were open before.

In this example, two subcontrols are created in the first line: One with the identifier Documents, another with the identifier SUBCONTROL. The reason for the identifier of the second subcontrol is that the IDM has no further information when processing the Add() method. In the second line only one more subcontrol is created with SUBCONTROL[2] as its identifier. As the IDM recognizes that a subcontrol for Documents already exists, it is unnecessary to create it once more. All of this leads to the situation, that on the OLE side there are two objects: one is the Documents collection and the other one an empty document. On the IDM side there are three objects: a subcontrol for the Documents collection and two subcontrols in the variables Doc1 and Doc2, both connected to the empty document on the OLE side. It is a matter of good programming style to avoid heaps of unnecessary subcontrol objects.

One may wonder at this point, what happens to all these subcontrols if they are not needed anymore. Chapter “Garbage Collection” deals with this.

3.3 Dynamic OLE Properties and Subcontrols

When dynamic properties of OLE objects are queried, this may lead to the creation of new subcontrols when these properties return objects (e.g. ActiveSheet of Microsoft Excel). At first this is no problem. In subsequent queries however, the IDM will not refer to a current, newly returned object but to the already existing subcontrol which has been cached by the IDM.

If this caching is unwanted or leads to problems, it can be avoided by two means:

Setting .connect of the concerned subcontrol to false provides no solution however, because  with this the subcontrol persists, only all further accesses will fail.

3.4 Garbage Collection

All implicitly created subcontrols have to be deleted again. For this purpose, the IDM remembers by how many objects a subcontrol is referenced. If it is determined that a subcontrol is no longer in use, neither by a variable (local, global, static) nor by a user-defined attribute, then the subcontrol is destroyed. Currently two strategies are applied, which are explained below.

  1. If an implicitly created subcontrol is assigned to a variable (or something similar), and later this variable is overwritten with another value, the subcontrol can be released, provided that it has no children.

    Example

    Doc := Co.Documents:Add();  // 2 Subcontrols are implicitly created (A for Documents

                                // and B for the document created by the Add() method).

    Doc := Co;                  // Subcontrol B is no longer used by the variable Doc

                                // and therefore can be destroyed. Now subcontrol A can

                                // determine that it has no more children and can destroy

                                // itself too.

  2. In awkward situations it may happen that a subcontrol does not notice it can be destroyed. This happens for example, when a newly created subcontrol has not been assigned anywhere. This situation lacks a trigger to throw away the object. For this reason the IDM occasionally starts a cleanup in which implicitly created subcontrols are checked if they are still required. Otherwise they are discarded.

Important

Since the IDM garbage collection only takes into consideration the references from the Rule Language, great care should be taken, if ObjectIDs of subcontrols are managed from the C interface (as well as the COBOL and C++ interfaces). If such an ID is temporarily stored in the application, this is not realized by the IDM. After the call of interface functions, the IDM may determine that the subcontrol is not needed anymore and dispose it accordingly. If control is transferred back to the application side, it can no longer be assumed that the previously saved ObjectID is still valid. Therefore it should be avoided to save subcontrols in the application. If this is wanted still, it is important to ensure that the object is still referenced in the Rule Language (for example by a global or static variable, or in a user-defined attribute). This guarantees that the subcontrol will not be thrown away.

3.4.1 Example

The following example shows how subcontrols can be used.

dialog Word

 

window WnWindow

{

  .active false;

  .width 400;

  .height 100;

  .title "Word.Document.8";

 

  on close

  {

    if Co.connect then

      Co:Quit();

    endif

    exit();

  }

 

  child control Co

  {

    .visible true;

    .active false;

    .xauto 0;

    .xleft 20;

    .xright 20;

    .yauto 0;

    .ytop 20;

    .ybottom 40;

    .mode mode_client;

    .name "Word.Application";

    .connect true;

  }

 

  child pushbutton PbOpen

  {

    .xauto 1;

    .xleft 100;

    .width 76;

    .yauto -1;

    .height 27;

    .ybottom 10;

    .text "Open Doc";

    on select

    {

      variable object Doc:=null;

 

      // Assumes that the file actually exists

       Doc := Co.Documents:Open("c:/tmp/olddocument.docx");

 

      if Doc <> null then

        print "DocName: " + Doc.FullName;

        print "DocSaveStatus: " + Doc.Saved;

      endif

    }

  }

 

  child pushbutton PbAdd

  {

    .xauto 1;

    .xleft 20;

    .width 76;

    .yauto -1;

    .height 27;

    .ybottom 10;

    .text "New Doc";

    on select

    {

      // Assumes that the file actually exists

      Co.Documents:Add("c:/tmp/newdocument.docx");

    }

  }

 

  child pushbutton PbClose

  {

    .xleft 180;

    .width 92;

    .yauto -1;

    .height 27;

    .ybottom 10;

    .text "Close Doc";

    on select

    {

      // Close first document (of the document list)

      Co.Documents:Item(1):Close();

    }

  }

 

  child pushbutton PbQuit

  {

    .xauto -1;

    .width 85;

    .xright 20;

    .yauto -1;

    .height 27;

    .ybottom 10;

    .text "Quit";

    on select

    {

      // Close Word

      Co:Quit();

    }

 }

}

 

on dialog start

{

  Co.Visible := true;

}