Neu vorgestellt, Programmierung

Dynamische Oberflächen-Objekte

Dynamische Fenster

Die Version 5.7.01 steht in den Startlöchern. Neben einer Reihe von Erweiterungen wird es mit dieser Version auch möglich sein, Fenster dynamisch zu erstellen.

Einleitung

Die Dialog-Gestaltung wird in CONZEPT 16 bisher im Designer vorgenommen. Die visuelle Erstellung hat den Vorteil, dass selbst große Dialoge mit vielen Unterobjekten zügig erstellt werden können. Genau diese Dialoge haben jedoch den Nachteil, dass beim Laden durch WinOpen() alle Objekte in den Speicher geladen werden. Bei der Ausführung durch WinDialogRun() entsteht zusätzlich die Problematik, dass für jedes enthaltene Fenster Benutzer-Ressourcen verbraucht werden (siehe Artikel Benutzer und GDI-Objekte).

Dynamische Erstellung

Abhilfe schafft hier die dynamische Erstellung der Objekte. Hierfür stellt CONZEPT 16 drei neue und einen erweiterten Befehl zur Verfügung.

WinCreate()

Der Befehl erstellt ein Fenster- oder graphisches Objekt vom angegebenen Typ (type) und liefert den Deskriptor auf dieses. Optional kann der Name sowie eine Beschriftung angegeben werden. Das Argument caption setzt eine entsprechende Eigenschaft beim Objekt voraus.

WinCreate(
  type        : handle, // Typ (_WinType...)
  opt name    : alpha,  // Name
  opt caption : alpha,  // Beschriftung
  opt parent  : handle, // Elternobjekt
  opt next    : handle  // Folgeobjekt
) : handle              // Objekt

Wird dem Befehl kein Parent-Objekt (Argument parent) übergeben, dann wird lediglich das Objekt erstellt und der Deskriptor zurückgeliefert. Im anderen Fall wird das Objekt auch in die Liste der Child-Objekte des angegebenen Parent-Objektes hinzugefügt.

main

  local
  {
    tDialog  : handle;
    tButton  : handle;
  }

{
  // Dialog erstellen
  tDialog # WinCreate(_WinTypeDialog,'','Dynamische Fenster sind toll');
  tDialog->wpArea # RectMake(0,0,320,100);

  // Button-Objekt erstellen und dem Dialog hinzufügen
  tButton # WinCreate(_WinTypeButton,'bnClick','Click!',tDialog);
  tButton->wpArea # RectMake(20,20,285,45);

  // Dialog ausführen
  tDialog->WinDialogRun(_WinDialogCenterScreen);

  // Dialog freigeben
  tDialog->WinClose();
}

Der Beispiel-Code erstellt einen Dialog mit WinCreate(). Seine Größe wird durch die Eigenschaft wpArea angepasst. Anschließend wird eine Schaltfläche generiert (_WinTypeButton). Sie bekommt den Name ‘bnClick’ und den Titel ‘Click!’. Im letzten Argument beim Befehl wird der Deskriptor auf den Dialog übergeben. Dadurch wird die Schaltfläche zum Dialog hinzugefügt. Der Rest stellt für geeichte Entwickler nichts Neues dar. Das Ergbnis zeigt Abb.1.

Dynamisch erstellter DialogAbb. 1: Dynamisch erstellter Dialog
WinAdd()

Der WinAdd()-Befehl wurde um die Möglichkeit erweitert, damit auch dynamisch erzeugte Oberflächen-Objekte einem Parent-Objekt hinzugefügt werden können.

WinAdd(
  parent      : handle, // Elternobjekt
  object      : handle, // Objekt
  opt options : int,    // Optionen (nur Mdi)
  opt next    : handle  // Folgeobjekt
) : int;                // Resultat

Im Beispiel zuvor wurde WinCreate() mit Angabe eine Parent-Objektes angegeben. In diesem Fall ist es nicht notwendig WinAdd() aufzurufen. Fehlt die Angabe des Parent-Objektes bei WinCreate(), dann wird lediglich ein Deskriptor für das Oberflächen-Objekt generiert. Erst der WinAdd()-Befehl fügt dann das Objekt einem Parent-Objekt hinzu. Der folgende Code demonstriert, was gemeint ist.

main

  local
  {
    tDialog  : handle;
    tButton  : handle;
  }

{
  // Dialog erstellen
  tDialog # WinCreate(_WinTypeDialog,'','Dynamische Fenster sind toll');
  tDialog->wpArea # RectMake(0,0,320,100);

  // Button-Objekt erstellen
  tButton # WinCreate(_WinTypeButton,'bnClick','Click!');
  tButton->wpArea # RectMake(20,20,285,45);

  // und dem Dialog hinzufügen
  tDialog->WinAdd(tButton);

  // Dialog ausführen
  tDialog->WinDialogRun(_WinDialogCenterScreen);

  // Dialog freigeben
  tDialog->WinClose();
}

