3 Das subcontrol-Objekt

OLE-Objekte, die als Server im IDM mit Hilfe des IDM-Objektes control genutzt werden können, haben in der Regel eine recht komplexe Struktur und können aus einer Reihe weiterer Kindobjekte bestehen. Zum Beispiel hat das OLE-Control Word.Application eine Kollektion Documents, wo die Dokumente verwaltet werden. Diese Dokumente wiederum haben Words, Sentences, Ranges usw. als eigene Kinder. Um auf diese Unterobjekte aus der Regelsprache zugreifen zu können, sprich Methoden aufrufen oder Attribute abfragen, wurde das Objekt subcontrol eingeführt. Das subcontrol repräsentiert somit ein OLE-Kindobjekt, das direkt oder indirekt von einem OLE-Server verwaltet wird. Der Ausgangspunkt für den Zugriff auf ein solches Objekt ist deswegen immer ein control.

Definition

{ export | reexport } subcontrol { <Bezeichner> }
{
  <Hierarchieattribute>
  <Objektspezifische Attribute>
}

Ereignisse

Keine

Kinder

subcontrol

Vater

control

subcontrol

Menü

Keins

3.1 Attribute

connect

.dialog

.external

.external[integer]

.firstrecord

.firstsubcontrol

.groupbox

.label

.lastrecord

.lastsubcontrol

.layoutbox

.model

.module

.notepage

.parent

.record[integer]

.recordcount

.scope

.subcontrol[integer]

.subcontrolcount

.toolbar

.userdata

.window

3.2 Implizite Erzeugung

Die herausragende Besonderheit der Subcontrols ist, dass diese Objekte implizit erzeugt werden können, ohne dass man vorher das Objekt statisch in einem Dialog definiert oder mit create() dynamisch erzeugt hat. Allerdings sollten bei Abfrage von dynamischen OLE Eigenschaften, welche ein Subcontrol erzeugen, unbedingt die Hinweise in Kapitel „Dynamische OLE-Eigenschaften und Subcontrols“ beachtet werden.

Vermutlich ist die implizite Verwendung dieser Objekte die in der Praxis am häufigsten eingesetzte. So kann man beispielsweise bei der Verwendung von Word als OLE-Server wie folgt auf die einzelnen Dokumente zugreifen:

Beispiel

Sei ControlCo irgendwo im Dialog definiert.

child control Co

{

  .mode mode_client;

  .name "Word.Application";

  .visible true;

  .connect true;

}

Dann könnte in einer Regel folgendes stehen:

rule OLETest ()

{

  variable object Doc;

  Doc := Co.Documents:Add();

  // Word besitzt eine Collection mit dem Namen Documents. Mit

  // Co.Documents wird auf dieses OLE-Objekt zugegriffen. Um das

  // Object im IDM ansprechen zu können, wird an dieser Stelle

  // ein Subcontrol mit dem Bezeichner "Documents" implizit

  // erzeugt, das als Kind von Control Co eingetragen wird.

  // Die :Add()-Methode ist ein Mitglied der Collection

  // "Documents". Deswegen kann sie nun auf das gerade eben

  // erzeugte Subcontrol angewandt werden. Dabei wird in Word

  // ein neues Document erzeugt, und als Rückgabewert ein Zeiger

  // auf das IDispatch-Interface dieses Dokuments

  // zurückgeliefert. Um ein entsprechendes Pendant auf der Seite

  // des IDM zu haben, wird auch hier ein Subcontrol als Kind

  // des ersten Subcontrols erzeugt, das selber mit dem

  // Word-Document verbunden ist. Dieses Objekt wird schliesslich

  // der Variablen "Doc" zugewiesen.

 

  // Nun kann das Subcontrol in Doc dazu benutzt werden, um

  // Methoden des OLE-Documents aufzurufen oder seine Properties

  // zu setzen/abzufragen.

  print Doc.FullName;

}

