R08-03, ## Documents ##, C++Builder 5


Rozdział 8. Edytory komponentów i edytory właściwości

Otwartość biblioteki VCL, umożliwiająca wzbogacanie jej o komponenty, spełniające różnorodne funkcje niezbędne użytkownikowi, tworzącemu skomplikowane aplikacje, nie wyczerpuje bynajmniej znamion elastyczności narzędzi typu RAD, do których zalicza się m.in. C++Builder. Udogodnienia tego środowiska wykraczają bowiem poza dostarczanie programistom gotowych komponentów, lecz w trosce o ich komfort pracy wkraczają także na etap projektowania aplikacji, czyniąc w jak największym stopniu przyjazną i intuicyjną obsługę tychże komponentów. Temu właśnie celowi służą opisywane w tym rozdziale specjalizowane edytory właściwości oraz edytory komponentów jako całości, które użytkownik może tworzyć stosownie do własnych potrzeb, i to przy wysiłku niewiele większym od tego potrzebnego do tworzenia typowych aplikacji.

Treść rozdziału ilustrowana będzie konkretnymi przykładami, których kod źródłowy znajduje się na załączonej płycie CD-ROM. Znajdują się tam między innymi cztery pakiety demonstracyjne w postaci gotowej do zainstalowania w środowisku IDE. Pierwszy z nich - EnhancedEditors.bpk - jest pakietem wyłącznie projektowym (designtime package), zawierającym implementację przykładowych edytorów właściwości i komponentów - ich wyszczególnienie zawiera tabela 8.1. Dwa kolejne pakiety - NewAdditionalComponentsDTP.bpk i NewAdditionalComponentsRTP.bpk - to pakiety implementujące nowe komponenty wykorzystywane w tym rozdziale; pierwszy z wymienionych pakietów jest pakietem wyłącznie projektowym (designtime), drugi natomiast jest pakietem wykonywalnym (runtime).

W katalogu Chapter10Packages (którego nazwę Czytelnik prawdopodobnie zmieni na bardziej intuicyjną) znajduje się podkatalog System, zawierający pliki systemowe niezbędne do prawidłowej pracy wymienionych pakietów; należy je skopiować do lokalizacji zawierającej pliki systemowe Windows - na przykład katalogu Windows\System w Windows 9x lub WINNT\System32 w Windows NT/2000.

Aby zainstalować pakiet projektowy, należy załadować go do IDE i z menu głównego wybrać opcję Component|Install Packages. Następnie w sekcji Design packages należy kliknąć przycisk Add, zlokalizować skompilowany plik pakietu (np. EnhancedEditors.bpl) i kliknąć przycisk Otwórz. W wyniku tej operacji na liście komponentów projektowych (w sekcji Design packages) pojawi się nowa pozycja o nazwie „Enhanced Property and Component Editors”. Kliknięcie przycisku OK spowoduje dokończenie instalacji.

Tabela 8.1. Edytory komponentów i właściwości zawarte w pakiecie EnhancedEditors

Edytor

Zarejestrowany?

TShapeTypePropertyEditor

Tak

TImageListPropertyEditor

Tak

TImageIndexProperty

Nie (abstrakcyjna klasa bazowa)

TPersistentDerivedImageIndexProperty

Tak

TComponentDerivedImageIndexProperty

Tak

TMenuItemImageIndexProperty

Tak

TTabSheetImageIndexProperty

Nie wymaga rejestracji

TToolButtonImageIndexProperty

Tak

TCoolBandImageIndexProperty

Nie wymaga rejestracji

TListColumnImageIndexProperty

Tak

TCustomActionImageIndexProperty

Nie wymaga rejestracji

THeaderSectionImageIndexProperty

Nie wymaga rejestracji

TDisplayCursorProperty

Tak

TDisplayFontNameProperty

Tak

TUnsignedProperty

Tak

TCharProprtyEditor

Tak

TSignedCharProperty

Tak

TUnsignedCharProperty

Tak

TImageComponentEditor

Tak

Tworzenie edytorów właściwości

Klasą bazową dla wszelkich edytorów właściwości jest TPropertyEditor. Dostarcza ona niezbędne środki zapewniające oglądanie i modyfikowanie właściwości komponentów w środowisku IDE. Jej deklarację prezentujemy na wydruku 8.1; znajduje się ona także w pliku DsgnIntf.hpp w podkatalogu Include\VCL lokalnej instalacji C++Buildera.

Wydruk 8.1. Deklaracja klasy TPropertyEditor

class DELPHICLASS TPropertyEditor;

typedef void __fastcall (__closure *TGetPropEditProc)(TPropertyEditor* Prop);

class PASCALIMPLEMENTATION TPropertyEditor : public System::TObject

{

typedef System::TObject inherited;

private:

_di_IFormDesigner FDesigner;

TInstProp *FPropList;

int FPropCount;

AnsiString __fastcall GetPrivateDirectory();

void __fastcall SetPropEntry(int Index, Classes::TPersistent* AInstance, Typinfo::PPropInfo APropInfo

);

protected:

__fastcall virtual TPropertyEditor(const _di_IFormDesigner ADesigner, int APropCount);

Typinfo::PPropInfo __fastcall GetPropInfo(void);

Extended __fastcall GetFloatValue(void);

Extended __fastcall GetFloatValueAt(int Index);

__int64 __fastcall GetInt64Value(void);

__int64 __fastcall GetInt64ValueAt(int Index);

Sysutils::TMethod __fastcall GetMethodValue();

Sysutils::TMethod __fastcall GetMethodValueAt(int Index);

int __fastcall GetOrdValue(void);

int __fastcall GetOrdValueAt(int Index);

AnsiString __fastcall GetStrValue();

AnsiString __fastcall GetStrValueAt(int Index);

Variant __fastcall GetVarValue();

Variant __fastcall GetVarValueAt(int Index);

void __fastcall Modified(void);

void __fastcall SetFloatValue(Extended Value);

void __fastcall SetMethodValue(const Sysutils::TMethod &Value);

void __fastcall SetInt64Value(__int64 Value);

void __fastcall SetOrdValue(int Value);

void __fastcall SetStrValue(const AnsiString Value);

void __fastcall SetVarValue(const Variant &Value);

public:

__fastcall virtual ~TPropertyEditor(void);

virtual void __fastcall Activate(void);

virtual bool __fastcall AllEqual(void);

virtual bool __fastcall AutoFill(void);

virtual void __fastcall Edit(void);

virtual TPropertyAttributes __fastcall GetAttributes(void);

Classes::TPersistent* __fastcall GetComponent(int Index);

virtual int __fastcall GetEditLimit(void);

virtual AnsiString __fastcall GetName();

virtual void __fastcall GetProperties(TGetPropEditProc Proc);

Typinfo::PTypeInfo __fastcall GetPropType(void);

virtual AnsiString __fastcall GetValue();

AnsiString __fastcall GetVisualValue();

virtual void __fastcall GetValues(Classes::TGetStrProc Proc);

virtual void __fastcall Initialize(void);

void __fastcall Revert(void);

virtual void __fastcall SetValue(const AnsiString Value);

bool __fastcall ValueAvailable(void);

DYNAMIC void __fastcall ListMeasureWidth(const AnsiString Value, Graphics::TCanvas* ACanvas, int &AWidth

);

DYNAMIC void __fastcall ListMeasureHeight(const AnsiString Value, Graphics::TCanvas* ACanvas, int &

AHeight);

DYNAMIC void __fastcall ListDrawValue(const AnsiString Value, Graphics::TCanvas* ACanvas, const Windows::TRect

&ARect, bool ASelected);

DYNAMIC void __fastcall PropDrawName(Graphics::TCanvas* ACanvas, const Windows::TRect &ARect, bool

ASelected);

DYNAMIC void __fastcall PropDrawValue(Graphics::TCanvas* ACanvas, const Windows::TRect &ARect, bool

ASelected);

__property _di_IFormDesigner Designer = {read=FDesigner};

__property AnsiString PrivateDirectory = {read=GetPrivateDirectory};

__property int PropCount = {read=FPropCount, nodefault};

__property AnsiString Value = {read=GetValue, write=SetValue};

};

Modyfikując wirtualne (virtual) i dynamiczne (DYNAMIC) metody klasy TPropertyEditor, można przystosowywać jej zachowanie do specyficznych warunków, tworząc tym samym nowe klasy edytorów właściwości. Drzewo genealogiczne standardowych reprezentantów tej rodziny przedstawia rysunek 8.1 - edytory znajdujące się na pozycjach „zacienionych” charakteryzują się odmiennym (w stosunku do klasy TPropertyEditor) sposobem graficznego reprezentowania właściwości (zajmiemy się nimi w dalszej części rozdziału). Wykaz najpowszechniej używanych edytorów właściwości zawiera tabela 8.2.

Rysunek 8.1. Hierarchia edytorów właściwości VCL

Tabela 8.2. Powszechnie używane edytory właściwości

Klasa edytora

Przeznaczenie i opis

Przykład edytowanej właściwości

TCaptionProperty

Właściwości typu AnsiString o nazwach Caption i Text.

Od swej klasy bazowej TStringProperty edytor ten różni się tym, iż wszelkie zmiany w edytowanej właściwości odzwierciedlane są na bieżąco w wyglądzie komponentu (edytor TStringProperty aktualizuje wygląd komponentu po zakończeniu edycji właściwości).

TForm::Caption

TEdit::Text

TCharProperty

Właściwości znakowe (char) oraz podtypy (typy okrojone) typu char. Wyświetla znak, będący wartością właściwości w postaci drukowalnej lub kod dziesiętny tego znaku poprzedzony znakiem #.

TMaskEdit::PasswordChar

TClassProperty

Właściwości obiektowe wyprowadzone z klasy TPersistent. Opublikowane właściwości klasy stanowiącej wartość właściwości dostępne są w rozwijalnej liście dostępnej za pośrednictwem znaku poprzedzającego nazwę właściwości.

TForm::Constraints

TColorProperty

Właściwości typu TColor. Właściwość wyświetlana jest w postaci symbolicznej nazwy cl_xxxx (o ile taka istnieje) lub w podziale na składowe RGB w postaci 0x00BBGGRR. Wprowadzana wartość może mieć postać wspomnianej nazwy lub liczby całkowitej, może też zostać wybrana z rozwijalnej listy. Dwukrotne kliknięcie edytowanej właściwości powoduje uruchomienie standardowego dialogu wyboru koloru.

TForm::Color

TComponentProperty

Właściwości wskazujące na komponenty VCL. W rozwijalnej liście wyświetlane są nazwy tych komponentów (na tym samym formularzu), których typ zgodny jest z typem edytowanej właściwości.

TToolbar::Images

TCursorProperty

Właściwości typu TCursor. Umożliwia wybór z listy zawierającej nazwy i obrazy poszczególnych kursorów.

TForm::Cursor

TEnumProperty

Właściwości wyliczeniowe. Poszczególne elementy typu wyliczeniowego wyświetlane są w rozwijalnej liście.

TForm::Align

TForm::BorderStyle

TFloatProperty

Właściwości zmiennoprzecinkowe (double, long double i float).

TF1Book::PrintLeftMargin

TF1Book::PrintRightMargin

TFontProperty

Właściwości określające czcionkę (TFont). Wyświetla standardowy dialog wyboru czcionki (przy kliknięciu wielokropka towarzyszącego właściwości) albo listę opublikowanych właściwości czcionki.

TForm::Font

TIntegerProperty

Właściwości całkowitoliczbowe.

TForm::Height

TForm::Width

TMethodProperty

Właściwości zdarzeniowe, stanowiące wskazanie na metody komponentu. W rozwijalnej liście wyświetlane są nazwy tych metod, których typ zgodny jest z typem edytowanej właściwości.

TForm::OnClick

TForm::OnClose

TOrdinalProperty

Właściwości typu porządkowego; z klasy tej wywodzą się właściwości dla poszczególnych typów porządkowych: całkowitoliczbowych (TIntegerProperty), znakowych (TCharProperty), wyliczeniowych (TEnumProperty), jak również typów zbiorowych (TSetProperty).

TPropertyEditor

Klasa bazowa dla wszystkich edytorów właściwości.

TSetElementProperty

Elementy właściwości typu zbiorowego. Edytowany element może zostać oznaczony jako należący do zbioru (true) lub nie należący do niego (false). Edycja właściwości sprowadza się do zaznaczania obecności (true) albo nieobecności (false) poszczególnych elementów w zbiorze.

TSetProperty

Właściwości typu zbiorowego. Edycja właściwości sprowadza się do zaznaczania obecności (true) albo nieobecności (false) poszczególnych elementów w zbiorze.

TForm::Anchors

TForm::BorderIcons

TStringProperty

Właściwości znakowe.

TForm::Hint

Przed przystąpieniem do projektowania specjalizowanego edytora dla określonej właściwości należy zastanowić się, czy edytor taki jest w ogóle potrzebny - być może wystarczający okaże się któryś z edytorów opisanych w tabeli 8.2; w przypadku tworzenia własnego komponentu może okazać się uzasadniona zmiana definicji niektórych jego właściwości. Jako że rozdział ten poświęcony jest jednak tworzeniu własnych edytorów, tego wątku nie będziemy dalej rozwijać.

Wyczerpujących informacji na temat najdrobniejszych szczegółów funkcjonowania dostępnych edytorów właściwości dostarczy z pewnością kod źródłowy ich implementacji (w Object Pascalu) dostępny w pliku DsgnIntf.pas w podkatalogu Source\ToolsApi lokalnej instalacji C++Buildera. Podobnie jak w przypadku tworzenia własnych komponentów, tak i w przypadku definiowania specjalizowanych edytorów właściwości istotnym zagadnieniem staje się wybór klasy bazowej. Od trafności tego wyboru zależy bowiem stopień wykorzystania istniejącego kodu, czyli po prostu oszczędność kodowania.

Wiele aspektów zachowania edytora właściwości określają jego atrybuty, których zestawienie znajduje się w tabeli 8.3. Stanowią one wartości typu TPropertyAttributes; zestaw atrybutów danego edytora zwracany jest przez jego metodę GetAttributes() jako wartość typu TPropertyAttributesSet.

Tabela 8.3. Ważniejsze atrybuty edytora właściwości

Wartość

Konsekwencje zastosowania

Metody wymagające (być może) przedefiniowania w klasie pochodnej

paAutoUpdate

Zmiany w edytowanej właściwości odzwierciedlane są na bieżąco w wyglądzie komponentu; przy braku tego atrybutu uaktualnienie wyglądu komponentu następuje po naciśnięciu klawisza Enter lub przeniesieniu skupienia poza edytowaną właściwość.

SetValue() - przekształca znakową reprezentację właściwości do jej postaci docelowej.

paDialog

W wierszu inspektora obiektów związanym z właściwością wyświetlany jest przycisk z wielokropkiem (ang. ellipsis), którego kliknięcie spowoduje uruchomienie dialogu określonego przez metodę Edit() edytora.

Edit() - określa scenariusz dialogu uruchamianego w wyniku kliknięcia przycisku z wielokropkiem.

paFullWidthName

Nazwa właściwości wypełnia całą szerokość okna inspektora obiektów - brak jest pola wartości.

paMultiSelect

Możliwa jest edycja właściwości w odniesieniu do kilku komponentów jednocześnie (szczegóły w dalszej części rozdziału).

paReadOnly

Właściwość nie może być modyfikowana w oknie inspektora obiektów.

paRevertable

Możliwe jest przywrócenie „oryginalnej” wartości właściwości za pomocą opcji Revert to Inherited menu kontekstowego inspektora obiektów.

paSortList

Jeżeli wartości danej właściwości dostępne są w postaci rozwijalnej listy, to występują w tej liście w kolejności posortowanej.

paSubProperties

Atrybut ten stanowi dla inspektora obiektów informację, iż z daną właściwością związane są podwłaściwości (ang. subproperties) podlegające edycji. Zewnętrznym tego przejawem jest poprzedzenie nazwy właściwości znakiem , którego kliknięcie spowoduje rozwinięcie listy wspomnianych podwłaściwości.

GetProperties() - wskazuje funkcję określającą sposób postępowania z podwłaściwościami.

paValueList

Dopuszczalne wartości dla danej właściwości (wszystkie lub niektóre) dostępne są do wyboru w postaci rozwijalnej listy.

GetValues() - definiuje poszczególne wartości do umieszczenia w liście, wywołując jednokrotnie dla każdej z nich funkcję przekazaną jako parametr.

Jak wynika z tabeli 8.3, użycie wybranych atrybutów wymaga zaimplementowania niektórych metod, pozostawionych jako puste w klasie bazowej, nie korzystającej z danego atrybutu. Do tego niezbędna jest oczywiście znajomość roli poszczególnych metod edytora właściwości - w tabeli 8.4 przestawiamy w związku z tym wirtualne i dynamiczne metody klasy TPropertyEditor, podlegające zazwyczaj przedefiniowaniu w klasach pochodnych.

Tabela 8.4. Wirtualne i dynamiczne metody edytorów właściwości

Metoda

Deklaracja i przeznaczenie

GetAttributes()

virtual TPropertyAttributes __fastcall GetAttributes(void);

Zwraca zbiór atrybutów charakterystycznych dla danego edytora.

GetValue()

virtual AnsiString __fastcall GetValue();

Zwraca łańcuch, reprezentujący wartość właściwości. W klasie TPropertyEditor zwraca wartość „(unknown)”.

SetValue()

virtual void __fastcall SetValue(const AnsiString Value);

Przekształca znakową reprezentację właściwości do jej typu docelowego. Jeżeli przekazany łańcuch jest nieprawidłowy dla danej właściwości, metoda ta powinna wygenerować wyjątek informujący o istocie błędu; zważywszy, iż metoda zwraca wynik amorficzny (void), zaś parametr wywołania jest niemodyfikowalny (const), wygenerowanie wyjątku stanowi jedyny sposób poinformowania o zaistniałym błędzie.

Edit()

virtual void __fastcall Edit(void);

Jeżeli wynik zwracany przez metodę GetAttributes() zawiera wartość paDialog, metoda Edit() wywoływana jest w reakcji na dwukrotne kliknięcie przycisku z wielokropkiem (ang. ellipsis) towarzyszącego właściwości w oknie inspektora obiektów. Metoda ta obowiązana jest także do weryfikacji wprowadzonych wyników - jeżeli nie są one prawidłowe, należy wygenerować wyjątek informujący o nieprawidłowościach.

GetValues()

virtual void __fastcall GetValues(Classes::TGetStrProc Proc);

Jeżeli wynik zwracany przez metodę GetAttributes() zawiera wartość paValueList, inspektor obiektów wywołuje tę metodę w celu skompletowania zawartości rozwijalnej listy związanej z edytowaną właściwością. Parametrem metody jest adres wewnętrznej funkcji IDE - każde wywołanie tej funkcji powoduje dodanie do wspomnianej listy kolejnej wartości przekazanej jako parametr. Treścią metody GetValues() powinien więc być ciąg wywołań funkcji przekazanej jako jej parametr - jednokrotnie dla każdej wartości, która pojawić się ma we wspomnianej liście.

Activate()

virtual void __fastcall Activate(void);

Wywoływana jest w momencie „podświetlenia” danej właściwości w oknie inspektora obiektów. Metoda ta ma pustą zawartość we wszystkich standardowych edytorach VCL, potencjalnie natomiast może być użyta do ustawienia zbioru atrybutów zwracanego przez metodę GetAttributes() - nie dotyczy to jednak atrybutów paSubProperties i paMultiSelect, których obecność (lub nieobecność) we wspomnianym zbiorze musi być już przesądzona w momencie wywołania metody Activate().

AllEqual()

virtual bool __fastcall AllEqual(void);

Jeżeli wynik zwracany przez metodę GetAttributes() zawiera wartość paMultiSelect, metoda ta wywoływana jest dla określenia, czy w stosunku do wszystkich zaznaczonych komponentów daje się określić wspólna wartość edytowanej właściwości. Jeżeli metoda zwróci wartość true, owa wspólna wartość powinna być zwrócona przez metodę GetValue(); jeżeli metoda zwróci wartość false, pole wartości edytowanej właściwości (w inspektorze obiektów) pozostaje niewypełnione.

AutoFill()

virtual bool __fastcall AutoFill(void);

Jeżeli wynik zwracany przez metodę GetAttributes() zawiera wartość paValueList, wynik zwracany przez niniejszą metodę określa, czy w stosunku do listy wyświetlanych wartości może być prowadzone wyszukiwanie przyrostowe (true), czy też inspektor obiektów ma pozostać niewrażliwy na jego próbę (false). Domyślnie metoda zwraca wartość true.

GetEditLimit()

virtual int __fastcall GetEditLimit(void);

Określa maksymalną długość łańcucha, jaki użytkownik może wprowadzić w charakterze znakowej reprezentacji edytowanej właściwości. Domyślnie metoda zwraca wartość 255.

GetName()

virtual AnsiString __fastcall GetName();

Określa nazwę edytowanej właściwości. Domyślnie nazwa ta pobierana jest z RTTI, z ewentualnym przekształceniem znaków podkreślenia na spacje. Przedefiniowanie niniejszej metody umożliwia prezentację właściwości w inspektorze obiektów pod nazwą inną niż jej nazwa w deklaracji klasy.

GetProperties()

virtual void __fastcall GetProperties(TGetPropEditProc Proc);

Odpowiedzialna jest za określenie listy podwłaściwości edytowanej właściwości - na podobnej zasadzie, jak czyniła to metoda GetValues() w stosunku do listy możliwych wartości. Parametrem wywołania metody GetProperties() jest adres wewnętrznej funkcji IDE, która powinna zostać wywołana jednokrotnie dla każdej podwłaściwości, mającej pojawić się w liście; argumentem wywołania powinien być egzemplarz edytora związanego z podwłaściwością - i tak na przykład dla podwłaściwości będącej elementem właściwości zbiorowej (edytor TSetProperty) jest to edytor pojedynczego elementu (TSetElementProperty), zaś dla właściwości obiektowej (TClassProperty) metoda GetProperties() korzysta z funkcji GetComponentProperties() w celu dostępu do poszczególnych (opublikowanych) podwłaściwości wskazywanego komponentu.

Initialize()

virtual void __fastcall Initialize(void);

Metodę tę wywołuje inspektor obiektów po utworzeniu obiektu edytora, bezpośrednio przed jego użyciem. Metoda ta nie jest wywoływana dla tych utworzonych egzemplarzy edytorów, które w ogóle nie zostaną użyte - co może się zdarzyć, gdy na formularzu zaznaczonych jest kilka komponentów: egzemplarze edytorów tworzone są bowiem dla wszystkich właściwości każdego z komponentów formularza, natomiast w inspektorze obiektów widoczne są wówczas tylko właściwości wspólne dla zaznaczonych komponentów. Domyślnie metoda Initialize() nie wykonuje żadnych czynności.

ListMeasureWidth()

DYNAMIC void __fastcall ListMeasureWidth(const AnsiString Value, Graphics::TCanvas* ACanvas, int &AWidth);

Wykorzystywana jest do obliczenia szerokości listy wartości (lub listy podwłaściwości) w sytuacji, gdy zbiór atrybutów zwracany przez metodę GetAttributes() zawiera wartość paOwnerDrawList. Metoda wywoływana jest jednokrotnie dla każdej wartości, mającej pojawić się w liście; parametr Value jest znakową reprezentacją tej wartości, ACanvas reprezentuje płótno, na którym nastąpi wyświetlenie pozycji, zaś AWidth odpowiedzialny jest za szerokość, na której pozycja zostanie wyświetlona - na wejściu do metody parametr ten inicjowany jest wartością standardowej szerokości i oczywiście może zostać zmieniony (przekazywany jest przez referencję), co jest konieczne między innymi w sytuacji, gdy tekstowej reprezentacji wartości towarzyszyć ma ikona.

ListMeasureHeight()

DYNAMIC void __fastcall ListMeasureHeight(const AnsiString Value, Graphics::TCanvas* ACanvas, int &AHeight);

Określa wysokość wyświetlanej pozycji na identycznej zasadzie, jak metoda ListMeasureWidth() określa jej szerokość.

ListDrawValue()

DYNAMIC void __fastcall ListDrawValue(const AnsiString Value, Graphics::TCanvas* ACanvas, const Windows::TRect &ARect, bool ASelected);

Odpowiedzialna jest za wypełnienie pozycji związanej z daną wartością w liście w sytuacji, gdy edytor wykorzystuje rysowanie specyficzne (tj. zbiór jego atrybutów zwracanych przez metodę GetAttributes() zawiera wartość paOwnerDrawList). Value jest znakową reprezentacją wyświetlanej wartości, ARect odpowiedzialny jest za prostokątny obszar (na płótnie ACanvas) przeznaczony dla jej wyświetlenia; początkowa wartość tego parametru obliczana jest z użyciem metod ListMeasureWidth() i ListMeasureHeight(), metoda ListDrawValue() może tę wartość zmodyfikować. Ostatni z parametrów (Selected) określa, czy wartość, na rzecz której wywołana została metoda, jest wartością aktualnie wybraną.

