R13-04.DOC

(548 KB) Pobierz
Szablon dla tlumaczy

Rozdział13

Komponenty międzyplatformowe

Trzy poprzednie rozdziały poświęcone były architekturze i projektowaniu komponentów VCL, przeznaczonych dla platformy MS Windows. W niniejszym rozdziale zajmiemy się podstawami projektowania komponentów CLX, umożliwiających tworzenie aplikacji zarówno dla Windows, jak i dla Linuksa. Tak się szczęśliwie składa, iż znaczna część umiejętności nabyta podczas projektowania komponentów VCL okaże się przydatna również w odniesieniu do komponentów CLX.

CLX — co to jest?

CLX — wymawiane najczęściej jako „clicks” — to akronim od Component Library for Cross-Platform, czyli „międzyplatformowej biblioteki komponentów”; pojawił się po raz pierwszy w związku z Kyliksem — wywodzącym się z Delphi narzędziem typu RAD dla Linuksa. Biblioteka CLX jest jednak czymś więcej niż tylko adaptacją VCL na gruncie Linuksa: jej obecność w Delphi 6 umożliwia (po raz pierwszy w historii Delphi) przekroczenie granic Windows i tworzenie aplikacji zgodnych zarówno z Delphi, jak i Kyliksem.

Ponadto biblioteka VCL (w Delphi) utożsamiana bywa raczej z komponentami wizualnymi (również w nazwie), co nie powinno dziwić wobec faktu, iż komponenty te stanowią większą jej część (i jednocześnie lwią część palety komponentów). Tymczasem architektura CLX jest nieco bardziej złożona, bo oprócz wizualnych komponentów grupy VisualCLX zawiera także komponenty BaseCLX, DataCLX i NetCLX.

BaseCLX to grupa klas i modułów wspólnych dla Delphi 6 i Kyliksa — należą do niej m.in. moduły System, SysUtils i Classes, określane w Delphi (od początku) mianem biblioteki RTL. Mimo iż moduły te stanowić mogą składniki aplikacji obydwu typów — VCL i CLX — za aplikację CLX zwykło się uważać taką, której strona wizualna zrealizowana została na podstawie klas grupy VisualCLX.

VisualCLX stanowi odmianę tego, co większość programistów skłonna jest uważać (w Delphi) za VCL, jednak oparta jest nie na standardowych kontrolkach Windows z bibliotek User32.DLL czy ComCtl32.DLL, lecz na tzw. widżetach[1] zawartych w bibliotece Qt. Do grupy DataCLX zaliczają się komponenty zapewniające dostęp do danych za pomocą nowej technologii dbExpress, natomiast nowe, międzyplatformowe oblicze WebBrokera ucieleśniają komponenty grupy NetCLX.

W niniejszym rozdziale skoncentrujemy się głównie na VisualCLX, ze szczególnym uwzględnieniem tworzenia nowych komponentów na bazie tej architektury. Opiera się ona (jak już wcześniej wspominaliśmy) na bibliotece Qt („cute”) firmy Troll Tech, stanowiącej niezależną od konkretnej platformy bibliotekę klas C++, realizujących funkcjonalność widżetów składających się na interfejs użytkownika. Ściślej — w chwili obecnej biblioteka Qt zawiera elementy charakterystyczne dla środowisk MS Windows oraz X Window System, może więc być wykorzystywana zarówno na potrzeby aplikacji windowsowych, jak i linuksowych; na jej podstawie zrealizowano właśnie linuksowy menedżer okien KDE.

Biblioteka Qt nie jest bynajmniej jedyną dostępną międzyplatformową biblioteką klas; to, iż Borland zdecydował się właśnie na nią, wynika z kilku istotnych przyczyn. Po pierwsze, jej klasy podobne są w dużym stopniu do klas komponentów VCL — na przykład ich właściwości zrealizowane zostały z udziałem metod dostępowych Getxxxx/Setxxxx, po drugie — wykorzystują podobny do VCL mechanizm powiadamiania o zdarzeniach (tzw. sygnały). Wreszcie po trzecie — jej widżety to nic innego jak standardowe kontrolki interfejsu użytkownika, spełniające tę samą rolę co standardowe kontrolki Windows. To wszystko pozwoliło na stworzenie komponentów biblioteki CLX przez „nadbudowanie” pascalowych otoczek wokół gotowych widżetów — zamiast budowania całej architektury „od zera”.

Architektura CLX

Jak przed chwilą stwierdziliśmy, VisualCLX jest grupą klas Object Pascala zbudowanych na bazie funkcjonalności widżetów biblioteki Qt — co stanowi analogię do komponentów VCL zbudowanych na bazie standardowych kontrolek Windows i biblioteki Windows API. Podobieństwo to nie jest bynajmniej przypadkowe, lecz wynika z jednego z celów projektowych: łatwości przystosowywania istniejących aplikacji VCL do architektury CLX. Rysunki 13.1 i 13.2 przedstawiają hierarchiczną strukturę klas w obydwu tych środowiskach; przyciemnione prostokąty na rysunku 13.1 wyróżniają podstawowe klasy biblioteki VCL.

 

