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:
-
class Punkt
{
public:
int x;
int y;
void ausgabe() //(1)
{
cout << x << " " << y << endl;
}
Punkt(){};
};
class Linie: public Punkt
{
private:
public:
int x2;
int y2;
Linie();
Linie (int xx, int yy,int xx2, int yy2){x=xx; y=yy;x2=xx2;y2=yy2;}
void ausgabe () //(2)
{
cout << x<<" "<<y<<" "<< x2<<" "<<y2<<endl;
}
};
//---------------------------------------------------------------------------
#pragma argsused
int main(int argc, char **argv)
{
Linie linie(10,10,100,100); //(3)
Punkt* linie_gecastet=&linie; //(4)
linie_gecastet->ausgabe(); //(5)
return 0;
}
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:
- 10 10
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:
- virtual void ausgabe()//(1)
Ergebnis 2 mit virtueller Deklaration:
- 10 10 100 100
Ein klassisches Beispiel ist die Methode Paint.
-
class PACKAGE TMyLabel :public TLabel
{
public:
void __fastcall Paint(){}
__fastcall TMyLabel(TComponent* Owner):TLabel(Owner){}
};
-
class PACKAGE TMyLabel :public TLabel
{
public:
void __fastcall Paint()
{
// man könnte hier ein Ereignis auslösen
TLabel::Paint();
}
__fastcall TMyLabel(TComponent* Owner):TLabel(Owner){}
};
-
class PACKAGE TMyLabel :public TLabel
{
public:
void __fastcall Paint()
{
SetBkMode(Canvas->Handle,TRANSPARENT); // macht den Hintergrund des Textes
// transparent
Canvas->TextOut(0,0,Caption);
}
__fastcall TMyLabel(TComponent* Owner):TLabel(Owner){}
};
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:
-
//---------------------------------------------------------------------------
class PACKAGE TMyRichEdit : public TRichEdit
{
private:
String IVDateiName;
__published:
__property String DateiName= {read=IVDateiName, write=IVDateiName};
public:
__fastcall TMyRichEdit(TComponent* Owner);
};
//---------------------------------------------------------------------------
Nun ein anderes Beispiel, in der die Variable teilweise gekapselt wird:
-
//---------------------------------------------------------------------------
class PACKAGE TMyRichEdit : public TRichEdit
{
private:
String IVDateiName;
void __fastcall SetDateiName(String Name) { IVDateiName=Name;}
__published:
__property String DateiName= {read=IVDateiName, write=SetDateiName};
public:
__fastcall TMyRichEdit(TComponent* Owner);
}; //---------------------------------------------------------------------------
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:
-
//---------------------------------------------------------------------------
class PACKAGE TMyRichEdit : public TRichEdit
{
private:
String IVDateiName;
void __fastcall SetDateiName(String Name) { IVDateiName=Name;}
AnsiString __fastcall GetDateiName() { return IVDateiName;}
__published:
__property String DateiName= {read=GetDateiName, write=SetDateiName};
public:
__fastcall TMyRichEdit(TComponent* Owner);
};
//---------------------------------------------------------------------------
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:
-
//---------------------------------------------------------------------------
class PACKAGE TMyRichEdit4 : public TRichEdit
{
private:
int IVtest;
__published:
__property int test={read=IVtest,write=IVtest,default=3};
public:
__fastcall TMyRichEdit4(TComponent* Owner);
};
//---------------------------------------------------------------------------
Dann sieht die Formulardatei (*.dmf) so aus:
-
object Form1: TForm1
Left = 192
Top = 108
Width = 696
Height = 480
Caption = 'Form1'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
PixelsPerInch = 96
TextHeight = 13
object MyRichEdit41: TMyRichEdit4
Left = 132
Top = 62
Width = 185
Height = 89
Lines.Strings = (
'MyRichEdit41')
TabOrder = 0
end
object MyRichEdit42: TMyRichEdit4
Left = 410
Top = 60
Width = 185
Height = 89
Lines.Strings = (
'MyRichEdit42')
TabOrder = 1
test = 5 // hier wurde der Wert gespeichert, da er nicht ==3 ist
end
end
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:
-
#pragma hdrstop
#include <condefs.h>
#include<iostream>
#pragma argsused
typedef double (*Funktionszeiger) (double);
//---------------------------------------------------------------------------
double A(double temp)
{
return temp+1;
}
//---------------------------------------------------------------------------
double B(double temp)
{
return temp+2;
}
//---------------------------------------------------------------------------
int main(int argc, char **argv)
{
Funktionszeiger Zeiger=B; // Der Funktionzeiger zeigt nun auf die Funktion B
cout <<Zeiger(4); // B gibt 4+2 zurück =Ausgabe 6.
Zeiger=A; // Der Funktinszeiger zeigt nun auf die Funktion A
cout <<endl <<Zeiger(4); // hier wird 5 Ausgeben
return 0;
}
Hier ein Beispiel, wie das Ereignis MyClickEvent definiert sein könnte:
-
typedef void __fastcall (__closure *MyClickEvent)(TObject *Sender,int X, int Y);//(1)
//---------------------------------------------------------------------------
class PACKAGE TMyRichEdit2 : public TRichEdit
{
private:
MyClickEvent IVMyOnClick; //(2)
__published:
__property MyClickEvent MyOnClick={read=IVMyOnClick,write=IVMyOnClick};//(5)
protected:
public: void __fastcall MyClick(int X,int Y)//(3)
{
if(IVMyOnClick)IVMyOnClick(this,X,Y) ;//(4)
}
void __fastcall WndProc(Messages::TMessage &Message)//(6)
{
Dispatch(&Message) ;//(7)
switch(Message.Msg)
{
case WM_LBUTTONDOWN://(8)
MyClick(Message.LParamLo,Message.LParamHi);//(9)
break;
}
}
__fastcall TMyRichEdit2(TComponent* Owner);
__published:
};
//---------------------------------------------------------------------------
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:
-
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TMyRichEdit2 *test=new TMyRichEdit2(this);
test->Parent=this;
test->SetBounds(10,10,100,100);
test->MyOnClick=testMyClick;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::testMyClick(TObject*Sender,int X,int Y)
{
ShowMessage((AnsiString)X+(AnsiString)" "+(AnsiString)Y);
}
//---------------------------------------------------------------------------
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.
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:
-
typedef void __fastcall (__closure *MyClickEvent)(TObject *Sender,TPoint Position);//(1)
//---------------------------------------------------------------------------
class PACKAGE TMyButton : public TButton
{
private:
MyClickEvent IVMyOnClick;
__published:
__property MyClickEvent MyOnClick={read=IVMyOnClick,write=IVMyOnClick};//(2)
protected:
public:
void __fastcall Click() //(3)
{
TPoint point;
GetCursorPos(&point);
if(IVMyOnClick)IVMyOnClick(this,point);
TButton::Click();//(4)
}
__fastcall TMyButton(TComponent*Owner):TButton(Owner){}
};
//---------------------------------------------------------------------------
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:
-
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TMyButton *test=new TMyButton(this);
test->Parent=this;
test->SetBounds(10,10,100,100);
test->OnClick=testMyClick;
test->MyOnClick=testMyOnClick;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::testMyClick(TObject*Sender)
{
ShowMessage("Click");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::testMyOnClick(TObject*Sender,TPoint point)//(1)
{
((TButton*)Sender)->Caption=(AnsiString)point.x+(AnsiString)" "+(AnsiString)point.y;
// Diese Werte beziehen sich auf den ganzen Bildschirm
}
//---------------------------------------------------------------------------
