Building components 01/de

From Free Pascal wiki

Einleitung

In diesem Tutorial geht es darum, eine komplett eigene Komponente zu erstellen und sie in Lazarus zu integrieren. Ihr werdet sehen, dass es gar nicht so schwer ist. Zunächst die Frage was ist eine Komponente eigentlich? Eine Komponente ist eine einfache Klasse, die meistens von einer vorherigen Klasse abgeleitet wird. Diese Klasse heißt auch „Abstracte Bases Klasse“, weil sie stellt ein Standardverhalten, welches für alle Komponenten gilt, zur Verfügung. Die meisten GUIs (engl. Graphical User Interface), haben sogar mehrere Bases Klassen,je nach Aufgabe. Es gibt in einer GUI verschiedene Bereiche z.b. eine sichtbare Komponente wie das TMemo, oder auch zunächst unsichtbare Komponenten wie einen Dialog oder die Timer Komponenten. Lazarus unterscheidet grob gesehen zwei Bereiche: Der Erste besteht aus Komponenten die vom Toolkit wie z.b. GTK2 zur Vergfügung gestellt wird. Diese Komponenten sind dann von TWinControl abgeleitet. Der ander Bereich besteht aus eigenen Komponenten, wie z.B. das Panel oder die OI-Komponente. Diese sind dann direkt von TCustomControl abgeleitet. TCustomControl spielt in diesem Kapitel ein große Rolle, denn wir wollen eine komplett eigene Komponente entwickeln.

Ziel dieses Kapitels

Die Komponente wird nur eine einfache Aufgabe erfüllen, aber sie zeigt wie in Lazarus üblicherweise vorgegangen wird. Das Ziel ist es, ein eigenes Panel zu entwickeln, welches im Gegensatz zum Standardpanel von Lazarus nicht nur eine Farbe hat, sondern ein Farbverlauf anzeigt, auf dem ein Text dargestellt werden kann. Wir werden Standardmethoden und Eigenschaften von TCustomControl verwenden. Außerdem werde ich zeigen, wie diese Komponente in Lazarus integriert werden kann und wie ein eigenes Icon zugewiesen werden kann. Auf einer Extraseite gebe ich einige Tipps, wie die Entwicklung beschleunigt werden kann. Es gibt eine ganze Reihe praktischer aber oft versteckter Funktionen im Code Editor (SynEdit) von Lazarus. Mein Lazarus: Version: 0.9.29 Beta, SVN-Revison: 22080M I386-linux-gtk2(beta)

Voraussetzungen

1.Eine funktionsfähige Lazarus-Umgebung, die jederzeit neu erstellt werden kann. Um das zu testen im Menü unter Werkzeuge\Lazarus kompilieren ausführen. Wenn das fehlerfrei durchgeführt wird, kann davon ausgegangen werden, dass es keine Probleme gibt.

2.Elementare Kenntnisse der Syntax von Object Pascal, außerdem sollte die Umgebung von Lazarus vertraut sein, z.B. wie kompiliere ich Programme und so weiter.

3.Erfahrung um Umgang mit der OOP sind unbedingt erforderlich.

Alle drei Punkte sind nicht Teil dieses Tutorials. Wenn trotzdem damit angefangen wird, kann es sehr schnell zu Problemen und zu Frust führen.

Sonstige Hinweise

Das Tutorial kann auf eigene Verantwortung durchgearbeitet werden. Für Schäden jeglicher Art bin ich nicht verantwortlich/haftbar.

Einige Hinweise und Tricks im Umgang mit Klassen

Hier habe ich einige Hinweise, Tricks und Tipps zusammengetragen, die den Umgang mit Klassen vereinfachen soll und die Entwicklung erheblich beschleunigen kann. Das geht meistens so, dass lästige Tipparbeit abgenommen wird.


Klassen schneller erzeugen

Um eine Klasse schneller zu erzeugen und nicht immer die gleichen Sachen schreiben zu müssen, gibt es die Code-Schablonen. Genutzt werden können sie mit STRG+J. Davor ein Wort eingeben, für Klassen z.B. classC und dann STRG+J und es wird der Standardheader einer Klasse erzeugt.


Typische Fehler vermeiden

Wenn eine Eigenschaft nicht im OI auftaucht, liegt es meistens daran, dass sie nicht installiert wurde. Das tritt meistens im Zusammenhang mit Klassen auf, beispielweise bei einer TStringList. Es empfiehlt sich auch bei jeder Eigenschaft das Schlüsselwort default zu nutzen. Beispiel:

property Test:String read fTest write fTest default „Hallo“;

Das ist allerdings nur für das Streaming relevant, daher müssen trotzdem alle Variablen im Konstruktor mit relevanten Werten belegt werden.


Methoden schneller erzeugen

Um Methoden schneller zu erzeugen, gibt es die Funktion Quelltext vervollständigen. Über eine Tastenkombination kann sie aufgerufen werden, bei mir ist das STRG+UMSCHALT+C


Die Übersicht in großen Units mit vielen Klassen und Datentypen behalten

