Das Builder Tutorial
Inhaltsverzeichnis Komponentenentwicklung Methoden, Eigenschaften und Ereignisse
Methoden
Eigenschaften
Standardwerte von Eigenschaften
Was sind Ereignisse
Standardereignisse

Methoden

Methoden sehen in der VCL genau wie im C++-Standard aus. Der borlandspezifischer Zusatz __fastcall soll lediglich den Methodenaufruf (angeblich) beschleunigen und kann auch weggelassen werden. Allerdings mit ein paar Ausnahmen. Der Konstruktor und der Destruktor muss mit __fastcall deklariert werden da diese in der VCL auch mit __fastall deklariert wurden. Ansonsten kommt die Fehlermeldung:
"[C++Fehler] MyRichEdit.h(21): Virtual function 'TMyRichEdit::TMyRichEdit(Classes::TComponent *)' conflicts with base class 'Comctrls::TRichEdit'. "
Des weiteren benötigen alle Methoden, die mit Eigenschaften zusammenhängen die Deklaration mit __fastcall.

Obwohl ich eigentlich Grundlagen von C++ nicht behandeln wollte, möchte ich dennoch kurz auf ein Grundkonzept der OOP eingehen. Der Polymorphie.
Polymorpie bedeutet so was wie "Vielgestaltig". Das alles hat im wesentlichen mit Klassenumwandlungen (casting) zu tun. Am besten ich erkläre dieses an einem Beispiel:

Nun ja, ist nicht die Wucht, aber erfüllt seine Zweck.
Erklärung:
Wir haben zwei Klassen. Die Klasse Linie ist von Punkt abgeleitet. Beide Funktionen haben die selbe Methode ausgabe( Siehe (1) und (2) ). Nun wird in Zeile (3) eine Instanz der Klasse Linie erzeugt. In Zeile (4) habe ich ein Zeiger auf die Klasse Punkt erstellt. Außerdem wird dort die erstellte linie dem Zeiger zugewiesen. Dabei findet eine Typumwandlung von Linie nach Punkt statt. Nun rufe ich in Zeile (5) die Methode ausgabe auf.
Ergebnis 1: Begründung: Da linie_gecastet ein Zeiger auf der Klasse Punkt ist, wird auch die Methode ausgabe der Klasse Punkt aufgerufen.
Nun kommen wir zur Polymorphie. Um immer die richtige Methode einer Klasse oder deren verwandten Klassen aufzurufen, werden virtuelle Methoden benutzt. In diesen Beispiel sollte ja eigentlich die Methode ausgabe der Klasse Linie aufgerufen werden. Die Klasse Punkt kennt aber diesen Umstand nicht. Sie kennt weder die Klasse Linie noch die Methoden oder Variablen dieser Klasse. Wenn aber die Methode in der Klasse Punkt mit virtual deklariert wird, sorgt der Compiler dafür, das die richtige Methode aufgerufen wird.

Ersetzen Sie nun die Zeile (1) durch: Wenn Sie diesen nun teste, werden Sie feststellen, dass nun die Methode der Klasse Linie aufgerufen wird obwohl linie_gecastet ein Zeiger auf Punkt ist !!
Ergebnis 2 mit virtueller Deklaration: Weitere Informationen zu diesen Thema finden Sie in Büchern oder C++-Tutorials. Ich spreche dieses Thema überhaupt an, da es einige virtuelle Methoden in der VCL gibt. Diese Methoden müssen oder können für jede neue Komponente neu definiert werden.
Ein klassisches Beispiel ist die Methode Paint.
In dieser Komponente habe ich die Methode Paint redefiniert. Deshalb hat die Methode Paint der Klasse TLabel hier in dieser Klasse keine Bedeutung mehr. Nun wird natürlich aus das Label nicht gezeichnet. Um nun doch die Methode Paint der Basisklasse TLabel zu nutzen, können Sie diese einfach aufrufen:
So was ist Sinnvoll, wenn man zum Beispiel ein Ereignis OnPaint hinzufügen möchte falls diese in der Basisklasse nicht definiert wurde. Sie können natürlich auch ganz auf die Methode der Basisklasse verzichten und den Text des Labels selber in ihrer Methode zeichnen:
Das ist so das Grundprinzip zum Überschreiben, Abändern oder um Funktionalitäten zu erweitern.