PropDrawName()

DYNAMIC void __fastcall PropDrawName(Graphics::TCanvas* ACanvas, const Windows::TRect &ARect, bool ASelected);

Odpowiedzialna jest za wyświetlenie nazwy właściwości w oknie inspektora obiektów; parametry ACanvas, ARect i Selected mają takie samo znaczenie, jak w przypadku metody ListDrawValue().

PropDrawValue()

DYNAMIC void __fastcall PropDrawValue(Graphics::TCanvas* Canvas, const Windows::TRect &ARect, bool ASelected);

Odpowiedzialna jest za wyświetlenie wartości właściwości w oknie inspektora obiektów; parametry ACanvas, ARect i Selected mają takie samo znaczenie, jak w przypadku metody ListDrawValue().

Do najczęściej przedefiniowywanych metod bazowej klasy edytora należą metody wymienione na pierwszych pięciu pozycjach tabeli 8.4; deklarację przykładowej klasy tego rodzaju - TCustomPropertyEditor - przedstawia wydruk 8.2.

Wydruk 8.2. Deklaracja przykładowej klasy niestandardowego edytora właściwości

class TCustomPropertyEditor : public TPropertyEditor

{

typedef TPropertyEditor inherited;

public:

virtual TPropertyAttributes __fastcall GetAttributes(void);

virtual AnsiString __fastcall GetValue();

virtual void __fastcall SetValue(const AnsiString Value);

virtual void __fastcall Edit(void);

virtual void __fastcall GetValues(Classes::TGetStrProc Proc);

protected:

#pragma option push -w-inl

inline __fastcall virtual

TCustomPropertyEditor(const _di_IFormDesigner ADesigner,

int APropCount)

: TPropertyEditor(ADesigner, APropCount)

{ }

#pragma option pop

public:

#pragma option push -w-inl

inline __fastcall virtual ~TCustomProperty(void) { }

#pragma option pop

};

Pewnego komentarza wymaga użyte na wydruku 8.2 słowo inherited, stanowiące coś więcej niż tylko synonim nazwy klasy bazowej („inherited” znaczy po angielsku to samo, co „odziedziczony”). Na gruncie Delphi jest ono istotnie słowem kluczowym, reprezentującym klasę bazową, w C++ nie ma ono samoistnego znaczenia i wymaga jawnego utożsamienia z identyfikatorem klasy bazowej za pomocą dyrektywy typedef. Z punktu widzenia semantyki C++ nie ma oczywiście różnicy (w kontekście deklaracji z wydruku 8.2) pomiędzy np. instrukcjami:

return TPropertyEditor::GetAttributes() << paValueList >> paMultiSelect

i

return inherited::GetAttributes() << paValueList >> paMultiSelect

występującymi w którejś z metod klasy TCustomPropertyEditor, druga z instrukcji wyraźnie jednak podkreśla związek z klasą bazową. Studiując zawartość plików *.hpp, zawierających definicje klas VCL w kategoriach C++Buildera, nietrudno zauważyć, iż opisana tendencja jest praktyką powszechnie stosowaną.

Przyjrzyjmy się teraz nieco dokładniej poszczególnym metodom przedefiniowanym przez klasę pochodną edytora.

Metoda GetAttributes()

Metoda ta jest bodaj najprostsza w implementacji, której istotą są różnice atrybutów definiowanego edytora w stosunku do klasy bazowej. Załóżmy na przykład, iż edytor TCustomPropertyEditor w przeciwieństwie do swego przodka TPropertyEditor ma posługiwać się rozwijalną listą wartości, nie zezwalając ponadto na edycję właściwości w warunkach zaznaczenia kilku komponentów: należy w tym celu wyłączyć atrybut paMultiSelect ze zbioru zwracanego przez metodę GetAttributes() klasy bazowej, jednocześnie dołączając do tegoż zbioru atrybut paValueList:

TPropertyAttributes __fastcall TCustomPropertyEditor::GetAttributes(void)

{

return inherited::GetAttributes() << paValueList >> paMultiSelect;

}

Tak naprawdę mamy tu do czynienia nie tyle z „wyłączaniem” (odpowiednio: „włączaniem”) atrybutu z (do) zbioru, ile z zapewnianiem obecności lub nieobecności danego atrybutu w zbiorze - niezależnie bowiem od tego, czy atrybut paMultiSelect występuje w zbiorze zwracanym przez TPropertyEditor::GetAttributes(), brak go będzie w zbiorze stanowiącym wynik TCustomPropertyEditor::GetAttributes(). Analogicznie ma się sprawa z atrybutem paValueList, który bezwarunkowo obecny będzie w zbiorze atrybutów edytora TCustomPropertyEditor. Wynika to wprost z definicji sumy i różnicy zbiorów (w rozumieniu teorii mnogości).

Metoda GetValue()

Wynikiem metody GetValue() jest uniwersalna, znakowa reprezentacja wartości edytowanej właściwości. Jej uzyskanie musi być oczywiście poprzedzone odczytaniem tej wartości w oryginalnej postaci, co ułatwiają pomocnicze metody klasy TPropertyEditor dedykowane standardowym typom właściwości. Niektóre z tych metod prezentujemy w tabeli 8.5.

Tabela 8.5. Pomocnicze metody odczytujące wartość właściwości

Metoda

Zwraca wartość…

GetFloatValue()

… typu Extended (long double) równoważną wartości właściwości zmiennoprzecinkowej (typu float, double i long double).

GetInt64Value()

… właściwości typu __int64 (64-bitowa liczba całkowita ze znakiem).

GetMethodValue()

… właściwości zdarzeniowej w postaci następującej struktury:

struct TMethod
{
void *Code;
void *Data;
}

GetOrdValue()

… właściwości typu porządkowego (char, signed char, unsigned char, int, unsigned, short i long) jako równoważną wartość typu int. Może być także używana do właściwości będących wskaźnikami, wymaga to jednak rzutowania reinterpret_cast<>.

GetStrValue()

… właściwości łańcuchowej (AnsiString).

GetVarValue()

… typu Variant, równoważną co do typu i wartości edytowanej właściwości. Klasa Variant, której deklarację zawiera plik nagłówkowy sysvari.h, jest reprezentacją na gruncie C++Buildera pascalowego typu o tej samej nazwie.

Oto przykład metody GetValue() edytora właściwości typu char:

AnsiString __fastcall TMyCharPropertyEditor::GetValue()

{

char ch = static_cast<char>(GetOrdValue());

if (ch > 32 && ch < 128)

{

return ch;

}

else

{

return AnsiString().sprintf("#%d", ch);

}

}

Zwróć uwagę, iż tzw. białe znaki (tj. znaki o kodzie mniejszym lub równym spacji), a także znaki z „górnej połówki” (tj. o kodzie większym lub równym 128) reprezentowane są w postaci kodu dziesiętnego poprzedzonego znakiem #.

Metoda SetValue()

Zadaniem metody SetValue() jest nadanie edytowanej właściwości wartości określonej przez łańcuch, będący parametrem wywołania. Łańcuch ten musi najpierw zostać skonwertowany do wartości o typie zgodnym z typem właściwości, po czym wartość ta przypisywana jest właściwości za pomocą jednej z metod pomocniczych przedstawionych w tabeli 8.6.

Tabela 8.6. Pomocnicze metody przypisujące wartość właściwości

Metoda

Nadaje wartość właściwości…

SetFloatValue()

… zmiennoprzecinkowej (typu float, double i long double) równoważną parametrowi typu Extended (long double).

SetInt64Value()

… typu __int64 (64-bitowa liczba całkowita ze znakiem).

SetMethodValue()

… zdarzeniowej na podstawie struktury TMethod, stanowiącej parametr wywołania.

SetOrdValue()

… typu porządkowego (char, signed char, unsigned char, int, unsigned, short i long) na podstawie parametru typu int. Może być także używana do nadawania wartości właściwości wskaźnikowej, wymaga to jednak rzutowania reinterpret_cast<>.

SetStrValue()

… łańcuchowej (AnsiString).

SetVarValue()

… obiektowej typu Variant.

Jednym z aspektów konwersji - pomiędzy znakową a docelową reprezentacją właściwości - jest niewykonalność tej konwersji, na co metoda SetValue() powinna zareagować wygenerowaniem stosownego wyjątku. Spójrzmy na poniższy przykład, dotyczący edytora właściwości całkowitej bez znaku:

void __fastcall TMyUnsignedPropertyEditor::SetValue(const AnsiString Value)

{

if (Value.ToInt() < 0)

{

throw EPropertyError("Wartość właściwości nie może być ujemna");

}

else

{

SetOrdValue(Value.ToInt());

}

}

Przede wszystkim jeżeli przekazany łańcuch Value nie jest konwertowalny do liczby całkowitej, jego wywoływana metoda ToInt() wygeneruje wyjątek EConvertError. Jeżeli natomiast skonwertowany łańcuch reprezentuje liczbę ujemną, metoda SetValue() wygeneruje charakterystyczny dla niej (we wszystkich standardowych klasach edytorów VCL) wyjątek EPropertyError.

Metoda Edit()

Zasadniczym zadaniem metody Edit() jest uczynienie edycji właściwości bardziej intuicyjną i przyjaźniejszą dla użytkownika; założenie to realizuje się najczęściej za pomocą specjalizowanego, modalnego okna dialogowego. Dla celów odczytu i zapisu edytowanej właściwości metoda Edit() może posługiwać się zarówno metodami GetValue() i SetValue(), jak również metodami pomocniczymi opisanymi w tabelach 8.5 i 8.6. Nie pisaliśmy jeszcze o tym, iż metody GetValue() i SetValue() są w istocie metodami dostępowymi właściwości Value, o wiele wygodniejszej w użyciu:

class PASCALIMPLEMENTATION TPropertyEditor : public System::TObject

{

...

__property AnsiString Value = {read=GetValue, write=SetValue};

...

}

Jeżeli chodzi o synchronizację danych uczestniczących w dialogu z aktualną wartością właściwości, to możliwe są dwa podejścia. W pierwszym z nich wszelkie ustawienia w oknie dialogowym odzwierciedlane są na bieżąco w edytowanej właściwości, zgodnie z drugim natomiast właściwość aktualizowana jest dopiero po zakończeniu dialogu z wartością nakazującą utrwalenie zmian (mrOK).

Zajmijmy się przez chwilę pierwszą ze wspomnianych koncepcji, czyli aktualizowaniem edytowanej właściwości na bieżąco; w przełożeniu na kod programu jest to aktualizowanie właściwości Value macierzystego obiektu-edytora. Zadanie to wykonuje się odmiennie dla dwóch różnych grup właściwości - tych „skalarnych”, reprezentowanych przez pojedynczą wartość, i tych obiektowych, określanych przez zestaw wartości-podwłaściwości, jak np. czcionki (jakkolwiek te ostatnie nie korzystają zazwyczaj z aktualizacji na bieżąco). Zwróćmy uwagę, iż formularz dialogowy wyświetlany jest w trybie modalnym, wszelkie więc czynności synchronizacyjne wykonywać się muszą niejako „w tle” - z punktu widzenia kodu metody Edit() cały dialog modalny jest bowiem po prostu wywołaniem metody ShowModal() formularza dialogowego.

Wskaźnik do edytowanej właściwości obiektowej uzyskuje się za pomocą metody GetOrdValue() macierzystego obiektu-edytora, powiązanej z rzutowaniem reinterpretacyjnym (reinterpret_cast<>); wszelkie operacje zdeterminowane przez scenariusz dialogu mogą być odtąd bez kłopotu odniesione do wskazywanego obiektu.

Aktualizowanie właściwości „skalarnej” jest trochę bardziej skomplikowane, wiąże się bowiem z koniecznością częstego wywoływania metod SetValue() i GetValue() jej edytora. Efekt ten najprościej osiągnąć można, deklarując referencję do właściwości Value obiektu-edytora i używając konsekwentnie tej referencji jako pośrednika w odwołaniach do identyfikowanej właściwości. Referencję tę można bez trudu przekazać jako dodatkowy parametr konstruktora formularza dialogowego.

Niezależnie od charakteru edytowanej właściwości, zalecane jest także zapamiętanie jej początkowych ustawień, by łatwo można było wycofać się z dialogu i uznać go za niebyły; jest to oczywiście znacznie prostsze w przypadku właściwości „skalarnych” niż obiektowych.

Poniższe wydruki - 8.3 i 8.4 - przedstawiają praktyczną realizację opisanej koncepcji w odniesieniu (odpowiednio) do właściwości obiektowej i „skalarnej”.

Wydruk 8.3. Dialogowa edycja synchroniczna właściwości obiektowej

// Najważniejsze fragmenty kodu formularza TMyPropertyForm

// W PLIKU NAGŁÓWKOWYM

//---------------------------------------------------------------------------//

#ifndef MyPropertyFormH

#define MyPropertyFormH

//---------------------------------------------------------------------------//

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

#include "HeaderDeclaringTPropertyClass"

//---------------------------------------------------------------------------//

class TMyPropertyForm : public TForm

{

__published: // komponenty obsługiwane przez IDE

private:

TPropertyClass* FPropertyClass;

...

tutaj inne deklaracje umożliwiające przechowanie ustawień początkowych

...

protected:

void __fastcall SetPropertyClass(TPropertyClass* Pointer);

public:

__fastcall TMyPropertyForm(TComponent* Owner);

__property TPropertyClass* PropertyClass = {read=FPropertyClass,

write=SetPropertyClass};

...

};

//---------------------------------------------------------------------------//

#endif

// W PLIKU IMPLEMENTACYJNYM

//---------------------------------------------------------------------------//

#include <vcl.h>

#pragma hdrstop

#include "MyPropertyForm.h"

//---------------------------------------------------------------------------//

#pragma package(smart_init)

#pragma resource "*.dfm"

//---------------------------------------------------------------------------//

__fastcall TMyPropertyForm::TMyPropertyForm(TComponent* Owner)

: TForm(Owner)

{

}

//---------------------------------------------------------------------------//

void __fastcall TMyPropertyForm::SetPropertyClass(TPropertyClass* Pointer)

{

FPropertyClass = Pointer;

if(FPropertyClass != 0)

{

...

zapamiętaj aktualne ustawienia edytowanej właściwości

...

}

}

//---------------------------------------------------------------------------//

// METODA Edit()

#include "MyPropertyForm.h" // nie zapomnij o dołączeniu pliku nagłówkowego

void __fastcall TCustomPropertyEditor::Edit(void)

{

// Utwórz formularz

std::auto_ptr<TMyPropertyForm*>

MyPropertyForm(new TMyPropertyForm(Application0));

// Skojarz go z macierzystym edytorem właściwości

MyPropertyForm->PropertyClass

= reinterpret_cast<TPropertyClass*>(GetOrdValue());

// Wyświetl formularz w trybie modalnym

MyPropertyForm->ShowModal();

}

//---------------------------------------------------------------------------//

Wydruk 8.4 jest nieco krótszy, ograniczyliśmy się bowiem jedynie do wskazania różnic w stosunku do wydruku 8.3:

Wydruk 8.4. Dialogowa edycja synchroniczna właściwości typu int

// Najważniejsze fragmenty kodu formularza TMyPropertyForm

//---------------------------------------------------------------------------//

// RÓŻNICE W PLIKU NAGŁÓWKOWYM:

class TMyPropertyForm : public TForm

{

__published: // komponenty obsługiwane przez IDE

private:

AnsiString& Value;

int OldValue;

...

public:

__fastcall TMyPropertyForm(TComponent* Owner, AnsiString& PropertyValue);

...

};

//---------------------------------------------------------------------------//

#endif

//---------------------------------------------------------------------------//

// W PLIKU IMPLEMENTACYJNYM NALEŻY ZMODYFIKOWAĆ KONSTRUKTOR:

//----------------------------------------------------------------------//

__fastcall TMyPropertyForm::TMyPropertyForm(TComponent* Owner,

AnsiString& PropertyValue)

: TForm(Owner),Value(PropertyValue)

{

OldValue = Value.ToInt(); // przechowanie początkowej wartości właściwości

}

//---------------------------------------------------------------------------//

// METODA Edit(), PRAWIE NIEZMIENIONA:

#include "MyPropertyForm.h" // nie zapomnij o dołączeniu pliku nagłówkowego

void __fastcall TCustomPropertyEditor::Edit(void)

{

// Create the form as before, but pass the extra parameter!

// Utwórz formularz jak poprzednio, przekazując jednak dodatkowy parametr

// (referencję do właściwości Value macierzystego edytora właściwości)

std::auto_ptr<TMyPropertyForm*>MyPropertyForm(

new TMyPropertyForm(Application0, Value));

// Wyświetl formularz w trybie modalnym

MyPropertyForm->ShowModal();

}

//---------------------------------------------------------------------------//

Druga ze wspomnianych koncepcji - „odłożona” aktualizacja po zakończeniu dialogu - jest praktyką częściej spotykaną. Zarys realizującej tę koncepcję metody Edit() przedstawia wydruk 8.5.

Wydruk 8.5. Dialogowa edycja z aktualizacją po zakończeniu dialogu

// nie zapomnij o dołączeniu pliku nagłówkowego zawierającego

// deklarację formularza dialogowego TMyPropertyDialog

#include "MyPropertyDialog.h"

void __fastcall TCustomPropertyEditor::Edit(void)

{

// utwórz formularz

std::auto_ptr<TMyPropertyDialog*> MyPropertyDialog(

new TMyPropertyDialog(Application));

...

ustaw początkowe wartości dialogu

...

// wyświetl formularz w trybue modalnym i zbadaj kod powrotu

if(MyPropertyDialog->ShowModal() == IDOK)

{

...

aktualizuj edytowaną właściwość

...

}

}

Formularz TMyProperty dialog niekoniecznie musi być sam przez się formularzem dialogowym, lecz wykorzystywać może specjalizowany komponent dialogowy, wywołując metodę Execute() tego ostatniego (o tym, jak przekształcić formularz dialogowy w niezależny komponent, możesz przeczytać w systemie pomocy C++Buildera pod hasłem „Making a Dialog Box a Component”).

Metoda GetValues()

W celu skompletowania wartości, tworzących rozwijalną listę podczas edycji właściwości, inspektor obiektów wywołuje metodę GetValues() edytora, przekazując jej adres swej wewnętrznej funkcji rejestracyjnej. Każdorazowe wywołanie tej funkcji spowoduje dodanie do listy nowej wartości w brzmieniu określonym przez parametr wywołania. Zasadniczo więc treść metody GetValues() powinna być ciągiem wywołań wspomnianej procedury rejestracyjnej, na przykład:

void __fastcall ModemSpeedPropertyEditor::GetValues(Classes::TGetStrProc Proc)

{

Proc(" 9600");

Proc(" 19200");

Proc(" 28800");

Proc(" 33600");

Proc(" 57600");

Proc("szybciej");

}

Właściwości klasy TPropertyEditor

Jedną z właściwości edytora - Value - opisaliśmy już przed chwilą. Oprócz niej klasa TPropertyEditor definiuje jeszcze trzy następujące, tylko do odczytu:

Właściwości i wyjątki

Zazwyczaj nie każda wartość zgodna z typem właściwości jest dla tej właściwości wartością poprawną. Prawidłowo skonstruowane aplikacje, edytory komponentów i edytory właściwości powinny wychwytywać próby nadawania właściwościom komponentu niedozwolonych wartości i reagować na nie generowaniem stosownych wyjątków. Generalnie, wartość właściwości może być modyfikowana w trzech następujących obszarach:

Kolejność, w jakiej wymieniono powyższe etapy, określa najbardziej ogólny schemat przepływu informacji w procesie edytowania właściwości, co schematycznie ilustruje rysunek 8.2; jak wynika z tego rysunku, etap pierwszy lub obydwa początkowe etapy mogą w ogóle nie wystąpić. Powstaje wobec tego pytanie - w którym z powyższych etapów zrealizować testowanie poprawności specyfikowanej wartości?

Rysunek 8.2. Badanie poprawności wartości przypisywanej właściwości komponentu

Najprostsza w implementacji jest oczywiście weryfikacja na etapie odrębnego dialogu modalnego - zakończenie tego dialogu z wartością „akceptującą” (mrOK) może być bowiem w łatwy sposób uzależnione od poprawności wprowadzonych danych.

Jeżeli edytor właściwości nie posługuje się odrębnym dialogiem, weryfikację wprowadzonej (w oknie inspektora obiektów) właściwości przeprowadzić można w treści jego metody SetValue() - w przypadku negatywnego wyniku weryfikacji wykonywanie metody zostaje przerwane i do przypisania niepoprawnej wartości po prostu nie dochodzi. Jeżeli jednak weryfikowana wartość pochodzi z odrębnego dialogu, jej odrzucenie przez metodę SetValue() może stwarzać konieczność powrotu do poprzedniego etapu, czyli wspomnianego dialogu, co jest już zadaniem nieco trudniejszym.

Najtrudniejszą do zrealizowania weryfikacją jest weryfikacja przeprowadzana przez metodę dostępową właściwości docelowej - odrzucenie proponowanej wartości na tym etapie może wiązać się z koniecznością powrotu do jednego z dwóch poprzednich etapów. Problem ten nie występuje oczywiście podczas wykonywania skompilowanej aplikacji, na tym bowiem etapie edytory właściwości nie mają nic do powiedzenia.

Rozwiązaniem najbardziej eleganckim - i jednocześnie najtrudniejszym w realizacji - wydaje się być niezależna weryfikacja prowadzona na każdym z wymienionych etapów, zgodnie z właściwymi tym etapom kryteriami. I tak np. w ramach niezależnego dialogu można zapewnić jedynie to, iż wprowadzany łańcuch znaków będzie miał postać konwertowalną do liczby całkowitej; metoda SetValue() edytora mogłaby sprawdzać, czy wynik tej konwersji jest liczbą nieujemną, zaś metoda dostępowa właściwości docelowej badałaby poprawność przypisywanej wartości w kontekście np. innych właściwości komponentu.

Rejestracja edytora właściwości

Pod pojęciem „rejestracji” rozumiemy tu zintegrowanie zdefiniowanej klasy edytora właściwości ze środowiskiem IDE, by inspektor obiektów świadom był jego istnienia i mógł z nim współpracować. Sama czynność rejestracji nie jest specjalnie skomplikowana i ogranicza się do wywołania funkcji RegisterPropertyEditor() (w ramach funkcji Register(), podobnie jak w przypadku rejestracji komponentu) - pewnego komentarza wymagają jednak parametry tego wywołania. Deklaracja funkcji RegisterPropertyEditor() znajduje się w pliku nagłówkowym dsgnintf.hpp i ma następującą postać:

extern PACKAGE void __fastcall RegisterPropertyEditor(

Typinfo::PTypeInfo PropertyType,

TMetaClass* ComponentClass,

const AnsiString PropertyName,

TMetaClass* EditorClass);

Znaczenie poszczególnych parametrów jej wywołania wyjaśnione jest w tabeli 8.7.

Tabela 8.7. Parametry wywołania funkcji RegisterPropertyEditor()

Parametr

Opis

PropertyType

Identyfikuje typ właściwości, do której ma zastosowanie rejestrowany edytor. Jeżeli typ ten jest wskazaniem na klasę z biblioteki VCL (lub pochodną), żądaną informację uzyskać można za pomocą makra __typeinfo, na przykład:

__typeinfo(TDialer).

W przeciwnym wypadku należy posłużyć się analogiczną wartością podobnej właściwości. Zajmiemy się tym zagadnieniem już za chwilę.

ComponentClass

Określa, czy rejestrowany edytor ma zostać wykorzystywany do „pasującej” właściwości w dowolnym komponencie, czy tylko w komponencie określonego typu. W pierwszym przypadku należy nadać parametrowi wartość 0, w drugim należy przypisać mu informację o typie komponentu, uzyskaną za pomocą operatora __classid(), na przykład:

__classid(TPPPConnection).

PropertyName

Określa nazwę, którą nosić musi właściwość, by miał do niej zastosowanie rejestrowany edytor. Jeżeli parametr ten jest pustym łańcuchem, lub jeżeli parametr ComponentClass ma wartość 0, edytor stosowany będzie do dowolnej właściwości zgodnej z parametrami PropertyType i ComponentClass.

EditorClass

Zawiera informację o typie rejestrowanego edytora, zwracaną przez operator __classid(), na przykład:

__classid(TDialerPropertyEditor).

Pewnego wyjaśnienia wymaga pierwszy z parametrów - PropertyType - określający typ właściwości związanej z rejestrowanym edytorem. Jeżeli typ ten jest klasą pochodną od klasy TObject, metoda ClassInfo() tej ostatniej zawiera już żądaną informację, dla wygody obudowaną w makro __typeinfo zdefiniowane w pliku Include\Vcl\sysmac.h:

#define __typeinfo(type) (PTypeInfo)TObject::ClassInfo(__classid(type))

Jeżeli jednak odnośna właściwość nie wywodzi się z klasy TObject, informację o jej typie uzyskać można z informacji PTypeInfo o jej klasie macierzystej, można też stworzyć ją ręcznie. Struktura TTypeInfo (na którą wskazuje typ PTypeInfo) zdefiniowana jest w pliku Include\Vcl\typinfo.hpp w następujący sposób:

struct TTypeInfo

{

TTypeKind Kind;

System::ShortString Name;

};

TTypeKind jest typem wyliczeniowym zdefiniowanym w tym samym pliku - jego elementy reprezentują poszczególne kategorie typów:

enum TTypeKind { tkUnknown, tkInteger, tkChar,

tkEnumeration, tkFloat, tkString,

tkSet, tkClass, tkMethod,

tkWChar, tkLString, tkWString,

tkVariant, tkArray, tkRecord,

tkInterface, tkInt64, tkDynArray };

typedef Set<TTypeKind, tkUnknown, tkDynArray> TTypeKinds;

Pole Name struktury TTypeInfo zawiera nazwę odnośnego typu - na przykład „int” lub „AnsiString”.

Pokażemy teraz szczegółowo, w jaki sposób wykorzystać przedstawione struktury do uzyskania interesującej nas informacji o typie właściwości.

Uzyskiwanie informacji o typie właściwości spoza biblioteki VCL

Opisywane tu postępowanie wymaga istnienia (czyli uprzedniego zaimplementowania) klasy VCL, której konkretną właściwość będziemy badać. Dysponując wówczas informacją PTypeInfo na temat samej klasy, jak również nazwą jej konkretnej właściwości, możemy uzyskać szczegółową informację na temat tej ostatniej. Informacja ta kryje się w polu PropType następującej struktury, stanowiącej jeden z elementów informacji o typach wykonywanej aplikacji (ang. RTTI - Runtime Type Information), która to struktura zdefiniowana jest w pliku Include\Vcl\typinfo.hpp:

struct TPropInfo

{

PTypeInfo *PropType;

void *GetProc;

void *SetProc;

void *StoredProc;

int Index;

int Default;

short NameIndex;

System::ShortString Name;

};

Wskaźnik do struktury TPropInfo, związanej z konkretną właściwością, otrzymać można za pomocą funkcji GetPropInfo() o następującej deklaracji:

extern PACKAGE PpropInfo __fastcall GetpropInfo(PTypeInfo TypeInfo,

const AnsiString PropName);

Pierwszy z parametrów wskazuje na informację o typie danej klasy, drugi zawiera nazwę odnośnej właściwości. Tak naprawdę funkcja GetPropInfo() jest funkcją przeciążoną (overloaded) i powyższa deklaracja określa tylko jeden z jej aspektów. Korzystają z niego trzy pozostałe aspekty; wszystkie one narzucają dodatkowe ograniczenia na dopuszczalny typ właściwości - ostatni parametr TTypeKinds jest zbiorem dopuszczalnych typów - dwa z nich natomiast w odmienny sposób postrzegają przedmiotową klasę, poprzez (odpowiednio) jej typ oraz wskaźnik do konkretnego egzemplarza:

extern PACKAGE PPropInfo __fastcall GetPropInfo(PTypeInfo TypeInfo,

const AnsiString PropName,

TTypeKinds AKinds);

extern PACKAGE PPropInfo __fastcall GetPropInfo(TMetaClass* AClass,

const AnsiString PropName,

TTypeKinds AKinds);

extern PACKAGE PPropInfo __fastcall GetPropInfo(System::TObject* Instance,

const AnsiString PropName,

TTypeKinds AKinds);

Pragnąc na przykład uzyskać informację o właściwości TFont::Name, zawierającej nazwę czcionki, uzyskujemy wpierw wskaźnik do odnośnej struktury TPropInfo

PPropInfo FontNamePropInfo = Typinfo::GetPropInfo(__typeinfo(TFont), "Name");

a następnie odczytujemy jej pole PropType:

PTypeInfo FontNameTypeInfo = *FontNamePropInfo->PropType;

Zmienna FontNameTypeInfo może być teraz wprost użyta jako pierwszy parametr wywołania funkcji RegisterPropertyEditor(). Można oczywiście zrezygnować ze zmiennych roboczych i przekazać w charakterze pierwszego parametru następujące wyrażenie:

*(Typinfo::GetPropInfo(__typeinfo(TFont), "Name"))->PropType;

Zauważmy jednocześnie, iż uzyskana informacja jest charakterystyczna nie dla konkretnej właściwości, lecz dla konkretnego jej typu - w tym przypadku AnsiString; może więc zostać wykorzystana dla dowolnej właściwości typu AnsiString.

Aby opisane postępowanie zakończyło się sukcesem, badana właściwość musi być opublikowaną właściwością którejś z istniejących klas VCL - dla właściwości niepublikowanych funkcja GetPropInfo() zwraca bowiem pusty wskaźnik. Należy także zachować ostrożność pod względem równoważności typów badanych właściwości. Właściwość (na przykład) TTimer::Interval jest właściwością typu Cardinal, który deklarowany jest (przez typedef) w pliku sysmac.h jako synonim typu unsigned int; można by na tej podstawie przypuszczać, iż informacja o typie właściwości TTimer::Interval daje się użyć w stosunku do jakiejś właściwości typu unsigned int - nie jest to niestety prawdą: aby uzyskać informację na temat właściwości typu unsigned int, musimy dysponować przynajmniej jedną zaimplementowaną klasą VCL, posiadającą opublikowaną właściwość dokładnie tego typu. W razie konieczności należy klasę taką zdefiniować ad hoc - czego przykładem jest wydruk 8.6, deklarujący klasę, zawierającą opublikowane właściwości podstawowych dla C++ typów; właściwości te nie reprezentują oczywiście żadnych użytecznych informacji, istotny jest jedynie sam fakt ich zdefiniowania i opublikowania.

Wydruk 8.6. Przykład klasy publikującej podstawowe typy właściwości, nie wywodzące się z VCL

class PACKAGE TNonVCLTypesClass : public TObject

{

public:

__published:

// Podstawowe typy całkowitoliczbowe

__property int IntProperty = {};

__property unsigned int UnsignedIntProperty = {};

__property short int ShortIntProperty = {};

__property unsigned short int UnsignedShortIntProperty = {};

__property long int LongIntProperty = {};

__property unsigned long int UnsignedLongIntProperty = {};

__property char CharProperty = {};

__property unsigned char UnsignedCharProperty = {};

__property signed char SignedCharProperty = {};

// Podstawowe typy zmiennoprzecinkowe

__property double DoubleProperty = {};

__property long double LongDoubleProperty = {};

__property float FloatProperty = {};

// Podstawowe typy boolowskie

__property bool BoolProperty = {};

// Typ AnsiString

__property AnsiString AnsiStringProperty = {};

private:

// #pragma option push -w-inl

inline __fastcall TNonVCLTypesClass() : TObject()

{ }

};

Chcąc teraz zarejestrować edytor dla właściwości typu unsigned int, nie będziemy mieli najmniejszych problemów z informacją o tymże typie:

RegisterPropertyEditor(

*(Typinfo::GetPropInfo(

__typeinfo(TNonVCLTypesClass),

"UnsignedIntProperty")

)->PropType,

__classid(TTestComponent),

"Size",

__classid(TUnsignedProperty)

);

Mimo względnej prostoty konstrukcje takie jak powyższa niezbyt są jednak wygodne w czytaniu; mając to na względzie, skonstruowaliśmy klasę TNonVCLTypeInfo, udostępniającą wspomniane informacje pod postacią swych metod - dzięki niej powyższa instrukcja upraszcza się do nieco czytelniejszej postaci:

RegisterPropertyEditor(

TNonVCLTypeInfo::UnsignedInt(),

__classid(TTestComponent),

"Size",

__classid(TUnsignedProperty)

);

Deklarację i implementację klasy TNonVCLTypeInfo przedstawiają wydruki 8.7 i 8.8. Prezentowany kod znajduje się również na dołączonej do książki płycie CD-ROM.

Wydruk 8.7. Deklaracja klasy TNonVCLTypeInfo

//----------------------------------------------------------------------//

#ifndef NonVCLTypeInfoH

#define NonVCLTypeInfoH

//----------------------------------------------------------------------//

#ifndef TypInfoHPP

#include <TypInfo.hpp>

#endif

//----------------------------------------------------------------------//

//class __declspec(delphiclass) TNonVCLTypesClass;

class PACKAGE TNonVCLTypeInfo : public TObject

{

public:

// Podstawowe typy całkowitoliczbowe

static PTypeInfo __fastcall Int();

static PTypeInfo __fastcall UnsignedInt();

static PTypeInfo __fastcall ShortInt();

static PTypeInfo __fastcall UnsignedShortInt();

static PTypeInfo __fastcall LongInt();

static PTypeInfo __fastcall UnsignedLongInt();

static PTypeInfo __fastcall Char();

static PTypeInfo __fastcall UnsignedChar();

static PTypeInfo __fastcall SignedChar();

// Podstawowe typy zmiennoprzecinkowe

static PTypeInfo __fastcall Double();

static PTypeInfo __fastcall LongDouble();

static PTypeInfo __fastcall Float();

// Podstawowy typ boolowski

static PTypeInfo __fastcall Bool();

// Klasa AnsiString

static PTypeInfo __fastcall AnsiString();

private:

// #pragma option push -w-inl

inline __fastcall TNonVCLTypeInfo() : TObject()

{ }

};

//----------------------------------------------------------------------//

Tu znajduje się deklaracja klasy TNonVCLTypesClass, prezentowana na wydruku 8.6

//----------------------------------------------------------------------//

#endif

Wydruk 8.8. Implementacja klasy TNonVCLTypeInfo

//----------------------------------------------------------------------//

#include <vcl.h>

#pragma hdrstop

#include "NonVCLTypeInfo.h"

//----------------------------------------------------------------------//

#pragma package(smart_init)

//----------------------------------------------------------------------//

PTypeInfo __fastcall TNonVCLTypeInfo::Int()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"IntProperty"))->PropType;

}

PTypeInfo __fastcall TNonVCLTypeInfo::UnsignedInt()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"UnsignedIntProperty"))->PropType;

}

//----------------------------------------------------------------------//

PTypeInfo __fastcall TNonVCLTypeInfo::ShortInt()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"ShortIntProperty"))->PropType;

}

PTypeInfo __fastcall TNonVCLTypeInfo::UnsignedShortInt()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"UnsignedShortIntProperty"))->PropType;

}

//----------------------------------------------------------------------//

PTypeInfo __fastcall TNonVCLTypeInfo::LongInt()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"LongIntProperty"))->PropType;

}

PTypeInfo __fastcall TNonVCLTypeInfo::UnsignedLongInt()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"UnsignedLongIntProperty"))->PropType;

}

//----------------------------------------------------------------------//

PTypeInfo __fastcall TNonVCLTypeInfo::Char()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"CharProperty"))->PropType;

}

PTypeInfo __fastcall TNonVCLTypeInfo::UnsignedChar()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"UnsignedCharProperty"))->PropType;

}

PTypeInfo __fastcall TNonVCLTypeInfo::SignedChar()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"SignedCharProperty"))->PropType;

}

//----------------------------------------------------------------------//

PTypeInfo __fastcall TNonVCLTypeInfo::Double()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"DoubleProperty"))->PropType;

}

PTypeInfo __fastcall TNonVCLTypeInfo::LongDouble()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"LongDoubleProperty"))->PropType;

}

PTypeInfo __fastcall TNonVCLTypeInfo::Float()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"FloatProperty"))->PropType;

}

//----------------------------------------------------------------------//

PTypeInfo __fastcall TNonVCLTypeInfo::Bool()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"BoolProperty"))->PropType;

}

//----------------------------------------------------------------------//

PTypeInfo __fastcall TNonVCLTypeInfo::AnsiString()

{

return *(Typinfo::GetPropInfo(__typeinfo(TNonVCLTypesClass),

"AnsiStringProperty"))->PropType;

}

//----------------------------------------------------------------------//

Mówiąc o zgodności typów właściwości - pod kątem zamiennego użycia związanych z nimi informacji TTypeInfo - nie wolno zapomnieć o jeszcze jednym istotnym fakcie. Otóż dla właściwości określonego typu informacja ta będzie różna w zależności od tego, czy klasę posiadającą tę właściwość zaimplementowano w Object Pascalu, czy w C++. I tak na przykład informacja zwracana przez TNonVCLTypeInfo::Char() będzie nieodpowiednia dla właściwości (na przykład) TMaskEdit::PasswordChar typu char - ta ostatnia zaimplementowana jest bowiem w Object Pascalu (w pliku Source\Vcl\mask.pas) i związaną z nią informację należy pobrać wprost z klasy TMaskEdit:

TPropInfo* VCLCharPropInfo = Typinfo::GetPropInfo(

__typeinfo(TMaskEdit),

"PasswordChar"

);

...

// rejestracja edytora dla właściwości typu char

// rodzimych komponentów VCL

RegisterPropertyEditor(

VCLCharPropInfo,

0,

"",

__classid(TCharPropertyEditor)

);

// rejestracja edytora dla właściwości typu char

// komponentów zaimplementowanych w C++

RegisterPropertyEditor(

TNonVCLTypeInfo::Char(),

0,

"",

__classid(TCharPropertyEditor)

);

Samodzielne tworzenie informacji o typie właściwości spoza biblioteki VCL

Alternatywą dla uzyskiwania wskaźnika do żądanej struktury TTypeInfo jest samodzielne jej stworzenie i posługiwanie się jej adresem. Egzemplarz struktury może być statyczny, może też być przydzielony dynamicznie; może też zostać ukryty przed użytkownikiem w ciele funkcji, zwracającej jedynie jego wskaźnik. Ideę tę ilustruje kod prezentowany na wydruku 8.9.

Wydruk 8.9. Tworzenie struktury TTypeInfo

//---------------------------------------------------------------------------//

// Wskaźnik do struktury udostępniany przez funkcję //

//---------------------------------------------------------------------------//

TTypeInfo* __fastcall AnsiStringTypeInfo(void)

{

static TTypeInfo TypeInfo;

TypeInfo.Name = "AnsiString";

TypeInfo.Kind = tkLString;

return &TypeInfo;

}

// albo

TTypeInfo* __fastcall AnsiStringTypeInfo(void)

{

TTypeInfo* TypeInfo = new TTypeInfo;

TypeInfo->Name = "AnsiString";

TypeInfo->Kind = tkLString;

return TypeInfo;

}

...

//---------------- Wywołanie funkcji rejestrującej--------------------//

RegisterPropertyEditor(AnsiStringTypeInfo(),

0 ,

"",

__classid(TAnsiStringPropertyEditor));

//---------------------------------------------------------------------------//

// Jawne egzemplarze struktury

//

//---------------------------------------------------------------------------//

// statyczny egzemplarz TTypeInfo

static TTypeInfo AnsiStringTypeInfo;

TypeInfo.Name = "AnsiString";

TypeInfo.Kind = tkLString;

RegisterPropertyEditor(&AnsiStringTypeInfo,

0 ,

"",

__classid(TAnsiStringPropertyEditor));

// albo

// dynamiczny egzemplarz TTypeInfo

TTypeInfo* AnsiStringTypeInfo = new TTypeInfo;

TypeInfo->Name = "AnsiString";

TypeInfo->Kind = tkLString;

RegisterPropertyEditor(AnsiStringTypeInfo,

0 ,

"",

__classid(TAnsiStringPropertyEditor));

Zwróć uwagę, iż dynamicznie utworzony egzemplarz struktury TTypeInfo nie jest zwalniany po wykorzystaniu (tj. po wywołaniu funkcji RegisterPropertyEditor()). Funkcja ta nie tworzy bowiem kopii struktury, lecz jedynie zapamiętuje jej wskaźnik w jednym z rekordów listy rejestracyjnej:

type

PPropertyClassRec = ^TPropertyClassRec;

TPropertyClassRec = record

Group: Integer;

PropertyType: PTypeInfo;

PropertyName: string;

ComponentClass: TClass;

EditorClass: TPropertyEditorClass;

end;

...

var

PropertyClassList: TList = nil;

...

procedure RegisterPropertyEditor(PropertyType: PTypeInfo; ComponentClass: TClass;

const PropertyName: string; EditorClass: TPropertyEditorClass);

var

P: PPropertyClassRec;

begin

if PropertyClassList = nil then

PropertyClassList := TList.Create;

New(P);

P.Group := CurrentGroup;

P.PropertyType := PropertyType;

P.ComponentClass := ComponentClass;

P.PropertyName := '';

if Assigned(ComponentClass) then P^.PropertyName := PropertyName;

P.EditorClass := EditorClass;

PropertyClassList.Insert(0, P);

end;

Jest to poniekąd zrozumiałe: informacja o typie rejestrowanego edytora pobierana jest najczęściej z permanentnie rezydujących struktur RTTI - opisane przed chwilą „ręczne” tworzenie struktur TTypeInfo wykonywane jest stosunkowo rzadko. Ponadto informacja ta musi być pobrana z RTTI, jeżeli rejestrowany edytor zastąpić ma inny edytor zarejestrowany w taki właśnie sposób.

Zasady zastępowania edytorów właściwości

Zarejestrowanie nowego edytora właściwości wprowadza oczywiście pewne zmiany w przyporządkowaniu określonych edytorów określonym właściwościom określonych komponentów. Mimo oczywistości tego faktu, rządzące tym zjawiskiem reguły nie są jednak tak oczywiste - wszak konkretny edytor może być przyporządkowany całej grupie właściwości danego typu, być może pochodzących z różnych komponentów.

Otóż aby nowo rejestrowany edytor zastąpił dotychczasowy edytor dla konkretnej właściwości, musi on cechować się co najmniej takim samym stopniem „specjalizacji” jak edytor istniejący. Jeżeli więc na przykład istnieje zarejestrowany w IDE edytor dla właściwości TShape komponentu TShapeType, po zarejestrowaniu nowego edytora dla właściwości typu TShape dowolnego komponentu właściwość TShapeType::TShape obsługiwana będzie nadal przez edytor dotychczasowy, jako ukierunkowany na konkretną klasę komponentu, czyli bardziej specjalizowany. Nowy edytor uruchamiany będzie przez IDE tylko dla właściwości TShape tych komponentów, dla których nie istnieje dedykowany (tj. ukierunkowany na konkretną klasę komponentu) edytor właściwości TShape.

Wykorzystanie grafiki w edytorach właściwości

Począwszy od wersji 5. C++Buildera niektóre edytory właściwości ilustrują wyświetlane wartości stosownymi ikonami, co czyni edycję bardziej intuicyjną i wygodniejszą dla użytkownika, mogącego teraz a priori poznać np. wygląd wybranego kursora. W związku z tym bazowa klasa wszystkich edytorów właściwości - TPropertyEditor - wzbogacona została o sześć nowych metod. Treść pięciu z nich, wymienionych poniżej, charakterystyczna jest dla klasycznej, „tekstowej” reprezentacji właściwości, wymaga więc przedefiniowania w klasie pochodnej, posługującej się dodatkowo wspomnianymi ikonami:

DYNAMIC void __fastcall ListMeasureWidth(const AnsiString Value,

Graphics::TCanvas*ACanvas,

int&AWidth);

DYNAMIC void __fastcall ListMeasureHeight(const AnsiString Value,

Graphics::TCanvas*ACanvas,

int&AHeight);

DYNAMIC void __fastcall ListDrawValue(const AnsiString Value,

Graphics::TCanvas*ACanvas,

const Windows::TRect&ARect,

bool ASelected);

DYNAMIC void __fastcall PropDrawValue(Graphics::TCanvas*ACanvas,

const Windows::TRect&ARect,

bool ASelected);

DYNAMIC void __fastcall PropDrawName(Graphics::TCanvas*ACanvas,

const Windows::TRect&ARect,

bool ASelected);

Metody te współdziałają z pozostałą, szóstą metodą:

AnsiString __fastcall GetVisualValue();

Znaczenie wymienionych metod wyjaśnia tabela 8.8.

Tabela 8.8. Nowe metody edytora właściwości związane z wyświetlaniem ikon

Metoda

Opis

ListMeasureWidth()

Umożliwia zmianę domyślnej szerokości przeznaczonej na wyświetlenie danej wartości w liście; największa z ostatecznych szerokości poszczególnych wartości jest jednocześnie dolną granicą szerokości całej listy.

ListMeasureHeight()

Umożliwia zmianę domyślnej wysokości przeznaczonej na wyświetlenie danej wartości w liście; wywołanie tej metody jest konieczne, jeżeli domyślna wysokość, wynikająca z tekstowej reprezentacji wartości, jest niewystarczająca do wyświetlenia towarzyszącej ikony.

ListDrawValue()

Dokonuje wyświetlenia wartości w liście.

PropDrawValue()

Określa wygląd pola wartości danej właściwości w sytuacji, gdy właściwość ta nie jest aktualnie edytowana (w czasie edycji pole to ma zawsze postać edytowalnego łańcucha AnsiString).

PropDrawName()

Określa wygląd pola nazwy danej właściwości.

GetVisualValue()

Istotą tej metody jest zapewnienie poprawnej postaci pola wartości danej właściwości w sytuacji, gdy zaznaczonych jest kilka komponentów. Jeżeli metoda AllEqual() zwraca wartość true, to oznacza, że wynikiem metody GetValue() jest wspólna wartość właściwości dla wszystkich komponentów - i ta właśnie wartość zwracana jest jako wynik metody GetVisualValue(). Zwrócenie przez metodę AllEqual() wartości false stanowi sygnał, iż nie można określić wspólnej wartości dla wszystkich zaznaczonych komponentów i metoda GetVisualValue() zwraca wówczas wartość true. Ten aspekt ogólnie pojętego edytowania właściwości ma charakter uniwersalny, niezależny od konkretnego edytora, dlatego też nie przewidziano możliwości przedefiniowywania metody GetVisualValue() w klasach pochodnych edytorów.

Symboliczny wpływ wymienionych metod na wygląd poszczególnych elementów inspektora obiektów przedstawia rysunek 8.3.

Rysunek 8.3. Związek wybranych metod edytora właściwości z oknem inspektora obiektów

Jak już wcześniej wspominaliśmy, przy tworzeniu nowego edytora właściwości należy poważnie zastanowić się nad wyborem jego klasy bazowej - jeżeli przykładowo docelowa właściwość będzie właściwością całkowitoliczbową, najbardziej prawdopodobnym kandydatem do tej roli będzie klasa TIntegerProperty. Na wydruku 8.10 przedstawiamy deklarację przykładowego edytora wyprowadzonego z klasy TEnumProperty; zakładamy przy tym, iż poza wzbogaceniem tej ostatniej o wyświetlanie ikon, ilustrujących poszczególne wartości, pozostałe aspekty jej funkcjonowania pozostają niezmienione.

Wydruk 8.10. Deklaracja edytora właściwości wzbogacającego klasę bazową o wyświetlanie ikon dla poszczególnych wartości

#include "DsgnIntf.hpp"

class TCustomImagePropertyEditor : public TEnumProperty

{

typedef TEnumProperty inherited;

public:

DYNAMIC void __fastcall ListMeasureWidth(const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AWidth);

DYNAMIC void __fastcall ListMeasureHeight(const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AHeight);

DYNAMIC void __fastcall ListDrawValue(const AnsiString Value,

Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected);

DYNAMIC void __fastcall PropDrawValue(Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected);

DYNAMIC void __fastcall PropDrawName(Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected);

protected:

#pragma option push -w-inl

inline __fastcall virtual

TCustomImagePropertyEditor(const _di_IFormDesigner ADesigner,

int APropCount)

: TEnumProperty(ADesigner,

APropCount)

{ }

#pragma option pop

public:

#pragma option push -w-inl

inline __fastcall virtual ~TCustomImagePropertyEditor(void)

{ }

#pragma option pop

};

W kolejnych częściach rozdziału opiszemy teraz szczegółowo każdą ze wspomnianych na wstępie pięciu metod podlegających przedefiniowaniu. W charakterze przykładu posłużymy się przy tym zdefiniowanym specjalnie na tę okazję, wyprowadzonym z klasy TEnumProperty, edytorem TShapeTypePropertyEditor przeznaczonym dla właściwości typu TShape, którego zastosowanie w konkretnym projekcie przedstawia rysunek 8.4. Wyjaśnienie parametrów wykorzystywanych przez wspomniane metody znajduje się natomiast w tabeli 8.9.

Rysunek 8.4. Edytor TShapeTypePropertyEditor w akcji

Tabela 8.9. Parametry metod edytora odpowiedzialnych za graficzną prezentację edytowanych właściwości

Parametr

Znaczenie

AWidth