Już na pierwszy rzut oka widać różnicę pomiędzy obydwiema hierarchiami — w architekturze CLX pojawiły się nowe (w stosunku do VCL) klasy, niektóre zostały przesunięte do innych gałęzi. Różnice te zaznaczone zostały na rysunku 13.2 za pomocą rozjaśnionych prostokątów. I tak, na przykład, komponent zegarowy (TTimer) nie wywodzi się już bezpośrednio z klasy TComponent, lecz z nowej klasy THandleComponent, stanowiącej klasę bazową do obsługi wszystkich tych przypadków, gdy komponent niewizualny wymaga dostępu do uchwytu (handle) jakiejś kontrolki Qt. Innym przykładem jest etykieta TLabel, nie będąca już kontrolką graficzną, lecz wywodząca się z klasy TFrameControl, która wykorzystuje różnorodne możliwości kształtowania obrzeża widżetów Qt.

 

Rysunek 13.1. Hierarchia klas VCL

 

Nieprzypadkowe jest także podobieństwo nazw klas bazowych kontrolek wizualnych — TWinControl (VCL) i TWidgetControl (CLX): charakterystyczny dla Windows człon „Win” ustąpił miejsca charakterystycznemu dla CLX „Widget”. Mając na względzie łatwość przenoszenia kodu źródłowego Borland zdefiniował klasę TWinControl także w bibliotece CLX, stanowi ona jednak tylko synonim klasy TWidgetControl. Można było oczywiście uniknąć tej nieco mylącej w skutkach (zwłaszcza dla nieświadomego użytkownika) operacji i utworzyć dwa oddzielne moduły dla obydwu grup kontrolek, a później odróżniać je za pomocą symboli kompilacji warunkowej; utrudniłoby to jednak przenoszenie kodu źródłowego (identyfikator TWinControl straciłby rację bytu, a jego systematyczna zmiana na TWidgetControl wymagałaby dodatkowej fatygi), zaś w aplikacjach międzyplatformowych konieczne byłoby utrzymywanie dwóch identyfikatorów na oznaczenie klasy bazowej kontrolek.

 

Notatka

Zwróć uwagę, iż utrzymywanie w pojedynczym pliku kodu dla obydwu typów komponentów (VCL i CLX) jest czymś jakościowo różnym od tworzenia kodu uniwersalnego komponentu CLX, dającego się użyć zarówno w Delphi 6, jak i w Kyliksie (takimi komponentami zajmiemy się w dalszej części rozdziału).

Rysunek 13.2. Hierarchia klas CLX

 

Na szczęście różnice przedstawione na rysunku 13.2 nie mają zbyt dużego znaczenia dla twórców aplikacji, ponieważ większość komponentów VCL posiada na gruncie CLX identycznie nazwane odpowiedniki. Nie mają jednak takiego szczęścia twórcy nowych komponentów — zmiany w hierarchii klas mają dla nich znaczenie zasadnicze.

Wskazówka

Strukturę hierarchii klas można łatwo zobaczyć za pomocą przeglądarki obiektów (Object Browser) w Delphi 6 i w Kyliksie; jednak ze względu na synonim TWinControl, uzyskamy dwie identyczne hierarchie (dla TWidgetControl i TWinControl).

Pomiędzy VCL i CLX istnieje jeszcze więcej podobieństw, których nie sposób uwzględnić na przedstawionych rysunkach. Na przykład znane z VCL płótno (Canvas) ma w CLX niemal identyczną naturę i wykorzystywane jest w bardzo zbliżony sposób, choć oczywiście różnice pomiędzy obydwoma środowiskami przesądzają o jego odmiennej implementacji: w VCL jest ono otoczką kontekstu urządzenia, zaś w CLX — analogicznego mechanizmu zwanego malarzem (painter), mimo to obydwa te mechanizmy reprezentowane są przez tę samą właściwość Handle. Ponadto, z uwagi na wymóg łatwości przenoszenia kodu, niemal identycznie wyglądają interfejsy komponentów w obydwu grupach — pod względem repertuaru właściwości publicznych (public) i publikowanych (published) oraz zdarzeń (OnClick, OnChange, OnKeyPress) i ich metod dyspozycyjnych (Click(), Change() i KeyPress()).

Z Windows do Linuksa

