Hinty

Adam Boduch

O czym będzie ten artykuł? Otóż o Hintach, czyli tzw. dymków podpowiedzi, które pojawiają się gdy nasuniesz kursor nad jakiś komponent.
W Delphi można to bardzo łatwo stworzyć, ale jeżeli chcemy uzyskać jakieś bardziej skomplikowane kształty to trzeba trochę popisać...

Zacznijmy od rzeczy prostszych, czyli...

Standardowe Hinty

Po pierwsze musisz zmienić właściwość ShowHint na True. Każdy widoczny komponent posiada taką właściwość. Jeżeli już ją zmienisz to odnajdź pole Hint. Tutaj trzeba wpisać swoją podpowiedź - wpisz obojętnie jaki tekst. Teraz uruchom program i najedź kursorem na komponent - wyświetli się dymek podpowiedzi.

Teraz coś trudniejszego. Chciałbyś np., aby osobny tekst pojawiał się w dymku i osobny na komponencie. Możesz użyć do tego celu komponentu StatusBar, zmienić jego właściwość SimpleBar na True oraz AutoHint na True.

Teraz ustawiając pole Hint jakiegoś komponentu tekst podpowiedzi musisz wpisać w dwóch częściach oddzielając go od siebie znakiem |.

Np. tekst podpowiedzi mógłby wyglądać następująco:

Jestem etykietą | To jest etykieta na której przechowuje się tekst

Tekst po lewej oddzielony znakiem | wyświetlił by się w dymku, a tekst po prawej na komponencie StatusBar.

W sekcji private umieść taki oto nagłówek:

procedure MyCoolHint(Sender: TObject);

Teraz uzupełnij ten kod w sekcji Implementation:

procedure TMainForm.MyCoolHint(Sender: TObject);
begin
{
  Jezeli dlugosc podpowiedzi bedzie dluzsza niz 0 to wyswietl
  podpowiedz na pasku tytulowym formy.
}
  if Length(Application.Hint) > 0 then
    Caption := Application.Hint;
end;

Jak widzisz klasa Application posiada właściwość Hint, która określa właśnie dymek podpowiedzi. Zawiera ona także inne ciekawe opcje:

HintColor Określa kolor tła podpowiedzi.
HintHidePause Definiuje określa czasu, w którym podpowiedź zostaje ukryta. Domyślnie jest to 2500 milisekund.
HintPause Określa czas jaki mija od chwili zatrzymania kursora nad komponentem do tego w jakim zostanie pokazana podpowiedź. Domyślnie 500 milisekund.
HintShortPause. Jeżeli przemieszczasz się kursorem pomiędzy kilkoma komponentem to następuje pewien czas, w którym stara podpowiedź zostaje schowana, a nowa pokazana - to właśnie definiuje ta właściwość. Wartość domyślna: 50 milisekund.

Zaawansowane "dymki"

To wymaga trochę więcej kodu. Delphi posiada klasę THintWindow, która odpowiedzialna jest za dymki. Należy więc zdefiniować klasę pochodną do THintWindow:

TMyHint = class(THintWindow)
  private
    FRegion: THandle;
    procedure FreeRegion;
  public
    destructor Destroy; override;
    procedure ActivateHint(ARect: TRect; const AHint: string); override;
    procedure Paint; override;
  end;

Zauważ, że większość metod jest przedefiniowana tzn., że istnieją w klasie bazowej. [jeżeli nie wiesz o co chodzi radzę przeczytać artykuł Klasy].

W tym przykładzie stworzymy bardziej zaawansowaną klasę, która zamiast prostokąta wyświetli elipsę z tekstem 3D. Do tego celu będziemy musieli użyć regionów, aby stworzyć kształt elipsy za pomocą funkcji API. Najpierw wygenerujmy procedurę ActivateHint - to w niej będzie się odbywać tworzenie nowego regionu:

procedure TMyHint.ActivateHint(ARect: TRect; const AHint: string);
begin
{  Ustaw dodatkowy margines po prawej stronie }
  with ARect do
    Right := Right + Canvas.TextWidth('XXXXX');

  BoundsRect := ARect; // przypisz zmienna do właściwości
  FreeRegion; // zwolnij dotychczasowy region

  with BoundsRect do
{
  Stworz nowy region w postaci elipsy.
}
    FRegion := CreateRoundRectRgn(0, 0, Width, Height, Width, Height);

  if FRegion <> 0 then
    SetWindowRgn(Handle, FRegion, True); // ustaw nowy region
  inherited ActivateHint(ARect, AHint); // wywolaj procedure z klasy bazowej
end;

Na początku z prawej strony dodawany jest dodatkowy margines. Funkcja TextWidth przelicza na piksele długość tekstu podanego w nawiasie. Naastępnie do zmiennej BoundsRect, która jest typu TRect zostaje przypisana zmienną ARect, która określa rozmiary regionu - podpowiedzi. Najważniejsze jednak następuje w linii, w której tworzona zostaje elipsa. Zostaje ona przypisana do zmiennej FRegion. Następnie przy pomocy polecenia SetWindowRgn zostaje ustawiony nowy region w postaci elipsy - mamy już konstrukcję dymku.

Tylko konstrukcję bowiem należy w tym dymku umieścić jakiś tekst:

procedure TMyHint.Paint;
var
  R: TRect;
begin
  R := ClientRect; // pobierz rozmiary okna dymku
  Inc(R.Left, 1);  // przesun o piksel w lewo

  Canvas.Font.Color := clSilver; // ustaw czcionke na bial

{  narysuj tekst wysrodkowany w pionie i w poziomie }
  DrawText(Canvas.Handle, PChar(Caption), Length(Caption), R,
           DT_NOPREFIX or DT_WORDBREAK or DT_CENTER or DT_VCENTER);

  Inc(R.Left, 2); // przesun o kolejne dwa piksele
  Canvas.Brush.Style := bsClear; // ustaw na przezroczyste
  Canvas.Font.Color := clBlack; // zmien czcionke na czarna

{  narysuj tekst ponownie inna czcionka z przesunieciem - daje efekt cienia }
    DrawText(Canvas.Handle, PChar(Caption), Length(Caption), R,
           DT_NOPREFIX or DT_WORDBREAK or DT_CENTER or DT_VCENTER);
end;

Tutaj wszystko odbywa się za pomocą funkcji API - DrawText. Jest to polecenie, które umożliwia narysowanie tekstu wycentrowanego w pionie jak i w poziomie.
Wszystko jest rysowane dwa razy, aby dać efekt cienia. Pierwszy tekst rysowany jest z czcionką koloru srebrnego, a drugi raz koloru czarnego z minimalnym przesunięciem.

To właściwie wszystko co najważniejsze. Trzeba jeszcze napisać jedną procedurę związaną z regionami, a mianowicie zwalniająca owy region.

procedure TMyHint.FreeRegion;
begin
  if FRegion <> 0 then // Czy region nie jest uzywany?
  begin
    SetWindowRgn(Handle, 0, True); // ustaw na brak regionu
    DeleteObject(FRegion); // usun region
    FRegion := 0;
  end;
end;

Ok - wykorzystanie nowej klasy możesz już sprawdzić. Umieść w module sekcje initialization i jako klase podpowiedzi ( HintWindowClass ) przypisz przed chwilą stworzoną klasę:

initialization
  HintWindowClass := TMyHint; // przypisz nowa klase podpowiedzi
  Application.HintColor := clWhite; // ustaw tlo Hinta na bialy

Gotowe. Oto cały kod programu:

(****************************************************************)
(*                                                              *)
(*                  THintWindow test programme                  *)
(*      Copyright (c) 2001 by Adam Boduch  <28.05.2001>         *)
(*                HTTP://PROGRAMOWANIE.OF.PL                    *)
(*              E - mail:  [email protected]                    *)
(*                                                              *)
(****************************************************************)

unit MainFrm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TMainForm = class(TForm)
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
  private
  { procedura wyswietlajca podpowiedz na pasku formy }
    procedure MyCoolHint(Sender: TObject);
  end;

{ nowa klasa podpowiedzi dziedziczaca z klasy THintWindow }
TMyHint = class(THintWindow)
  private
    FRegion: THandle;
    procedure FreeRegion;
  public
    destructor Destroy; override;
    procedure ActivateHint(ARect: TRect; const AHint: string); override;
    procedure Paint; override;
  end;


var
  MainForm: TMainForm;

implementation

{$R *.DFM}

destructor TMyHint.Destroy;
begin
  FreeRegion; // wywolaj procedure
  inherited Destroy;
end;

procedure TMyHint.FreeRegion;
begin
  if FRegion <> 0 then // Czy region nie jest uzywany?
  begin
    SetWindowRgn(Handle, 0, True); // ustaw na brak regionu
    DeleteObject(FRegion); // usun region
    FRegion := 0;
  end;
end;

procedure TMyHint.ActivateHint(ARect: TRect; const AHint: string);
begin
{  Ustaw dodatkowy margines po prawej stronie }
  with ARect do
    Right := Right + Canvas.TextWidth('XXXXX');

  BoundsRect := ARect; // przypisz zmienna do wlasciwosci
  FreeRegion; // zwolnij dotychczasowy region

  with BoundsRect do
{
  Stworz nowy region w postaci elipsy.
}
    FRegion := CreateRoundRectRgn(0, 0, Width, Height, Width, Height);

  if FRegion <> 0 then
    SetWindowRgn(Handle, FRegion, True); // ustaw nowy region
  inherited ActivateHint(ARect, AHint); // wywolaj procedure z klasy bazowej
end;

procedure TMyHint.Paint;
var
  R: TRect;
begin
  R := ClientRect; // pobierz rozmiary okna dymku
  Inc(R.Left, 1);  // przesun o piksel w lewo

  Canvas.Font.Color := clSilver; // ustaw czcionke na bial

{  narysuj tekst wysrodkowany w pionie i w poziomie }
  DrawText(Canvas.Handle, PChar(Caption), Length(Caption), R,
           DT_NOPREFIX or DT_WORDBREAK or DT_CENTER or DT_VCENTER);

  Inc(R.Left, 2); // przesun o kolejne dwa piksele
  Canvas.Brush.Style := bsClear; // ustaw na przezroczyste
  Canvas.Font.Color := clBlack; // zmien czcionke na czarna

{ narysuj tekst ponownie inna czcionka z przesunieciem - daje efekt cienia }
    DrawText(Canvas.Handle, PChar(Caption), Length(Caption), R,
           DT_NOPREFIX or DT_WORDBREAK or DT_CENTER or DT_VCENTER);
end;


procedure TMainForm.MyCoolHint(Sender: TObject);
begin
{
  Jezeli dlugosc podpowiedzi bedzie dluzsza niz 0 to wyswietl
  podpowiedz na pasku tytulowym formy.
}
  if Length(Application.Hint) > 0 then
    Caption := Application.Hint;
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
// ustaw procedure podpowiedzi na wczesniej zdefiniowana
  Application.OnHint := MyCoolHint; 
end;

initialization
  HintWindowClass := TMyHint; // przypisz nowa klase podpowiedzi
  Application.HintColor := clWhite; // ustaw tlo Hinta na bialy

end.

Oczywiście możesz tworzyć bardziej skomplikowane kształty ( w procedurze ActivateHint) lub w samym dymku podpowiedzi umieszczać np. bitmapkę (procedura Paint).

2 komentarzy

Zaawansowane hinty [efekt transparentu przeźroczystości]:
Polecam Unity: psvRndHint, psvTransparentHint ze stronki
http://cvs.sourceforge.net/viewcvs.py/psvlib/hint/

skad mozan sciagnac komponent do pokazywania baloników ?? zeby same wyskakiwały