Beim Aufruf des WinCreate-Befehls wird nun kein Parent-Objekt mehr übergeben. Danach wird WinAdd() aufgerufen, um die Schaltfläche dem Dialog hinzuzufügen.

WinRemove() / WinDestroy()

Neben der Erstellung von Oberflächen-Elementen gibt es auch die Möglichkeit, diese zur Laufzeit wieder zu entfernen. Hierzu stehen die Befehle WinRemove() und WinDestroy() zur Verfügung.

WinRemove(
  object : handle // Objekt
)

WinDestroy(
  object        : handle, // Objekt
  children_only : logic   // Nur Kindobjekte zerstören
)

Der Befehl WinRemove() entfernt ein durch WinCreate() erstelltes, dynamisches Oberflächenobjekt von seinem Parent-Objekt. Sofern das Objekt zu diesem Zeitpunkt auf dem Bildschirm sichtbar war, ist es nun nur noch als Objekt ohne Benutzeroberfläche vorhanden. Der Deskriptor kann nach WinRemove() ganz normal weiterverwendet werden, um beispielsweise Eigenschaften zu setzen oder das Objekt einem neuen Parent-Objekt zuzuordnen.

// Button-Objekt entfernen
tButton->WinRemove();

// Caption ändern
tButton->wpCaption # 'Klick mich!';

// Einem Parent-Objekt hinzufügen
tNewParent->WinAdd(tButton);

Nach WinRemove() sind die vom Objekt verbrauchten Benutzer-Ressourcen wieder freigegeben und stehen dem Betriebssystem wieder zur Verfügung.
Der WinDestroy-Befehl zerstört das dynamische Objekt. Nach seiner Rückkehr ist der übergebene Deskriptor nicht mehr gültig. WinDestroy() führt zugleich auch ein WinRemove() durch, sofern das Oberflächen-Objekt noch einem Parent-Objekt zugeordnet ist.

// Button-Objekt zerstören
tButton->WinDestroy();

// Laufzeitfehler!
tButton->wpCaption # 'Klick mich!'; 

WinDestroy() erlaubt es auch, nur die Kind-Objekte eines dynamisch erzeugten Oberflächen-Objektes zu zerstören. Das Objekt selber ist danach noch gültig, seine untergeordneten Objekte sind nach dem Aufruf jedoch zerstört und nicht mehr vorhanden.

// Alle Kindobjekte des Dialoges zerstören
tDialog->WinDestroy(true);
Sag mir was du bist

Wenn man herausfinden möchte, ob es sich bei einem Oberflächen-Objekt um ein klassisches oder ein dynamisch erstelltes handelt, hilft der Befehl WinInfo() weiter.

sub EvtClicked
(
  aEvt : event;    // Ereignis
)
: logic;
{
  if (aEvt:obj->WinInfo(_WinDynamic) = 1)
    WinDialogBox(0,'WinInfo',
                   'Ich bin dynamisch \:-)',
                   _WinIcoInformation,
                   _WinDialogOk,1);
  else
    WinDialogBox(0,'WinInfo',
                   'Ich bin klassisch §:-(',
                   _WinIcoInformation,
                   _WinDialogOk,1);
}
Klassisch und dynamisch – side by side

Die Erstellung dynamischer Oberflächen-Objekte integriert sich nahtlos in das bestehende klassische Modell der Oberflächen-Erzeugung. So kann natürlich ein Dialog nach wie vor mit WinOpen() aus der Datenbank geladen werden. Anschließend können dynamisch erstellte Objekte mittels WinAdd() hinzugefügt werden.

Ausblick

Die in der Version 5.7.01 realisierte Umsetzung beschränkt die Erstellung von Oberflächen-Objekten auf die Objekt-Typen, welche den Löwen-Anteil der Objekte in Dialogen ausmachen (wie z.B. Eingabe-Objekte, Schaltflächen und Checkboxen).
Die dynamische Erstellung von RecList und RecView ist in dieser Version z.B. nicht möglich. Diese Limitation liegt vor, da die dynamische Erstellung noch keine graphischen Unterobjekte (wie z.B. RecList- oder RecView-Spalten) erfasst.
Wir werden die fehlenden Objekte jedoch zeitnah in das neue System integrieren. Letztendlich wird es dann auch möglich sein die bestehenden Befehle auf das neue System zu adaptieren, wie das folgende Beispiel anhand des GanttGraph-Objektes zeigt.
Bisheriges System (Intervall erstellen und hinzufügen):

WinGanttIvlAdd(gantt, xpos, ypos, len, name[, caption]) : handle;

Neues System:

Ivl # WinCreate(_WinTypeInterval,name[,caption]);
WinAdd(gantt,Ivl);

Ivl->wpArea # RectMake(xpos,ypos,xpos+len-1,0);

if (gantt->wpAutoUpdate)
  gantt->WinUpdate(_WinUpdState,_WinGntRefresh);