Bieżąca szerokość (w pikselach) tekstu reprezentującego wartość właściwości w oknie inspektora obiektów, z uwzględnieniem skrajnych spacji

AHeight

Domyślna wysokość pola przeznaczonego na wypisanie wartości. Wartość ta większa jest o 2 piksele od wysokości potrzebnej na wypisanie (ustaloną czcionką) tekstu „Ag” (równej ACanvas->TextHeight("Ag")) jako przykładowego tekstu zajmującego wszystkie elementy „pionowej” kompozycji czcionki - patrz rysunek 8.5

ACanvas

„Płótno” bieżąco edytowanego elementu w inspektorze obiektów

ARect

Region płótna, którego dotyczy operacja rysowania lub wypisywania

ASelected

Stan „rysowanego” elementu w inspektorze obiektów (true - wybrany, false - nie wybrany)

Rysunek 8.5. Obliczenie wysokości tekstu

Odniesienie poszczególnych elementów wymienionych w tabeli 8.9 do rzeczywistego elementu podlegającego „rysowaniu” przedstawia rysunek 8.6, na który będziemy często powoływać się w dalszej dyskusji.

Rysunek 8.6. Znaczenie poszczególnych parametrów metod edytora odpowiedzialnych za prezentację edytowanej właściwości

Metoda ListMeasureWidth()

Wejściowa wartość parametru AWidth równa jest szerokości wypisywanego tekstu, równej ACanvas->TextWidth(Value). Jeżeli wypisywanemu tekstowi towarzyszyć ma ikona, wartość ta musi zostać zmodyfikowana stosownie do tego faktu: przy założeniu kwadratowego kształtu ikony dodatkowa szerokość równa będzie wysokości komórki (równej ACanvas->TextHeight("Ag")+2). Na rysunku 8.6 ikona towarzysząca tekstowi jest kwadratem o boku 16 pikseli, więc domyślna, 18-pikselowa wysokość komórki jest dla niej wystarczająca; dla obrazków o innej szerokości należy oczywiście użyć innej, stosownej wartości. Tekst przedefiniowanej z uwzględnieniem tego faktu metody ListMeasureWidth() przedstawia wydruk 8.11.

Wydruk 8.11. Przedefiniowana metoda ListMeasureWidth()

void __fastcall TShapeTypePropertyEditor::ListMeasureWidth(

const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AWidth)

{

AWidth += (ACanvas->TextHeight("Ag")+2);

}

Metoda ListMeasureHeight()

Z wyjątkiem przypadków, gdy towarzyszący tekstowi obrazek przekracza swą wysokością domyślną wysokość komórki, wejściowa wartość AHeight nie powinna być modyfikowana. W szczególności nie powinna być ona zmniejszana (a jeżeli już, to nigdy poniżej wartości ACanvas->TextHeight("Ag")+2), gdyż grozi to obcięciem wyświetlanego tekstu. Zależnie od okoliczności wyjściowa wartość parametru AHeight może być wynikiem powiększenia jego wartości wejściowej o stałą wartość, bądź też przyrównania jej do rzeczywistej wysokości obrazka. Obydwa te podejścia ilustruje przedefiniowana metoda ListMeasureHeight() na wydruku 8.12.

Wydruk 8.12. Przedefiniowana metoda ListMeasureHeight()

void __fastcall TShapeTypePropertyEditor::ListMeasureHeight(

const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AHeight)

{

AHeight += wartość zwiększająca

}

// albo:

void __fastcall TShapeTypePropertyEditor::ListMeasureHeight(

const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AHeight)

{

if ((ACanvas->TextHeight("Ag")+2) < wysokość obrazka )

{

AHeight = wysokość obrazka

}

}

Metoda ListDrawValue()

Ta metoda jest najbardziej skomplikowaną spośród tutaj opisywanych, dokonuje ona bowiem fizycznego „rysowania” elementów listy na przeznaczonych do tego „płótnach”. Proponowany zarys jej postaci przedstawia wydruk 8.13; wskazane jest skonfrontowanie jego treści z rysunkiem 8.6.

Wydruk 8.13. Schemat przedefiniowywania metody ListDrawValue()

void __fastcall TCustomImagePropertyEditor::ListDrawValue

(const AnsiString Value,

Graphics::TCanvas*ACanvas,

const Windows::TRect&ARect,

bool ASelected)

{

// Zadeklaruj zmienną vRight typu int wskazującą prawą krawędź obrazka

// (przedrostek v oznacza, że mamy do czynienia ze zmienną - zgodnie

// z konwencją przyjętą w module DsgnIntf.pas)

try

{

//Krok 1 - Zachowaj wartość tych właściwości płótna, które będą zmieniane

//Krok 2 - Wyczyść obramowanie obszaru podlegającego modyfikacji.

// Jest to konieczne ze względu na ewentualne pozostałości po

// operacjach poprzednio wykonywanych w tym obszarze.

// Kiedy na przykład IDE przystępuje do wyświetlenia edytowanej

// właśnie właściwości, otacza komórkę żółto-czarną ramką i nadaje

// tłu kolor ciemnoniebieski. Gdy następnie skupienie, przenoszone

// jest na inną właściwość, czyszczona jest tylko ta część płótna,

// która była modyfikowana

//Krok 3 Wykonaj czynności wstępne: wypełnij obszar kolorem tła i otocz

// go ramką, jeśli jest aktualnie wybrany (ASelected = true)

//

// Ustaw kolor pióra na identyczny z kolorem wyświetlanego tekstu

// - kryje się on pod identyfikatorem clWindowText. Jest to

// najodpowiedniejszy kolor obrzeża dla obrazka towarzyszącego

// tekstowi.

//

// Zapewnij kolor tła identyczny z kolorem inspektora obiektów

// (clButtonFace). Jeżeli pozycja nie jest aktualnie wybrana,

// ustaw również w ten sam sposób kolor pióra, by ramka nie

// odróżniała się od tła.

//

//Krok 4 Określ wartość podlegającą wyświetleniu

//Krok 5 Narysuj żądany obrazek na płótnie

//Krok 6 Przywróć wartości początkowe zmodyfikowanym właściwościom

// płótna

}

__finally

{

// Wykonaj jedną z dwóch poniższych operacji w celu wyświetlenia na

// płótnie tekstu reprezentującego aktualną wartość właściwości:

//

// 1. Wywołaj metodę ListDrawValue() klasy bazowej, podając położenie

// lewego boku prostokąta w miejscu prawej krawędzi obrazka:

//

// inherited::ListDrawValue(Value,

// ACanvas,

// Rect(

// vRight,

// ARect.Top,

// ARect.Right,

// ARect.Bottom

// ),

// ASelected

// );

//

// ALBO:

//

// 2. Wypisz tekst w sposób bezpośredni, nie odwołując się do klasy

// bazowej

//

// ACanvas->TextRect(

// Rect(

// vRight,

// ARect.Top,

// ARect.Right,

// ARect.Bottom

// ),

// vRight+1,

// ARect.Top+1,

// Value

// );

}

}

Rzeczywisty kod odwzorowujący powyższy schemat przedstawiamy na wydruku 8.14. Prezentowany tekst odpowiedzialny jest za kompletne narysowanie każdej z pozycji listy - tekstowej nazwie kształtu towarzyszy jego graficzny odpowiednik, co widzieliśmy już na rysunku 8.4.

Wydruk 8.13. Implementacja przedefiniowanej metody ListDrawValue()

void __fastcall TShapeTypePropertyEditor::ListDrawValue

(const AnsiString Value,

Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected)

{

// Zadeklaruj zmienną vRight typu int wskazującą prawą krawędź obrazka

int vRight = ARect.Bottom - ARect.Top + ARect.Left;

try

{

//Krok 1 - Zachowaj wartość tych właściwości płótna, które będą

// zmieniane:

TColor vOldPenColor = ACanvas->Pen->Color;

TColor vOldBrushColor = ACanvas->Brush->Color;

//Krok 2 - Wyczyść obramowanie obszaru podlegającego modyfikacji.

ACanvas->Pen->Color = ACanvas->Brush->Color;

ACanvas->Rectangle(ARect.Left, ARect.Top, vRight, ARect.Bottom);

//Krok 3 Wykonaj czynności wstępne

if(ASelected) // wybierz kolor pióra

{ // stosownie do stanu

ACanvas->Pen->Color = clYellow; // wybrania pozycji

} //

else

{

ACanvas->Pen->Color = clBtnFace;

}

ACanvas->Brush->Color = clBtnFace; // ustaw kolor tła

// na identyczny z kolorem tła

// inspektora obiektów

ACanvas->Rectangle( ARect.Left + 1, // wypełnij obszar kolorem tła:

ARect.Top + 1, // kolory pióra i pędzla

vRight - 1, // są identyczne

ARect.Bottom - 1 ); //

//Krok 4 Określ wartość podlegającą wyświetleniu; jest ona dostępna

// pod odziedziczoną metodą GetEnumValue()

TShapeType ShapeType = TShapeType(GetEnumValue(GetPropType(), Value));

//Krok 5 Narysuj żądany obrazek na płótnie

ACanvas->Pen->Color = clBlack;

ACanvas->Brush->Color = clWhite;

switch(ShapeType)

{

case stRectangle : ACanvas->Rectangle(ARect.Left+2,

ARect.Top+4,

vRight-2,

ARect.Bottom-4);

break;

case stSquare : ACanvas->Rectangle(ARect.Left+2,

ARect.Top+2,

vRight-2,

ARect.Bottom-2);

break;

case stRoundRect : ACanvas->RoundRect(ARect.Left+2,

ARect.Top+4,

vRight-2,

ARect.Bottom-4,

(ARect.Bottom-ARect.Top-6)/2,

(ARect.Bottom-ARect.Top-6)/2);

break;

case stRoundSquare : ACanvas->RoundRect(ARect.Left+2,

ARect.Top+2,

vRight-2,

ARect.Bottom-2,

(ARect.Bottom-ARect.Top)/3,

(ARect.Bottom-ARect.Top)/3);

break;

case stEllipse : ACanvas->Ellipse(ARect.Left+1,

ARect.Top+2,

vRight-1,

ARect.Bottom-2);

break;

case stCircle : ACanvas->Ellipse(ARect.Left+1,

ARect.Top+1,

vRight-1,

ARect.Bottom-1);

break;

default : break;

}

//Krok 6 Przywróć wartości początkowe zmodyfikowanym właściwościom

// płótna

ACanvas->Pen->Color = vOldPenColor;

ACanvas->Brush->Color = vOldBrushColor;

}

__finally

{

// Wybierz jeden ze sposobów wyświetlenia tekstu wartości:

//

// 1. Odwołanie do odziedziczonej metody ListDrawValue():

inherited::ListDrawValue(Value,

ACanvas,

Rect(

vRight,

ARect.Top,

ARect.Right,

ARect.Bottom

),

ASelected

);

// 2. Bezpośrednie wypisywanie na płótnie:

//

// ACanvas->TextRect(

// Rect(

// vRight,

// ARect.Top,

// ARect.Right,

// ARect.Bottom

// ),

// vRight+1,

// ARect.Top+1,

// Value

// );

}

}

Rysowanie poszczególnych figur geometrycznych jest tu operacją oczywistą, pewnego komentarza wymaga natomiast określenie, z jakim kształtem mamy aktualnie do czynienia. Otóż do odczytu aktualnej wartości właściwości wyliczeniowej (typ TShapeType jest typem wyliczeniowym) służy metoda GetEnumValue(), otrzymująca pod postacią parametrów zarówno informację o typie edytowanej właściwości wyliczeniowej (otrzymaną za pomocą metody GetPropType() ) jak również tekstową reprezentację odnośnego elementu; wynik funkcji równy jest numerowi porządkowemu elementu i musi być ostatecznie skonwertowany do właściwego typu wyliczeniowego:

TShapeType ShapeType = TShapeType(GetEnumValue(GetPropType(), Value));

Zamiast metody GetPropType() można też użyć równoważnego wyrażenia:

*GetPropInfo()->PropType

Warto zapamiętać ten schemat, gdyż jest on powszechnie stosowany przy konstrukcji większości edytorów właściwości.

Metoda PropDrawValue()

Ta metoda odpowiedzialna jest za wyświetlenie aktualnej wartości edytowanej właściwości, obok nazwy tej ostatniej. Wysokość komórki jest przy tym sztywno określona przez parametr ARect i nie można jej zmienić. Jako że wyświetlanie aktualnej „wartości” jest tu w istocie wyświetlaniem poszczególnych elementów listy wartości, metoda PropDrawValue() może z powodzeniem scedować swe zadanie na metodę ListDrawValue(), podając jako pierwszy z parametrów wywołania wyświetlany łańcuch zwracany przez metodę GetVisualValue() i kopiując pozostałe parametry. W przypadku, gdy wyświetlony ma być pusty łańcuch, całą sprawę załatwić można jeszcze prościej - wypisując ów łańcuch za pomocą metody ACanvas->TextRect, bądź wywołując odziedziczoną metodę PropDrawValue(). Przedefiniowaną w opisany sposób metodę PropDrawValue() przedstawia wydruk 8.15.

Wydruk 8.15. Implementacja metody PropDrawValue()

void __fastcall TShapeTypePropertyEditor::PropDrawValue

(Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected)

{

if( GetVisualValue() != "" )

{

ListDrawValue(GetVisualValue(), ACanvas, ARect, ASelected);

}

else

{

ACanvas->TextRect( ARect,

ARect.Left+1,

ARect.Top+1,

GetVisualValue() );

// można też użyć konstrukcji:

// inherited::PropDrawValue(ACanvas, ARect, ASelected);

//

}

}

Metoda PropDrawName()

Metoda ta odpowiedzialna jest za wypełnienie pola nazwy właściwości; podobnie jak w przypadku PropDrawValue() wysokość komórki jest zadana a priori i nie można jej zmienić. Przypadki, kiedy przedefiniowanie standardowej metody TPropertyEditor przynosi rzeczywiste korzyści, są naprawdę rzadkie i sprowadzają się w zasadzie do konieczności umieszczenia obok nazwy właściwości jakiegoś piktogramu, symbolizującego jej charakter. Jeden z edytorów naszego przykładowego pakietu EnhancedEditors - TImageListPropertyEditor - opatruje właściwości typu TCustomImageList* ikoną charakterystyczną dla komponentu TImageList, czego przykład widzimy na rysunku 8.7. Wydruk 8.16 przedstawia natomiast przedefiniowaną metodę PropDrawName() tegoż edytora.

Rysunek 8.7. Poprzedzenie ikoną nazwy właściwości

Wydruk 8.16. Przykład przedefiniowanej metody PropDrawName()

void __fastcall TImageListPropertyEditor::PropDrawName

(Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected)

{

// prostokąt przeznaczony na tekst nazwy

TRect ValueRect = ARect;

try

{

// Wyczyść przedmiotowy obszar płótna

ACanvas->FillRect(ARect);

if(GetName() != "") // czy nazwa nie jest pustym łańcuchem?

{

if(Screen->PixelsPerInch > 96)

{

ACanvas->Draw( ARect.Left + 1,

ARect.Top + 2,

ImageListPropertyImage );

}

else

{

ACanvas->Draw( ARect.Left + 1,

ARect.Top,

ImageListPropertyImage );

}

// zmodyfikuj prostokąt przeznaczony na tekst nazwy

ValueRect = Rect( ARect.Left + 16 + 2,

ARect.Top,

ARect.Right,

ARect.Bottom );

}

}

__finally

{

// wypisanie tekstu niezależnie od tego, czy udało się narysowanie ikony

inherited::PropDrawName(ACanvas, ValueRect, ASelected);

// można również użyć następującej konstrukcji:

// ACanvas->TextRect(

// ValueRect,

// ValueRect.Left+1,

// ValueRect.Top+1,

// GetName()

// );

}

}

Połączyłem listingi 10.16 i 10.17 w jeden listing

Zmienna ValueRect reprezentuje tu prostokąt, w którym wypisana będzie nazwa właściwości. Początkowo prostokąt ten tożsamy jest z całą komórką inspektora obiektów przeznaczoną na nazwę właściwości, jednak po udanym narysowaniu ikony zmniejszany jest lewostronnie o jej rozmiar (plus dwupikselowy margines). Rysowany obrazek ukrywa się pod właściwością ImageListPropertyImage, inicjowaną w konstruktorze zawartością pobraną z zasobu:

__fastcall TImageListPropertyEditor::TImageListPropertyEditor

(const _di_IFormDesigner ADesigner,

int APropCount)

: TComponentProperty(ADesigner, APropCount)

{

ImageListPropertyImage = new Graphics::TBitmap();

ImageListPropertyImage->LoadFromResourceName(

reinterpret_cast<int>(HInstance),

"RESOURCE_ImageListProperty"

);

}

Wyświetlany obrazek pozycjonowany jest odmiennie w zależności od tego, czy używane są duże (rozdzielczość co najmniej 96 pikseli na cal), czy małe czcionki.

Instalowanie pakietów zawierających edytory

Jak już wcześniej pisaliśmy, rejestracja edytora właściwości, polegająca na jego integracji z IDE, wykonywana jest przez funkcję RegisterPropertyEditor(). Funkcja ta musi być wywołana w treści funkcji o nazwie Register(), zamkniętej w przestrzeń nazw (namespace) o nazwie tożsamej z nazwą pliku, w której występuje - z dokładnością do wielkości liter: nazwa przestrzeni namespace musi rozpoczynać się od wielkiej litery i w dalszej części być pisana wyłącznie małymi literami. Oto przykład rejestracji edytora TShapeTypePropertyEditor:

Wydruk 8.17. Rejestracja pakietu edytora

#include <vcl.h>

#pragma hdrstop

#include "NameOfThisFile"

// plik zawierający deklarację edytora

#include "PropertyEditors.h"

#include <TypInfo.hpp>

// Plik zawierający deklarację TPropInfo* i GetPropInfo()

#include "GetTypeInfo.h"

//--------------------------------------------------------------------------//

#pragma package(smart_init)

//--------------------------------------------------------------------------//

namespace Nameofthisfile // nazwa rozpoczyna się wielką literą,

// później wyłącznie małe litery

{

void __fastcall PACKAGE Register()

{

// informacja o typie właściwości TShape::Shape

TPropInfo* TShapeTypePropInfo =

Typinfo::GetPropInfo(__typeinfo(TShape), "Shape");

// rejestracja edytora TShapeTypePropertyEditor dla dowolnej

// (tj. bez względu na nazwę) właściwości typu TShapeType

// w dowolnym komponencie

RegisterPropertyEditor(

EnumTypeInfo("TShapeType")*TShapeTypePropInfo->PropType,

0, //dowolny komponent

"", //dowolna właściwość typu TShapeType

__classid(TShapeTypePropertyEditor));

}

}

Przyjęliśmy dla uproszczenia, iż nazwa prezentowanego na wydruku 8.17 pliku źródłowego to NameOfThisFile.cpp, co znajduje swe odzwierciedlenie m.in. w dyrektywie namespace. Dyrektywa #include "NameOfThisFile" jest właściwie zbędna, podobnie jak i sam plik NameOfThisFile.h; kod pliku implementacyjnego włączany jest bowiem do kodu źródłowego pakietu za pomocą makra USEUNIT().

Ponadto, jako że pakiety zawierające wyłącznie edytory (komponentów --> i (lub)[Author:AG] właściwości) przydatne są jedynie na etapie projektowania, należy zaznaczyć opcję DesignTime only na karcie Description opcji pakietu.

Wykorzystanie kolekcji obrazków w edytorach właściwości

Ikony towarzyszące elementom rozwijalnej listy w edytorze TShapeTypePropertyEditor stanowiły jedynie dodatek do tekstowej reprezentacji owych elementów, nie posiadając samoistnego znaczenia. Przyjrzyjmy się teraz sytuacji diametralnie przeciwnej, kiedy to właściwość podlegająca edycji identyfikuje pewien obrazek, dokładniej - jest indeksem obrazka we wskazanej liście typu, np. TCustomImageList. Jak łatwo się domyślić, elementami rozwijalnej listy dopuszczalnych właściwości są tutaj obrazki zmagazynowane w ramach komponentu TCustomImageList (lub pochodnego, np. TImageList) i teraz to one grają pierwsze skrzypce, a ewentualne ich opisy tekstowe posiadają li tylko znaczenie pomocnicze. Wskazanie na ów komponent „magazynujący” może znajdować się w tym samym komponencie, co edytowana właściwość, bądź też w jego komponencie rodzicielskim (być może dalszego rzędu); w dalszej części pokażemy praktyczne konsekwencje tej różnicy.

Na potrzeby naszych rozważań skonstruowaliśmy na bazie klasy TImage komponent TEnhancedImage, którego zadaniem jest po prostu reprezentowanie obrazka identyfikowanego przez indeks (określony przez właściwość ImageIndex) w liście obrazków wskazanej przez właściwość ImageList. Jeżeli właściwość ImageList nie wskazuje na żadną listę (jest pustym wskaźnikiem), komponent ten zachowuje się identycznie jak jego komponent bazowy TImage.

Deklarację komponentu TEnhancedImage przedstawia wydruk 8.18.

Wydruk 8.18. Deklaracja komponentu TEnhancedImage

//----------------------------------------------------------------------//

#ifndef EnhancedImageH

#define EnhancedImageH

//---------------------------------------------------------------------//

#include <SysUtils.hpp>

#include <Controls.hpp>

#include <Classes.hpp>

#include <Forms.hpp>

//--------------------------------------------------------------------//

//--------------------------------------------------------------------//

class PACKAGE TEnhancedImage : public TImage

{

typedef TImage inherited;

private:

// lista obrazków (komponent magazynujący):

TCustomImageList* FImageList;

// indeks obrazka we wskazanej liście

Imglist::TImageIndex FImageIndex;

// czy używana jest zewnętrzna lista, czy też komponent funkcjonuje

// jak komponent TImage?

bool FUseImageList;

protected:

virtual void __fastcall SetUseImageList(bool NewUseImageList);

virtual void __fastcall SetImageIndex(Imglist::TImageIndex NewImageIndex);

virtual void __fastcall SetImageList(Imglist::TCustomImageList* NewImageList);

virtual void __fastcall UpdatePicture(void);

// Override Notification

virtual void __fastcall Notification(TComponent* AComponent, TOperation Operation);

public:

__fastcall TEnhancedImage(TComponent* Owner);

__published:

__property bool UseImageList = {read=FUseImageList,

write=SetUseImageList,

default=false};

__property TCustomImageList* ImageList = {read=FImageList,

write=SetImageList,

default=0};

__property Imglist::TImageIndex ImageIndex = {read=FImageIndex,

write=SetImageIndex,

default=-1};

};

//---------------------------------------------------------------------//

//---------------------------------------------------------------------//

#endif

Nowe metody i właściwości komponentu związane są z obsługą wspomnianej listy i indeksu oraz z ich wpływem na aktualnie reprezentowany obrazek, kryjący się pod właściwością Picture. Zapewniono także wrażliwość komponentu na usunięcie listy, przedefiniowując odpowiednio metodę Notification(). Charakterystyczne dla komponentu metody znajdują się we fragmencie kodu implementacyjnego prezentowanym na wydruku 8.19.

Wydruk 8.19. Nowe metody komponentu TEnhancedImage

// konstruktor

__fastcall TEnhancedImage::TEnhancedImage(TComponent* Owner)

: TImage(Owner),

FUseImageList(false),

FImageIndex(-1),

FImageList(0)

{

}

//------------------------------------------------------------------------//

void __fastcall TEnhancedImage::SetUseImageList(bool NewUseImageList)

{

if(NewUseImageList != UseImageList)

{

FUseImageList = NewUseImageList;

UpdatePicture();

}

}

//------------------------------------------------------------------------//

void __fastcall TEnhancedImage::SetImageIndex(Imglist::TImageIndex NewImageIndex)

{

if(NewImageIndex != FImageIndex)

{

FImageIndex = NewImageIndex;

UpdatePicture();

}

}

//------------------------------------------------------------------------//

void __fastcall TEnhancedImage::SetImageList(Imglist::TCustomImageList* NewImageList)

{

if(NewImageList != FImageList)

{

FImageList = NewImageList;

if(ImageList == 0) ImageIndex =-1;

}

}

//------------------------------------------------------------------------//

void __fastcall TEnhancedImage::UpdatePicture(void)

{

if( UseImageList

&& ImageList != 0

&& ImageIndex >= 0

&& ImageIndex < ImageList->Count )

{

std::auto_ptr<Graphics::TBitmap> Image(new Graphics::TBitmap());

ImageList->GetBitmap(ImageIndex, Image.get());

Picture->Assign(Image.get());

// Ewentalnie:

// Graphics::TBitmap* Image = new Graphics::TBitmap();

//

// try

// {

// ImageList->GetBitmap(ImageIndex, Image);

// Picture->Assign(Image);

// }

// __finally

// {

// delete Image;

// }

}

}

//------------------------------------------------------------------------//

