R12-04.DOC

(411 KB) Pobierz
Szablon dla tlumaczy

Rozdział 12.

Zaawansowane techniki tworzenia komponentów

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.

Komponenty pseudowizualne

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 kompo­nent 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ę kom­ponentów pseudowizualnych (pseudo-visual components).

Niestandardowe podpowiedzi kontekstowe

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:

  1. Zdefiniowanie klasy reprezentującej nowy typ podpowiedzi.
  2. Zwolnienie egzemplarza domyślnej klasy podpowiedzi.
  3. Zdefiniowanie nowej klasy podpowiedzi jako klasy domyślnej.
  4. Stworzenie egzemplarza nowej klasy podpowiedzi.

 

Tworzenie klasy pochodnej do THintWindow

Istotą nowej klasy podpowiedzi — TddgHintWindow — jest zmiana najbardziej (wyda­wał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 — two­rzenia 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;

end;

 

procedure TddgHintWindow.CreateParams(var Params: TCreateParams);

{ okienko podpowiedzi kontekstowej pozbawione jest obrzeża }

begin

  inherited CreateParams(Params);

  Params.Style := Params.Style and not ws_Border;  // usuń obrzeże

end;

 

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

}

 

 

 

begin

  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

  end;

end;

 

procedure TddgHintWindow.ActivateHint(Rect: TRect; const AHint: string);

{

  Aktywowanie podpowiedzi kontekstowej

}

 

begin

  with Rect do

    Right := Right + Canvas.TextWidth('WWWW');  // niewielki prawy margines

  BoundsRect := Rect;

  FreeCurrentRegion;

 

  { utwórz region w kształcie zaokrąglonego prostokąta }

  FRegion := CreateRoundRectRgn(0, 0, Width, Height, Width, Height);

  if FRegion <> 0 then

    SetWindowRgn(Handle, FRegion, True);         // przypisz region do okna

 

  inherited ActivateHint(Rect, AHint);

end;

 

procedure TddgHintWindow.Paint;

{

  Narysowanie zawartości okienka podpowiedzi kontekstowej w odpowiedzi

  na komunikat WM_PAINT.

}

 

 

var

  R: TRect;

begin

  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);

end;

 

var

  OldHintClass: THintWindowClass;

 

function SetNewHintClass(AClass: THintWindowClass): THintWindowClass;

var

  DoShowHint: Boolean;

begin

  Result := HintWindowClass;         // dotychczasowa klasa okna podpowiedzi

  DoShowHint := Application.ShowHint;

 

  if DoShowHint then

    Application.ShowHint := False;   // zwolnij dotychczasowe okno podpowiedzi

 

  HintWindowClass := AClass;         // przypisz nową klasę

  if DoShowHint then

    Application.ShowHint := True;    // i utwórz nowe okno podpowiedzi

end;

 

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 Cre­ateParams(), wyłączającej flagę WS_BORDER w polu Style parametrów tworzonego okna. Za wypisanie tekstu podpowiedzi odpowiedzialna jest metoda Paint() — tekst wy­pisywany jest w centrum okienka podpowiedzi, w kolorze systemowym clInfoText, przeznaczonym właśnie dla podpowiedzi kontekstowych.

Okienka eliptyczne

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 zajmo­wanego na ekranie, okna Win32 API zawsze są prostokątami, a odstępstwo od prostokąt­nego kształtu dotyczy tej części okna, która podlega wyświetlaniu i odświeża­niu. 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ą procedu­ry 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.

Zmiana domyślnej klasy podpowiedzi

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.

Zastosowanie klasy TddgHintWindow

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.

 

Animowane komponenty

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.

Komponent TddgMarquee

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.

 

Wykorzystanie roboczej bitmapy

Pierwszym pytaniem związanym z roboczą bitmapą jest pytanie o jej roz­miar; musi być wystarczający do przechowania całości tekstu. Dla uproszczenia po­traktujmy 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():

 

var

  Metrics: TTextMetric;

 

begin

...

Zgłoś jeśli naruszono regulamin