Wohlgemerkt: das neue System wird das alte nicht ersetzen. Es bietet lediglich eine Verallgemeinerung des bestehenden Systems durch die neuen Befehle.

17 Kommentare

17 Kommentare “Dynamische Oberflächen-Objekte”

  1. Ich habe gerade festgestellt, das die Objekte _WinTypeGanttGraph, sowie _WinTypeGanttAxis nicht dynamisch erzeugt werden können. Wäre es problematisch WinCreate() dafür zu erweitern?

  2. @alco
    Vielleicht haben Sie ja dennoch die Möglichkeit, die bereits unterstützten Objekte zu nutzen.
    Die fehlenden Objekte werden wir natürlich bei der weiteren Planung der Version 5.7 berücksichtigen. So bald es diesbezüglich Neuigkeiten gibt, werden wir diese wieder im Blog veröffentlichen.

  3. Auch ich freue mich riesig über die dynamischen Objekte! Haupteinsatzgebiet dafür wäere bei uns jedoch die Data/Reclist bzw. das dynamische hinzufügen von Spalten (da müssen wir wohl noch etwas warten?). Bezgl. des Schlüsselwortes "private" möchte ich mich sh@softweaver anschließen. Das wäre wirklich prima.

  4. @sh@softweaver
    Zunächst vielen Dank für die Kritik und die Anregungen.

    Ich kann Ihre Kritik bezüglich des späten Zeitpunkts für die Erweiterung der dynamischen Oberflächen-Erstellung schon verstehen. Sie sprechen hierbei andere Entwicklungsumgebungen an, die das schon seit Jahren unterstützen. CONZEPT 16 ist aber ein Datenbank-Entwicklungssystem. Deshalb lag der Schwerpunkt immer auf einer möglichst einfachen Integration von Oberfläche und Datenbank ohne das der Entwickler sich um die Objekt-Erstellung und Initialisierung kümmern muss.

    Sie haben außerdem die Objekt-Orientierung in CONZEPT 16 angesprochen. Wir haben uns darüber auch bereits Gedanken gemacht und diese wurden auch in dem Workshop "Effiziente Programmierung am 17.10.2012" mit den Teilnehmern diskutiert. Das Resultat war eindeutig bezüglich der Notwendigkeit der Objektorientierung in CONZEPT 16. Auch hier werden Taten folgen.

  5. Zum einen kann ich mich nur den anderen Kommentatoren anschließen und mich für diese wichtige Erweiterung bedanken; das Feature GUI-Objekte dynamisch erzeugen zu können, habe ich schon oft vermisst, ihr habt damit einen wichtigen Schritt in Richtung objektorientierte Programmierung vollzogen! Gleichzeitig muss ich euch aber auch kritisieren: wieso erst jetzt? Andere Programmiersprachen, bzw. GUI-Frameworks können das schon seit 20 Jahren und noch länger!

    Wer bis jetzt nur mit C16 gearbeitet hat, ist sich der Möglichkeiten vielleicht gar nicht bewusst: man kann z.B. ein Fenster im XML-Format konfigurieren und ein eigens dafür entwickelter Parser liest die Daten ein und erstellt das Fenster dynamisch! Noch einen Schritt weiter gedacht, könnte man seinen Kunden eine Art "Skriptsprache" zur Verfügung stellen, ähnlich wie bei SAP/ABAP!

    Was ich mir jetzt noch wünschen würde, wäre innerhalb einer Prozedur Funktionen zu schützen. Es muss noch gar nicht voll objektorientiert sein, ein Schlüsselwort "private" würde mir schon reichen:

    sub private function() : logic

    Eine solche Funktion dürfte dann nur innerhalb einer Prozedur aufgerufen werden, alle anderen Prozeduren die versuchen die Funktion aufzurufen erhalten einen Compilerfehler! Ich denke das wäre nicht schwer zu implementieren oder?

  6. Das ist wirklich eine super Erweiterung, die man schon oft hätte brauchen können. Toll, dass dies nun integriert wurde!

    Hoffentlich werden auch die noch ausstehenden Fenster-Objekte sowie die von Kilian gewünschte Erweiterung im Bereich der Druck-Objekte möglist zeitnahe umgesetzt.

    An dieser Stelle einmal ein herzliches Dankeschön an das ganze Team der vectorsoft AG für dessen unermüdlichen Einsatz.

  7. Das finde ich grossartig. Insbesondere die Möglichkeit, neue Elemente dynamisch hinzuzufügen, dürfte sich als sehr nützlich erweisen.

    Es wäre schön, wenn es dieselben Möglichkeiten auch für die Druckdokumente, -formulare usw. gäbe.

  8. @Th.Eichele
    Bei den klassischen Objekten können wir das Entfernen oder Zerstören nicht zulassen, da dadurch das WITH-Konstrukt beeinflusst würde. Der schnelle Zugriff auf Objekte wird durch die Definition eines Dialoges zur Compile-Zeit festgelegt.

Kommentar abgeben