void __fastcall TEnhancedImage::Notification(TComponent* AComponent, TOperation Operation)

{

inherited::Notification(AComponent, Operation);

if(Operation == opRemove)

{

if(AComponent == ImageList) ImageList = 0;

// tu ewentualne dodatkowe testy

}

}

Konstruktor nie kryje w sobie żadnych niespodzianek - komponent inicjowany jest jako niezwiązany z żadną listą. Metody SetUseImageList() i SetImageIndex() są metodami dostępowymi właściwości (odpowiednio) UseImageList i ImageIndex - jeżeli nowo przypisywane im wartości różne są od dotychczasowych, następuje uaktualnienie właściwości Picture. Podobnie ma się rzecz z właściwością ImageList.

Wspomnianym uaktualnieniem właściwości Picture zajmuje się metoda UpdatePicture(). Sprawdza ona, czy komponent powiązany jest z jakąś listą i czy jego właściwość ImageIndex jest (w kontekście tej listy) poprawnym indeksem. Jeżeli tak, to obrazek identyfikowany w liście przez ów indeks kopiowany jest do roboczej bitmapy, która następnie kopiowana jest do właściwości Picture. Zwróć uwagę, iż obiekt bitmapy lokowany jest na stosie przy użyciu szablonu automatycznego wskaźnika (auto_ptr), zostanie więc zwolniony przy zakończeniu metody, bez względu na ewentualne wyjątki zaistniałe w trakcie jej realizacji. Aby zbytnio nie obciążać stosu, można też zrealizować roboczą bitmapę jako klasyczny obiekt na stercie, którego zwolnienie zapewnia konstrukcja __try/__finally:

Graphics::TBitmap* Image = new Graphics::TBitmap();

try

{

ImageList->GetBitmap(ImageIndex, Image);

Picture->Assign(Image);

}

__finally

{

delete Image;

}

Wreszcie, metoda Notification() przedefiniowana została w ten sposób, iż w przypadku zwolnienia listy wskazywanej przez właściwość ImageList komponent zostaje przestawiony w tryb zgodności z klasą TImage. Zwróć uwagę, iż wartość 0 przypisywana jest nie prywatnemu polu FImageList, lecz bazującej na nim właściwości ImageList, co uruchamia metodę SetImageList, ustawiającą dodatkowo indeks na „nieprawidłową” wartość -1.

Zajmijmy się teraz stworzeniem edytora dla właściwości ImageIndex naszego komponentu TEnhancedImage; typ tej właściwości - TImageIndex - jest synonimem typu int, zatem edytor nasz wyprowadzimy z klasy bazowej TIntegerProperty i nazwiemy TImageIndexPropertyEditor. Jego deklarację przedstawia wydruk 8.20.

Wydruk 8.20. Deklaracja klasy TImageIndexPropertyEditor

class PACKAGE TImageIndexPropertyEditor : public TIntegerProperty

{

typedef TIntegerProperty inherited;

private:

// obrzeże wokół obrazka (w pikselach)

static const int Border = 2;

// maksymalna wyświetlana szerokość i wysokość obrazka

static const int MaxImageWidth = 64;

static const int MaxImageHeight = 64;

protected:

Imglist::TCustomImageList* __fastcall GetComponentImageList(void);

public:

virtual TPropertyAttributes __fastcall GetAttributes(void);

virtual void __fastcall GetValues(Classes::TGetStrProc Proc);

DYNAMIC void __fastcall ListMeasureWidth(const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AWidth);

DYNAMIC void __fastcall ListMeasureHeight(const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AHeight);

DYNAMIC void __fastcall ListDrawValue(const AnsiString Value,

Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected);

DYNAMIC void __fastcall PropDrawValue(Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected);

// właściwość tylko do odczytu, udostępniająca wskaźnik

// do listy obrazków macierzystego komponentu

__property Imglist::TCustomImageList* ComponentImageList

= {read=GetComponentImageList};

protected:

#pragma option push -w-inl

inline __fastcall virtual TImageIndexPropertyEditor

(

const _di_IFormDesigner ADesigner,

int APropCount

):TIntegerProperty(

ADesigner,

APropCount

)

{ }

#pragma option pop

public:

#pragma option push -w-inl

inline __fastcall virtual ~TImageIndexPropertyEditor(void)

{ }

#pragma option pop

};

//--------------------------------------------------------------------------//

//--------------------------------------------------------------------------//

#endif

Podobnie jak w przypadku edytora TShapeTypePropertyEditor, zajmiemy się teraz szczegółami implementacji wybranych metod edytora TImageIndexPropertyEditor.

Metoda GetAttributes()

Edytor nasz umożliwiać ma wybór „wartości” z rozwijalnej listy, należy więc wpisać na jego listę atrybutów wartość paValueList. Jednocześnie edytowana właściwość ma być niewidoczna w przypadku zaznaczenia kilku komponentów, więc na liście atrybutów nie może znajdować się wartość paMultiSelect:

TPropertyAttributes __fastcall TImageIndexPropertyEditor::GetAttributes(void)

{

return (inherited::GetAttributes() << paValueList >> paMultiSelect);

}

Metoda GetComponentImageList()

Ta metoda ma kluczowe znaczenie dla poprawnego funkcjonowania edytora, udostępnia bowiem wskaźnik do listy obrazków danego komponentu lub informuje o braku takiej listy, zwracając wartość NULL.

Wszystko rozpoczyna się od uzyskania wskaźnika do macierzystego komponentu edytowanej właściwości; ponieważ brak atrybutu paMultiSelect zapewnia, że zaznaczony jest tylko jeden komponent, jego adres jest wartością wyrażenia GetComponent(0).

Mając już wskaźnik komponentu, należy upewnić się, iż jest to komponent klasy TEnhancedImage lub pochodnej - można to bardzo łatwo zrealizować za pomocą rzutowania dynamicznego.

Ostatnią czynnością będzie oczywiście odczytanie właściwości ImageList wskazywanego komponentu, po zweryfikowaniu jego klasy. Oto kompletna treść metody:

Imglist::TCustomImageList* __fastcall

TImageIndexPropertyEditor::GetComponentImageList(void)

{

TEnhancedImage* Component = dynamic_cast<TEnhancedImage*>(GetComponent(0));

if(Component) // czy komponent jest klasy TEnhancedImage?

{

// tak, zwróć wskaźnik jego listy obrazków

return Component->ImageList;

}

return 0;

}

W przypadku gdy lista obrazków wykorzystywanych przez dany komponent znajduje się w jego komponencie macierzystym, jej odnalezienie jest nieco bardziej skomplikowane; zajmiemy się tym w dalszej części rozdziału.

Metoda GetValues()

Metoda ta zajmuje się, jak wiadomo, skompletowaniem zawartości rozwijalnej listy. W naszym przypadku każdy z obrazków w rozwijalnej liście reprezentowany będzie przez swój indeks w liście TCustomImageList:

void __fastcall TImageIndexPropertyEditor::GetValues

(Classes::TGetStrProc Proc)

{

TCustomImageList* ImageList = ComponentImageList;

if(ImageList != 0)

{

for(int i = 0; i<ImageList->Count; ++i) Proc(IntToStr(i));

}

}

Zwróć uwagę, iż wskazanie na listę - magazyn obrazków zapamiętane zostało w pomocniczej zmiennej roboczej. Z punktu widzenia poprawności kodu jest to bez znaczenia, lecz odczytanie zmiennej lokalnej trwa przecież znacznie szybciej niż bezpośredni odczyt właściwości komponentu, który teraz wykonuje się tylko raz.

Metody ListMeasureWidth() i ListMeasureHeight()

Metody wymienione w tytule odpowiedzialne są za określenie wymiarów wyświetlanych obrazków. W przeciwieństwie do edytora TShapeTypePropertyEditor, gdzie beztrosko dostosowywaliśmy szerokość i wysokość komórek inspektora obiektów do wymiarów ikon - przy milczącym założeniu, że te ostatnie będą raczej niewielkie, jak na ikony przystało - tym razem musimy wziąć pod uwagę pewne realistyczne ograniczenia. Wyobraźmy sobie, jak wyglądałaby rozwijalna lista, w której każdy z obrazków zajmowałby (powiedzmy) połowę wysokości ekranu? Przypadek ten ukazuje dobitnie, iż obrazki zbyt duże (czyli zbyt szerokie i (lub) zbyt wysokie) muszą być poddane przeskalowaniu, które łatwo jest wykonać za pomocą metody StretchDraw() płótna TCanvas.

Jako ograniczenie zarówno szerokości, jak i wysokości obrazka wybraliśmy arbitralnie wielkość 64 pikseli. Mieści się ona jeszcze w akceptowalnych granicach (nawet przy rozdzielczości 640×480 widocznych będzie kilka pozycji) i jest jednocześnie na tyle duża, by rozróżnić można było najważniejsze szczegóły wyświetlanych obrazków. No i oczywiście jest potęgą dwójki, co tak lubiane jest przez programistów… Każdy z wyświetlonych obrazków musi ponadto zachować odstęp od krawędzi komórki i wypisywanego tekstu - jego wielkość określona jest przez zmienną Border, arbitralnie ustawioną na 2 piksele.

Oto treść metod ListMeasureWidth() i ListMeasureHeight():

Wydruk 8.21. Implementacja metod ListMeasureWidth() i ListMeasureHeight()

void __fastcall TImageIndexPropertyEditor::ListMeasureWidth(

const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AWidth)

{

TCustomImageList* ImageList = ComponentImageList;

if(ImageList != 0)

{

if(ImageList->Width < MaxImageWidth)

{

AWidth += ImageList->Width + Border*2;

}

else

{

AWidth += MaxImageWidth + Border*2;

}

}

}

//-------------------------------------------------------------------------//

void __fastcall TImageIndexPropertyEditor::ListMeasureHeight(

const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AHeight)

{

TCustomImageList* ImageList = ComponentImageList;

if(ImageList != 0)

{

if( ImageList->Height < MaxImageHeight && ImageList->Height > AHeight)

{

AHeight = ImageList->Height + Border*2;

}

else if(ImageList->Height > AHeight)

{

AHeight = MaxImageHeight + Border*2;

}

}

}

Metoda ListMeasureHeight() jest nieco bardziej skomplikowana, bowiem oprócz rozmiarów obrazka należy jeszcze wziąć pod uwagę wysokość wypisywanego tekstu, która określa dolne ograniczenie na wysokość komórki.

Metoda ListDrawValue()

Jak można się spodziewać, jest to najbardziej skomplikowana metoda naszego edytora. Jest ona odpowiedzialna za całość reprezentacji każdej z pozycji rozwijalnej listy, a więc przede wszystkim za poprawne narysowanie wszystkich obrazków. Jej treść przedstawia wydruk 8.22.

Wydruk 8.22. Implementacja metody ListDrawValue()

void __fastcall TImageIndexPropertyEditor::ListDrawValue(

const AnsiString Value,

Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected)

{

TRect ValueRect = ARect;

try

{

TCustomImageList* ImageList = ComponentImageList;

// wyczyść obszar podlegający wypełnieniu

ACanvas->FillRect(ARect);

if(ImageList != 0 && Value != "")

{

int ImageWidth = ImageList->Width;

int ImageHeight = ImageList->Height;

if(ImageWidth > MaxImageWidth || ImageHeight > MaxImageHeight)

{

// rysowanie z przeskalowaniem

std::auto_ptr<Graphics::TBitmap> Image(new Graphics::TBitmap());

// ustaw rozmiary bitmapy na identyczne z obrazkiem

Image->Width = ImageWidth;

Image->Height = ImageHeight;

// skopiuj obrazek na płótno bitmapy

ImageList->Draw(Image->Canvas, 0, 0, StrToInt(Value), true);

if(ImageWidth > MaxImageWidth) ImageWidth = MaxImageWidth;

if(ImageHeight > MaxImageHeight) ImageHeight = MaxImageHeight;

// określ obszar płótna komórki na którym ma znaleźć się obrazek

TRect ImageRect = Rect( ARect.Left + Border,

ARect.Top + Border,

ARect.Left + Border + ImageWidth,

ARect.Top + Border + ImageHeight );

// wkomponuj obrazek w prostokąt docelowy

ACanvas->StretchDraw(ImageRect, Image.get());

}

else

{

// rysowanie bezpośrednio na płótnie komórki,

// z zachowaniem obrzeża

ImageList->Draw( ACanvas,

ARect.Left + Border,

ARect.Top + Border,

StrToInt(Value),

true );

}

ValueRect = Rect( ARect.Left + ImageWidth + Border*2,

ARect.Top,

ARect.Right,

ARect.Bottom );

}

}

__finally

{

// wypisz tekst wartości bez względu na wynik rysowania obrazka

inherited::ListDrawValue(Value, ACanvas, ValueRect, ASelected);

}

}

Widzimy tu większość znajomych elementów metody TShapeTypePropertyEditor:: ListDrawValue(), nowością jest natomiast przeskalowywanie „niewymiarowych” obrazków. Każdy taki obrazek kopiowany jest najpierw (w naturalnej wielkości) na roboczą bitmapę, skąd kopiowany jest z przeskalowaniem do wyznaczonego obszaru na płótnie komórki. Obrazki nie wymagające przeskalowania rysowane są bezpośrednio na płótnie komórki. Na zakończenie, niezależnie od ewentualnych wyjątków związanych z rysowaniem obrazków, wypisywana jest tekstowa reprezentacja wartości.

Metoda PropDrawValue()

Jak wiadomo, bieżąca wartość właściwości, wypisywana obok jej nazwy, musi zmieścić się w standardowej wysokości komórki; jeżeli ma jej towarzyszyć jakiś symbol graficzny, to co najwyżej w postaci małej ikony. Podobnie więc jak w przypadku metody ListDrawValue() musimy uporać się z „wtłoczeniem” obrazka w pewne maksymalne ramy, toteż kod obydwu funkcji jest podobny; zamiast wartości MaxImageWidth i MaxImageHeight używana jest jednak wartość CurrentMaxSide, obliczona na podstawie wysokości komórki z uwzględnieniem jednopikselowych odstępów od jej poziomych krawędzi. Oto treść metody PropDrawValue():

Wydruk 8.23. Implementacja metody PropDrawValue()

void __fastcall TImageIndexPropertyEditor::PropDrawValue(

Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected)

{

TRect ValueRect = ARect;

try

{

TCustomImageList* ImageList = ComponentImageList;

// wyczyść obszar podlegający rysowaniu

ACanvas->FillRect(ARect);

if(ImageList != 0

&& GetVisualValue() != ""

&& GetVisualValue().ToInt() >= 0

&& GetVisualValue().ToInt() < ImageList->Count)

{

int ImageWidth = ImageList->Width;

int ImageHeight = ImageList->Height;

// Calculate the MaxSide as we want a square to display our image

int CurrentMaxSide = ARect.Bottom - ARect.Top - 2;

if(ImageWidth > CurrentMaxSide || ImageHeight > CurrentMaxSide)

{

std::auto_ptr<Graphics::TBitmap> Image(new Graphics::TBitmap());

// ustaw rozmiary bitmapy na identyczne z obrazkiem

Image->Width = ImageWidth;

Image->Height = ImageHeight;

// skopiuj obrazek na płótno bitmapy

ImageList->Draw(Image->Canvas, 0, 0, StrToInt(Value), true);

if(ImageWidth > CurrentMaxSide) ImageWidth = CurrentMaxSide;

if(ImageHeight > CurrentMaxSide) ImageHeight = CurrentMaxSide;

// określ obszar płótna komórki na którym ma znaleźć się obrazek

TRect ImageRect = Rect( ARect.Left + 2,

ARect.Top + 1,

ARect.Left + 2 + ImageWidth,

ARect.Top + 1 + ImageHeight );

// wkomponuj obrazek w prostokąt docelowy

ACanvas->StretchDraw(ImageRect, Image.get());

}

else

{

// rysowanie bezpośrednio na płótnie komórki,

// z zachowaniem jednopikselowego obrzeża

ImageList->Draw( ACanvas,

ARect.Left + 2,

ARect.Top + 1,

StrToInt(Value),

true );

}

ValueRect = Rect( ARect.Left + ImageWidth + 2,

ARect.Top,

ARect.Right,

ARect.Bottom );

}

}

__finally

{

// wypisz tekst wartości bez względu na wynik rysowania obrazka

inherited::PropDrawValue(ACanvas, ValueRect, ASelected);

}

}

Uważny Czytelnik z pewnością spostrzegł jedną ze słabych stron edytora TImageIndexPropertyEditor. Otóż w przypadku obrazka „niewymiarowego” podejmuje się próbę „wtłoczenia” go w założone ramy, bez troszczenia się jednak o jego proporcje (ang. aspect ratio), co oczywiście może w karykaturalny sposób zdeformować przedstawianą treść. Wzbogacenie metod ListDrawValue() i PropDrawValue() edytora o funkcję skalowania z zachowaniem proporcji z pewnością uczyniłoby go bardziej atrakcyjnym, lecz jednocześnie skomplikowało jego treść - co z punktu widzenia zasadniczego celu, jakim jest przedstawienie zasad funkcjonowania edytorów właściwości, jest raczej niepożądane. Nic jednak nie stoi na przeszkodzie, by rozszerzenia takiego dokonali samodzielnie zainteresowani Czytelnicy.

Wykorzystanie listy obrazków komponentu rodzicielskiego

Zajmiemy się teraz przypadkiem, kiedy to właściwość wskazująca na listę obrazków nie należy do przedmiotowego komponentu, zawierającego indeks obrazka, lecz do jednego z jego komponentów rodzicielskich. Jednym z takich komponentów jest komponent TMenuItem, dla którego właściwości ImageIndex stworzymy edytor TMenuItemImageIndexProperty. Jego deklarację przedstawia wydruk 8.24.

Wydruk 8.24. Deklaracja klasy TMenuItemImageIndexProperty

#include "Dsgnintf.hpp"

class PACKAGE TMenuItemImageIndexProperty :public TIntegerProperty

{

typedef TIntegerProperty inherited;

private:

static const int Border =2;

static const int MaxImageWidth =64;

static const int MaxImageHeight =64;

protected:

virtual Imglist::TCustomImageList*__fastcall GetParentImageList(void);

public:

virtual TPropertyAttributes __fastcall GetAttributes(void);

virtual void __fastcall GetValues(Classes::TGetStrProc Proc);

DYNAMIC void __fastcall ListMeasureWidth(

const AnsiString Value,

Graphics::TCanvas*ACanvas,

int&AWidth);

DYNAMIC void __fastcall ListMeasureHeight(

const AnsiString Value,

Graphics::TCanvas*ACanvas,

int&AHeight);

DYNAMIC void __fastcall ListDrawValue(

const AnsiString Value,

Graphics::TCanvas*ACanvas,

const Windows::TRect&ARect,

bool ASelected);

DYNAMIC void __fastcall PropDrawValue(

Graphics::TCanvas*ACanvas,

const Windows::TRect&ARect,

bool ASelected);

// właściwość tylko do odczytu, udostępniająca wskaźnik

// do listy obrazków macierzystego komponentu

__property Imglist::TCustomImageList*ParentImageList

={read=GetParentImageList};

protected:

#pragma option push -w-inl

inline __fastcall virtual TMenuItemImageIndexProperty(

const _di_IFormDesigner ADesigner,

int APropCount):TIntegerProperty(

ADesigner,

APropCount)

{}

#pragma option pop

public:

#pragma option push -w-inl

inline __fastcall virtual ~TMenuItemImageIndexProperty(void)

{}

#pragma option pop

};

Ponieważ komponent macierzysty nie posiada już wskaźnika do listy TCustomImageList, wskaźnik taki musi być poszukiwany wśród jego komponentów rodzicielskich. W związku z tym zamiast właściwości ComponentImageList mamy teraz właściwość ParentImageList z metodą dostępową GetParentImageList(), poszukującą żądanej właściwości wśród komponentów rodzicielskich. Jej implementację przedstawia wydruk 8.25.

Wydruk 8.25. Implementacja metody GetParentImageList()

Imglist::TCustomImageList*__fastcall

TMenuItemImageIndexProperty::GetParentImageList(void)

{

TMenuItem*Component =dynamic_cast<TMenuItem*>(GetComponent(0));

if(Component)

{

TMenuItem*ParentMenuItem =Component->Parent;

while(ParentMenuItem !=0 &&ParentMenuItem->SubMenuImages ==0)

{

ParentMenuItem =ParentMenuItem->Parent;

}

if(ParentMenuItem !=0)

{

return ParentMenuItem->SubMenuImages;

}

else

{

TMenu*ParentMenu =Component->GetParentMenu();

if(ParentMenu !=0)return ParentMenu->Images;

}

}

return 0;

}

Metoda GetParentImageList() rozpoczyna swą pracę od zlokalizowania macierzystego komponentu edytowanej właściwości i upewnienia się, iż jest to komponent klasy TMenuItem. Jeżeli komponent ten reprezentuje opcję menu na niższym (zagnieżdżonym) poziomie, posiada on komponenty rodzicielskie (również klasy TMenuItem) na kolejno wyższych poziomach i wśród tych komponentów prowadzone jest poszukiwanie żądanej listy, tj. sprawdzana jest ich właściwość SubMenuImages. Iteracja prowadzona jest aż do znalezienia komponentu z niezerową wartością tej właściwości albo dojścia na najwyższy poziom menu. W tym drugim przypadku żądaną listę wskazuje właściwość Images „głównego” komponentu TMenu, którego adres łatwo można otrzymać, wywołując metodę dowolnego podporządkowanego mu komponentu TMenuItem, czyli na przykład „wyjściowego”, macierzystego komponentu zawierającego edytowaną właściwość ImageIndex.

Przedmiotową listę obrazków znaleźć można jednak znacznie prościej - tak się bowiem składa, iż komponent TMenuItem wyposażony został w metodę realizującą wprost opisane poszukiwanie. Oto ona:

function TMenuItem.GetImageList: TCustomImageList;

var

vItem: TMenuItem;

vMenu: TMenu;

begin

Result := nil;

vItem := Parent;

while (vItem <> nil) and (vItem.SubMenuImages = nil) do

vItem := vItem.Parent;

if vItem <> nil

then

Result := vItem.SubMenuImages

else

begin

vMenu := GetParentMenu;

if vMenu <> nil

then

Result := vMenu.Images;

end;

end;

A skoro tak, to nasza metoda GetParentImageList() może być zapisana w nieco prostszej postaci:

Imglist::TCustomImageList*__fastcall

TMenuItemImageIndexProperty::GetParentImageList(void)

{

TMenuItem*Component =dynamic_cast<TMenuItem*>(GetComponent(0));

if(Component)

{

return Component->GetImageList();

}

return 0;

}

W charakterze kolejnego przykładu przedstawimy jeszcze jeden obiekt wykorzystujący listę obrazków: będzie nim jedna z sekcji „nagłówka” tabelek - THeaderSection. Obiekt ten zalicza się do kategorii elementów kolekcji, wywodzi się bowiem z klasy TCollectionItem i jako taki związany jest ze swą macierzystą kolekcją THeaderSections stanowiącą dla niego klasę-pojemnik (ang. container class) i wskazywaną przez właściwość Collection. Komponentem rodzicielskim dla tej kolekcji jest komponent reprezentujący nagłówek jako całość - THeaderControl - i dostępny za pomocą jej metody GetOwner(); to właśnie on zarządza żądaną listą obrazków, ukrywającą się pod jego właściwością Images.

Wszystko to byłoby niezwykle proste, gdyby nie pewien szkopuł: otóż metoda THeaderSections::GetOwner() jest metodą chronioną (protected), a więc niedostępną dla bezpośredniego wywołania. Aby udostępnić ją użytkownikowi, należałoby zdefiniować klasę pochodną do THeaderSections i „wypromować” w niej metodę GetOwner() na metodę publiczną (public). Taka klasa, nie definiująca żadnych nowych elementów, lecz jedynie udostępniająca niedostępne normalnie metody i właściwości, nosi nazwę klasy dostępowej (ang. access class) - w naszym przypadku będzie ona miała następującą deklarację:

class THeaderSectionsAccess :public THeaderSections

{

public:

DYNAMIC Classes::TPersistent*__fastcall GetOwner(void);

};

Edytor dla właściwości ImageIndex obiektu THeaderSection będzie się różnił od opisywanego przed chwilą TMenuItemImageIndexProperty jedynie metodą GetParentImageList(), realizującą scenariusz przed chwilą opisany; na wydruku 8.26 przedstawiamy jej implementację.

Wydruk 8.26. Implementacja metody THeaderSectionImageIndexProperty::GetParentImageList

Imglist::TCustomImageList* __fastcall

THeaderSectionImageIndexProperty::GetParentImageList(void)