Zum Seitenanfang


Eigenschaften

Eigenschaften sind im Sinne von __property's sind Borlandspezifisch. Das bedeutet, das diese Eigenschaften zum Beispiel nicht in VC++ benutzt werden können. Diese Tatsache schränkt Klassen mit __property's auf die VCL ein. Des weiteren gibt es keine__property's im C++-Standard. Eigenschaften selbst können keine Werte speichern. Sie brauchen eine Variable (oder Methode), die von dieser Eigenschaft angesprochen werden kann. Diese Variablen werden normalerweise im private oder protected-Bereich der Klasse deklariert. Nun könnten Sie behaupten, dass man dann doch einfacher diese Variable im public-Bereich der Klasse deklarieren kann. Dann bräuchte man die Eigenschaft nicht mehr. An sich haben Sie mit solch einer Aussage recht. Allerdings können Sie diese Variable nicht im Objektinspector sichtbar machen. Außerdem sollte man Variablen immer kapseln damit Sie die Kontrolle über die Variable behalten. Eigenschaften unterstützen diese Kapselung in verschiedenen Stufen.

Der Aufbau einer Eigenschaft sieht in etwa so aus:

__property Rückgabewert Eigenschaftsname  { read= Variable/Methode ,write= Variable/Methode ,default= Defaultwert  }


Um nun den Umgang mit Eigenschaften zu demonstrieren greife ich wieder auf unserer TMyRichEdit- Komponente zurück. Hier wollen wir die Eigenschaft DateiName hinzufügen. Hierzu brauchen wir zuerst mal eine Variable die den Dateinamen aufnimmt. Ich habe den Namen IVDateiName gewählt. "IV" ="Interne Variable". Jeder macht das anders, denk ich. Bei mir hat sich diese Notation eingebürgert. Andere nehmen ein "F" oder gar keine Notation. Über Sinn und Unsinn von Notationen möchte ich in diesen Ausführungen auch nicht diskutieren. Ich benutze diese Notation als Differenzierung von den Eigenschaften.

Eine Eigenschaft, welche eine Variable entspricht die im public-Bereich deklariert wäre, würde in etwa so aussehen:

Die Eigenschaft ist unter __published deklariert, damit diese im Objektinspector sichtbar ist. Der Zusatz __property definiert DateiName als Eigenschaft DateiName. DateiName kann nun gelesen(read) und beschrieben(write) werden. Da ich hier in beiden Fällen den Variablennamen IVDateiName angegeben habe, werden die Werte dieser Variable direkt gelesen und geschrieben. Diese Eigenschaft ist nicht gekapselt, da der User dieser Komponente die Variable direkt über diese Eigenschaft ändern kann.

Nun ein anderes Beispiel, in der die Variable teilweise gekapselt wird: Wie bereits erwähnt benötigen alle Methoden, die mit Eigenschaften zusammenhängen die Deklaration mit __fastcall.
Die Methode SetDateiName muss ein Aufrufsparameter vom Typ AnsiString haben, da diese Funktion die Variable ja setzen soll. Einen Rückgabewert braucht diese Funktion logischerweise nicht. Wenn also der User den Dateinamen ändert, wird dieser Wert an diese Funktion übergeben. Was mit dem Wert gemacht wird, legen Sie in der Funktion fest. Zum Beispiel können Sie bei bestimmten Werten eine Variabelenänderung verhindern oder zusätzliche Änderungen, die mit einer Eigenschaft zusammenhängen anpassen. Auch können Sie hier ein Ereignis zur Änderung der Variable auslösen ( das haben wir ja auch vor ;) ). Natürlich können Sie zum Auslesen der Eigenschaft ebenfalls eine Funktion einsetzen.
Dieses würde dann so aussehen:
Allerdings hat sich die zweite Variante eingebürgert, da das Lesen einer Eigenschaft keine Probleme verursacht. Hier kann man eigentlich mit Ruhe den Variabelennamen benutzen.