Wer eine Unit mit sehr vielen Klassen und Datentypen hat, verliert schnell die Übersicht. Um das zu vermeiden, gibt es in Lazarus den Code-Explorer. Er analysiert den Code und erstellt einen Baum, um das Navigieren zu erleichtern.

Lösungen zu Problemen finden

Da Lazarus komplett Open Source ist, gibt es für sehr viele Probleme bereits Lösungen. Um in Erfahrung zu bringen, wie z.b. ein OI-Editor geschrieben wird, kann folgendermaßen vorgegangen werden: Wenn in der Komponenten-Leiste der Eintrag RTTI auftaucht, kann dort der OI aufs Form platziert werden. Jetzt einfach im Source-Code-Editor die rechte Maustaste drücken und den ersten Menüeintrag auswählen: Zur Deklaration springen. Jetzt könnt ihr euch z.b. die uses Klausel anschauen. Einige Namen dürften sofort auffallen. Eine andere Möglichkeit ist die Funktion in Dateien Suchen.

Los geht's

Wir erstellen uns eine neue Unit. Die Unit sollte so aussehen:

unit Unit1; 

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils; 

implementation

end.

jetzt werden wir diese Unit erweitern.

unit Unit1; 

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Controls, Graphics; 

type
  TMyPanel = class(TCustomControl)
  private

  protected

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Paint; override;
  published

  end; // TMyPanel 

implementation

end.

Zunächst habe ich zwei Units hinzugefügt. In der Unit Controls Steckt die Klasse TCustomControl, die wir verwenden wollen. In der Unit Graphics sind einige sinnvolle Konstanten definiert, wie z.b. clRed oder auch die Systemfarben. Jetzt habe ich eine erste Klasse erstellt, die direkt von TCustomControl abgeleitet ist. Ich habe der Klasse den Namen TMyPanel gegeben. Das T ist nur Geschmackssache. Es hat sich in der Benutzergemeinde eingebürgert, vor komplexeren eigenen Datentypen wie Klassen oder Record z.b. ein T zu schreiben. Meiner Meinung nach basiert das auf historischen Gründen. Früher gab es für DOS ein GUI mit dem Namen TVision. Dieses gibt es sogar noch heute und ist sogar in FPC enthalten. In dieser Klasse werden einige Standarmethoden überschrieben wie z.b. Create und Destroy. An dieser Stelle eine kleine Übersicht über weitere nützliche Methoden:

Create Wird beim Initialisieren der Klasse aufgerufen.

Destroy Wird beim Freigeben der Klasse aufgerufen.

Resize Wenn die Komponente vergrößert wird.

MouseDown Bei einem Mausklick.

MouseUp Wenn die Maustaste losgelassen wird.

MouseMove Wenn die Maus bewegt wird.

KeyDown Wenn eine Steuertaste, wie z.B. die Pfeiltasten, gedrückt wird.

KeyUp Wenn eine Taste losgelassen wird.

KeyPress Wenn ein Buchstabe gedrückt wird.

WMEraseBkgnd Ist für das Löschen des Hintergrunds gedacht.

EraseBackground Ist für das Löschen des Hintergrunds gedacht.

Je nach Aufgabe können die hier vorgestellten Methoden praktisch sein. Diese Methoden werden automatisch aufgerufen von der LCL, je nach Botschaft die eingeht. Es gibt sehr viele Botschaften. Für einige gibt es eine Methode, für andere nicht. Wenn eine Botschaft wie z.b. WMEraseBkgnd aufgerufen werden soll, muss die Unit Messages eingebunden werden. Wie hier gesehen werden kann, übernimmt die Klasse TCustomControl sehr viele Aufgaben für uns.

Hier eine weitere Übersicht, welche Aufgaben von der Klasse übernommen werden:

  1. User Events werden automatisch zur Verfügung gestellt.
  2. Wenn das Fenster neu gezeichnet wird, werden automatisch alle Komponenten neu gezeichnet.
  3. Wenn das darunter liegende Fenster vergrößert wird, werden alle Komponenten benachrichtigt.
  4. Bei Bedarf werden Scrollbalken angezeigt.
  5. Die Integration in die Lazarus IDE wird zur Verfügung gestellt.

Zur Erinnerung: Unser Sourcecode sieht im Moment so aus:

unit Unit1; 

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Controls, Graphics; 

type
  TMyPanel = class(TCustomControl)
  private

  protected

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Paint; override;
  published

  end; // TMyPanel 

implementation

end.

Für das Panel, das wir erstellen wollen, werden wir es so erweitern:

unit Unit1; 

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Controls, Graphics; 

