W poprzednim rozdziale przedstawiliśmy podstawowe zasady tworzenia nowych komponentów i włączania ich do palety. Niniejszy rozdział opisuje techniki bardziej zaawansowane; przedstawimy m.in. komponenty pseudowizualne, budowę i zasady działania edytora właściwości, niestandardowe edytory komponentów oraz kolekcje komponentów.
Treść niniejszej książki obfituje w przykłady zastosowań komponentów widocznych, których przykładami są TButton i TEdit oraz komponentów niewidocznych, jak chociażby prezentowany w poprzednim rozdziale TTimer, czy też bazodanowy komponent TTable. Obecnie przedstawimy grupę komponentów o cechach pośrednich — komponenty te nie zasługują na miano wizualnych, gdyż nie można ich umieścić w palecie komponentów; z drugiej strony manifestują one swą obecność podczas wykonywania programu na równi z innymi widocznymi komponentami. Z tego względu noszą nazwę komponentów pseudowizualnych (pseudo-visual components).
Podpowiedzi kontekstowe — prostokąty pojawiające się po zatrzymaniu kursora myszy na większości obiektów Win32 — to właśnie jeden z przykładów komponentów pseudowizualnych. Pokażemy teraz, w jaki sposób można manipulować ich parametrami; między innymi, jak sam za chwilę zobaczysz, niekoniecznie muszą mieć kształt prostokątny!
Konstrukcję przykładowego projektu podzielimy na cztery etapy:
Istotą nowej klasy podpowiedzi — TddgHintWindow — jest zmiana najbardziej (wydawałoby się) „przyrodzonej” cechy podpowiedzi — jej prostokątnego kształtu. Nowy komponent ma kształt (skrajnie) zaokrąglonego w narożnikach prostokąta. Stanowi to skądinąd okazję do omówienia frapującego aspektu interfejsu Win32 API, a mianowicie — tworzenia okienek o kształcie innym niż prostokątny! Klasa TddgHintWindow zdefiniowana jest w module RndHint.pas, którego treść prezentujemy na wydruku 12.1.
Wydruk 12.1. Implementacja nieprostokątnego okienka podpowiedzi kontekstowej
unit RndHint;
interface
uses Windows, Classes, Controls, Forms, Messages, Graphics;
type
TddgHintWindow = class(THintWindow)
private
FRegion: THandle;
procedure FreeCurrentRegion;
public
destructor Destroy; override;
procedure ActivateHint(Rect: TRect; const AHint: string); override;
procedure Paint; override;
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
destructor TddgHintWindow.Destroy;
begin
FreeCurrentRegion;
inherited Destroy;
procedure TddgHintWindow.CreateParams(var Params: TCreateParams);
{ okienko podpowiedzi kontekstowej pozbawione jest obrzeża }
inherited CreateParams(Params);
Params.Style := Params.Style and not ws_Border; // usuń obrzeże
procedure TddgHintWindow.FreeCurrentRegion;
{ Regions, like other API objects, should be freed when you are }
{ through using them. Note, however, that you cannot delete a }
{ region which is currently set in a window, so this method sets }
{ the window region to 0 before deleting the region object. }
{ Regiony, podobnie jak inne obiekty API, powinny zostać zwolnione
po wykorzystaniu. Nie można jednak usunąć obiektu regionu przypisanego
aktualnie do okna, należy go wpierw usunąć z okna
}
if FRegion <> 0 then
begin // jeżeli zdefiniowano region...
SetWindowRgn(Handle, 0, True); // usuń go z okna
DeleteObject(FRegion); // zwolnij obiekt regionu
FRegion := 0; // wyzeruj wskaźnik do regionu
procedure TddgHintWindow.ActivateHint(Rect: TRect; const AHint: string);
{
Aktywowanie podpowiedzi kontekstowej
with Rect do
Right := Right + Canvas.TextWidth('WWWW'); // niewielki prawy margines
BoundsRect := Rect;
{ utwórz region w kształcie zaokrąglonego prostokąta }
FRegion := CreateRoundRectRgn(0, 0, Width, Height, Width, Height);
SetWindowRgn(Handle, FRegion, True); // przypisz region do okna
inherited ActivateHint(Rect, AHint);
procedure TddgHintWindow.Paint;
Narysowanie zawartości okienka podpowiedzi kontekstowej w odpowiedzi
na komunikat WM_PAINT.
var
R: TRect;
R := ClientRect; // pobierz współrzędne prostokąta
// opisanego na okienku podpowiedzi
Inc(R.Left, 1); // obetnij 1 piksel z lewej strony
Canvas.Brush.Color := clInfoBk; // wypełnij region tłem
FillRgn(Canvas.Handle, FRegion, Canvas.Brush.Handle);
Canvas.Font.Color := clInfoText; // kolor pierwszoplanowy
// wypisz treść podpowiedzi w sposób wyśrodkowany
DrawText(Canvas.Handle, PChar(Caption), Length(Caption), R,
DT_NOPREFIX or DT_WORDBREAK or DT_CENTER or DT_VCENTER);
OldHintClass: THintWindowClass;
function SetNewHintClass(AClass: THintWindowClass): THintWindowClass;
DoShowHint: Boolean;
Result := HintWindowClass; // dotychczasowa klasa okna podpowiedzi
DoShowHint := Application.ShowHint;
if DoShowHint then
Application.ShowHint := False; // zwolnij dotychczasowe okno podpowiedzi
HintWindowClass := AClass; // przypisz nową klasę
Application.ShowHint := True; // i utwórz nowe okno podpowiedzi
initialization
OldHintClass := SetNewHintClass(TddgHintWindow);
finalization
SetNewHintClass(OldHintClass);
end.
Zwróć uwagę na ważny fakt, iż okno reprezentujące podpowiedź musi być pozbawione obrzeża — prostokątne obrzeże eliptycznego regionu obnażyłoby natychmiast cały trik; brak obrzeża jest wynikiem dodatkowej instrukcji obecnej w metodzie CreateParams(), wyłączającej flagę WS_BORDER w polu Style parametrów tworzonego okna. Za wypisanie tekstu podpowiedzi odpowiedzialna jest metoda Paint() — tekst wypisywany jest w centrum okienka podpowiedzi, w kolorze systemowym clInfoText, przeznaczonym właśnie dla podpowiedzi kontekstowych.
Wspominaliśmy przed chwilą o możliwości tworzenia okienek o kształcie różnym od prostokątnego, musimy jednak uściślić tę wypowiedź: otóż w kontekście obszaru zajmowanego na ekranie, okna Win32 API zawsze są prostokątami, a odstępstwo od prostokątnego kształtu dotyczy tej części okna, która podlega wyświetlaniu i odświeżaniu. Rozróżnienie tych dwóch kategorii możliwe jest dzięki specyficznym obiektom API zwanym regionami. Z każdym oknem może być (chociaż nie musi) związany w danej chwili obiekt-region (co najwyżej jeden); wszystkie operacje związane z wyświetlaniem zawartości okna ograniczone są tylko do jego bieżącego regionu. Win32 API udostępnia szereg funkcji do tworzenia regionów o różnorodnych kształtach, między innymi:
· CreateEllipticRgn(), CreateEllipticRgnIndirect() — dla regionów eliptycznych;
· CreatePolygonRgn() — dla regionów w kształcie wielokątów;
· CreatePolyPolygonRgn() — dla regionów tworzonych przez grupę wielokątów, być może nakładających się;
· CreateRectRgn(), CreateRectRgnIndirect() — dla regionów prostokątnych
· CreateRoundRectRgn() — dla regionów w kształcie prostokątów z zaokrąglonymi narożnikami;
· ExtCreateRegion() — dla regionu tworzonego przez transformację istniejącego regionu;
· CombineRgn() — dla regionu stanowiącego kombinację dwóch regionów.
Dokładny opis wymienionych funkcji znajduje się w systemie pomocy Win32 API.
Przypisanie stworzonego regionu do konkretnego okna odbywa się za pomocą procedury SetWindowRgn() — od tej chwili wszystkie operacje wyświetlania związane z tym oknem będą ograniczone tylko do zdefiniowanego regionu.
Ostrzeżenie
Operując regionami Win32 API, musisz być świadom dwóch efektów ubocznych. Po pierwsze — jako że odświeżaniu podlega jedynie region okna, okno to pozbawione jest zazwyczaj obrzeża i paska tytułowego; nie można więc takiego okna przesuwać, zamykać, maksymalizować itp. bezpośrednio na ekranie — da się to uczynić jedynie w sposób programowy. Po drugie — region przypisany do okna jest własnością Win32 API, nie można więc zwalniać związanego z nim obiektu ani go modyfikować; wszelkie tego typu operacje muszą być poprzedzone odłączeniem regionu od okna, czego ilustracją jest chociażby metoda FreeCurrentRegion() klasy TddgHintWindow.
Analizując treść modułu RndHint.pas łatwo zauważysz, iż w jego części inicjacyjnej następuje zmiana dotychczasowej klasy okna podpowiedzi na TddgHintWindow; dotychczasowa klasa zapamiętywana jest w celu późniejszego jej przywrócenia, które odbywa się w sekcji finalization. W taki oto sposób klasa TddgHintWindow staje się obowiązującą klasą podpowiedzi kontekstowych dla aplikacji używającej wspomnianego modułu.
Przed „podmianą” domyślnej klasy podpowiedzi należy jednak zwolnić jej (ewentualnie istniejące) okno — wykonuje się to bardzo prosto, poprzez ustawienie na False właściwości ShowHint obiektu Application. W momencie przypisania tej właściwości wartości True następuje tworzenie nowego okna, o klasie wskazywanej przez globalną zmienną HintWindowClass.
Specyfiką klasy TddgHintWindow jest sposób integrowania jej z aplikacjami, odmienny niż w przypadku „zwykłych” komponentów. Otóż z uwagi na to, iż obiekt tej klasy (czyli po prostu — okno podpowiedzi) tworzony jest w części inicjacyjnej modułu, moduł ten nie powinien być częścią pakietu projektowego, zaś sam komponent nie powinien być umieszczany w palecie komponentów, bo i tak na nic się tam nie przyda. Aby spowodować zmianę kształtu podpowiedzi kontekstowych aplikacji, wystarczy wykonać czynność znacznie prostszą — mianowicie umieścić nazwę modułu RndHint w dyrektywie uses któregokolwiek z modułów.
Efektownym elementem wielu aplikacji są różnego rodzaju animacje, na przykład te o charakterze ozdobnym, towarzyszące okienkom About lub O programie… (wystarczy wyświetlić okno About Delphi 6 i, przy wciśniętym lewym klawiszu Alt, wpisać słowo team). Można się bawić i jednocześnie uczyć — skonstruujemy więc za chwilę komponent o podobnym działaniu.
Istotą funkcjonowania komponentu TddgMarquee jest pionowe przewijanie długiego tekstu, którego fragment obserwowany jest przez prostokątne okienko — niczym widoczny fragment markizy sklepowej (stąd nazwa komponentu). Klasą bazową dla komponentu jest TCustomPanel posiadający kilka potrzebnych elementów, między innymi estetyczne trójwymiarowe obrzeże. Łańcuchy składające się na przewijany tekst przechowywane są natomiast w liście typu TStringList.
Zrozumienie zasady działania komponentu stanie się prostsze, jeżeli potraktujemy wyświetlany tekst nie jako ciąg łańcuchów, lecz jako bitmapę. Komponent dokonuje „wypisania” swych łańcuchów na roboczej bitmapie i kopiuje początek tej bitmapy na swe własne płótno (używając funkcji API BitBlt()); następnie przesuwa się o kilka pikseli w głąb bitmapy roboczej i kopiuje jej inny fragment. Uzyskujemy w ten sposób symulację przewijania. Proces ten powtarzany jest aż do osiągnięcia końca bitmapy roboczej.
Podobnie jak w poprzednim rozdziale, rozpoczniemy od ogólnego zarysu funkcjonalnego komponentu, obejmującego wykorzystywane przez niego mechanizmy składowe. Są cztery takie mechanizmy:
· wypisywanie tekstu na bitmapie roboczej,
· kopiowanie fragmentów bitmapy roboczej na płótno komponentu,
· wewnętrzny zegar regulujący szybkość symulowanego przewijania,
· niezbędne metody klasy komponentu, wraz z konstruktorem i destruktorem,
· pozostałe właściwości i metody.
Pierwszym pytaniem związanym z roboczą bitmapą jest pytanie o jej rozmiar; musi być wystarczający do przechowania całości tekstu. Dla uproszczenia potraktujmy tę bitmapę jako prostokątny obszar; jego szerokość musi odpowiadać szerokości okna wyświetlania (nie ma potrzeby przechowywania tekstu, który znika poza prawą krawędzią). Problemem staje się natomiast wyznaczenie jego wysokości — można ją wyznaczyć mierząc wysokość poszczególnych linii tekstu za pomocą funkcji API GetTextMetric():
Metrics: TTextMetric;
...
wilmac1977