Generell gilt folgendes: Wird auf der Seite von OLE als Rückgabewert einer Methode oder Property ein Zeiger auf ein IDispatch-Interface zurückgeliefert oder wird dieser in einem Parameter an den IDM übergeben, so erzeugt der IDM an dieser Stelle ein Subcontrol, das mit dem IDispatch-Interface verbunden ist. D.h. das .connect-Attribut eines solchen Subcontrols ist bereits gleich true.

In der Regel wird der Bezeichner (Label) eines so erzeugten Objektes nicht gesetzt, so dass im Tracefile so etwas wie subcontrol Co.SUBCONTROL[2] erscheint. Eine Ausnahme bildet dabei der Zugriff auf die Properties, die OLE-Objekte zurückliefern. Da diese in der Regel Collections darstellen und dem IDM somit der Name bekannt ist, bekommt ein implizit erzeugtes Subcontrol in diesem Fall als Bezeichner den Namen der Property.

Beispiel

Doc1 := Co.Documents:Add();

Doc2 := Co.Documents:Item(1);  // Annahme: In Word gab es zuvor

                               // keine geoeffneten Dokumente.

In diesem Beispiel werden in der ersten Zeile zwei Subcontrols erzeugt: Eines mit dem Bezeichner Documents, ein anderes mit dem Bezeichner SUBCONTROL. Der Bezeichner des zweiten Subcontrols erklärt sich dadurch, dass der IDM beim Verarbeiten der Add()-Methode keine weiteren Informationen zur Verfügung hat. In der zweiten Zeile wird nur noch ein Subcontrol mit dem Bezeichner SUBCONTROL[2] erzeugt. Das Erzeugen des Subcontrols für Documents entfällt, da der IDM an dieser Stelle erkennt, dass das Objekt bereits existiert. Es entsteht deswegen folgende Situation. Auf der Seite von OLE haben wir zwei Objekte: zum einem die Collection Documents zum anderen das leere Dokument. Auf der Seite vom IDM haben wir drei Objekte: ein Subcontrol für die Collection Documents und zwei Subcontrols in den Variablen Doc1 und Doc2, die mit dem leeren Dokument auf der OLE-Seite verbunden sind. Es bleibt also dem IDM-Programmierer überlassen, durch guten Programmierstil eine Flut von überflüssigen Subcontrols zu vermeiden.

Ein besorgter Leser mag sich jetzt vielleicht fragen, was denn nun mit all den Subcontrols geschehen muss, wenn diese nicht länger gebraucht werden. Sehen Sie dazu die Erläuterungen in Kapitel „Garbage Collection“.

3.3 Dynamische OLE-Eigenschaften und Subcontrols

Beim Aufruf einer dynamischen Eigenschaft eines OLE-Objekts kann es zum Anlegen eines neuen Subcontrols kommen, wenn diese Eigenschaft (z.B. .ActiveSheet bei Microsoft Excel) ein Objekt zurückgibt. Dies ist erst einmal unproblematisch. Bei einem erneuten Aufruf dieser dynamischen Eigenschaft referenziert der IDM nun aber nicht das neue, jetzt gelieferte Objekt, sondern das bereits vorhanden und angelegte Subcontrol, welches der IDM quasi cached.

Sollte dieses Caching unerwünscht bzw. problematisch sein, kann es wie folgt umgangen werden:

Ein Setzen von .connect des betroffenen Subcontrols auf false ist hingegen keine Lösung, da das Subcontrol erhalten bleibt und lediglich alle weiteren Zugriffe fehl schlagen.

3.4 Garbage Collection