{

THeaderSection* Component = dynamic_cast<THeaderSection*>(GetComponent(0));

if(Component)

{

THeaderSections* HeaderSections =

dynamic_cast<THeaderSections*>(Component->Collection);

if(HeaderSections)

{

TPersistent* Owner =

static_cast<THeaderSectionsAccess*>(HeaderSections)->GetOwner();

THeaderControl* HeaderControl = dynamic_cast<THeaderControl*>(Owner);

if(HeaderControl)

{

return HeaderControl->Images;

}

}

}

return 0;

}

Uniwersalne podejście do edycji właściwości ImageIndex

Poza opisanymi przed chwilą obiektami TMenuItem i THeaderSection biblioteka VCL zawiera jeszcze kilka innych klas wywodzących się z TPersistent lub TComponent i posiadających właściwość ImageIndex. Ich zestawienie zawiera tabela 8.10.

Tabela 8.10. Obiekty VCL posiadające właściwość ImageIndex

Klasa komponentu

Genealogia

TCoolBand

*TCollectionItem*TPersistent

TCustomAction

*TComponent*TPersistent

THeaderSection

*TCollectionItem*TPersistent

TListColumn

*TCollectionItem*TPersistent

TMenuItem

*TComponent*TPersistent

TTabSheet

*TWinControl*TControl*TComponent*TPersistent

TToolButton

*TGraphicControl*TControl*TComponent*TPersistent

Na podstawie doświadczeń nabytych przy konstrukcji edytorów dotychczas opisanych można by pokusić się o generalne rozwiązanie edycji właściwości ImageIndex dla wszystkich obiektów zestawionych w tabeli 8.10. Rozwiązanie to ma postać klasy bazowej, z której łatwo wyprowadzić edytor dla konkretnego obiektu. Deklarację tej klasy przedstawia wydruk 8.27.

Wydruk 8.27. Deklaracja abstrakcyjnej klasy bazowej dla edytorów właściwości ImageIndex

class PACKAGE TImageIndexProperty : public TIntegerProperty

{

typedef TIntegerProperty inherited;

private:

static const int Border = 2;

static const int MaxImageWidth = 64;

static const int MaxImageHeight = 64;

protected:

virtual Imglist::TCustomImageList* __fastcall GetImageList(void) = 0;

public:

virtual TPropertyAttributes __fastcall GetAttributes(void);

virtual void __fastcall GetValues(Classes::TGetStrProc Proc);

DYNAMIC void __fastcall ListMeasureWidth(const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AWidth);

DYNAMIC void __fastcall ListMeasureHeight(const AnsiString Value,

Graphics::TCanvas* ACanvas,

int& AHeight);

DYNAMIC void __fastcall ListDrawValue(const AnsiString Value,

Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected);

DYNAMIC void __fastcall PropDrawValue(Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

bool ASelected);

__property Imglist::TCustomImageList* RemoteImageList =

{read=GetImageList};

protected:

#pragma option push -w-inl

inline __fastcall virtual TImageIndexProperty(const _di_IFormDesigner

ADesigner,

int APropCount)

: TIntegerProperty(ADesigner,

APropCount)

{ }

#pragma option pop

public:

#pragma option push -w-inl

inline __fastcall virtual ~TImageIndexProperty(void)

{ }

#pragma option pop

};

Jako że wymienione w tabeli 8.10 obiekty różnią się (z punktu widzenia właściwości ImageIndex) jedynie relacją do listy zawierającej żądane obrazki, jedyną metodą wymagającą przedefiniowania w klasach pochodnych jest metoda GetImageList(). Przedefiniowanie to jest konieczne, bowiem metoda ta jest metodą czysto wirtualną (ang. pure virtual), a więc klasa TImageIndexProperty jest abstrakcyjną klasą bazową (ang. abstract base class).

Jak łatwo zauważyć, tabela 8.10 wprowadza podział prezentowanych obiektów na dwie grupy: tych wywodzących się z klasy TComponent (czyli komponentów) i pozostałych, wywodzących się jedynie z klasy TPersistent. Implementację metody GetImageList() w klasie bazowej dla tej drugiej grupy (TPersistentDerivedImageIndexProperty) przedstawia wydruk 8.28.

Wydruk 8.28. Implementacja metody GetImageList() dla obiektów VCL wywodzących się z klasy TPersistent

//--------------------------------------------------------------------------//

// //

// TComponentAccess //

// //

//--------------------------------------------------------------------------//

class TComponentAccess :public TComponent

{

public:

DYNAMIC Classes::TPersistent*__fastcall GetOwner(void);

};

//--------------------------------------------------------------------------//

// //

// TPersistentDerivedImageIndexProperty //

// //

//--------------------------------------------------------------------------//

__fastcall

TPersistentDerivedImageIndexProperty::TPersistentDerivedImageIndexProperty

(const _di_IFormDesigner ADesigner,

int APropCount)

:TImageIndexProperty(ADesigner,

APropCount)

{

FParentImageListName ="Images";

}

//---------------------------------------------------------------------------//

Imglist::TCustomImageList*__fastcall

TPersistentDerivedImageIndexProperty::GetImageList(void)

{

TPersistent*Parent =static_cast<TComponentAccess*>

(GetComponent(0))->GetOwner();

while(Parent !=0 &&!dynamic_cast<TComponent*>(Parent))

{

Parent =static_cast<TComponentAccess*>(Parent)->GetOwner();

}

if(Parent ==0)return 0;

PPropInfo PropInfo

=Typinfo::GetPropInfo(static_cast<PTypeInfo>(Parent->ClassInfo()),

FParentImageListName);

if(PropInfo ==0)return 0;

return

(

reinterpret_cast<TCustomImageList*>

(Typinfo::GetOrdProp(Parent,PropInfo))

);

}

Prezentowana metoda startuje od bezpośredniego przodka macierzystego obiektu w górę hierarchii obiektów rodzicielskich i kończy swą wędrówkę przy zaistnieniu jednego z dwóch przypadków: wyczerpania listy lub napotkania obiektu wywodzącego się z klasy TComponent. W pierwszym przypadku metoda ostatecznie zwraca wartość NULL, w drugim natomiast badana jest właściwość o nazwie Images komponentu rodzicielskiego dla znalezionego komponentu i zwracana jako wynik metody po uprzednim rzutowaniu jej na wskaźnik TCustomImageList*.

Analogiczna metoda dla komponentów - tj. tych obiektów wymienionych w tabeli 8.10, które wywodzą się z klasy TComponent - jest o tyle prostsza, iż opisane przeszukiwanie w ogóle jest niepotrzebne, bowiem już obiekt „bezpośrednio rodzicielski” (czyli ten wskazywany przez własciwość Parent) sam jest komponentem, więc można od razu skorzystać ze znanych mechanizmów RTTI. Implementację funkcji GetImageList() dla tej klasy obiektów przedstawia wydruk 8.29.

Wydruk 8.29. Implementacja metody GetImageList() dla obiektów VCL wywodzących się z klasy TComponent

Imglist::TCustomImageList*__fastcall

TComponentDerivedImageIndexProperty::GetImageList(void)

{

TComponent*Parent

=static_cast<TComponentAccess*>(GetComponent(0))->GetParentComponent();

if(Parent ==0) return 0;

PPropInfo PropInfo

=Typinfo::GetPropInfo(static_cast<PTypeInfo>(Parent->ClassInfo()),

FParentImageListName);

if(PropInfo ==0)return 0;

return

(

reinterpret_cast<TCustomImageList*>(Typinfo::GetOrdProp(Parent,PropInfo))

);

}

Niestety, implementacje z wydruków 8.28 i 8.29 nie przydadzą się w przypadku trzech spośród wymienionych w tabeli 8.10 klas, z bardzo prozaicznego powodu, jakim jest nieadekwatność nazwy właściwości wskazującej listę obrazków, domyślnie przyjętej jako Images: dla listy TListColumn właściwość ta nosi nazwę SmallImages, dla TMenuItem - SubMenuImages; dla przycisku TToolButton nazwa Images aktualna jest jedynie wówczas, gdy przycisk jest dostępny (enabled), w przeciwnym wypadku lista obrazków wskazywana jest przez właściwość DisabledImages. Dla obiektów TListColumn i TMenuItem wystarczy więc prosta zmiana łańcucha przypisywanego polu FParentImageListName, dla obiektu TToolButton wymagana jest odmienna postać samej metody (patrz wydruk 8.30).

Wydruk 8.30. Poszukiwanie listy obrazków dla komponentu TToolButton

Imglist::TCustomImageList* __fastcall TToolButtonImageIndexProperty::GetImageList(void)

{

TToolButton* Component = dynamic_cast<TToolButton*>(GetComponent(0));

if(Component)

{

TToolBar* ParentToolBar = dynamic_cast<TToolBar*>(Component->Parent);

if(ParentToolBar)

{

if(Component->Enabled) return ParentToolBar->Images;

else return ParentToolBar->DisabledImages;

}

}

return 0;

}

Ostatecznie więc obiekty VCL, posiadające właściwość ImageIndex, posługują się (w celu edycji tej właściwości) pięcioma edytorami, których drzewo genealogiczne przedstawia rysunek 8.8.

Rysunek 8.8. Edytory właściwości ImageIndex obiektów biblioteki VCL

Tworzenie edytorów komponentów

Możliwości IDE w zakresie uatrakcyjniania pracy z komponentami nie kończą się na edytorach właściwości. Ingerencja użytkownika sięgać może dalej niż na poziom poszczególnych właściwości: specjalizowane edytory komponentów (bo o nich mowa) umożliwiają określenie zachowania się komponentu jako całości podczas konfigurowania różnorodnych jego elementów, w szczególności zmianę domyślnych ustawień w tym względzie.

Klasami bazowymi dla tworzenia specjalizowanych edytorów komponentów są TComponentEditor i TDefaultEditor; związek między nimi ukazuje rysunek 8.9, gdzie drukiem wytłuszczonym zaznaczono jedyną rzeczywiście zaimplementowaną metodę - Edit(); pozostałe metody klasy TComponentEditor posiadają pustą treść bądź ograniczają się jedynie do zwrócenia standardowego wyniku 0 (GetVerbCount()) i w takiej postaci dziedziczone są przez klasę TDefaultEditor.

Rysunek 8.9. Dziedziczenie z klasy TComponentEditor

Standardowe zachowanie się komponentu w reakcji na dwie najbardziej powszechne operacje - dwukrotne kliknięcie lewym przyciskiem myszy oraz kliknięcie prawym - określone przez metody klas TComponentEditor i TDefaultEditor, opisane jest w tabeli 8.11.

Tabela 8.11. Obsługa standardowych operacji myszą przez klasy TComponentEditor i TDefaultEditor

Operacja

Standardowa reakcja

Wywoływane metody standardowe

Kliknięcie prawym przyciskiem

Wyświetlenie menu kontekstowego

Najpierw wywoływana jest metoda GetVerbCount() w celu określenia liczby dodatkowych opcji menu kontekstowego.

Tekstowa postać każdej z tych opcji uzyskiwana jest przez wywołanie metody GetVerb().

Przed wyświetleniem każdej opcji wywoływana jest dla niej metoda PrepareItem() w celu przeprowadzenia ewentualnych modyfikacji.

W przypadku kliknięcia którejś z nowo dodanych opcji wywoływana jest dla niej metoda ExecuteVerb(), wykonująca specjalizowane czynności związane z opcją.

Dwukrotne kliknięcie lewym przyciskiem

TComponentEditor:
Jeżeli do menu kontekstowego dodano jakieś opcje (GetVerbCount()>0), wykonywana jest akcja związana z pierwszym wyświetlanym elementem.

Wywoływana jest metoda Edit().

TDefaultEditor:
W oknie edytora kodu tworzony jest szablon funkcji zdarzeniowej dla jednego ze zdarzeń OnChange, OnCreate lub OnClick zależnie od tego, dla którego z nich najwcześniej deklarowana jest właściwość zdarzeniowa. Jeżeli komponent nie obsługuje żadnego z wymienionych zdarzeń, wybierana jest pierwsza deklarowana właściwość zdarzeniowa. Jeżeli komponent w ogóle nie posiada właściwości zdarzeniowych, nie jest wykonywana żadna akcja.

Analizując tę tabelę, nietrudno zauważyć różnicę pomiędzy implementacjami metody Edit() w obydwu wymienionych klasach. Jeżeli więc nowo tworzony edytor komponentu ma tworzyć szablon jakiejś metody zdarzeniowej w odpowiedzi na dwukrotne kliknięcie, TDefaultEditor z pewnością stanowić będzie dla niego odpowiedniejszą klasę bazową niż TComponentEditor.

Aby prawidłowo zaimplementować poszczególne metody edytora komponentu, należy najpierw poznać spełniane przez nie zadania; jest to przedmiotem opisu zamieszczonego w tabeli 8.12.

Tabela 8.12. Metody wirtualne klas TComponentEditor i TDefaultEditor

Metoda

Przeznaczenie

int GetVerbCount(void)

Określa liczbę opcji dodawanych do menu kontekstowego.

AnsiString GetVerb(int index)

Określa tekstową reprezentację opcji menu kontekstowego z następującymi zastrzeżeniami: znak ampersanda (&) określa „akcelerator” (hotkey) dla opcji (na podstawie poprzedzanego znaku). Reprezentacja opcji wywołującej dialog powinna kończyć się wielokropkiem (...). Tekst „-” (myślnik) oznacza nie opcję, lecz separator (divider) pomiędzy opcjami.

void PrepareItem(int Index, const Menus::TMenuItem* AItem)

Wywoływana jest dla każdej opcji menu kontekstowego przed jej wyświetleniem, co umożliwia dokonanie pewnych akcji uzupełniających, na przykład uczynienie danej opcji niedostępną
(AItem->Enable=false).

void ExecuteVerb(int Index)

Wywoływana jest w momencie kliknięcia którejś z dodanych opcji menu kontekstowego. Index oznacza numer opcji.

void Edit(void)

Wywoływana jest w momencie dwukrotnego kliknięcia komponentu; jej domyślny scenariusz opisany został w tabeli 8.11.

void EditProperty
(TPropertyEditor* PropertyEditor, bool& Continue,
bool& FreeEditor)

(tylko TDefaultEditor)

Decyduje o tym, dla którego zdarzenia generowany będzie szablon funkcji obsługi w odpowiedzi na dwukrotne kliknięcie komponentu.

void Copy(void)

Wywoływana w momencie kopiowania edytowanego komponentu do schowka. Stanowi okazję do utworzenia w schowku dodatkowego obrazu komponentu w specyficznym formacie, charakterystycznym dla jakiejś zewnętrznej aplikacji.

Wydruki 8.31 i 8.32 przedstawiają deklaracje przykładowych klas edytorów wyprowadzonych z klas (odpowiednio) TComponentEditor i TDefaultEditor.

Wydruk 8.31. Deklaracja przykładowego edytora wyprowadzonego z klasy TComponentEditor

#include "dsgnintf.hpp"

class TCustomComponentEditor : public TComponentEditor

{

typedef TComponentEditor inherited;

public:

// dwukrotne kliknięcie

virtual void __fastcall Edit(void);

// kliknięcie prawym przyciskiem

// MENU KONTEKSTOWE - krok 1

virtual int __fastcall GetVerbCount(void);

// - krok 2

virtual AnsiString __fastcall GetVerb(int Index);

// - krok 3 (NIEOBOWIĄZKOWY)

virtual void __fastcall PrepareItem(int Index,

const Menus::TMenuItem* AItem);

// - krok 4

virtual void __fastcall ExecuteVerb(int Index);

// kopiowanie do schowka

virtual void __fastcall Copy(void);

public:

#pragma option push -w-inl

inline __fastcall virtual

TCustomComponentEditor(

Classes::TComponent* AComponent,

_di_IFormDesigner ADesigner

): TComponentEditor(AComponent, ADesigner)

{ }

#pragma option pop

public:

#pragma option push -w-inl

inline __fastcall virtual ~TCustomComponentEditor(void) { }

#pragma option pop

};

Wydruk 8.32. Deklaracja przykładowego edytora wyprowadzonego z klasy TDefaultEditor

#include "dsgnintf.hpp"

class TCustomDefaultEditor : public TDefaultEditor

{

typedef TDefaultEditor inherited;

protected:

// dwukrotne kliknięcie

// WYBÓR ZDARZENIA

virtual void __fastcall EditProperty(TPropertyEditor* PropertyEditor,

bool& Continue,

bool& FreeEditor);

public:

// kliknięcie prawym przyciskiem

// MENU KONTEKSTOWE - krok 1

virtual int __fastcall GetVerbCount(void);

// - krok 2

virtual AnsiString __fastcall GetVerb(int Index);

// - krok 3 (NIEOBOWIĄZKOWY)

virtual void __fastcall PrepareItem(int Index,

const Menus::TMenuItem* AItem);

// - krok 4

virtual void __fastcall ExecuteVerb(int Index);

// kopiowanie do schowka

virtual void __fastcall Copy(void);

public:

#pragma option push -w-inl

inline __fastcall virtual

TCustomDefaultEditor(Classes::TComponent* AComponent,

_di_IFormDesigner ADesigner

): TDefaultEditor(AComponent, ADesigner)

{ }

#pragma option pop

public:

#pragma option push -w-inl

inline __fastcall virtual ~TCustomDefaultEditor(void) { }

#pragma option pop

};

Analizując powyższe wydruki, nietrudno zauważyć różnicę pomiędzy metodami Edit() w obydwu rodzajach edytorów.

Podobnie jak we wcześniejszej części rozdziału przyjrzymy się teraz szczegółom implementacji wymienionych w tabeli metod przykładowych edytorów obydwu rodzajów - TCustomComponentEditor i TCustomDefaultEditor.

Metoda Edit()

Wobec oczywistości tej metody w klasie TComponentEditor (patrz tabela 8.11) ograniczymy się tutaj do jej implementacji w klasie wyprowadzonej z TDefaultEditor. Jej zadaniem jest realizacja dialogu uruchamianego w wyniku dwukrotnego kliknięcia komponentu, który to dialog może być bardziej specjalizowany niż standardowe generowanie szkieletu funkcji zdarzeniowej. Przykładem dialogu tej klasy jest np. dialog uruchamiany przez edytor komponentu TChart, którego formularz przedstawia rysunek 8.10.

Rysunek 8.10. Przykładowy dialog realizowany przez metodę Edit() edytora komponentu

Podobnie jak w przypadku edytorów właściwości łączność realizowanego dialogu z danymi edytowanego komponentu może mieć dwojaki charakter - dane te mogą mianowicie być uaktualniane na bieżąco albo dopiero po zatwierdzeniu dialogu (tj. zakończeniu go z wynikiem mrOK). Niezależnie od wybranego rodzaju aktualizacji wymaga się ponadto, by IDE powiadamiane było o każdej modyfikacji komponentu - powiadamianie to realizowane jest przez metodę Modified() projektanta formularzy, dlatego też instrukcja

if (Designer) Designer->Modified()

powinna stanowić integralną część implementacji metody Edit().

Przy aktualizacji na bieżąco wszelkie modyfikacje komponentu, jak również związane z tym powiadomienia pod adresem IDE, wykonywane są przez metodę Execute() formularza dialogowego wyświetlanego przez metodę ShowModal() w trybie modalnym. Wynika stąd, iż formularz ten powinien dysponować zarówno adresem egzemplarza edytowanego komponentu, jak i referencją do właściwości Designer edytora zawierającej adres projektanta formularzy. W przykładzie na wydruku 8.33 egzemplarz edytowanego komponentu wskazywany jest przez właściwość ComponentClass, zaś referencję do adresu projektanta formularzy zawiera pole Designer. Nie należy oczywiście zapominać o zrobieniu użytku z tego pola (Designer->Modified()) przy każdej aktualizacji danych komponentu.

Wydruk 8.33. Edycja komponentu z aktualizacją „na bieżąco”

// Najważniejsze fragmenty kodu formualzra dialogowego:

//

// W PLIKU NAGŁÓWKOWYM

//---------------------------------------------------------------------------//

#ifndef MyComponentEditorFormH

#define MyComponentEditorFormH

//---------------------------------------------------------------------------//

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

#include "HeaderDeclaringTComponentClass"

//---------------------------------------------------------------------------//

class TMyComponentEditorForm : public TForm

{

__published: // IDE-managed Components

private:

TComponentClass* FComponentClass;

_di_IformDesigner& Designer;

...

inne deklaracje niezbędne do przywrócenia początkowych ustawień komponentu

w przypadku anulowania dialogu

...

protected:

void __fastcall SetComponentClass(TComponentClass* Pointer);

public:

__fastcall TMyComponentEditorForm(TComponent* Owner,

_di_IformDesigner& EditorDesigner);

__property TComponentClass* ComponentClass = {read=FComponentClass,

write=SetComponentClass};

...

};

//---------------------------------------------------------------------------//

#endif

// W PLIKU IMPLEMENTACYJNYM

//---------------------------------------------------------------------------//

#include <vcl.h>

#pragma hdrstop

#include "MyComponentEditorForm.h"

//---------------------------------------------------------------------------//

#pragma package(smart_init)

#pragma resource "*.dfm"

//---------------------------------------------------------------------------//

__fastcall

TMyComponentEditorForm::

TMyComponentEditorForm(TComponent* Owner,

_di_IformDesigner& EditorDesigner)

: TForm(Owner), Designer(EditorDesigner)

{

// zwróć uwagę, iż inicjowane jest tutaj pole Designer

}

//---------------------------------------------------------------------------//

void __fastcall TMyPropertyForm::SetComponentClass(TComponentClass* Pointer)

{

FComponentClass = Pointer;

if(FComponentClass != 0)

{

...

zapamiętaj aktualne wartości właściwości komponentu i wyświetl je

...

}

}

//---------------------------------------------------------------------------//

// METODA Edit()

#include "MyComponentEditorForm.h" // Remember this

void __fastcall TCustomComponentEditor::Edit(void)

{

// utwórz formularz

std::auto_ptr<TMyComponentEditorForm*>

MyComponentEditorForm(new TMyComponentEditorForm(Application));

// przypisz zdres edytowango komponentu

MyComponentEditorForm->ComponentClass

= dynamic_cast<TComponentClass*>(Component);

// Uruchom dialog

MyPropertyForm->ShowModal();

}

Aktualizacja „odłożona” (do zakończenia dialogu) danych komponentu ma przebieg znacznie prostszy. Do stosownych właściwości formularza dialogowego przepisywane są wartości odpowiednich właściwości edytowanego komponentu; następnie formularz uruchamiany jest w trybie modalnym, po którego zakończeniu badana jest przyczyna tego zakończenia - jeżeli wskazuje ona na zatwierdzenie dialogu (mrOK lub IDOK, wszystko jedno) właściwości komponentu uaktualniane są wartościami ustalonymi w dialogu, po czym IDE informowane jest o zmodyfikowaniu komponentu. Procedurę tę przedstawia wydruk 8.34.

Wydruk 8.34. Edycja komponentu z aktualizacją „odłożoną”

#include "MyComponentEditorDialog.h" // Include the header for the Dialog!

// Dialog is TMyComponentDialog

void __fastcall TCustomComponentEditor::Edit(void)

{

TMyComponent* MyComponent = dynamic_cast<TMyComponent*>(Component);

if(MyComponent != 0)

{

// utwórz formularz dialogowy

std::auto_ptr<TMyComponentDialog*>

MyComponentDialog(new TMyComponentDialog(Application0));

...

przepisz aktualne wartości wybranych własciwości edytowanego komponentu

do odpowiednich właściwości formularza dialogowego, na przykład:

MyComponentDialog->value1 = MyComponent->value1;

MyComponentDialog->value2 = MyComponent->value2;

...

// wyświetl formularz w trybie modalnym

// i zbadaj przyczynę zakończenia dialogu

if(MyPropertyDialog->ShowModal() == IDOK)

{

...

przepisz wartości ustalone w dialogu do odpowiednich własciwości

edytowanego komponentu, na przykład:

MyComponent->value1 = MyComponentDialog->value1;

MyComponent->value2 = MyComponentDialog->value2;

...

if(Designer) Designer->Modified(); // NIE ZAPOMNIJ!

}

}

else

{

throw EInvalidPointer

("Edycja niemożliwa: docelowy komponent jest niedostępny");

}

}

Analizując wydruk 8.34 warto zwrócić uwagę na jedną istotną rzecz. Dynamiczne rzutowanie typów, prowadzące do otrzymania wskaźnika edytowanego komponentu, może (choć nie powinno) zwrócić wartość NULL; należy wówczas wygenerować stosowny wyjątek.

Metoda EditProperty()

Najczęściej metodę tę przedefiniowuje się w celu generowania szablonu funkcji zdarzeniowej dla pewnego szczególnego zdarzenia (lub grupy zdarzeń). Metoda TDefaultEditor::Edit() wywołuje tę metodę dla każdej opublikowanej właściwości komponentu:

procedure TDefaultEditor.CheckEdit(PropertyEditor: TPropertyEditor);

var

FreeEditor: Boolean;

begin

FreeEditor := True;

try

if FContinue

then

