Freitag, 25. Juli 2008

entwickler.com Magazine Konferenzen Akademie Entwickler-Forum Jobbörse Bücher
Software & Support Verlag




April 2006
aus Der Entwickler Ausgabe: 3.2004
Bunte Hinweise
Erstellen einer eigenen Tooltip-Komponente mit Delphi
von Dennis Powering

Wer kennt Sie nicht, die kleinen Hinweise, die Erscheinen wenn man mit der Maus über ein Bildschirmelement fährt? Solche Tooltips - auch Hints genannt - geben dem Anwender kleine Hilfestellungen bei der Benutzung einer Software ohne gleich in der Hilfedatei nachschlagen zu müssen. Erfahren Sie in diesem Artikel wie Sie diesen Hinweisen ein eigenes, auf die Benutzeroberfläche Ihrer Software abgestimmtes, Design verpassen.


Sicherlich könnte man nun meinen, dass ein neues Aussehen der Tooltips gar nicht nötig wäre und für den Erfolg der Software andere Kriterien wichtiger sind. Man kann es aber auch als das berühmte i-Tüpfelchen ansehen, welches beim Benutzer Aufmerksamkeit erregt und der Software ein einheitliches Erscheinungsbild gibt. Im folgendem soll an einem kleinen Beispiel gezeigt werden, wie eine Komponente in Delphi erstellt wird, welche die vorhandenen Tooltips ersetzt. Um ein wenig Farbe hineinzubringen wird das Hint-Fenster mit einem bunten Seitenstreifen versehen (Abb. 1). Die Komponente wird dabei in zwei Schritten erstellt:

  • Erzeugen einer Komponente TCustomHint, welche den Benutzer die Farben und Gestaltung des Hint-Fensters festlegen lässt und
  • Erstellen einer neuen Klasse TCustomHintWindow, die den eigentlichen Programmcode zur Anzeige des Fensters enthält.


Abb. 1 So soll später der fertige Tooltip aussehen

Erzeugen einer neuen Komponente
Zunächst wird in Delphi über den Menüpunkt Datei - Neu eine neue Komponente mit dem Vorfahrtyp TComponent erzeugt, als Klassennamen wählen wir TCustomHint. Nachdem das Grundgerüst für die Komponente erzeugt wurde, fügen wir in die uses-Klausel alle Units ein, die wir im Laufe dieses Beispiels benötigen. Das verhindert spätere Fehlermeldungen beim Kompilieren.

uses
Windows, SysUtils, Classes, Controls, Graphics, Forms, Types, Dialogs;

TCustomHint bekommt zunächst nur eine einzige Eigenschaft, nämlich ob die Komponente aktiviert oder deaktiviert ist, sowie einen Konstruktor und einen Destruktor. Das Setzen der Eigenschaft FActive erfolgt über eine eigene Prozedur, in die später noch der Quellcode zum Anzeigen des Hint-Fensters eingefügt wird (Listing 1).



Listing 1

TCustomHint = class(TComponent)
private
FActive : Boolean;
procedure SetActive(const Value: Boolean);
public
constructor Create(Aowner: Tcomponent); override;
destructor Destroy; override;
published
property Active : Boolean read FActive write SetActive;
end;

implementation

constructor TCustomHint.Create(AOwner: TComponent);
begin
inherited;
FActive := False;
end;

destructor TCustomHint.Destroy;
begin
inherited;
end;

procedure TCustomHint.SetActive(const Value: Boolean);
begin
if FActive = Value then Exit;
FActive := Value;
end;

Die Zeile property Active : Boolean read FActive write SetActive bedeutet, dass im Objekt Inspektor die Eigenschaft Active angezeigt werden soll. Beim Auslesen (read) kann dabei direkt auf die Variable FActive zugegriffen werden, beim Zuweisen eines Wertes (write) wird der Umweg über die Prozedur SetActive gegangen. Im Konstruktor werden Grundeinstellungen durchgeführt, wie hier das initialisieren der Variablen FActive mit dem Wert False. Den Destruktor benötigen wir später, um Speicher freizugeben.


Nun ist es an der Zeit sich zu überlegen, welche Eigenschaften die Komponente noch bekommen soll. Der Benutzer soll bei der Gestaltung des Tooltips möglichst viele Freiheiten haben, deshalb geben wir ihm die Möglichkeit, die Hintergrundfarbe, die Farbe des kleinen Seitenstreifens, den Zeichensatz für die Textausgabe, sowie eine Maximalbreite für das Hint Fenster anzugeben. TCustomHint wird also um folgende Eigenschaften und Methoden ergänzt:

private
FFont : TFont;
FBorderColor : TColor;
FColor : TColor;
FMaxWidth : Integer;
procedure SetFont(const Value: TFont);
procedure SetMaxWidth(const Value: Integer);

published
property Font : TFont read FFont write SetFont;
property BorderColor : TColor read FBorderColor write FBorderColor;
property Color : TColor read FColor write FColor;
property MaxWidth : Integer read FMaxWidth write SetMaxWidth;

Im Konstruktor wird ein neues Font-Objekt erzeugt und die gerade definierten Variablen werden auf Standardwerte gesetzt, z.B.:

FBorderColor := clSkyBlue;
FColor := clWhite;
FFont := TFont.Create;
FFont.Assign(TCustomForm(AOwner).Font);
FMaxWidth := 200;

Das Font-Objekt muss abschließend im Destruktor mit FFont.Free freigegeben werden. Fehlen noch die beiden Prozeduren SetFont und SetMaxWidth und die Komponente ist vorerst vollständig:

procedure TCustomHint.SetFont(const Value: TFont);
begin
FFont.Assign(Value);
end;

procedure TCustomHint.SetMaxWidth(const Value: Integer);
begin
if (Value < 50) or (Value > 1000) then ShowMessage('Wert muss zwischen 50..1000 liegen!')
else FMaxWidth := Value;
end;

Um später auch von anderen Klassen auf die Eigenschaften der Komponente zugreifen zu können, wird im Implementation-Abschnitt eine globale Variable für TCustomHint erzeugt:

var CustomHint1 : TCustomHint;

Diese wird nun noch entsprechend der FActive-Eigenschaft gesetzt:

procedure TCustomHint.SetActive(const Value: Boolean);
begin
if FActive = Value then Exit;
FActive := Value;
if FActive then begin
CustomHint1 := self;
end else begin
CustomHint1 := nil;
end;
end;

Jetzt ist es an der Zeit für einen kleinen Testlauf. Installieren Sie die Komponente wie gewohnt über den Menüpunkt Komponenten I Komponente installieren. Nun sollte TCustomHint in der Komponentenpalette verfügbar sein. Wenn Sie eine neue Anwendung erzeugen und den CustomHint auf einem Formular platzieren, müsste der Objekt Inspektor ähnlich wie in Abbildung 2 gezeigt aussehen und die Einstellungen veränderbar sein. Wenn bis hierhin alles funktioniert können Sie der Komponente jetzt "Leben einhauchen".


Abb. 2 Der Objektinspektor mit den Eigenschaften der Komponente

Tipps & Tricks
Wenn Sie sich bisher noch nicht mit dem Schreiben von eigenen Komponenten beschäftigt haben oder den neuen Tooltip ohne Komponente direkt in ein Programm integrieren wollen, dann reicht es aus, die Klasse TCustomHintWindow in den type-Abschnitt Ihrer Unit einzufügen. Um den Hint zu aktivieren muss dann lediglich irgendwo im Programm (am Besten im OnCreate-Ereignis des Hauptformulars) die Zeile HintWindowClass := TCustomHintWindow; stehen.

TCustomHintWindow
In Delphi werden Hint-Fenster mit Hilfe der Klasse THintWindow aus der Unit Controls erzeugt, welche ein Popup-Fenster mit dem entsprechenden Hilfetext anzeigt. Um nun ein eigenes Fenster zu verwenden werden wir zunächst eine neue Klasse von THintWindow ableiten und dann verschiedene Methoden überschreiben und mit eigenen Funktionen belegen. Die neue Klasse nennen wir TCustomHintWindow.

TCustomHintWindow = class(THintWindow)
private
FBitmap : TBitmap;
protected
procedure Paint; override;
procedure CreateParams(var Params: TCreateParams); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure ActivateHint(Rect: TRect; const AHint: string); override;
function CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect; override;
end;

Methoden, die von der Elternklasse überschrieben werden, sind durch das Schlüsselwort override gekennzeichnet. Bevor die einzelnen Methoden und Eigenschaften mit Inhalten versehen werden, folgt zunächst eine kurze Erklärung, für welchen Zweck sie vorgesehen sind:
  • FBitmap ist ein Bitmap, auf welches der Text und die Grafik gezeichnet werden. FBitmap wird in der Paint-Methode auf der Zeichenfläche des Hint-Fensters ausgegeben.
  • Paint: Wird immer dann aufgerufen, wenn das Fenster die Aufforderung zum Neuzeichnen (WM_PAINT) erhält.
  • CreateParams: In dieser Methode können die Fenstereigenschaften geändert werden
  • ActivateHint wird vor der Anzeige des Fensters aufgerufen. Hier können die Koordinaten, an denen der Hint erscheinen soll angepasst werden. In dieser Methode füllen wir auch FBitmap mit dem Hint-Text und Grafikelementen.
  • CalcHintRect ermittelt, wie groß das Fenster sein muss, damit der Text vollständig angezeigt wird.