Alle implizit erzeugten Subcontrols müssen auch wieder gelöscht werden. Zu diesem Zweck merkt sich der IDM, von wie vielen Objekten ein Subcontrol gerade referenziert wird. Wird dabei festgestellt, dass ein Subcontrol von keiner Variablen (lokal, global, statisch) oder benutzerdefiniertem Attribut mehr verwendet wird, so wird das Subcontrol zerstört. Es gibt dabei zwei Strategien, die im Folgenden vorgestellt werden. Wichtig: Folgende Erklärungen sind auf jeden Fall mit einem Änderungsvorbehalt versehen, da es nicht abzusehen ist, ob in Zukunft andere Strategien eingesetzt werden.

  1. Wird ein implizit erzeugtes Subcontrol einer Variablen (oder Ähnlichem) zugewiesen, und wird diese Variable zu einem späteren Zeitpunkt wieder mit einem anderen Wert überschrieben, so kann das Subcontrol wieder freigegeben werden, vorausgesetzt, es hat keine Kinder.

    Beispiel

    Doc := Co.Documents:Add();  // 2 Subcontrols (A für Documents und B für Dokument, das

                                // von Add() erzeugt worden ist) werden implizit erzeugt.

    Doc := Co;                  // Das Subcontrol B wird nicht länger von Variable Doc

                                // benutzt und kann deswegen zerstoert werden. Nun kann

                                // auch Subcontrol A feststellen, dass es keine Kinder

                                // mehr besitzt und kann sich ebenfalls zerstoeren.

  2. Bei ungünstigen Konstellationen kann es passieren, dass ein Subcontrol es nicht merkt, dass es eigentlich zerstört werden kann. Dies passiert z.B., wenn ein gerade erzeugtes Subcontrol nirgendwo zugewiesen wird. In diesem Fall fehlt das auslösende Moment, um das Objekt wegzuwerfen. Deswegen startet der IDM von Zeit zu Zeit Säuberungsaktionen. Dabei werden implizit erzeugte Subcontrols daraufhin getestet, ob diese nicht mehr gebraucht werden und im positiven Fall weggeworfen.

Wichtig

Da die Garbage-Collection vom IDM nur die Referenzen aus der Regelsprache berücksichtigen kann, ist Vorsicht geboten, wenn die ObjectIDs von Subcontrols aus der C-Schnittstelle (für COBOL und C++ gilt das gleiche) verwaltet werden. Wenn eine solche ID in C irgendwo zwischengespeichert wird, bekommt der IDM dieses nicht mit. Nach dem Aufruf der C-Funktion könnte der IDM feststellen, dass das Subcontrol nicht mehr gebraucht wird, und dieses entsorgen. Wird die Kontrolle wieder auf die C-Seite übergeben, kann dort nicht mehr davon ausgegangen werden, dass die zuvor gespeicherte ObjektID noch gültig ist. Es sollte deswegen vermieden werden, die Subcontrols in C zu speichern. Möchte man das trotzdem tun, muss sicher gestellt werden, dass das Objekt in der Regelsprache noch referenziert wird (z.B. in einer globalen oder statischen Variablen oder in einem benutzerdefinierten Attribut). Nur so kann garantiert werden, dass das Subcontrol nicht weggeworfen wird.

3.5 Beispiel

In dem folgenden kleinen Beispiel wird gezeigt, wie man Subcontrols verwenden kann:

dialog Word

 

window WnFenster

{

  .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;

 

        // Setzt voraus, dass die Datei tatsaechlich existiert

        Doc := Co.Documents:Open("d:/tmp/altesdokument.doc");

 

        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

    {

        Co.Documents:Add("d:/tmp/neuesdokument.doc");

        // Setzt voraus, dass die Datei tatsaechlich existiert

    }

  }

  child pushbutton PbClose

  {

    .xleft 180;

    .width 92;

    .yauto -1;

    .height 27;

    .ybottom 10;

    .text "Close Doc";

    on select

    {

        // Erstes (bzgl. der Documents-Liste) Document schliessen

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

    }

  }

  child pushbutton PbQuit

  {

    .xauto -1;

    .width 85;

    .xright 20;

    .yauto -1;

    .height 27;

    .ybottom 10;

    .text "Quit";

    on select

    {

        // Word beenden

        Co:Quit();

    }

  }

}

on dialog start

{

    Co.Visible := true;

}