EditProperty(PropertyEditor, FContinue, FreeEditor);

finally

if FreeEditor

then

PropertyEditor.Free;

end;

end;

procedure TDefaultEditor.Edit;

var

Components: TDesignerSelectionList;

begin

Components := TDesignerSelectionList.Create;

try

FContinue := True;

Components.Add(Component);

FFirst := nil;

FBest := nil;

try

GetComponentProperties(Components, tkAny, Designer, CheckEdit);

if FContinue then

if Assigned(FBest) then

FBest.Edit

else if Assigned(FFirst) then

FFirst.Edit;

finally

FFirst.Free;

FBest.Free;

end;

finally

Components.Free;

end;

end;

Załóżmy, iż edytowany komponent dedykowany jest transmisji szeregowej; prawdopodobnie najczęściej używanym jego zdarzeniem będzie zdarzenie OnDataReceived generowane w momencie skompletowania kolejnej porcji otrzymanych danych. Aby dwukrotne kliknięcie komponentu powodowało generowanie szkieletu funkcji zdarzeniowej dla tego właśnie zdarzenia, trzeba uczulić na nie metodę EditProperty() naszego specjalizowanego edytora komponentu:

void __fastcall TCustomSerialCommEditor::EditProperty(

TPropertyEditor* PropertyEditor,

&bool Continue,

&bool FreeEditor);

{

if (

PropertyEditor->ClassNameIs("TMethodProperty")

&&

(CompareText(PropertyEditor->GetName(), "OnDataReceived") == 0)

)

{

inherited::EditProperty(PropertyEditor, Continue, FreeEditor);

}

}

Sprawdza się tu, czy właściwość, dla której wywołano metodę, jest właściwością zdarzeniową (TMethodProperty) poświęconą zdarzeniu OnDataReceived; należy zaznaczyć, iż wykorzystana funkcja CompareText(), nie jest wrażliwa na wielkość liter.

Jeżeli wspomniane zachowanie (tj. generowanie pustego szablonu funkcji zdarzeniowej) dotyczyć ma grupy wybranych zdarzeń, pojedyncza instrukcja if przekształca się w łańcuch takich instrukcji, na przykład:

if (

PropertyEditor->ClassNameIs("TMethodProperty")

&&

(CompareText(PropertyEditor->GetName(), "OnEvent1") == 0)

)

{

inherited::EditProperty(PropertyEditor, Continue, FreeEditor);

}

else if (

PropertyEditor->ClassNameIs("TMethodProperty")

&&

(CompareText(PropertyEditor->GetName(), "OnEvent2") == 0)

)

{

inherited::EditProperty(PropertyEditor, Continue, FreeEditor);

}

else if (

PropertyEditor->ClassNameIs("TMethodProperty")

&&

(CompareText(PropertyEditor->GetName(), "OnEvent2") == 0)

)

{

inherited::EditProperty(PropertyEditor, Continue, FreeEditor);

}

else

...

co oczywiście można „zwinąć” do pojedynczej instrukcji ze złożonym warunkiem:

if (

PropertyEditor->ClassNameIs("TMethodProperty")

&&

(CompareText(PropertyEditor->GetName(), "OnEvent1") == 0)

)

||

( PropertyEditor->ClassNameIs("TMethodProperty")

&&

(CompareText(PropertyEditor->GetName(), "OnEvent2") == 0)

)

||

( PropertyEditor->ClassNameIs("TMethodProperty")

&&

(CompareText(PropertyEditor->GetName(), "OnEvent3") == 0)

)

{

inherited::EditProperty(PropertyEditor, Continue, FreeEditor);

}

else

...

Metoda GetVerbCount()

Metoda ta powinna zwrócić liczbę opcji dodawanych do standardowego menu kontekstowego. Ewentualne separatory (dividers) również liczone są jako opcje.

int __fastcall TMyCustomEditor::GetVerbCount(void)

{

return 4;

}

Metoda GetVerb()

Zadaniem tej metody jest zwrócenie tekstowej reprezentacji opcji dodawanej do menu kontekstowego. Pojedynczy myślnik („-”) oznacza separator (divider) pomiędzy opcjami.

AnsiString __fastcall TMyCustomEditor::GetVerb(int Index)

{

switch(Index)

{

case 0 : return "&Edytuj Komponent...";

case 1 : return "&O komponencie...";

case 2 : return "-";

case 3 : return "&Przywróć standardowe"

default : return ""

}

}

Znak ampersanda (&) poprzedza literę określającą klawisz „akceleratora” (ang. hotkey). Akceleratory zdefiniowane w dodawanych opcjach mają pierwszeństwo przed akceleratorami przypisywanymi standardowo przez IDE domyślnym opcjom menu kontekstowego - w przypadku konfliktów IDE stara się zmienić te ostatnie tak, by konflikty te ustąpiły. Opcje dodawane przez metodę GetVerb() automatycznie oddzielane są przez IDE od opcji standardowych dodatkowym separatorem, o który użytkownik nie musi się już troszczyć.

Metoda PrepareItem()

Metoda ta, nowa dla wersji 5. C++Buildera, niekoniecznie musi być implementowana. Stwarza ona jednak okazję do zmodyfikowania opcji menu przed jej wyświetleniem, na przykład w celu jej ukrycia (Visible=false), zablokowania (Enable=false) czy też dodania do niej podmenu. Pierwszy parametr wywołania metody jest indeksem opcji, drugi natomiast wskazuje na obiekt TMenuItem, reprezentujący tę opcję. Jest jednak pewien problem: parametr ten opatrzony jest mianowicie klauzulą const, wspomniany obiekt nie może więc być modyfikowany bezpośrednio - należy najpierw utworzyć dla niego alternatywny wskaźnik, umożliwiający taką modyfikację; umożliwia to rzutowanie const_cast<>, zmieniające status nienaruszalności. Powróćmy do treści prezentowanej przed chwilą metody GetVerb() i załóżmy, iż chcielibyśmy zablokować możliwość edycji komponentu (opcja o indeksie 0) i usunąć z menu opcję przywracającą ustawienia standardowe (indeks 3). Realizująca ten cel metoda PrepareItem() powinna mieć postać następującą:

Wydruk 8.35. Przykład przedefiniowania metody PrepareItem()

void __fastcall TMyCustomEditor::PrepareItem

(int Index, const Menus::TMenuItem* AItem)

{

switch(Index)

{

case 0 :

{

TMenuItem* MenuItem = const_cast<TMenuItem*>(AItem);

MenuItem->Enabled = false;

break;

}

case 1 : break;

case 2 : break;

case 3 : break;

{

TMenuItem* MenuItem = const_cast<TMenuItem*>(AItem);

MenuItem->Visible = false;

break;

}

default : break;

}

}

Można także przypisać danej opcji ikonę pobieraną z zasobu, na przykład tak:

TMenuItem* MenuItem = const_cast<TMenuItem*>(AItem);

MenuItem->BitMap->LoadFromResourceName

(reinterpret_cast<int>(Hinstance),

"BITMAPNAME");

Ma to jednak tę wadę, iż nieudana próba załadowania bitmapy spowoduje załamanie całego IDE; lepiej więc posłużyć się w tym celu gotową kolekcją obrazków, opisywaną wcześniej w związku z edytorami właściwości.

Dodawanie własnych funkcji zdarzeniowych do opcji menu kontekstowego

Wzbogacając opcję menu kontekstowego o obsługę wybranego zdarzenia, należy zrobić dwie rzeczy. Po pierwsze, należy zdefiniować stosowną funkcję obsługi tego zdarzenia jako metodę edytora komponentu (funkcja zdarzeniowa musi być metodą obiektu, nie może być funkcją statyczną). Następnie należy przypisać (w treści metody PrepareItem()) wskaźnik tej metody do stosownej właściwości zdarzeniowej obiektu TMenuItem, uzyskawszy wpierw jego wskaźnik, zezwalający na modyfikację.

Oto przykład tej operacji dla zdarzenia OnAdvancedDrawMenuItem. Najpierw w klasie edytora komponentu zdefiniować należy metodę o nazwie AdvancedDrawMenuItem1 (zakładamy, iż rzecz dotyczy opcji o indeksie 1, choć nazwa metody nie jest tu specjalnie istotna):

// w pliku nagłówkowym

protected:

virtual void __fastcall AdvancedDrawMenuItem1

(System::TObject* Sender,

Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

TOwnerDrawState State);

...

// w pliku implementacyjnym

virtual void __fastcall TMyCustomEditor::AdvancedDrawMenuItem1

(System::TObject* Sender,

Graphics::TCanvas* ACanvas,

const Windows::TRect& ARect,

TOwnerDrawState State);

{

...

tutaj scenariusz obsługi zdarzenia

Sender wskazuje na odnosny obiekt TMenuItem

...

}

Następnie w treści metody PrepareItem() należy przypisać wskaźnik tej metody do odpowiedniej właściwości obiektu TMenuItem (zakładamy, iż dotyczy to opcji o indeksie 1):

void __fastcall TMyCustomEditor::PrepareItem

(int Index, const Menus::TMenuItem* AItem)

{

switch(Index)

{

...

case 1 :

{

TMenuItem* MenuItem = const_cast<TMenuItem*>(AItem);

MenuItem­->OnAdvancedDrawItem = AdvancedDrawMenuItem1

break;

}

...

}

}

Można by w ten sposób potraktować pozostałe zdarzenia związane z opcjami menu kontekstowego: OnMeasureItem, OnDrawItem i OnClick. Nie zaleca się jednak przedefiniowywania zdarzenia OnClick, bowiem obsługą kliknięcia opcji zajmuje się metoda ExecuteVerb() i nie ma raczej powodu, by to zmieniać. Także zdarzenie OnDrawItem jest starszą i mniej elastyczną wersją zdarzenia OnAdvancedDrawItem, którego przedefiniowanie pokazaliśmy przed chwilą.

Zdarzenie OnMeasureItem umożliwia określenie wysokości i szerokości wyświetlanej opcji, poprzez modyfikację ich ustawień standardowych:.

protected void __fastcall MeasureItem1(System::TObject* Sender,

Graphics:TCanvas::ACanvas,

int& Width,

int& Height);

Zmiana domyślnej szerokości opcji odniesie jednak skutek tylko w dwóch przypadkach - jeżeli jest to najszersza opcja menu i jej szerokość zostanie zmniejszona, bądź też szerokość innej opcji zwiększona zostanie ponad aktualną szerokość całego menu.

Jeżeli opcji towarzyszy ikona, wyjściowa wartość parametru Width nie powinna obejmować jej szerokości, odnosi się bowiem tylko do samego tekstu opcji; należy zatem nadać parametrowi Width wartość stanowiącą różnicę pomiędzy całkowitą szerokością opcji a szerokością ikony - ta ostatnia równa jest wysokości określonej przez parametr Height, co ilustruje rysunek 8.11 dla opcji Flip Children.

Rysunek 8.11. Wymiarowanie opcji menu kontekstowego

Zmieniając standardową obsługę zdarzenia OnAdvancedDrawItem, możemy nadać odnośnej opcji menu kontekstowego zupełnie niestandardowy wygląd, o czym świadczy chociażby rysunek 8.12, przedstawiający menu kontekstowe edytora TImageComponentEditor w odniesieniu do komponentu TImage.

Rysunek 8.12. Menu kontekstowe edytora TImageComponentEditor

Dodawanie podmenu do opcji menu kontekstowego

Obiekt TMenuItem posiada metodę Add(), umożliwiającą dołączanie kolejnych podopcji do jego (początkowo pustego) podmenu. Metoda ta (jako przeciążona) posiada także aspekt umożliwiający dodanie całej tablicy opcji (TMenuItems) w pojedynczym wywołaniu. Opcje podmenu, same będąc obiektami TMenuItem, podlegają tym samym zasadom, co opcje „głównego” menu kontekstowego.

Oto przykład dołączania podmenu do opcji o indeksie 1. Dla uproszczenia zakładamy, iż liczba opcji podmenu zawarta jest w statycznej zmiennej NoOfSubMenusForItem1, zaś treścią kolejnych opcji są kolejne liczby nieujemne poprzedzone identyfikatorem „Sub-Menu”.

Na początku należy oczywiście przydzielić tablicę obiektów TMenuItem; najlepiej by tablica ta była prywatnym (private) polem klasy edytora:

TMenuItem* SubMenuItemsFor1[NoOfSubMenusForItem1];

Kolejna rzecz to przedefiniowanie konstruktora edytora. Jest on pustą funkcją „wstawialną” (inline), którą należy przekształcić do następującej postaci:

__fastcall TCustomComponentEditor::TCustomComponentEditor(

Classes::TComponent*AComponent,

_di_IFormDesigner ADesigner)

:TComponentEditor(AComponent,ADesigner)

{

for(int i=0;i<NoOfSubMenusForItem1;++i)

{

SubMenuItemsFor1 [i ] ==new TMenuItem(Application);

SubMenuItemsFor1 [i ]->Caption.sprintf(“Sub-Menu %d ”,i);

...

}

...

}

W pliku nagłówkowym należy także usunąć towarzyszące deklaracji konstruktora dyrektywy #pragma option push i #pragma option pop.

Utworzona tablica opcji podmenu powinna zostać zwolniona w destruktorze edytora, który na podobnej zasadzie należy przekształcić do poniższej postaci:

__fastcall TCustomComponentEditor::~TCustomComponentEditor(void)

{

for(int i=0;i<NoOfSubMenusForItem1;++i)

{

delete SubMenuItemsFor1[i];

}

}

Związanie utworzonych opcji podmenu z macierzystą opcją menu kontekstowego odbywa się w ramach metody PrepareItem():

void __fastcall TMyCustomEditor::PrepareItem

(int Index, const Menus::TMenuItem* AItem)

{

switch(Index)

{

...

case 1 :

{

TMenuItem* MenuItem = const_cast<TMenuItem*>(AItem);

MenuItem­->Add(SubMenuItemsFor1, NoOfSubMenusFor1-1)

}

}

}

Przypisywanie podmenu opcjom menu kontekstowego nie może odbywać się w ramach metody ExecuteVerb(), gdyż grozi to nieprzewidywalnymi skutkami.

Metoda ExecuteVerb()

Metoda ta obejmuje swą treścią czynności, które mają być wykonane w wyniku wybrania określonej opcji menu kontekstowego; jedyny parametr wywołania zawiera indeks wybranej opcji. Oto przykład:

void __fastcall TMyCustomEditor::ExecuteVerb(int Index)

{

switch(Index)

{

case 0 : // "&Edytuj Komponent..."

EditComponent();

break;

case 1 : // "&O komponencie..."

EditComponent();

break;

case 2 : // nic nie rób - separator opcji

break;

case 3 : // "&Przywróć standardowe"

ResetComponent();

break;

default : break;

}

}

Z poszczególnymi opcjami menu kontekstowego mogą być związane pewne funkcje dialogowe. Na szczególną uwagę zasługuje sytuacja, kiedy to metoda Edit() oferuje już niezbędny dialog, który bezsensownie byłoby implementować jeszcze raz w ramach obsługi którejś z opcji (jak opcja o indeksie 0 w powyższym fragmencie). Najwygodniej wówczas wydzielić cały dialog w ramach odrębnej prywatnej metody i wywoływać ją zarówno wewnątrz metody Edit(), jak i w metodzie ExecuteVerb() w związku z konkretną opcją.

Metoda Copy()

Metoda ta umożliwia utworzenie w schowku dodatkowego obrazu komponentu, w specyficznym formacie przydatnym dla jakiejś zewnętrznej aplikacji. Metoda ta nie musi być implementowana, ów dodatkowy obraz komponentu nie będzie wówczas tworzony.

Poniższy fragment przedstawia schemat kopiowania do schowka grafiki stanowiącej zawartość komponentu TImage.

#include "Clipbrd.hpp"

void __fastcall TImageComponentEditor::Copy(void)

{

// krok 1: uzyskanie odpowiedniego wskaźnika do komponentu

TImage*Image =dynamic_cast<TImage*>(Component);

//krok 2: weryfikacja typu komponentu

if(Image)

{

//krok 3: stwórz strukturę danych o formacie

// rozpoznawanym przez schowek

WORD AFormat;

unsigned AData;

HPALETTE APalette;

Image->Picture->SaveToClipboardFormat(AFormat,AData,APalette);

//krok 4: uzyskaj wskaźnik do globalnego egzemplarza obiektu schowka

TClipboard*TheClipboard = Clipboard();

//krok 5: kopiuj dane do schowka

TheClipboard->SetAsHandle(AFormat,AData);

}

}

Kopiowanie rozpoczyna się od uzyskania wskaźnika typu TImage*, wskazującego na docelowy komponent; jeżeli ten ostatni nie wywodzi się z klasy TImage, kopiowanie zostaje zaniechane.

Po uzyskaniu wskaźnika do komponentu należy przekształcić grafikę reprezentowaną przez właściwość Picture do postaci akceptowalnej przez schowek. Listę standardowych formatów schowka znaleźć można w systemie pomocy C++Buildera; możliwa jest także rejestracja niestandardowych formatów, tematyka ta wykracza jednak poza zakres niniejszego rozdziału.

W kolejnym kroku uzyskiwany jest wskaźnik do globalnego obiektu reprezentującego schowek - dostarcza go funkcja Clipboard() deklarowana w pliku nagłówkowym Clipbrd.hpp. Obiekt schowka jest obiektem typu singleton i nie należy w związku z tym tworzyć nowych egzemplarzy klasy TClipboard.

W ostatnim kroku dane umieszczane są w schowku za pomocą jednej z metod klasy TClipboard.

Co prawda całą tę operację można było wykonać znacznie prościej:

void __fastcall TImageComponentEditor::Copy(void)

{

TImage*Image =dynamic_cast<TImage*>(Component);

if(Image)

{

Clipboard()->Assign(Image->Picture);

}

}

Zaprezentowaliśmy jednak podejście bardziej złożone, by pokazać kilka szczegółów współpracy komponentów ze schowkiem.

Jako że cały opisywany tu scenariusz rozgrywa się na etapie projektowania, niezmiernie istotne jest, by operacje wykonywane przez metodę Copy() nie interferowały z „zasadniczym” kopiowaniem i wklejaniem danych z poziomu IDE. Metoda Copy() powinna być w związku z tym wywoływana w sposób jawny, najlepiej za pośrednictwem jednej z opcji menu kontekstowego.

Użyteczne wydaje się także uzupełnienie naszego edytora o metodę Paste(), dokonującą wklejania obrazków ze schowka wprost do komponentu TImage:

void __fastcall TImageComponentEditor::Paste(void)

{

TImage*Image =dynamic_cast<TImage*>(Component);

if(Image)

{

Image->Picture->Assign(Clipboard());

}

}

Rejestracja edytorów komponentów

Rejestracja edytora komponentu wykonywana jest podobnie do rejestracji edytora właściwości. Dokonuje jej funkcja RegisterComponentEditor() zadeklarowana następująco:

extern PACKAGE void __fastcall RegisterComponentEditor(

TMetaClass* ComponentClass,

TMetaClass* ComponentEditor

);

Funkcja ta posiada tylko dwa parametry przekazujące informację o typie (odpowiednio) edytowanego komponentu oraz samego edytora. Informacja taka udostępniana jest przez operator __classid(). Oto przykład rejestracji edytora TImageComponentEditor dla komponentu TImage:

RegisterComponentEditor(__classid(TImage),

__classid(TImageComponentEditor)

);

Podobnie jak funkcja RegisterPropertyEditor(), tak i funkcja RegisterComponentEditor() musi być wywoływana w ramach funkcji Register(). Nowo rejestrowany edytor komponentu zastępuje dotychczasowy edytor dla tegoż komponentu.

Wykorzystanie predefiniowanych obrazków w edytorach komponentów i edytorach właściwości

Duża część naszej dotychczasowej dyskusji dotyczyła graficznej prezentacji edytowanych komponentów i ich właściwości, rozdział ten stanowi więc dobrą okazję do tego, by omówić wykorzystanie w tym celu obrazków „predefiniowanych”, czyli tworzonych w postaci niezależnych plików zasobowych. Prezentowane tu koncepcje są na tyle ogólne, iż nie ograniczają się jedynie do edytorów właściwości czy komponentów, lecz posiadają odniesienie do projektów w ogóle.

Predefiniowane obrazki powinny mieć format bitmap (*.bmp), zaś rozdzielczość ich kolorów nie powinna wykraczać poza 8 bitów (256 kolorów). Obrazki takie można z łatwością tworzyć za pomocą większości edytorów graficznych, w tym również za pomocą Image Editora dostarczanego wraz z C++Builderem.

Po stworzeniu poszczególnych obrazków należy połączyć je w pojedynczy plik, zwany plikiem zasobowym (ang. resource file). Można to zrobić na kilka sposobów - jednym z nich jest utworzenie za pomocą Image Editora skompilowanego pliku zasobowego (ang. compiled resource file) z rozszerzeniem *.res. Należy zainicjować nowy plik zasobowy, dodając następnie do niego kolejne bitmapy; trzeba jednocześnie zwracać uwagę na nazwy reprezentujące poszczególne bitmapy w pliku zasobowym, bowiem nazwy te wykorzystywane będą później do identyfikacji poszczególnych bitmap.

Inny sposób polega na stworzeniu źródłowego pliku zasobowego *.rc, zwanego krótko skryptem zasobowym. Używając dowolnego edytora tekstowego, chociażby tego z IDE C++Buildera (ikona Text na stronie New okna New Items), należy wypełnić go wierszami w postaci:

<identyfikator zasobu> BITMAP <nazwa pliku>.bmp

<identyfikator zasobu> może być liczbą całkowitą lub łańcuchem i musi być unikatowy dla każdej bitmapy. Każdemu identyfikatorowi przypisywana jest unikatowa wartość w pliku nagłówkowym, tworzonym automatycznie podczas kompilacji projektu. Oto przykład jednego z plików *.rc pakietu EnhancedEditors z załączonej do książki płyty CD-ROM:

RESOURCE_CopyImage BITMAP "CopyImage.bmp"

RESOURCE_PasteImage BITMAP "PasteImage.bmp"

RESOURCE_GreyedPasteImage BITMAP "GrayedPasteImage.bmp"

RESOURCE_GreyedCopyImage BITMAP "GrayedCopyImage.bmp"

RESOURCE_ActiveWritersGuildLogo BITMAP "ActiveWritersGuildLogo.bmp"

RESOURCE_InActiveWritersGuildLogo BITMAP "InActiveWritersGuildLogo.bmp"

RESOURCE_ImageListProperty BITMAP "ImageListProperty.bmp"

(ujmowanie nazw plików w cudzysłowy nie jest konieczne).

„Ręczne” tworzenie zawartości pliku zasobowego ma przewagę nad jego automatycznym tworzeniem przez Image Editora. Przede wszystkim edycja pliku tekstowego jest zadaniem znacznie łatwiejszym niż edycja skompilowanego pliku *.res. Ponadto Image Editor ogranicza zawartość tworzonych przez siebie plików zasobowych do bitmap, kursorów i ikon; tworząc skrypt, możemy umieszczać w nim zasoby dowolnych typów.

Dodawanie plików zasobowych do pakietów

Skompilowany (*.res) lub źródłowy (*.rc) plik zasobowy może być w łatwy sposób dołączony do pakietu. Po załadowaniu pakietu do IDE należy kliknąć przycisk Add na pasku narzędziowym edytora pakietów i na wyświetlonej karcie Add Unit umieścić w polu Unit file name nazwę pliku zasobowego, posiłkując się ewentualnie przyciskiem Browse. Po kliknięciu przycisku OK do pliku źródłowego pakietu dodany zostanie jeden z poniższych wierszy:

USERES("<nazwa>.res"); // dla pliku .res

USERC("<nazwa>.rc"); // dla pliku .rc

W czasie kompilacji pakietu pliki *.res dołączane są bezpośrednio przez konsolidator, natomiast pliki *.rc podlegają dodatkowej kompilacji.

Alternatywną możliwość dołączenia do pakietu pliku *.res daje dyrektywa:

#pragma resource "<nazwa>.res".

Wykorzystanie zasobów w edytorach komponentów i edytorach właściwości

Znajdujące się w plikach zasobowych kolekcje obrazków mogą być z powodzeniem wykorzystane w sposób wcześniej opisany - jako ikony opatrujące wartości w rozwijalnej liście, jako samodzielne elementy tej listy itp. Załadowanie obrazka (lub zestawu obrazków) sprowadza się do wywołania pojedynczej funkcji - i tak na przykład obiekt TBitmap udostępnia w tym celu dwie metody wykorzystujące (odpowiednio) nazwę albo numer zasobu w pliku zasobowym:

void __fastcall LoadFromResourceName(int Instance,

const AnsiString ResName);

void __fastcall LoadFromResourceID(int Instance,

int ResID);

Pierwszy parametr (Instance) jest uchwytem pakietu zawierającego plik zasobowy. Uchwyt ten dostępny jest pod globalną zmienną HInstance zadeklarowaną w pliku Include\Vcl\Sysinit.hpp jako