Mimo wielu podobieństw pomiędzy analogicznymi elementami komponentów VCL i CLX, istniejące między tymi środowiskami różnice dają znać o sobie tym wyraźniej, im bliższa staje się zależność konkretnego elementu od mechanizmu charakterystycznego tylko dla jednego ze środowisk. I tak, w środowisku Kyliksa traci sens większość odwołań do Win32 API; mechanizmy typowe jedynie dla Windows — jak np. MAPI — muszą być zastąpione równoważnymi mechanizmami linuksowymi, a używające ich komponenty nie nadają się po prostu do przeniesienia na platformę linuksową. Z kolei niektóre problemy rozwiązywane przez funkcje biblioteki RTL muszą być rozwiązane w inny sposób — przykładem może być czułość Linuksa na wielkość liter w nazwach plików; Pascal, niewrażliwy na wielkość liter w identyfikatorach, staje się pod Kyliksem wrażliwy na wielkość liter w nazwach modułów w dyrektywach uses!

Linux pozbawiony jest też wielu znanych z Windows mechanizmów systemowych — nie ma tu technologii COM, są jednak obsługiwane interfejsy; nie ma też dokowania okien, „dwukierunkowej” (bidirectional) obsługi tekstu, lokalizowania charakterystycznego dla krajów azjatyckich itp.

Pewnym problemem dla autorów aplikacji i komponentów jest istnienie oddzielnych modułów, dedykowanych tylko określonym platformom, na przykład kod kontrolek windowsowych znajduje się w pliku Controls.pas, zaś kod dla widżetów CLX — w pliku QControls.pas. Stwarza to możliwość „pomieszania” obydwu środowisk w sytuacji, gdy komponent CLX lub aplikacja przeznaczona dla Kyliksa opracowywane są w środowisku Delphi 6. Tak skonstruowany komponent, jeżeli zawiera elementy typowe wyłącznie dla VCL, będzie bez problemu pracował w Delphi 6, najczęściej jednak odmówi współpracy pod Kyliksem. Można uniknąć takiej sytuacji, gdy, za radą Borlanda, komponenty i aplikacje przeznaczone dla Kyliksa będziemy opracowywać pod Kyliksem — niestety, środowisko Kyliksa jest (w zgodnej opinii programistów) mniej komfortowe od Delphi 6.

Wskazówka

Wobec opisanych konsekwencji różnic pomiędzy VCL i CLX, nie wydaje się uzasadnione używanie komponentów CLX w aplikacjach przeznaczonych wyłącznie dla Windows.

Nie ma komunikatów…

Linux (a raczej — podsystem X Window) nie implementuje typowego dla Windows mechanizmu komunikatów; w efekcie nie do zaakceptowania jest w Kyliksie kod źródłowy odwołujący się do identyfikatorów w rodzaju wm_LButtonDown, wm_SetCursor czy wm_Char. Reagowaniem na zachodzące w systemie zdarzenia zajmują się w bibliotece Qt wyspecjalizowane klasy — dzieje się tak niezależnie od platformy systemowej, tak więc komponent CLX nie jest zdolny reagować na komunikaty nawet pod Windows; zamiast znanych z Delphi metod z klauzulą message (np. CMTextChanged()), powinien on korzystać z równoważnych metod dynamicznych (TextChanged()), co wyjaśnimy dokładniej w następnym punkcie.

Przykładowe komponenty

W niniejszym punkcie przyjrzymy się nieco dokładniej przykładom transformacji komponentów VCL na równoważne komponenty CLX. Na początek zajmiemy się popularnym „spinerem” — to pomocniczy komponent współpracujący najczęściej z polem edycyjnym, dokonujący jego automatycznej inkrementacji lub dekrementacji; realizuje on wiele interesujących mechanizmów (jak specyficzne rysowanie), współpracę z klawiaturą i myszą, przyjmowanie i utratę skupienia (focus) itp.

Trzy kolejne komponenty to pochodne bazowego spinera. Pierwszy z nich wzbogacony jest o obsługę myszy i wyświetlanie specyficznych kursorów już na etapie projektowania, drugi realizuje współpracę z listą obrazków (ImageList), trzeci natomiast współpracuje z kontrolką reprezentującą pole bazy danych.

Wskazówka

Wszystkie prezentowane w tym rozdziale moduły nadają się do wykorzystania zarówno w Delphi 6, jak i w Kyliksie.

Podstawa — komponent TddgSpinner

Rysunek 13.3 przedstawia trzy egzemplarze komponentu TddgSpinner na formularzu aplikacji CLX. W odróżnieniu od pionowo ułożonych strzałek, charakterystycznych dla windowsowego spinera, komponent ten posiada odpowiednie przyciski w układzie poziomym: inkrementujący i dekrementujący.

 

Rysunek 13.3. Komponent TddgSpinner pomocny przy wprowadzaniu liczb całkowitych

 