type
  TMyPanel = class(TCustomControl)
  private
    fTextStyle:TTextStyle;
    fGradientDirection:TGradientDirection;
    fStartColor, fEndColor:TColor;

  protected

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Paint; override;
  published
    property GradientDirection:TGradientDirection read fGradientDirection write SetGradientDirection;
    property StartColor:TColor index 0 read GetColor write SetColor;
    property EndColor:TColor index 1 read GetColor write SetColor;

    property Alignment:TAlignment read fTextStyle.Alignment write SetAlignment;
    property Layout:TTextLayout read fTextStyle.Layout write SetLayout;
    property ShowPrefix:boolean read fTextStyle.ShowPrefix write SetShowPrefix;
    property Wordbreak:Boolean read fTextStyle.Wordbreak write SetBreak;
    property SystemFont:Boolean read fTextStyle.SystemFont write SetSystemFont;

  end; // TMyPanel 

implementation

end.

Ganz wichtig sind die Property's im published Bereich. Die hier eingetragenen Eigenschaften werden beim drauf klicken im Lazarus-Designer im OI (Objekt Inspektor) angezeigt. Die Setter Methoden sind insofern notwendig, da hier ein automatisches Neuzeichnen der Komponenten realisiert werden kann. mit fTextStyle hat es eine Besonderheit: Beim Datentyp TTextStyle handelt es sich um einen Record, dieses wird aber im OI nicht angezeigt. Auch wenn die RTTI hierfür Methoden und Möglichkeiten vorsieht, wird es einfach vom OI nicht unterstützt. Jetzt sind wir soweit und können die Methoden ausschreiben. Um das zu vereinfachen, und um Zeit zu sparen, gibt es im Kapitel mit den Hinweisen ein Trick.

function TMyPanel.GetColor(AIndex: integer): TColor;
begin
  case AIndex of
    0: result:=fStartColor;
    1: result:=fEndColor;
  end; // case
end; // TMyPanel.GetColor

procedure TMyPanel.SetAlignment(const AValue: TAlignment);
begin
  if fTextStyle.Alignment=AValue then exit;
  fTextStyle.Alignment:=AValue;
  Paint;
end; // TMyPanel.SetAlignment

procedure TMyPanel.SetBreak(const AValue: Boolean);
begin
  if fTextStyle.Wordbreak=AValue then exit;
  fTextStyle.Wordbreak:=AValue;
  Paint;
end; // TMyPanel.SetBreak

procedure TMyPanel.SetColor(AIndex: integer; const AValue: TColor);
begin
  case AIndex of
    0: fStartColor:=AValue;
    1: fEndColor:=AValue;
  end; // case
  Paint;
end; // TmyPanel.SetColor

procedure TMyPanel.SetGradientDirection(const AValue: TGradientDirection);
begin
  if fGradientDirection=AValue then exit;
  fGradientDirection:=AValue;
  Paint;
end; // TmyPanel.SetGradientDirection

procedure TMyPanel.SetLayout(const AValue: TTextLayout);
begin
  if fTextStyle.Layout=AValue then exit;
  fTextStyle.Layout:=AValue;
  Paint;
end; // TmyPanel.SetLayout

constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fGradientDirection:=gdVertical;
  fStartColor:=clBlue;
  fEndColor:=clYellow;
  fTextStyle.Layout:=tlCenter;
  fTextStyle.Alignment:=taCenter;
end; // TMyPanel.Create

destructor TMyPanel.Destroy;
begin
  inherited Destroy;
end; // TMyPanel.Destroy

procedure TMyPanel.Paint;
var
  r:TRect;
begin
  inherited Paint;
  r:=Rect(0,0,Width,Height);
  Canvas.GradientFill(r,StartColor, EndColor,GradientDirection);
  Canvas.TextRect(r,r.left,r.top,Caption,fTextStyle);
end; // TMyPanel.Paint

end.

Für einen ersten Test sollten wir diese Komponente dynamisch erzeugen, dass heißt manuell per Sourcecode. Dazu fügen wir die unit2 in unit1 ein. Mit folgendem Code kann nun die Komponente erzeugt werden, vorausgesetzt es befindet sich ein Panel mit dem Namen Panel1 auf dem From.

procedure TForm1.FormCreate(Sender: TObject);
begin
  myPanel:=TMyPanel.Create(Panel1);
  myPanel.Align:=alClient;
  myPanel.Parent:=Panel1;
  myPanel.Caption:='Dies ist ein einfacher Test von Heute';
    Button:=TButton.Create(myPanel);
    Button.Left:=10;
    Button.Top:=10;
    Button.Caption:='Klick Mich';
    Button.Parent:=myPanel;
end;

From Die Variable myPanel und Button sollte im private Bereich des Forms angelegt werden. Ein erster Test sollte folgendes Bild zeigen

MyCreateComponentImage01.png

Die IDE Integration

Im letzten Schritt geht es darum, die Komponente zu installieren. Da das jedoch nicht Teil dieses Tutorials ist, verweise ich einfach auf die Lazarus Dokumentation:

  1. Pakete erstellen und Komponente installieren Lazarus_Packages/de
  2. Icon für die Eigene Komponente erstellen Extending_the_IDE/de#Einer_neuen_Komponente_ein_Icon_für_die_Komponentenpalette_verpassen

Weitere Nützliche Links zum Thema

  1. Extending_the_IDE/de
  2. Streaming_components/de