extern PACKAGE HINSTANCE HInstance;

Typ HINSTANCE jest jednak typem wskaźnikowym, definiowanym w pliku Include\wtypes.h jako void*, wymaga więc rzutowania na typ int:

reinterpret_cast<int>(Hinstance)

Oprócz ładowania pojedynczych obrazków możliwe jest ładowanie całych ich zestawów. Przykładowo obiekt TImageList udostępnia w tym celu dwie następujące metody, wywoływane cyklicznie dla każdego ładowanego elementu listy:

bool __fastcall ResourceLoad(TResType ResType,

AnsiString Name,

Graphics::TColor MaskColor);

bool __fastcall ResInstLoad(int Instance,

TResType ResType,

System::AnsiString Name,

Graphics::TColor MaskColor);

Do użycia wewnątrz pakietu przydatna jest tylko druga z nich. Znaczenie parametru Instance jest takie samo, jak opisane przed chwilą. Drugi parametr określa typ ładowanego obrazka - rtBitmap oznacza bitmapę, rtIcon - ikonę, rtCursor - kursor. Ostatni parametr (MaskColor) ma znaczenie tylko wówczas, gdy właściwość Mask obiektu TImageList równa jest true.

Jak więc widać, ładowanie zasobów nie jest czynnością zbyt skomplikowaną. Pozostaje jednak pytanie - w którym miejscu kodu ładowanie to ma być wykonywane? Wydruk 8.36 przedstawia jedno z możliwych rozwiązań - ładowanie zasobu w momencie, gdy okazuje się on niezbędny:

Wydruk 8.36. Załadowanie i wyświetlenie obrazka z pliku zasobowego

void __fastcall TImageComponentEditor::AdvancedDrawMenuItem3(

System::TObject*Sender,

Graphics::TCanvas*ACanvas,

const Windows::TRect&ARect,

TOwnerDrawState State)

{

std::auto_ptr<Graphics::TBitmap>Logo(new Graphics::TBitmap());

if(State.Contains(odSelected))

{

Logo->LoadFromResourceName(reinterpret_cast<int>(HInstance),

"RESOURCE_ActiveWritersGuildLogo");

}

else

{

Logo->LoadFromResourceName(reinterpret_cast<int>(HInstance),

"RESOURCE_InActiveWritersGuildLogo");

}

ACanvas->Draw(ARect.Left,ARect.Top,Logo);

}

Efektem wykonania powyższego kodu jest wyświetlenie logo przedstawionego na rysunku 8.12.

Rozwiązanie to jest jednak o tyle niepraktyczne, iż ładowanie i wyświetlanie logo odbywa się każdorazowo, gdy kursor myszy wejdzie w jego obszar lub obszar ten opuści; roboczy obiekt bitmapy jest bowiem lokowany na stosie i niszczony po wyjściu z funkcji. Znacznie efektywniej byłoby załadować „na stałe” obydwie bitmapy logo w ramach konstruktora klasy TImageComponentEditor i zwolnić je dopiero w destruktorze. Rozwiązanie takie jest przedmiotem wydruku 8.37.

Wydruk 8.37. Usprawnione zarządzanie zasobami

// DEKLARACJA KLASY

#include "dsgnintf.hpp"

class TImageComponentEditor :public TDefaultEditor

{

typedef TComponentEditor inherited;

private:

virtual void __fastcall AdvancedDrawMenuItem3(

System::TObject*Sender,

Graphics::TCanvas*ACanvas,

const Windows::TRect&ARect,

TOwnerDrawState State);

virtual void __fastcall MeasureMenuItem3(

System::TObject*Sender,

Graphics::TCanvas*ACanvas,

int&Width,

int&Height);

// dane prywatne

Graphics::TBitmap*ActiveWritersGuildLogo;

Graphics::TBitmap*InActiveWritersGuildLogo;

public:

// Kliknięcie prawym przyciskiem

// MENU KONTEKSTOWE

// krok 1

virtual int __fastcall GetVerbCount(void);

//krok 2

virtual AnsiString __fastcall GetVerb(int Index);

//krok 3

virtual void __fastcall PrepareItem(int Index,

const Menus::TMenuItem*AItem);

//krok 4

virtual void __fastcall ExecuteVerb(int Index);

// kopiowanie obrazka do Schowka

virtual void __fastcall Copy(void);

//wklejanie obrazka ze Schowka

virtual void __fastcall Paste(void);

public:

__fastcall virtual TImageComponentEditor(Classes::TComponent*AComponent,

di_IFormDesigner ADesigner);

public:

__fastcall virtual ~TImageComponentEditor(void);

};

// IMPLEMENTACJA

//--------------------------------------------------------------------------//

// KONSTRUKTOR //

//--------------------------------------------------------------------------//

__fastcall TImageComponentEditor::TImageComponentEditor(

Classes::TComponent*AComponent,

_di_IFormDesigner ADesigner)

:TDefaultEditor(AComponent,ADesigner)

{

ActiveWritersGuildLogo =new Graphics::TBitmap();

InActiveWritersGuildLogo =new Graphics::TBitmap();

ActiveWritersGuildLogo->LoadFromResourceName(

reinterpret_cast<int>(HInstance),

"RESOURCE_ActiveWritersGuildLogo");

InActiveWritersGuildLogo->LoadFromResourceName(

reinterpret_cast<int>(HInstance),

"RESOURCE_InActiveWritersGuildLogo");

}

//--------------------------------------------------------------------------//

// DESTRUKTOR //

//--------------------------------------------------------------------------//

__fastcall TImageComponentEditor::~TImageComponentEditor(void)

{

delete ActiveWritersGuildLogo;

delete InActiveWritersGuildLogo;

}

//--------------------------------------------------------------------------//

void __fastcall

TImageComponentEditor::AdvancedDrawMenuItem3(

System::TObject*Sender,

Graphics::TCanvas*ACanvas,

const Windows::TRect&ARect,

TOwnerDrawState State)

{

if(State.Contains(odSelected))

{

ACanvas->Draw(ARect.Left,ARect.Top,ActiveWritersGuildLogo);

}

else

{

ACanvas->Draw(ARect.Left,ARect.Top,InActiveWritersGuildLogo);

}

}

Na koniec jeszcze jedna uwaga: stosując właściwe nazewnictwo zasobów w pliku zasobowym, umożliwiamy ładowanie ich w pętli programowej. W poniższym przykładzie kolejne obrazki identyfikowane są nazwami RESOURCE_Imagexx, gdzie xx zmienia się od 01 do 17:

// w deklaracji klasy:

TImageList*ImageList;

//--------------------------------------------------------------------------//

// w konstruktorze/

ImageList =new TImageList(this);

ImageList->Masked =false;

for(int i=0;i<17;++i)

{

ImageList->ResInstLoad(reinterpret_cast<int>(HInstance),

rtBitmap,

AnsiString("RESOURCE_Image").cat_sprintf(“%.2d ”,i+1),

clWhite);

}

//--------------------------------------------------------------------------//

// w destruktorze

delete ImageList;

//-------------------------------------------------------------------------//

Podział właściwości na kategorie i ich rejestracja

Począwszy od wersji 5. C++Builder umożliwia podział właściwości komponentów na kategorie, co m.in. umożliwia posegregowanie ich w ten sposób w oknie inspektora obiektów (patrz rys. 8.13). Kategoria właściwości reprezentowana jest przez klasę wywodzącą się z klasy bazowej TPropertyCategory. W tym podrozdziale pokażemy związek kategorii właściwości z komponentami VCL.

Rysunek 8.13. Właściwości komponentu w podziale na kategorie.

Kategorie i ich tworzenie

C++Builder predefiniuje 13 standardowych klas kategorii zestawionych w tabeli 8.13.

Tabela 8.13. Predefiniowane kategorie C++Buildera

Nazwa kategorii

Klasa kategorii

Action

TActionCategory

Data

TDataCategory

Database

TDatabaseCategory

Drag, Drop and Docking

TDragNDropCategory

Help and Hints

THelpCategory

Input

TInputCategory

Layout

TLayoutCategory

Legacy

TLegacyCategory

Linkage

TLinkageCategory

Locale

TLocaleCategory

Localizable

TLocalizableCategory

Miscellaneous

TMiscellaneousCategory

Visual

TVisualCategory

Deklaracje klas wymienionych w tabeli 8.13 zawarte są w pliku Dsgnintf.hpp.

Zestaw predefiniowanych kategorii może być uzupełniony o kategorie definiowane przez użytkownika. Nowa kategoria musi zostać wyprowadzona z klasy TPropertyCategory lub którejś z jej klas pochodnych. Wymaga się, by nowa klasa kategorii przedefiniowywała metody Name() i Description() - tak naprawdę przedefiniowywanie metody Description(), zwracającej opis kategorii, nie jest konieczne, lecz z drugiej strony nie wymaga ono większego wysiłku.

Wydruki 8.38 i 8.39 przedstawiają szablony kodu dokonującego rejestracji nowej kategorii. Frazę NameOfCategory należy na nich zastąpić nazwą kategorii, należy ponadto ustawić właściwy wynik metod Name() i Description ().

Wydruk 8.38. Kod deklarujący nową kategorię

#include <Dsgnintf.hpp>

class PACKAGE TNameOfCategory : public TPropertyCategory

{

typedef TPropertyCategory inherited;

public:

#pragma option push -w-inl

virtual AnsiString __fastcall Name()

{

return Name(__classid(TNameOfCategory));

}

#pragma option pop

static AnsiString __fastcall Name(System::TMetaClass* vmt);

#pragma option push -w-inl

virtual AnsiString __fastcall Description()

{

return Description(__classid(TNameOfCategory));

}

#pragma option pop

static AnsiString __fastcall Description(System::TMetaClass* vmt);

#pragma option push -w-inl

// konstruktor

inline __fastcall TNameOfCategory (void) : TPropertyCategory() { }

#pragma option pop

#pragma option push -w-inl

// destruktor

inline __fastcall virtual ~TNameOfCategory (void) { }

#pragma option pop

};

Statycznie zadeklarowane metody Name() i Description() muszą zostać przedefiniowane - pierwsza z nich decyduje o nazwie kategorii widocznej w oknie inspektora obiektów:

Wydruk 8.39. Kod implementacyjny dla nowej kategorii

AnsiString __fastcall TNameOfCategory::Name(System::TMetaClass* vmt)

{

return "nazwa kategorii";

}

AnsiString __fastcall TNameOfCategory::Description(System::TMetaClass* vmt)

{

return "opis kategorii";

}

Zaliczenie właściwości do konkretnej kategorii

C++Builder udostępnia dwie funkcje umożliwiające zaliczenie do wskazanej kategorii (odpowiednio) pojedynczej właściwości lub grup właściwości - RegisterPropertyInCategory() oraz RegisterPropertiesInCategory(); są one zadeklarowane w pliku Dsgnintf.hpp. Jak można się domyślić, funkcje te powinny być wywoływane w ramach funkcji Register() zawartej w pakiecie. Tak naprawdę rejestracji podlegają jednak nie same właściwości (lub ich grupy), lecz tzw. filtry (lub grupy filtrów) stanowiące coś w rodzaju masek określających grupy właściwości - w tym kontekście nazwy funkcji rejestrujących są trochę mylące.

Do zarejestrowania pojedynczego filtru służy przeciążona funkcja RegisterPropertyInCategory(), występująca w czterech następujących aspektach, różniących się sposobem określenia informacji o rejestrowanym filtrze:

extern PACKAGE TPropertyFilter*__fastcall

RegisterPropertyInCategory(TMetaClass*ACategoryClass,

const AnsiString APropertyName);

extern PACKAGE TPropertyFilter*__fastcall

RegisterPropertyInCategory(TMetaClass*ACategoryClass,

TMetaClass*AComponentClass,

const AnsiString APropertyName);

extern PACKAGE TPropertyFilter*__fastcall

RegisterPropertyInCategory(TMetaClass*ACategoryClass,

Typinfo::PTypeInfo APropertyType,

const AnsiString APropertyName);

extern PACKAGE TPropertyFilter*__fastcall

RegisterPropertyInCategory(TMetaClass*ACategoryClass,

Typinfo::PTypeInfo APropertyType);

Analogiczna funkcja umożliwia jednoczesne zarejestrowanie grupy filtrów:

extern PACKAGE TPropertyCategory*__fastcall

RegisterPropertiesInCategory(TMetaClass*ACategoryClass,

const System::TVarRec*AFilters,

const int AFilters_Size);

extern PACKAGE TPropertyCategory*__fastcall

RegisterPropertiesInCategory(TMetaClass*ACategoryClass,

TMetaClass*AComponentClass,

const AnsiString*AFilters,

const int AFilters_Size);

extern PACKAGE TPropertyCategory*__fastcall

RegisterPropertiesInCategory(TMetaClass*ACategoryClass,

Typinfo::PTypeInfo APropertyType,

const AnsiString*AFilters,

const int AFilters_Size);

Każda z przedstawionych funkcji rejestracyjnych generuje pojedynczy obiekt TPropertyFilter lub grupę takich obiektów; na ich podstawie IDE jest w stanie określić, do której kategorii należą które właściwości. Kategorie nie muszą być przy tym rozłączne - dana właściwość może należeć do więcej niż jednej kategorii. Deklarację obiektu-filtra TPropertyFilter przedstawia wydruk 8.40.

Wydruk 8.40. Deklaracja obiektu TPropertyFilter

class DELPHICLASS TPropertyFilter;

class PASCALIMPLEMENTATION TPropertyFilter : public System::TObject

{

typedef System::TObject inherited;

private:

Masks::TMask* FMask;

TMetaClass*FComponentClass;

Typinfo::TTypeInfo *FPropertyType;

int FGroup;

public:

__fastcall TPropertyFilter(const AnsiString APropertyName,

TMetaClass* AComponentClass,

Typinfo::PTypeInfo

APropertyType);

__fastcall virtual ~TPropertyFilter(void);

bool __fastcall Match(const AnsiString APropertyName,

TMetaClass* AComponentClass,

Typinfo::PTypeInfo

APropertyType);

__property TMetaClass* ComponentClass = {read=FComponentClass};

__property Typinfo::PTypeInfo PropertyType = {read=FPropertyType};

};

Najważniejszymi elementami obiektu-filtra są trzy następujące pola:

Zestaw metaznaków maski nazwy i ich znaczenie przedstawia tabela 8.14.

Tabela 8.14. Metaznaki używane w masce nazwy właściwości

Znak

Dopasowanie

*

Zastępuje dowolny ciąg znaków alfanumerycznych.

?

Zastępuje dowolny, pojedynczy znak alfanumeryczny.

[zestaw]

Określa podzbiór pasujących znaków alfanumerycznych w postaci przedziału domkniętego, zbioru lub ich kombinacji. Przykładowo [AbcdE0-9] określa dowolny znak ze zbioru AbcdE0123456789.

[!zestaw]

Określa wszystkie znaki alfanumeryczne z wyjątkiem ich podzbioru specyfikowanego w postaci przedziału domkniętego, zbioru lub ich kombinacji. Przykładowo [!AbcDEf-j] określa wszystkie znaki alfanumeryczne z wyjątkiem AbcDEfghij.

Mechanizm dopasowania maski jest wrażliwy na wielkość liter.

Szczegółowy opis parametrów funkcji rejestracyjnych wraz przykładami ich użycia przedstawia tabela 8.15.

Tabela 8.15. Parametry funkcji rejestrujących właściwości w konkretnych kategoriach

Parametr

Znaczenie

TMetaClass* ACategoryClass

Specyfikuje kategorię, której dotyczy rejestrowany filtr, w postaci wartości zwracanej przez operator __classid().

Przykład: __classid(TMyCategory).

TMetaClass* AComponentClass

Określa klasę komponentu, do której należeć musi właściwość, by czyniła zadość rejestrowanemu filtrowi.

Przykład: __classid(TMyComponent).

const AnsiString PropertyName

Określa maskę nazwy właściwości.

Przykłady:

"Shape"

"OnMouse*"

"OnKey*".

Typinfo::PTypeInfo APropertyType

Określa typ właściwości pasującej do filtra. Przykład:

static TTypeInfo IntTypeInfo;

IntTypeInfo.Name = "int";

IntTypeInfo.Kind = tkInteger;

const AnsiString* AFilters,

const int AFilters_Size

Określają tablicę, zawierającą maski nazw właściwości i rozmiar tej tablicy. W wywołaniu mogą być zastąpione przez makro OPENARRAY.

const System::TVarRec* AFilters,

const int AFilters_Size

Określają tablicę wariantową, specyfikującą zestaw właściwości. Każdy element tej tablicy może być łańcuchem (AnsiString), elementem metaklasy (TMetaClass*) lub wskazaniem na strukturę TTypeInfo. Parametry te mogą być w wywołaniu zastąpione przez makro ARRAYOFCONST.

Oto przykład makra określającego trzy maski nazw właściwości:

OPENARRAY(AnsiString, "Shape", "OnMouse*", "OnKey*")

Zależnie od listy parametrów każda z funkcji rejestrujących (dokładniej: każdy z aspektów przeciążenia tych funkcji) generuje określoną postać filtra. Powiązanie listy parametrów z postacią generowanego filtra przedstawiają tabele 8.16 i 8.17.

Tabela 8.16. Filtry generowane przez funkcję RegisterPropertyInCategory()

Parametry

Generowany filtr
(maska, komponent, właściwość)

(TMetaClass*ACategoryClass,
const AnsiString APropertyName)

(APropertyName,0,0)

(TMetaClass*ACategoryClass,
TMetaClass*AComponentClass,
const AnsiString APropertyName)

(APropertyName, AComponentClass,0)

(TMetaClass*ACategoryClass,
Typinfo::PTypeInfo APropertyType,
const AnsiString APropertyName)

(APropertyName,0,APropertyType)

(TMetaClass*ACategoryClass,
Typinfo::PTypeInfo APropertyType,
const AnsiString APropertyName)

("", 0, APropertyType)

Tabela 8.17. Filtry generowane przez funkcję RegisterPropertiesInCategory()

Parametry

Generowany filtr
(maska, komponent, właściwość)

(TMetaClass*ACategoryClass,
const System::TVarRec*AFilters,
const int AFilters_Size)

Jeśli AFilters[i] jest łańcuchem:

(AFilters[i],0,0)

Jeśli AFilters[i] jest elementem metaklasy:
("", AFilters[i],0)

Jeśli AFilters[i] jest wskaźnikiem PTypeInfo:

("",0,AFilters[i])

(TMetaClass*ACategoryClass,
TMetaClass*AComponentClass,
const AnsiString*AFilters,
const int AFilters_Size)

(AFilters[i],AComponentClass,0)

(TMetaClass*ACategoryClass,
Typinfo::PTypeInfo APropertyType,
const AnsiString*AFilters,
const int AFilters_Size)

(AFilters[i],0,APropertyType)

Przykłady wykorzystania funkcji rejestrujących filtry właściwości przedstawia wydruk 8.41.

Wydruk 8.41. Przykłady rejestracji właściwości w konkretnych kategoriach

namespace Nameoffilecontainingthisregistration

{

void __fastcall PACKAGE Register()

{

// 1 - - Rejestracja pojedynczego filtra w kategorii TMouseCategory.

// Generowany filtr: ("OnMouse*", 0, 0), czyli wszystkie właściwości

// o nazwie zaczynającej się od OnMouse

//

// RegisterPropertyInCategory(TMetaClass* ACategoryClass,

// const AnsiString APropertyName);

RegisterPropertyInCategory(__classid(TMouseCategory),

"OnMouse*");

// 2 - - Rejestracja dwóch filtrów w kategorii TMouseCategory.

// Generowane są dwa filtry:

//

// ("", 0, CursoTypeInfo), czyli wszystkie właściwośći typu TCursor

// ("OnMouse*", 0, 0), czyli wszystkie właściwości o nazwie

// zaczynającej się od OnMouse

//

// RegisterPropertiesInCategory(TMetaClass* ACategoryClass,

// const System::TVarRec* AFilters,

// const int AFilters_Size);

PTypeInfo CursorTypeInfo

= *Typinfo::GetPropInfo(__typeinfo(TForm),"Cursor")->PropType;

RegisterPropertiesInCategory(__classid(TMouseCategory),

ARRAYOFCONST(

( CursorTypeInfo,

AnsiString("OnMouse*"),

AnsiString("EventName2") )) );

// 3 - - Register two property filters for TMouseCategory.

// The first filter is ("OnClick", 0, 0), i.e. for any property

// (probably event) whose name is "OnClick".

// The second filter is ("OnDblClick", 0, 0), i.e. for any property

// (probably event) whose name is "OnDblClick".

// Use :

// RegisterPropertiesInCategory(TMetaClass* ACategoryClass,

// TMetaClass* AComponentClass,

// const AnsiString* AFilters,

// const int AFilters_Size)

// 3 - - Rejestracja dwóch filtrów w kategorii TMouseCategory.

// Generowane są dwa filtry:

//

// ("OnClick", 0, 0), czyli dowolna właściwość o nazwie OnClick

// ("OnDblClick", 0, 0), czyli dowolna właściwość o nazwie

// OnDblClick

//

// RegisterPropertiesInCategory(TMetaClass* ACategoryClass,

// TMetaClass* AComponentClass,

// const AnsiString* AFilters,

// const int AFilters_Size)

TMetaClass* AnyComponent = 0;

RegisterPropertiesInCategory( __classid(TMouseCategory),

AnyComponent,

OPENARRAY( AnsiString,

("OnClick",

"OnDblClick") ) );

}

}

Zwróć uwagę, iż w trzecim przykładzie parametr określający dopuszczalny typ komponentu jest pustym wskaźnikiem; nie można było użyć w tej roli stałej 0, gdyż kompilator nie potrafiłby jednoznacznie określić właściwego aspektu przeciążonej funkcji - stała 0 mogłaby bowiem równie dobrze oznaczać pusty wskaźnik PTypeInfo. W analogiczny sposób pusty wskaźnik PTypeInfo w przykładzie nr 2 należałoby zadeklarować jako:

PTypeInfo AnyPropertyType = 0;

Aby to lepiej zrozumieć, wystarczy spojrzeć na treść metody GetValues() prostego edytora właściwości boolowskich:

procedure TBoolProperty.GetValues(Proc: TGetStrProc);

begin

Proc('False');

Proc('True');

end;

przyp. tłum.

Dla uniknięcia zbytniej komplikacji tekstu określenia „komponent rodzicielski” używamy w tym rozdziale na określenie zarówno komponentu wskazywanego przez właściwość Parent, jak i komponentów rodzicielskich tego ostatniego, będących „dziadkami” przedmiotowego komponentu - czyli w sensie przechodniego domknięcia relacji rodzicielstwa - przyp. tłum.

Metoda GetComponent()klasy TPropertyEditor deklarowana jest następująco:

Classes::TPersistent* __fastcall GetComponent(int Index);

Liczba mnoga („komponentów rodzicielskich”) użyta jest tutaj w sensie przechodniego domknięcia relacji rodzicielstwa, wyjaśnionego w jednym z poprzednich przypisów - przyp. tłum.

Celowo mówimy tu ogólnie o „obiektach”, nie „komponentach”, rezerwując to ostatnie określenie dla egzemplarzy klas wywodzących się z klasy TComponent - przyp. tłum.

Przykład zastosowania niestandardowego formatu wymiany danych ze schowkiem znajduje się np. w rozdziale 17. książki Delphi 4. Vademecum profesjonalisty (Helion, Gliwice 1999) - przyp. tłum.

Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

1

2 D:\helion\C++Builder 5\R08-03.DOC

dla redakcji: czyż nie zgrabniej byłoby tu napisać „komponentów i/lub właściwości” na przekór temu, co zalecacie w swoich wytycznych? (AG)



Wyszukiwarka

Podobne podstrony:
R14-03, ## Documents ##, C++Builder 5
R17-03, ## Documents ##, C++Builder 5
R18-03, ## Documents ##, C++Builder 5
R04-03, ## Documents ##, C++Builder 5
R13-03, ## Documents ##, C++Builder 5
R09-03, ## Documents ##, C++Builder 5
R05-03, ## Documents ##, C++Builder 5
R07-03, ## Documents ##, C++Builder 5
R03-03, ## Documents ##, C++Builder 5
R15-03, ## Documents ##, C++Builder 5
R16-03, ## Documents ##, C++Builder 5
R02-03, ## Documents ##, C++Builder 5
R11-03, ## Documents ##, C++Builder 5
r-13-00, ## Documents ##, C++Builder 5
r-12-00, ## Documents ##, C++Builder 5
2011 03 03 Document 001
r08-01, ## Documents ##, XML Vademecum profesjonalisty
r08-05, ## Documents ##, flash5biblia
r08-02, ## Documents ##, CorelPHOTO-PAINT 10

więcej podobnych podstron