Aspekte objektorientierter Programmierung mit Delphi - Von Pascal nach Delphi IFB-Veranstaltungen : 19.657 / 19.666A und weitere
←
→
Transkription von Seiteninhalten
Wenn Ihr Browser die Seite nicht korrekt rendert, bitte, lesen Sie den Inhalt der Seite unten
IFB-Veranstaltungen : 19.657 / 19.666A und weitere Von Pascal nach Delphi Aspekte objektorientierter Programmierung mit Delphi Ulrich Mayr, Trier
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Inhaltsverzeichnis OOP-Konzepte von Delphi verstehen und anwenden 1. Projekte mit vielen Komponenten gleichen Typs / Projekt Nim-Spiel ( Version 1 ) Mehrere Komponenten in einem Feld zusammenfassen Prozeduren zur Ereignisbehandlung variabel formulieren Ereignisse einer Komponente verstehen Ergänzende Betrachtungen Übungsphase : Projekt Lottozahlen1 2. Projekte mit vielen Komponenten gleichen Typs / Projekt Nimspiel (Version 2 ) Erzeugen von Komponenten zur Laufzeit Verwenden von Konstruktoren Ergänzende Betrachtungen Übungsphase : Projekt Lottozahlen2 3. Einige Delphi Grundbegriffe Klassen, Instanzen, Vererbung, VCL Instanzen sind Zeigervariable Zuweisungskompatibilität von Instanzen Eigenschaften (Properties) Ereignisse 4. Entwicklung neuer Komponenten für die VCL Die Komponente TLabel1 ( Definition / Registrierung) Die Sichtbarkeitsstufen private, protected, public, published Verwendung von TLabel1 5. Anhang : Botschaften U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 2
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi 1. Das Projekt Nim-Spiel (Version 1) Wir wollen eine einfache Version eines Nim-Spiels realisieren. Der Nutzer spielt gegen den Computer. Spielregeln : Es werden zufällig 10 bis 14 Hölzchen bereitgestellt. Abwechselnd zieht der Nutzer und der Computer ein oder zwei Hölzchen. Wer das letzte Hölzchen nehmen muss hat verloren. Übung : Laden Sie das Programm Nimm1.exe und spielen Sie einige Runden um mit dem Ablauf vertraut zu werden. Mit Hilfe der visuellen Programmierung lässt sich schnell ein Formular erstellen auf dem 14 schmale Rechtecke als Hölzchen und drei Knöpfe zu finden sind. unit Nim; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, ExtCtrls; const N = 14; type TForm1 = class(TForm) Label_Anzahl: TLabel; Label_Status: TLabel; Shape1 : TShape; .... Shape14: TShape; Button_Nehmen : TButton; Button_Neu : TButton; Button_Rechnerzug : TButton; public end; var Form1: TForm1; U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 3
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Das Projekt soll nun schrittweise ergänzt werden, sodass folgender Aufbau entsteht : unit Nim; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, ExtCtrls; const N = 14; type TForm1 = class(TForm) Label_Anzahl: TLabel; Label_Status: TLabel; Shape1 : TShape; .... Shape14: TShape; Button_Nehmen : TButton; Button_Neu : TButton; Button_Rechnerzug : TButton; procedure FormCreate(Sender: TObject); procedure Shape1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure Button_NehmenClick(Sender: TObject); procedure Button_NeuClick(Sender: TObject); procedure Button_RechnerzugClick(Sender: TObject); public Holz : array[1..N] of TShape; function Anzahl(Farbe:TColor):integer; { Anzahl der sichtbaren Hölzer mit bestimmter Farbe } end; var Form1: TForm1; Für die Verwaltung der 14 Rechtecke wäre es komfortabel, diese in einem Feld zu haben etwa Holz : array[1..n] of TShape. Das lässt sich auf folgendem Weg erreichen. Man deklariert im Formular ein solches Feld . Damit ist aber noch nicht festgelegt, dass mit dem Feld Holz die Formen U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 4
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Shape1 bis Shape14 gemeint sind. Deshalb müssen entsprechende Zuweisungen formuliert werden. Dies erfolgt am besten beim Erzeugen des Formulars also in der Methode Formcreate. procedure TForm1.FormCreate(Sender: TObject); var s : string; i : integer; begin { Es geht auch weniger elegant } for i:= 1 to N do begin s:= 'Shape'+IntToStr(i); Holz[i] := TShape(findcomponent(s)); end; end; Dabei wird für Shape1 bis Shape14 zuerst der Name des Shapes in s gespeichert. Danach sucht die Funktion findcomponent die Komponente mit dem Namen s und diese wird der Variablen Holz[1] .. Holz[14] zugeordnet. Der Funktionswert hat allerdings den Typ TObject. Deshalb muss noch eine Typumwandlung in den Typ TShape stattfinden. Holz[i] := TShape(findcomponent(s)); Interpretation : Suche die Komponente mit dem Namen s. Diese ist vom Typ TShape. Weise sie der Variablen Holz[i] zu. Mit dieser Maßnahme lassen sich die Hölzchen leicht verwalten. Zum Beispiel kann eine Funktion Anzahl formuliert werden, die zählt wieviele Hölzer einer vorgegebenen Farbe sichtbar sind. function TForm1.Anzahl(Farbe:TColor):integer; var i : integer; begin result:= 0; for i:= 1 to N do begin with Holz[i] do if visible and brush.color=Farbe then result:= result + 1; end; end; U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 5
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Das Verhalten der Hölzchen beim Anklicken muss noch festgelegt werden. Lästig wäre es für jedes der 14 Hölzchen eine eigene Ereignisbehandlungsprozedur für das Ereignis OnMouseDown zuzuweisen wie etwa für Shape1 im folgenden Beispiel. procedure TForm1.Shape1MouseDown(Sender: TObject; --- ); begin if Button_Nehmen.enabled then begin if Shape1.brush.color = clred then Shape1.brush.color:= clwhite else if Anzahl(clred)< 2 then Shape1.brush.color:= clred; Label_Anzahl.caption := IntToStr(Anzahl(clwhite)); end; end; Eine Vereinfachung lässt sich erreichen, indem man diese Prozedur so allgemein schreibt, dass sie für alle Hölzchen gültig ist und dann diese Prozedur jedem Hölzchen für die Behandlung des Mouse-Down-Ereignis im Objektinspektor zuweist. procedure TForm1.Shape1MouseDown(Sender: TObject; --- ); var Shape : TShape; begin if Button_Nehmen.enabled then begin Shape:= TShape(Sender); if Shape.brush.color = clred then Shape.brush.color:= clwhite else if Anzahl(clred)< 2 then Shape.brush.color:= clred; Label_Anzahl.caption := IntToStr(Anzahl(clwhite)); end; end; U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 6
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Mit dem Parameter Sender vom Typ TObject bekommt die Prozedur die Kom- ponente genannt, auf die geklickt wurde, also das angeklickte Hölzchen. Wenn wir dieses nun rot oder weiß färben wollen, können wir nicht einfach auf Sender.brush.color zugreifen, da Sender als eine Instanz vom Typ TObject diese Eigenschaft nicht besitzt. Wir müssen dem System erst mitteilen, dass Sender sogar den Nachfolgertyp TShape hat, bevor wir solche Eigenschaften ansprechen, die nicht in TObject zu finden sind. Statt Sender wird TShape(Sender) benutzt, was soviel bedeutet wie die Instanz Sender, die eigentlich vom Nachfolgertyp TShape ist. Man spricht von Typumwandlung. An dieser Stelle kann auf die Zuweisungs- kompatiblität von Instanzen verschiedener Typen eingegangen werden, die von TObject abgeleitet sind. Übung : Projekt Lottozahlen1 Es soll eine Lottoziehung simuliert werden. Laden Sie die Anwendung Lotto1.exe und machen Sie sich mit dem Vorgehen vertraut. Entwickeln Sie diese Anwendung. Sie können das halbfertige Projekt Lotto1.dpr vervollständigen oder das Projekt von Anfang an erzeugen. Die Felder zum Ankreuzen haben den Typ TCheckBox. Beim Anklicken erhalten sie ein Häkchen, das beim erneuten Anklicken wieder verschwindet. In der Eigenschaft checked ( boolean) wird festgehalten ob das Feld ausgewählt ist. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 7
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi 2. Das Projekt Nim-Spiel (Version 2) Erzeugung von Komponenten zur Laufzeit In der ersten Version des Nimspiels haben wir gesehen, wie man 14 Komponenten Shape1 bis Shape14 in ein Feld Holz[1] bis Holz[14] bringt und eine einzige Ereignisbehandlungsprozedur für alle 14 Komponenten formulieren kann. Ein Schönheits- fehler dabei ist das doppelte Auftreten von Variablen für die 14 Komponenten. Wir werden die 14 Hölzchen in einer 2. Version nicht durch Entwurf mit Hilfe des Objektinspektors erzeugen, sondern zur Laufzeit beim Start des Programms. In der Deklaration von TForm1 entfallen dann die Variablen Shape1 bis Shape14. Wenn wir das Feld Holz im Typ TForm1 deklarieren mit : ' Holz : array[1..14] of TShape ' sind damit nur 14 Zeigervariable festgelegt die den Wert nil haben. Bevor man auf das Feld Holz zugreifen kann, müssen die Instanzen erzeugt und die nötigen Eigenschaften wie Größe und Position gesetzt werden. Beim Visuellen Programmieren wird die Erzeugung der Instanzen automatisch durchgeführt, die Einstellung der Eigenschaften beim Entwurf über den Objektinspektor. Die Vereinbarungen für TForm1 haben dann folgende Gestalt : type TForm1 = class(TForm) Label_Anzahl: TLabel; Label_Status: TLabel; Shape1 :TShape; { kann nach Erstellen der Prozedur Shape1MouseDown gelöscht werden } Button_Nehmen: TButton; Button_Neu: TButton; U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 8
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Button_Rechnerzug: TButton; procedure FormCreate(Sender: TObject); procedure Button_NehmenClick(Sender: TObject); procedure Button_NeuClick(Sender: TObject); procedure Button_RechnerzugClick(Sender:TObject); procedure Shape1MouseDown(Sender: TObject; --- ); public Holz : array[1..N] of TShape; function Anzahl(Farbe:TColor):integer; end; Die Prozedur Shape1MouseDown bleibt erhalten. Bei ihrer Erstellung müssten wir eigentlich ohne die Hilfe des Objektinspektors auskommen und die richtige Parameterliste für die Behandlung des Ereignisses OnMouseDown von Hand eintragen. Mit einem Trick kann der Inspektor uns doch behilflich sein. Wir ziehen eine Figur Shape1 auf das Formular und schreiben für sie die Ereignisbehandlungsprozedur Shape1MouseDown wie bei Version 1. Anschließend löschen wir Shape1 wieder. Die Prozedur Shape1MouseDown bleibt aber erhalten. Diese Prozedur wird nun allen Hölzchen Holz[1] bis Holz[14] als Behandlungsroutine für das Ereignis OnMouseDown zugewiesen. Zuerst müssen die Instanzen Holz[1] bis Holz[14] erzeugt werden. Das geschieht am besten wenn das Formular erschaffen wird in der Prozedur FormCreate. procedure TForm1.FormCreate(Sender: TObject); var i : integer; begin for i:= 1 to N do begin Holz[i] := TShape.create(self); with Holz[i] do begin Parent := self; Top := 104+(i-1)*25; Height := 15; Left := 30; Width := 120; U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 9
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi OnMouseDown:= Shape1MouseDown; end; end; randomize; end; Jede Figur Holz[i] wird zuerst mit dem Aufruf des Konstruktors Create der Klasse TShape erzeugt. Der Parameter self gibt hier an, dass das Formular Besitzer der Figur Holz[i] ist. In dieser Anweisung steckt das Reservieren von Speicherplatz ( Prozeduraufruf new ) und das anschließende Initialisieren einiger Eigenschaften. Nach dem Erzeugen müssen die Eigenschaften von Holz[i] angepasst werden. Das sind vor allem Position im Formular und Ausdehnung. Dann wird dem Ereignis OnMouseDown die bereits erstellte Prozedur Shape1MouseDown zugeordnet. Somit bekommen alle Hölzer das gewünschte Verhalten. Shape1 Holz[1] Holz[14] ------------------------ ------------------------ ------------------------ OnMouseDown OnMouseDown OnMouseDown procedure TForm1.Shape1MouseDown (Sender: TObject; - ); var Shape : TShape; begin if Button_Nehmen.enabled then begin Shape:= TShape(Sender); if Shape.brush.color = clred then Shape.brush.color:= clwhite else if Anzahl(clred)< 2 then Shape.brush.color:= clred; Label_Anzahl.caption := IntToStr(Anzahl(clwhite)); end; end; U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 10
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Anmerkungen : 1) Wird eine Komponente innerhalb des Entwurfsmodus gelöscht, so verbleiben Methoden, die für die Ereignisbehandlung dieser Komponenten geschrieben wurden, im Quelltext. Begründung : Die Methoden können auch von anderen Komponenten benutzt werden. Soll die Methode gelöscht werden, so löscht man den Anweisungsteil und den Deklarationsteil der Methode. Bei der nächsten Übersetzung erkennt das System, dass die Methode überflüssig ist und löscht sie und alle Zeuger, die von Komponenten auf die Methode zeigen. 2) Die Verbindung von einer Komponente zu einer Ereignisbehandlungsprozedur ist nur lose über einen Zeiger geregelt. Wir hätten statt für Shape1 auch eine Methode TForm1.Form1MouseDown(sender:TOject; --- ) für das Formular schreiben können mit demselben Inhalt wie TForm1.Shape1MouseDown und diese Methode allen Shapes Holz[1] bis Holz[14] als Ereignis OnMouseDown zuweisen. Allerdings hätten wir dann Form1.OnMouseDown löschen müssen. Da diese Methode nicht für das Klicken auf das Formular geeignet ist. 3) Bei einer Zuweisung wie Holz[i].OnMOuseDown := Shape1MouseDown; muss die rechte Seite eine Methode einer Klasse sein deren Parameterliste festgelegt ist. Bei OnMouseDown ist eine Parameterliste (Sender: TObject; --- ) erforderlich. Eine Prozedur mit dieser Parameterliste, die nicht zu einer Klassendefinition gehört ist ungeeignet. Begründung : Methoden sind verschieden von gewöhnlichen Prozeduren. Zwar ist die Deklaration einer Methode und die einer gewöhnlichen Prozedur gleich. Sie unterscheiden sich aber beim Aufruf. Beim Aufruf einer Methode z.B. Memo1.clear wird als Parameter die Variable Memo1 mit übergeben. Übung : Projekt Lottozahlen2 Ändern Sie die Anwendung Lottozahlen1 so ab, dass die Ankreuzfelder dynamisch zur Laufzeit erzeugt werden. Speichern Sie die Projekt- und die Unit des Formulars in einem neuen Ordner. So verlieren Sie nicht Version1. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 11
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi 3. Erläuterung einiger Konzepte von Delphi ( Object Pascal) Klassen, Objekte (Instanzen), Vererbung, VCL Delphi stellt eine Fülle von Komponenten zur Gestaltung der grafischen Benutzeroberfläche von Anwendungen bereit. Die zugehörigen Datentypen ( Klassen ) sind in der Visual Component Library festgelegt. Die VCL ist streng objektorientiert aufgebaut. Variable solcher Datentypen werden Objekte oder Klasseninstanzen genannt. Instanzen einer Klasse enthalten Attribute ( wie Records ) und Methoden zur Bearbeitung der Attribute. Beispiel : VAR Shape1 : TShape; { Shape1 ist eine Instanz der Klasse TShape } Die Definition der Klasse TShape im Quellcode ( unit ExtCtrls ) : TShapeType = (stRectangle, stSquare, stRoundRect, stRoundSquare, stEllipse, stCircle); TShape = class(TGraphicControl) private FPen : TPen; Vorgänger von TShape FBrush : TBrush; FShape : TShapeType; procedure SetBrush(Value: TBrush); procedure SetPen(Value: TPen); procedure SetShape(Value: TShapeType); protected procedure Paint; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published procedure StyleChanged(Sender: TObject); property Align; Erweiterungen, property Anchors; die zu der property Brush: TBrush read FBrush write SetBrush; Funktionalität von property DragCursor; TGraphicControl property DragKind; hinzukommen property DragMode; property Enabled; property Constraints; property ParentShowHint; property Pen: TPen read FPen write SetPen; property Shape: TShapeType read FShape write SetShape default stRectangle; property ShowHint; property Visible; property OnContextPopup; --------------------------------- property OnStartDrag; end; U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 12
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Vererbung Die Klasse TShape ist abgeleitet von der Klasse TGraphicControl. Eine Instanz von TShape erbt die gesamte Funktionalität, die zur Klasse TGraphicControl gehört. Darüber hinaus kommt weitere Funktionalität ( Methoden, Eigenschaften,...) hinzu. Wird eine Methode (beispielsweise paint), die bereits existiert hat, nochmals definiert (Überschrieben), so gilt für eine Instanz der Nachfolgerklasse nur die neue Definition. Durch Vererbung entstehen Klassen, die immer komplexer werden. Der Urahn aller Klassen ist TObject. Die Funktionalität von TObject ist daher in allen anderen Klassen enthalten. TShape Der Programmentwickler kann die VCL durch Ableiten neuer Klassen erweitern. So gibt es für die verschiedensten Zwecke maßgerecht veränderte Komponenten. Wir behandeln später ein solches Beispiel, indem wir die Klasse TLabel zu TLabel1 erweitern, Darüberhinaus kann der algorithmische Kern einer Anwendung mit Hilfe der VCL objektorientiert gestaltet werden ( objektorientierte Modellierung ). Man formuliert die eigenen Variablen als Instanzen von Klassen, die man von TObject direkt ableitet. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 13
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Instanzen sind Zeigervariable Holz[1] StRectangle Shape : visible : true Shape1 Height : 150 ..................... ..................... Anmerkungen : 1. Bei Zuweisungen wie Holz[1] := Shape1 wird lediglich der Zeiger aus Shape1 in die Zeigervariable Holz[i] gebracht. Eine Duplizierung aller Daten der Instanz Shape1 findet nicht statt. Eine Veränderung in den Eigenschaften von Holz[i] gilt auch für Shape1. 2. Die Schreibweise beim Zugriff auf Eigenschaften einer Instanz ist gegenüber dem klassischen Pascal geändert. Die Derefenzierung entfällt. Shape1.visible := true statt Shape1^.visible:= true 3. Instanzen müssen erzeugt werden bevor man auf eine Eigenschaft zugreifen kann. Beim visuellen Entwerfen eines Formulars geschieht das automatisch. Instanzen, die nicht über den Objektinspektor, sondern allein über den Quellcode in der Anwendung auftreten, müssen erzeugt werden. Dazu dienen besondere Methoden, die Konstruktoren. Der Konstruktor Create ist bereits in TObject enthalten und wird in den Nachfolgerklassen überschrieben. Daher wechseln die Parameterlisten je nach Klasse. Holz[I] := TShape.Create(Form1); U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 14
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Zuweisungskompatibilität bei Instanzen A := B ist erlaubt, wenn die Klasse von B ein Nachfolger der Klasse von A ist. Beispiel : var Objekt : TObject; Komponente : TComponent; Shape : TShape; Erlaubt ist Objekt := Komponente; Objekt := Shape; Komponente:= Shape; Nicht erlaubt ist z.B. TShape Shape := Komponente; Komponente:= Objekt; Anmerkungen : 1) Diese Regelung ist möglich, weil jeder Quellcode, der für eine Instanz der Vorgängerklasse geschrieben wurde auch für eine Instanz der Nachfolgerklasse sinnvoll ist, da die gesammte Funktionalität vererbt wird. 2) Da TObject keinen Vorgänger hat und alle Klassen von TObject abstammen, kann einer Instanz A der Klasse TObject jede Instanz B einer beliebigen Klasse zugeordnet werden. Bei der Zuweisung bleiben alle Eigenschaften des erweiterten Typs erhalten. Angesprochen können diese verborgenen Eigenschaften dadurch, dass man eine Typumwandlung durchführt. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 15
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Typumwandlung Hinter einer Instanz kann sich durch Wertzuweisung auch eine Instanz einer Nachfolgerklasse verbergen (s. o.). In Ereignisbehandlungsprozeduren können sogar Instanzen von unterschiedlichem Typ als Sender auftreten. Dann kann in der Prozedur der Typ von Sender abgefragt und je nach Typ unterschiedlich reagiert werden. Z.B. : if sender is TShape then TShape(Sender).brush.color:= clred ; Der Typ der Instanz Sender wird abgefragt. Ist Sender eine Instanz der Klasse TShape, so kann auf die Farbe zugegriffen werden. TShape(sender) bedeutet anschaulich übersetzt : Die Instanz Sender, hinter der sich sogar eine Instanz des Nachfolgertyps TShape verbirgt. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 16
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Eigenschaften ( Properties ) Eigenschaften sind besondere Attribute einer Klasse - auf die durch einen speziellen Schreib-, Lesemechanismus zugegriffen werden kann - denen man im Objektinspektor Werte zuweisen kann ( bei registrierten Komponenten ) Beispiel : var Shape1 : TShape; Die Zuweisung Shape1.Shape := StCircle; bewirkt, dass das Attribut Shape von Shape1 auf StCircle gesetzt wird. Zusätzlich wird die Figur Shape1 am Bildschirm als Kreis gezeichnet. Das Projekt Eigenschaft.dpr verdeutlicht dies. procedure TForm1.Button1Click(Sender: TObject); begin if Shape1.Shape=StCircle then Shape1.Shape:=STRectangle else Shape1.Shape:= succ(Shape1.Shape); end; U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 17
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Die Definition der Klasse TShape im Quellcode ( unit ExtCtrls ) : TShapeType = (stRectangle, stSquare, stRoundRect, stRoundSquare, stEllipse, stCircle); TShape = class(TGraphicControl) private FPen : TPen; FBrush : TBrush; FShape : TShapeType; procedure SetBrush(Value: TBrush); procedure SetPen(Value: TPen); procedure SetShape(Value: TShapeType); protected procedure Paint; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published procedure StyleChanged(Sender: TObject); property Align; property Anchors; property Brush: TBrush read FBrush write SetBrush; --------------------------------- property Pen: TPen read FPen write SetPen; property Shape: TShapeType read FShape write SetShape default stRectangle; property ShowHint; property Visible; property OnContextPopup; --------------------------------- property OnStartDrag; end; In der Zeile FShape : TShapeType; { TShapeType = (stRectangle, --- , stEllipse, stCircle); } ist das Attribut FShape deklariert, in dem die Form der Figur festgehalten wird. FShape ist im Private-Abschnitt vereinbart. Das bedeutet, dass FShape bei der Verwendung der Unit ExtCtrls nicht direkt angesprochen werden kann. In der Zeile property Shape: TShapeType read FShape write SetShape default stRectangle; wird nun vereinbart, wie der Nutzer auf die Form der Figur, die Eigenschaft Shape zugreifen kann. Beim lesenden Zugriff auf Shape soll der Inhalt des Attributs FShape genommen werden. Beim schreibenden Zugriff auf Shape soll die Methode SetShape benutzt werden. Als Anfangswert (default) für FShape soll stRectangle genommen werden. Die Methode SetShape ist im Implementationsteil der Unit ExtCtrls folgendermaßen deklariert. : U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 18
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi procedure TShape.SetShape(Value: TShapeType); begin if FShape Value then begin FShape ändern FShape := Value; Invalidate; end; Veranlasst, dass die Figur neu end; gezeichnet wird Sichtbarkeitsattribute bei der Vereinbarung von Klassen Bei der Definition eines Klassentyps in einer unit1 können die Attribute und Methoden der Klasse in 4 Sichtbarkeitsstufen eingeteilt werden. Diese Sichtbarkeitsattribute haben erst dann eine Bedeutung, wenn man den Klassentyp in eine andere Unit ( z.B.unit2 ) importiert und dort verwenden will. private - die hier vereinbarten Attribute und Methoden dürfen in unit2 nicht verwendet werden. Sie sind in unit2 unbekannt. protected - die hier vereinbarten Attribute und Methoden dürfen in unit2 nur beim Ableiten einer Nachfolgerklasse verwendet werden. ( Der Entwickler neuer Komponenten kann sie verwenden, der Anwender von unit1 nicht ) public - die hier vereinbarten Attribute und Methoden dürfen in Unit2 ohne Einschränkung verwendet werden. Dieselbe Verfügbarkeit erhält man, wenn keine Sichtbarkeitsstufe genannt ist. published - Wird bei der Definition neuer Komponenten verwendet, die in die Komponentenpalette aufgenommen werden sollen. Die Bedeutung ist wie bei public. Aber zusätzlich werden die hier genannten Eigenschaften, falls sie nicht zu komplex sind, im Objektinspektor angezeigt. Sie können auch im Objektinspektor zur Entwurfszeit gesetzt werden. Bemerkungen : Bei allen Projekten, in denen nicht daran gedacht ist, die Unit1 des Hauptformulars in einer anderen Unit zu verwenden, spielen die Sichtbarkeitsstufen bei den Klassendefinitionen in Unit1 keine Rolle. Man kann sie unter private oder public eintragen. Wichtig werden die Sicherheitsattribute wenn man neue Komponenten entwickeln will bzw. objektorintiertes Modellieren bei der Erstellung eigener Anwendungen verwenden will. Eigenschaften der Stufe protected sind für den Anwender nicht sichtbar. Der Entwickler kann diese aber in einem Nachfolger als public bereitstellen. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 19
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Ereignisse ( Events ) Ereignisse sind besondere Eigenschaften einer Klasse Es handelt sich dabei um Zeiger auf Ereignisbehandlungsmethoden. Beispiel : In TControl ist das Ereignis OnMouseDown als protected deklariert und kann in allen Nachfolgern veröffentlicht werden ( published ). Ein Auszug aus dem Delphi-Quelltext : type TMouseEvent = procedure (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X,Y: Integer) of object; TControl = class(TComponent) .... private FOnMouseDown : TMouseEvent; .... protected property OnMouseDown : TMouseEvent read FOnMouseDown write FOnMouseDown; .... end; In den Nachfolgern von TControl ist OnMouseDown veröffentlicht, d.h. in die Stufe published übernommen worden. Die Eigenschaft kann daher im Objektinspektor gesetzt werden. Das bedeutet, dass in FOnMouseDown ein Zeiger (Adresse) auf eine Methode gespeichert wird, deren Parameterliste im Typ TMouseEvent festgelegt ist : (Sender:TObject; Button:TMouseButton; Shift:TShiftState; X,Y:Integer) Der Hinweis 'of object' besagt, dass die zugewiesene Prozedur Methode eines Objekts sein muss. Die Zuweisung einer isolierten Prozedur mit derselben Parameterliste ist nicht erlaubt, da sich Aufrufe von Klassenmethoden und Prozeduren unterscheiden. Beim Aufruf einer Methode wird zusätzlich zu den Parametern der Zeiger self auf die aktuelle Klasseninstanz mit übergeben. Die Zuweisung für ein veröffentlichtes Ereignis wie z.B. OnMouseDown ist bequem über den Objektinspektor zu bewerkstelligen. Wählt man das Ereignis OnMouseDown an, so erstellt der Editor bereits eine Lehrprozedur (in TForm1) mit der passenden Parameterliste und speichert einen Zeiger auf diese Methode in FOnMouseDown ab. Der Anwender muss nur noch den passenden Qellcode eintragen. Sind bereits Methoden vom Typ TMouseEvent vorhanden, so bietet der Objektinspektor, diese zur Auswahl für FOnMouseDown an. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 20
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Drückt der Nutzer des Programms die linke Maustaste wenn der Cursor auf der Komponente steht, für die das OnMouseDown festgelegt wurde, so wird schließlich die Methode ausgeführt, auf die FOnMouseDown zeigt. Will man das Verhalten beim Ereignis OnMouseDown zur Laufzeit ändern, so kann man OnMouseDown eine andere Methode vom Typ TMouseEvent zuordnen. Diese Methode muss natürlich bereits (auf Vorrat) vereinbart und implementiert sein. Ebenso muss beim Erzeugen einer Komponente zur Laufzeit vorgegangen werden. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 21
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi 4. Entwicklung neuer Komponenten für die VCL Beispiel : Eine Überschrift (TLabel) soll so mit einem Speicherplatz Zahl kombiniert werden, dass bei Änderung der Zahl, diese auch sofort angezeigt wird. Die neue Komponente TLabel1 wird durch Ableitung aus TLabel gewonnen. Delphi bietet im Menue: Datei -> neu -> Komponente das Gerüst einer Unit in der die Eigenschaften der neuen Komponente festgelegt werden. unit Label1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TLabel1 = class(TLabel) private FZahl : integer; procedure setZahl(x:integer); published Property Zahl:integer read FZahl write setZahl; end; procedure Register; implementation procedure TLabel1.setZahl(x:integer); begin FZahl := x; Caption := IntToStr(x); TLabel1 wird in die Palette end; 'Beispiele' als Komponente aufgenommen procedure Register; begin RegisterComponents('Beispiele', [TLabel1]); end; end. TLabel1 besitzt die Eigenschaft Zahl mit FZahl als Speicherplatz und der Methode setZahl zum Ändern der Eigenschaft. Durch das Registrieren wird TLabel1 in die Komponentenpalette übernommen und kann in Anwendungen benutzt werden. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 22
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Verwendung der Komponente TLabel1 in einem Programm Beim Drücken von - Button_Vor wird die Zahl von Label_Zahl im 1 erhöht - Button_Null wird Zahl von Label_Zahl auf 0 gesetzt. unit Zaehlen; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Label1; type TForm1 = class(TForm) Button_Vor : TButton; Button_Null : TButton; Label_Zahl : TLabel1; procedure Button_VorClick(Sender: TObject); procedure Button_NullClick(Sender: TObject); end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Button_VorClick(Sender: TObject); begin with Label_Zahl do begin Label_Zahl.setZahl ( FZahl + 1); zahl:= zahl+1; if Zahl > 20 then Zahl:=0; end; end; procedure TForm1.Button_NullClick(Sender: TObject); begin Label_Zahl.zahl:=0; end; end. Label_Zahl.setZahl (0); Anmerkung : In Button_VorClick und Button_NullClick dürfen Label_Zahl.FZahl und Label_Zahl.setZahl selbst nicht benutzt werden. Da sie in der Vereinbarung für TLabel1 ( unit Label1 ) im private-Abschnitt deklariert wurden. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 23
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi 5. Anhang : Botschaften ( Messages ) Der Begriff Ereignis im vorherigen Kapitel wird in mehrfacher Bedeutung gebraucht. Einmal ist damit eine besondere Eigenschaft gemeint, die einen Zeiger auf eine Methode beinhaltet. Außerdem wird als Ereignis ein Vorfall (z.B. Eingaben des Benutzers durch Tastatur, Maus) bezeichnet, den das Betriebssystem Windows wahrnimmt und daraufhin die betreffende Anwendung informiert, damit diese adäquat reagieren kann. Zur Information erhält die betreffende Anwendung ( Instanz Application vom Typ TApplication ) eine Botschaft (Message) in ihre Botschaftsschlange (Messagequeue) gelegt. Die Botschaft ist ein Datensatz, in dem alle nötigen Informationen über das Ereignis im Sinne von Geschehnis zusammengefasst sind. Der Instanz Application obliegt es nun, an erster Stelle für die Entnahme der eingetroffenen Botschaft und ihre Beantwortung, durch Auslösen einer entsprechenden Maßnahme (Botschaftsbehandlungsmethode) zu sorgen. Der Mechanismus zur Reaktion auf Ereignisse ist kompliziert. Zum einen sind dabei viele Instanzen beteiligt von Windows über Application, bis hin zu den einzelnen Komponenten. Außerdem nimmt Delphi dem Anwender wie auch dem Programmierer die Arbeit weitgehend ab, indem es die Mechanismen in den Instanzen Application und den Komponenten verbirgt. Wenn aber in einigen Fällen ein Eingreifen in den Botschaftsverteilungs- und -beantwortungsmechanismus nötig ist, fällt eine zutreffende Diagnose und die Behebung von Fehlern um so schwerer. Im Folgenden soll der Weg der Botschaftsbehandlung skizziert werden. 1. Wir betrachten zuerst wie die Instanz Application in einer Schleife (Messageloop) die Botschaften bearbeitet. Application.Run; { Aufruf der Messageloop in der Projektdatei } Die folgenden Fragmente aus dem Quellcode des Delphisystems folgen dem Weg den Vearbeitung der Botschaften. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 24
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi procedure TApplication.Run; begin ... repeat if not ProcessMessage then Idle; { Messageloop } until Terminated; .... end; Der Aufruf der Funktion TApplication.ProcessMessage versucht eine Botschaft aus der Botschaftsschlange zu holen und bei Erfolg wird die weitere Verarbeitung veranlasst. function TApplication.ProcessMessage: Boolean; var Handled: boolean; Msg : TMsg; begin Result := false; if PeekMessage( Msg, 0, 0, 0, PM_Remove) then begin .... Handled:= false; if Assigned(FOnMessage) then FOnMessage(Msg,Handled); if not Handled and .... then begin .... TranslateMessage(Msg); DispatchMessga(Msg); end; .... end; end; PeekMessage versucht eine Botschaft Msg der Botschaftsschlange zu entnehmen. Wenn das gelungen ist wird Msg der Bearbeitung zugeführt. Wurde für das Ereignis Application.OnMessage eine Methode vereinbart, Abfrage mit Assigned(FOnMessage) , so wird diese zuerst durchgeführt, FOnMessage(Msg,Handled). In dieser Prozedur kann durch Setzen der Variablen Handled festgelegt werden, ob die Botschaft noch weiter verarbeitet werden soll. TranslateMessage(Msg) übersetzt Msg für die weitere Verarbeitung in eine andere Form (virtuelle Tastencodes). DispatchMessage(Msg) sendet die Botschaft an ihren eigentlichen Bestimmungsort ein Fenster (Formular, Edit-Feld usw.). Genauer : DispatchMessage ruft die Fensterprozedur MainWndProc des betroffenen Fensters auf und übergibt Msg als Parameter. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 25
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Der weitere Weg der Verarbeitung liegt in der Verantwortung des speziellen Fensters, das die Botschaft erhält. Beispiel: Bevor wir die Verarbeitung von Msg durch die Prozeduren des betreffenden Fensters weiter verfolgen, betrachten wir, wie man mit dem Vereinbaren einer Methode für das Ereignis Application.OnMessage eine Vorverarbeitung aller Botschaften erreichen kann. Alle Botschaften für Application werden in einem Memo-Feld anzeigt. unit event1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type TForm1 = class(TForm) Memo1:TMemo; Shape1:TShape; Edit1:TEdit; Timer1:TTimer; Label1: TLabel; Label2: TLabel; Label3: TLabel; procedure FormCreate(Sender: TObject); procedure Shape1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private procedure ShowMessage(var Msg: TMsg; var Handled:Boolean); end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.ShowMessage(var Msg:TMsg;var Handled:Boolean); const n : Integer =0; begin inc(n); Memo1.Lines.Add(IntToStr(n)+' : '+IntToStr(Msg.Message)); end; procedure TForm1.FormCreate(Sender: TObject); begin Verhalten OnMessage Application.Title := 'Ereignisse'; wird festgelegt (Showmessage) Application.OnMessage := ShowMessage; end; procedure TForm1.Shape1MouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin Shape1.brush.Color := clred; end; end. 2. Wir verfolgen nun den weiteren Weg der Botschaftsbearbeitung durch die Methoden des betroffenen Fensters. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 26
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Aus Application.ProcessMessage wurde die Prozedur MainWndProc des betreffenden Fensters übergeben. procedure TWincontrol.MainWndProc(var Message: TMessage); begin begin try try WndProc(Message); finally .... end; except Application.HandleException(self); end; end; MainWndProc selbst beantwortet die Botschaft nicht sondern kümmert sich u.a. um Fehlerbehandlung (Exceptions) und reicht Message an WndProc weiter. procedure TWincontrol.WndProc(var Message: TMessage); begin begin { spezifische Vorbehandlung der Botschaften je nach aktuellem Steuerelement } .... inherited WndProc(Message); end; Die Kette der Aufrufe inherited WndProc(Message) endet bei TControl.WndProc. Dort wird Dispatch(Message) aufgerufen, ererbt von TObject. procedure TControl.WndProc(var Message: TMessage); begin ... case Message.Msg of WM_Mouse_Move: Application.HintMouseMessage(self,Message) .... Dispatch(Message); end; Dispatch(Message) versucht nun die zu Message passende Ereignisbehandlungs- routine (Message-Handler) für das aktuelle Steuerelement aufzurufen. Ist keine U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 27
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Ereignisbehandlungsroutine definiert worden, so wird die Methode DefaultHandler von TObject aufgerufen. TObject.DefaultHandler(var Message); virtual veranlaßt den größten Teil des Standardverhaltens von Fenstern unter Windows. Die lange Kette der Aufrufe zur Botschaftsverabeitung : Application.Run { Messageloop } Application.ProcessMessage PeekMessage(Msg) FOnMessage(Msg,Handled) Translate(Msg) DispatchMessage(Msg) TWinControl.MainWndProc(Msg) TaktuellControl.WndProc(Msg) virtuell ......... TControl.WndProc(Msg) Dispatch(Msg) FOnMouseDown oder ähnliches DefaultHandler(Msg) virtuell ............... Es gibt einige Möglichkeiten für den Programmierer auf die Botschaftsverarbeitung Einfluss zu nehmen. 1. Festlegen von Prozeduren für Ereignisse der einzelnen Steuerelemente. Im Objektinspektor oder dynamisch (s. obiges Beispiel). 2. Überschreiben von WndProc. Z.B. herausfiltern von bestimmten Botschaften und deren Behandlung für eine bestimmte Klasse von Steuerelementen. 3. Vorverarbeitung aller Botschaften bevor sie zu den Steuerelementen gelangen durch die Zuweidung einer Methode an das Ereignis Application.OnMessage. 4. Überschreiben von DefaultHandler. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 28
IFB-Veranstaltung : Objektorientiertes Programmieren mit Delphi Bemerkung : Für eine häufig auftretende Situation sollte man durch gezieltes Eingreifen in die Botschaftsverarbeitung Abhilfe schaffen können. Problem : Hat man z.B. für einen Knopf dem Ereignis OnClick eine umfangreiche, zeitraubende Berechnung zugewiesen, deren Ergebnisse in einem Memofeld kontinuierlich ausgegeben werden sollen, so werden die Ergebnisse erst nach der Beendigung aller Rechnungen aktualisiert. Ursache : Die Botschaften, die bei den Berechnungen produziert werden, und die Ausgabe der Ergebnisse veranlassen sollen, geraten in die Botschaftsschlange Application holt sie erst nach Beantwortung der aktuellen Botschaft zur Verabeitung ab. Abhilfe : In der zeitaufwendigen Prozedur kann selbst Application.ProcessMessage aufgerufen werden. Die Beantwortung der aktuellen Botschaft wird dann unterbrochen und Application kann die nächste Botschaft aus der Schlange vorziehen und beantworten (z.B. eine Ausgabe am Bildschirm). Danach wird die Beantwortung der alten Botschaft fortgesetzt. U. Mayr : Aspekte objektorientierter Programmierung mit Delphi S. 29
Sie können auch lesen