Standardwerte von Eigenschaften

Standardwerte, die eine Eigenschaft annehmen soll, sollten im Konstruktor initialisiert werden.
Die Zuweisung eines default wie zum Beispiel:

__property int test = { read = IVtest , write = IVtest , default  = 3 } ;

erfüllt diese Aufgabe nicht.
Dieses default in der Deklaration ist nur für das speichern der Werte dieser Eigenschaft zuständig. In diesem Beispiel mit der Eigenschaft test ist ein default-Wert=3 angegeben.
Wenn also der Wert der Eigenschaft nicht 3 ist, wird der Wert der Eigenschaft in der Formulardatei gespeichert. Wenn der Wert 3 ist, wird er nicht gespeichert.
Beispiel:
Ziehen Sie nun zwei Objekte dieser Komponente in ein Formular. Setze bei einen Objekt den Wert test auf 3 bei den anderen auf 5.
Dann sieht die Formulardatei (*.dmf) so aus:

Zum Seitenanfang


Was sind Ereignisse

Ein Ereignis ist ein Zeiger auf einer Methode. Diese werden von Closures gekapselt. Vom Prinzip her kann man sich das etwa so vorstellen:

Dieses Beispiel zeigt, wie mit Funktionszeigern gearbeitet wird. Ereignisse selbst sind solche Funktionszeiger. Mit dem einzigen Unterschied, das diese vom Typ __closure sind.
Hier ein Beispiel, wie das Ereignis MyClickEvent definiert sein könnte:
In Zeile (1) wird ein Ereignis erstellt. Dieser Zeigertyp ist praktisch eine Schablone.
In Zeile (2) wird ein Zeiger des Typs MyClickEvent mit den Namen "IVMyOnClick" erzeugt. Hier wird die Adresse der vom Anwendungsentwickler zugeordnete Methode für das Ereignis in Zeile (5) gespeichert.
Wenn man sich die Zeile (3) genau anschaut, stellt man fest, dass es sich hier tatsächlich um eine Eigenschaft handelt. Ist es ja auch. Eigenschaften speichern (indirekt) einen Wert. In Zeile (5) bzw. Zeile (2) wird ja auch etwas gespeichert. Nämlich die Adresse der Methode. So gesehen sind Ereignisse aus der Sicht des Komponentenentwicklers Eigenschaften.
Ein Ereignis ist, wenn man's genau betrachtet zum Beispiel, wenn ich mich vertippe *g*. Also wenn etwas passiert oder ausgelöst wird. Das eigentliche Ereignis hier ist also der Zeitpunkt, wo die Methode, welche der Anwendungsentwickler über die Eigenschaft ( hier MyOnClick) zugeordnet hat, aufgerufen wird. Erst dann ist diese Eigenschaft für den User der Komponente zum Ereignis geworden. Sie müssen also dafür sorgen, das diese Methode, die z.B hier in IVMyOnClick gespeichert ist, zum richtigen Zeitpunkt aufgerufen wird. Dieses geschieht in diesem Beispiel in der Methode MyClick Zeile(4). Diese Methode wird in Zeile (9) aufgerufen, wenn die Botschaftenschleife in Zeile (6) (gehe ich später drauf ein) eine WM_LBUTTONDOWN- Nachricht empfängt. Der Methodenaufruf erfolgt mit den Werten der Mausposition. In Zeile(4) wird nun geprüft ob der Funktionszeiger etwas gespeichert hat ( also nicht = NULL ist). Wenn dieses zutrifft wird die Methode aufgerufen. Nun ist das Ereignis für den Anwendungsentwickler ausgelöst.
Hier mal ein Beispiel, wie der User dieses Ereignis abfangen könnte:
Vom Grundprinzip her sind alle Ereignisse der VCL so aufgebaut. Nun könnten Sie behaupten, dass man in Zeile (9) genauso gut gleich die Zeile(4) einfügen könnte. Man würde ja eine Methode sparen. Ist auch richtig. Doch stellen Sie sich mal vor, jemand möchte eine Kompo von ihrer Komponente ableiten. Nun möchte er dieses Ereignis intern verwenden oder abändern. Dann hat er ein Problem wenn Sie das Ereignis einfach so auslösen. Er kann das Ereignis nicht mehr beeinflussen.
Durch die Methode in Zeile (3) ist dagegen das Abfangen des Ereignisses recht einfach. Auch lassen sich schnell mal weitere Ereignisse dadurch auslösen wenn die Methode überschrieben wird und entsprechenden Code eingefügt wird.