Zunächst beginnen wir mit Konstruktor und Destruktor, in denen lediglich die Variable FBitmap initialisiert und freigegeben wird. Der Befehl inherited bewirkt dabei einen Aufruf der gleichnamigen Methode aus der Vorgängerklasse, da dort noch weitere, für die Funktion wichtige, Befehle ausgeführt werden. Daraus ergibt sich folgender Quelltext:

constructor TCustomHintWindow.Create(AOwner: TComponent);
begin
inherited;
FBitmap := TBitmap.Create;
end;

destructor TCustomHintWindow.Destroy;
begin
FBitmap.Free;
inherited;
end;

In der Methode Paint wird das soeben erzeugte Bitmap auf der Zeichenfläche (Canvas) des Hint Fensters ausgegeben. Dieses geschieht durch einen Aufruf von:

procedure TCustomHintWindow.Paint;
begin
Canvas.Draw(0,0,FBitmap);
end;

Um das Hint-Fenster befindet sich zu diesem Zeitpunkt noch ein störender Rahmen. Um diesen zu entfernen, müssen die Fenster-Eigenschaften geändert werden. Dazu greift man in der Methode CreateParams auf die Variable Params zu und entfernt die Eigenschaft WS_BORDER aus der Variablen.

procedure TCustomHintWindow.CreateParams(var Params : TCreateParams);
begin
inherited;
Params.Style := Params.Style AND NOT WS_BORDER;
end;

Als nächstes muss die Größe des Fensters ermittelt werden, damit der Text vollständig ausgegeben werden kann. Dieses geschieht in der Funktion CalcHintRect, welche als Rückgabewert die Größe des benötigten Anzeigebereichs als Rechteckkoordinaten liefert. Als erstes wird als Ergebnis ein Rechteck festgelegt, welches nur die Werte 0 und als Breite den Wert enthält, welchen der Benutzer über die Komponente einstellen kann (CustomHint1.MaxWidth). Ein inherited ist in dieser und den folgenden Methoden nicht nötig, da die Ausgabe komplett neu gestaltet werden soll, also ohne auf bestehende Methoden der Vorgängerklasse zurückzugreifen.

function TCustomHintWindow.CalcHintRect(MaxWidth: Integer; const AHint: string; AData: Pointer): TRect;
begin
Result := Rect(0, 0, CustomHint1.MaxWidth, 0);
end;

In der Variablen AHint wird der Text des Tooltips übergeben. Dieser Text wird zusammen mit dem gerade erzeugten Rechteck und dem Parameter DT_CALCRECT an die Funktion DrawText übergeben. Diese Funktion benutzt man eigentlich, um Text auf einer Zeichenfläche auszugeben. Durch den Parameter DT_CALCRECT erreichen wir aber, das die Funktion als Ergebnis die Höhe der gesamten Textausgabe zurückgibt. Das bedeutet, dass nun sowohl die benötigte Höhe des Fensters als auch die Breite bekannt sind. Die Höhe wird zu dem Ergebnisrechteck hinzugefügt (mit einem kleinen Aufschlag von 2 Pixeln, damit die Ausgabe nicht zu eng wird).

Canvas.Font := CustomHint1.Font;
Result.Bottom := DrawText(Canvas.Handle, PChar(AHint), -1, Result, DT_CALCRECT or DT_LEFT or DT_WORDBREAK or DT_NOPREFIX) + 2;

Zum Schluss wird die Breite des Rechtecks noch ein wenig vergrößert, damit der bunte Streifen mit hineinpasst:

Inc(Result.Right, 20);

Damit kommen wir auch schon zur letzten -und umfangreichsten- Methode, der Prozedur ActivateHint. Diese enthält als Parameter wieder den Text des Hints, AHint, sowie ein Rechteck Rect mit den Bildschirmkoordinaten des Fensters (die in der Funktion CalcHintRect berechnete Größe ist in der Variablen schon automatisch berücksichtigt). Da sich die Koordinaten der Zeichenfläche des Bitmaps von denen des Bildschirms unterscheiden, muss die Fenstergröße aus der Variablen Rect herausgerechnet werden. Diese wird dann in einer neuen Variablen r gespeichert:

procedure TCustomHintWindow.ActivateHint(Rect: TRect; const AHint: string);
var
r : TRect;
begin
r.Left := 0;
r.Top := 0;
r.Right := Rect.Right - Rect.Left + 2;
r.Bottom := Rect.Bottom - Rect.Top + 2;
end;

Nun können wir mit den Zeichenoperationen beginnen. Zunächst wird die Größe des Bitmaps auf die des Fensters eingestellt und mit der gewünschten Hintergrundfarbe gefüllt:

FBitmap.Width := r.Right;
FBitmap.Height := r.Bottom;
FBitmap.Canvas.Brush.Color := CustomHint1.Color;
FBitmap.Canvas.FillRect(r);

Als nächstes wird der Text ausgegeben. Damit noch Platz für den kleinen Seitenstreifen bleibt, erfolgt die Textausgabe um 17 Pixel nach rechts versetzt. DrawText wird hier ohne den Parameter DT_CALCRECT aufgerufen, da wir die Höhe ja schon berechnet haben und den Text diesmal wirklich auf die Zeichenfläche kopieren wollen:

FBitmap.Canvas.Font := CustomHint1.Font;
r.Left := 17;
DrawText(FBitmap.Canvas.Handle, PChar(AHint), -1, r, DT_LEFT or DT_WORDBREAK or DT_NOPREFIX);
r.Left := 0;

Jetzt ist noch der Seitenstreifen in der entsprechenden Farbe auszugeben, und die Zeichenoperationen sind vollständig:

FBitmap.Canvas.Brush.Color := CustomHint1.BorderColor;
r.Right := 15;
FBitmap.Canvas.FillRect(r);

Ein Problem gilt es jetzt noch zu lösen: Wenn der Tooltip dicht am Bildschirmrand aufgerufen wird kann es sein, dass er nur teilweise sichtbar ist. Um das zu umgehen, werden die Koordinaten überprüft und gegebenenfalls korrigiert:

if Rect.Bottom > Screen.Height then Rect.Top := Screen.Height + Rect.Top - Rect.Bottom;
if Rect.Right > Screen.Width then Rect.Left := Screen.Width + Rect.Left - Rect.Right;
if Rect.Left < 0 then Rect.Left := 0;
if Rect.Top < 0 then Rect.Top := 0;

Zum Abschluss muss das Fenster natürlich noch an der entsprechenden Position angezeigt werden, welches durch einen Aufruf der Funktion SetWindowPos geschieht:

SetWindowPos(Handle, HWND_TOPMOST,Rect.Left,Rect.Top,Rect.Right-Rect.Left, Rect.Bottom-Rect.Top, SWP_SHOWWINDOW or SWP_NOACTIVATE);

Damit ist die Klasse TCustomHintWindow vollständig.

Zum Schluss
Ganz fertig sind Sie allerdings noch nicht. Denn die Anwendung weiß an dieser Stelle noch nicht, dass sie auch die neue Klasse zum Anzeigen der Tooltips benutzen soll. Das müssen wir ihr erst noch über die globale Variable HintWindowClass mitteilen. Die Zuweisung erfolgt in der Prozedur SetActive:

if FActive then begin
CustomHint1 := self;
HintWindowClass := TCustomHintWindow;
end else begin
CustomHint1 := nil;
HintWindowClass := THintWindow;
end;

Jetzt können Sie die Komponente installieren, testen und nach Ihren Vorstellungen verändern.

Fazit
Sie haben nun das nötige Wissen, um Ihre eigenen Tooltips zu gestalten. Wie Sie dieses Wissen anwenden bleibt ganz und gar Ihrer Phantasie überlassen. Denkbar sind zum Beispiel Transparenzeffekte, aber auch kleine Animationen sind möglich. Diese Dinge gehören aber eher in den Bereich der Grafikprogrammierung und werden deshalb an dieser Stelle nicht behandelt. Und denken Sie daran: Weniger ist manchmal mehr, eine dezente Aufwertung der Hints ist für den Benutzer angenehmer als eine Effektdemonstration.


    Hat Ihnen dieser Artikel gefallen? Dann abonnieren Sie das Entwickler Magazin direkt über unser Online-Formular.



zur vorherigen Seite
zurück
an den Anfang der Seite
nach oben
Diesen Artikel drucken
drucken
Diesen Artikel weiterempfehlen
empfehlen
Software & Support Verlag GmbH