Wydruk 13.1 przedstawia kompletny kod źródłowy modułu QddgSpin.pas implementującego komponent TddgSpinner. Podobnie jak spiner windowsowy, wywodzi się on z klasy TCustomControl — tyle że w tym przypadku jest to klasa CLX i komponent może być używany zarówno w Windows, jak i pod Linuksem.

Choć migracja na platformę CLX rzadko wiąże się ze zmianą nazw komponentów, to jednak zasadą jest poprzedzanie nazwy modułu VCL literą Q dla podkreślenia zależności tegoż modułu od biblioteki Qt.

Notatka

Każdy z prezentowanych wydruków zawiera „wykomentowane” linie stanowiące część kodu VCL; komentarze oznaczone dodatkowo jako VCL –> CLX podkreślają elementy specyficzne dla przenoszenia komponentu z VCL do CLX.

Wydruk 13.1. QddgSpin.Pas — kod źródłowy komponentu TddgSpinner

{==================================================================

  QddgSpin

 

  Niniejszy moduł implementuje komponent TddgSpinner z biblioteki CLX,

  zawierający poziomo ułożone przyciski zmieniające wprowadzaną wartość

  (w odróżnieniu od windowsowego spinera posiadającego pionowo ułożone

  strzałki). Komponent ten wywodzi się z CLX-owej wersji TCustomControl

  i demonstruje wiele interesujących możliwości, jak przyjmowanie i utrata

  skupienia, specyficzne rysowanie i współpracę z myszą

 

 

 

  Copyright © 2001 by Ray Konopka

==================================================================}

 

unit QddgSpin;

 

interface

 

uses

  SysUtils, Classes, Types, Qt, QControls, QGraphics;

  (*

  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,

  ImgList;

  *)

 

type

  TddgButtonType = ( btMinus, btPlus );

  TddgSpinnerEvent = procedure (Sender: TObject; NewValue: Integer;

                              var AllowChange: Boolean ) of object;

 

  TddgSpinner = class( TCustomControl )

  private

    // Pola komponentu

    FValue: Integer;

    FIncrement: Integer;

    FButtonColor: TColor;

    FButtonWidth: Integer;

    FMinusBtnDown: Boolean;

    FPlusBtnDown: Boolean;

 

    // Wskaźniki do procedur zdarzeniowych

    FOnChange: TNotifyEvent;

    FOnChanging: TddgSpinnerEvent;

 

    (*

    // VCL->CLX:  poniższe metody komunikacyjne nie są dostępne w CLX

 

    // Obsługa komunikatu Windows

    procedure WMGetDlgCode( var Msg: TWMGetDlgCode );

      message wm_GetDlgCode;

 

    // Obsługa komunikatu komponentu

    procedure CMEnabledChanged( var Msg: TMessage );

      message cm_EnabledChanged;

    *)

  protected

    procedure Paint; override;

    procedure DrawButton( Button: TddgButtonType; Down: Boolean;

                          Bounds: TRect ); virtual;

 

    // Metody obsługi

    procedure DecValue( Amount: Integer ); virtual;

    procedure IncValue( Amount: Integer ); virtual;

 

    function CursorPosition: TPoint;

    function MouseOverButton( Btn: TddgButtonType ): Boolean;

 

    // VCL->CLX:  EnabledChanged zastępuje metodę komunikacjną

    //           cm_EnabledChanged

    //

    procedure EnabledChanged; override;

 

    // Nowe metody obsługi zdarzeń

    procedure Change; dynamic;

    function CanChange( NewValue: Integer ): Boolean; dynamic;

 

    // przedefiniowane metody obsługi zdarzeń

    procedure DoEnter; override;

    procedure DoExit; override;

    procedure KeyDown(var Key: Word; Shift: TShiftState); override;

 

    procedure MouseDown( Button: TMouseButton; Shift: TShiftState;

                         X, Y: Integer ); override;

    procedure MouseUp( Button: TMouseButton; Shift: TShiftState;

                       X, Y: Integer ); override;

 

    (*

    // VCL->CLX:  poniższe deklaracje zostały zmienione w CLX

 

    function DoMouseWheelDown( Shift: TShiftState;

                             MousePos: TPoint ): Boolean; override;

 

    function DoMouseWheelUp( Shift: TShiftState;

                             MousePos: TPoint ): Boolean; override;

    *)

 

    function DoMouseWheelDown( Shift: TShiftState;

                       const MousePos: TPoint ): Boolean; override;

 

    function DoMouseWheelUp( Shift: TShiftState;

                       const MousePos: TPoint ): Boolean; override;

 

 

    // Metody dostępowe właściwości

    procedure SetButtonColor( Value: TColor ); virtual;

    procedure SetButtonWidth( Value: Integer ); virtual;

    procedure SetValue( Value: Integer ); virtual;

...

Zgłoś jeśli naruszono regulamin