Diese Vorgehensweise findet man auch in allen Standardereignissen. Wo wir schon beim nächsten Kapitel wären.

Zum Seitenanfang


Standardereignisse

Standardereignisse sind zum Beispiel:
OnClick,OnMouseDown,OnEnter,OnExit,OnKeyDown, usw.
Der Funktionszeiger für die Standardereignisse, welche nur die nach TObject gecastete Instanz benötigt sieht so aus:

typedef void __fastcall (__closure *TNotifyEvent)(System::TObject* Sender);

Die Methoden, welche aufgerufen werden sind ähnlich der Methode im vorherigen Kapitel Zeile(3).

Diese Methoden der Standardereignisse im eigentlichen Sinne sind zum Beispiel :
Abgeleitet von TControl:
DYNAMIC void __fastcall Click(void);
DYNAMIC void __fastcall DblClick(void);
DYNAMIC void __fastcall MouseDown(TMouseButton Button, Classes::TShiftState Shift, int X, int Y);
DYNAMIC void __fastcall MouseMove(Classes::TShiftState Shift, int X, int Y);
DYNAMIC void __fastcall MouseUp(TMouseButton Button, Classes::TShiftState Shift, int X, int Y);
DYNAMIC void __fastcall DoStartDrag(TDragObject* &DragObject);
DYNAMIC void __fastcall DoEndDrag(System::TObject* Target, int X, int Y);
DYNAMIC void __fastcall DragCanceled(void);
DYNAMIC void __fastcall VisibleChanging(void);

von TWinControl:
DYNAMIC void __fastcall DoEnter(void);
DYNAMIC void __fastcall DoExit(void);

Nun kann man diese Methoden überschreiben.
Wenn man zum Beispiel ein neues OnClick- Ereignis erzeugen möchte, in dem die Koordinaten der Mouse mit übergeben werden, sieht das in etwa so aus:

In Zeile(3) habe ich die Methode Click() überschrieben. Die Zeile(4) ruft die alte Click()- Methode auf. Wenn diese nicht aufgerufen wird, so wird auch das Ereignis OnClick, welche ja in der Methode ausgelöst wird, auch nicht ausgelöst. Wenn also das Standardereignis nicht ausgelöst werden soll, so lösche die Zeile(4). Man kann hier auch das Auslösen der alten OnClick-Eigenschaft mit einer Bedingung verknüpfen.
In Zeile (1) habe ich eine neue Ereignisschablone erstellt, die außer dem Sender auch noch die Koordinaten der Maus mit aufnimmt.
Dann könnte das Abfangen des Users in etwa so aussehen:
In Zeile (1) wird, wenn die Komponente installiert wurde, beim Doppelklick auf das Ereignis MyOnClick automatisch mit den richtigen Parametern generiert. So gesehen müssen Sie sich nicht um die Implementierung des Anwendungsentwicklers kümmern.

Zum Seitenanfang


Zum Seitenanfang | Artikel drucken

Das Builder Tutorial im Netz:
http://buildertut.c-plusplus.de

© Das Builder Tutorial Team