Aspekte objektorientierter Programmierung mit Delphi - Von Pascal nach Delphi IFB-Veranstaltungen : 19.657 / 19.666A und weitere

Die Seite wird erstellt Runa Schulz
 
WEITER LESEN
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