Henryk Budzisz ZPORR Koszalin 2009
Komponenty.NET Struktura platformy.net Język C++/CLI Interfejsy w.net Definiowanie właściwości komponentów Delegacyjny model obsługi zdarzeń Implementacja komponentów 2
Struktura platformy.net 3
Biblioteki klas Biblioteka klas, dostępna w pakiecie.net Framework SDK, składa się z klas, interfejsów i stałych. Elementy biblioteki są pogrupowane w hierarchiczną strukturę przestrzeni nazw (ang. namespace). Biblioteki klas umożliwiają dostęp do usług systemowych i stanowią podstawę tworzenia aplikacji, komponentów i kontrolek.net. Zadaniem przestrzeni nazw jest zawężenie zakresu, w którym obowiązują nazwy typów. Dzięki temu mogą istnieć dwie klasy o tej samej nazwie pod warunkiem, że są zdefiniowane w różnych przestrzeniach nazw. Podstawowe klasy zgrupowane są w przestrzeni System. 4
Przestrzeo nazw System Przestrzeń nazw System zawiera klasy podstawowe oraz klasy bazowe, które definiują najczęściej używane referencyjne i skalarne typy danych, zdarzenia, procedury obsługi zdarzeń, interfejsy, atrybuty i klasy przeznaczone do obsługi wyjątków. Inne klasy tej przestrzeni nazw udostępniają usługi związane z konwersją typów danych, manipulowaniem parametrami metod, obliczeniami i stałymi matematycznymi, zdalnym i lokalnym przekazywaniem wywołań, zarządzaniem środowiskiem wykonywania aplikacji oraz nadzorowaniem zarządzanego i niezarządzanego kodu. Jedną ze składowych jest kolejna przestrzeń o nazwie System.ComponentModel, zawierająca klasy i interfejsy pomocne w tworzeniu komponentów i kontrolek. 5
Przestrzeo System.ComponentModel Przestrzeń System.ComponentModel udostępnia klasy (135 klas) i interfejsy (27) zapewniające funkcjonowanie komponentów i kontrolek zarówno w trybie projektowania (design-time), jaki w trybie uruchomieniowym (run-time). Podstawowe klasy to: Component i Container. Podstawowe interfejsy to: IComponent i IContainer. Pozostałe klasy umożliwiają implementację atrybutów i deskryptorów, konwersję typów (TypeConverter), dowiązanie do źródeł danych oraz licencjonowanie komponentów (License, LicenseManager, LicenseProvider i LicenseProviderAttribute). 6
Komponent w.net Z punktu widzenia programisty Komponent w.net jest klasą, która w sposób bezpośredni lub pośredni implementuje interfejs System.ComponentModel.IComponent Co to jest interfejs? Jak klasa implementuje interfejs? Jak definiuje się właściwości komponentu? Jak obsługiwać zdarzenia? 7
C++/CLI C++/CLI (Common Language Infrastructure) to język programowania oparty na C++, umożliwiający programowanie aplikacji korzystających za wspólnego środowiska uruchomieniowego CLR, jak i bez użycia CLR. C++ zarządzany C++ natywny Biblioteka klas.net C++ natywny Biblioteka MFC CLR System operacyjny Sprzęt (hardware) 8
Kod natywny i zarządzany W kodzie natywnym, obiekty tworzy się przy użyciu operatora new. Tak utworzone obiekty dostępne są przez wskaźnik. Po wykorzystaniu obiektu, trzeba pamiętać o usunięciu go z pamięci (operator delete). std::string* stdstr; // deklaracja wskaźnika stdstr = new std::string("string niezarządzany");... delete stdstr; // usunięcie łańcucha W kodzie zarządzanym, obiekty tworzy się przy użyciu operatora gcnew. Tak utworzone obiekty dostępne są przez odniesienie. Obiekty wykorzystane są automatycznie usuwane z pamięci (odśmiecanie). System::String^ clrstr; // deklaracja odniesienia clrstr = gcnew System::String("String zarządzany");... 9
Klasa zarządzana Klasa poprzedzona słowem ref class (jedno słowo kluczowe) staje się klasą zarządzaną (garbage-collected). Czas życia obiektu tej klasy kontrolowany jest przez CLR. Nie ma potrzeby jawnego wywołania operatora delete. Przykład: ref class Test { public: Test():i(0){ private: int i; ; // Test Klasa zarządzana nie ma domyślnego konstruktora kopiującego ani domyślnego operatora przypisania. Parametry funkcji (również konstruktorów) nie mogą mieć wartości domyślnych. Można definiować właściwości (słowo kluczowe property). void main () { while(true) { // pętla bez końca Test^ tst = gcnew Test; // tworzone obiekty są automatycznie usuwane 10
Klasa wartości Klasa poprzedzona słowem value class (jedno słowo kluczowe) jest przeznaczona do grupowania pewnej liczby danych w jedną całość. Obiekt klasy wartości może być umieszczony zarówno na stosie (jak zmienne typów standardowych) jak i na stercie CLR (operator gcnew). Przykład: value class Wynik { public: Wynik(double czas, double Par) : t(czas),par(par) { private: double t; // czas od początku pomiaru double par; // wartość mierzonego parametru ; // Wynik void main () { Wynik wynik(0.5, 0.342); // umieszczenie na stosie 11
Interfejs w.net Interfejs zawiera deklaracje metod, zdarzeń i właściwości, ale nie może zawierać ich implementacji. Nie może również zawierać deklaracji zmiennych (pól klasy) z wyjątkiem pól statycznych. Oprócz pól statycznych może zawierać także deklaracje innych składowych statycznych (metod, zdarzeń lub właściwości). Wszystkie składowe interfejsu mają publiczne prawa dostępu. Interfejs podlega dziedziczeniu. 12
Deklaracja interfejsu prawa_dostępu interface class nazwa {; prawa_dostępu interface class nazwa : interfejs_bazowy {; gdzie: prawa_dostępu - prawa dostępu do interfejsu spoza podzespołu.net (.NET assembly): public lub private (domyślne) Przykład: public interface class Interface_A { void Function_1(); void Function_2(); ; // Interface_A Nazwy interfejsów przyjęło się rozpoczynać od litery I, np.: IContainer. 13
Implementacja interfejsu Implementacja interfejsu jest realizowana w oparciu o składnię dziedziczenia przykład: public ref class MyClass : public Interface_A { virtual void Function_1() { Console::WriteLine( Function_1 ); virtual void Function_2() { Console::WriteLine( Function_2 ); ; // MyClass Ponieważ interfejs jest w Visual C++ typem zarządzanym, klasa implementująca interfejs musi być również klasą zarządzaną. Klasa może implementować wiele interfejsów. 14
Właściwości Definiowanie właściwości Funkcje dostępowe Właściwości proste Właściwości indeksowane 15
Rozwiązanie klasyczne pola klasy Pola klasy przechowują dane reprezentujące atrybuty obiektu opisywanego przez tą klasę. class Okno { public: void Szer(int nowa_szer) // ustaw szerokość i odrysuj { szer = nowa_szer; Rysuj(); int Szer(void) { return szer; // podobnie dla pola wys private: void Rysuj() {... int szer, wys; ; // Okno // pobierz szerokość 16
Rozwiązanie z użyciem właściwości Definicja właściwości zawiera standardowe funkcje dostępowe (akcesory), o z góry określonych nazwach: set i get. Przykład: public ref class Okno { public: property int Szer { void set(int nowa_szer) { // ustaw szerokość i odrysuj szer = nowa_szer; Rysuj(); int get(void) { return szer; // szer // podobnie dla właściwości Wys private: void Rysuj() {... int szer, wys; ; // Okno // pobierz szerokość 17
Definiowanie właściwości Właściwość definiuje się w klasie lub interfejsie przy użyciu słowa kluczowego property. modyfikator property typ nazwa { <deklaracja akcesorów> // brak średnika gdzie: modyfikator opcjonalny modyfikator static lub virtual, Przykład: public ref class Okno { public: property int Szer { // deklaracja akcesorów property int Wys { // deklaracja akcesorów ; Właściwość jest objęta prawami dostępu na takich samych zasadach jak inne składowe klasy lub interfejsu. 18
Funkcje dostępowe (akcesory) Właściwość może zawierać funkcję pobierającą (getter), funkcję ustawiającą (setter) lub obie funkcje. Składnia deklaracji: modyfikator void set(typ); modyfikator typ get(); gdzie: modyfikator opcjonalny modyfikator static lub virtual. Typ argumentu settera i wynik gettera muszą być jednakowe i zgodne z typem właściwości. Setter nie zwraca żadnego wyniku. Getter nie ma natomiast argumentów. Poza tymi ograniczeniami definicje funkcji mogą być dowolne. Deklaracja akcesorów może być poprzedzona deklaracją praw dostępu. Przy braku deklaracji, prawa dostępu są takie same jak dla właściwości. Prawa dostępu dla settera i gettera mogą być różne, ale w żadnym przypadku nie mogą rozszerzać praw dostępu określonych dla właściwości. 19
Przykład definiowania akcesorów Definicja akcesorów może być połączona z deklaracją (jak w poprzednim przykładzie) lub znajdować się poza klasą, z zastosowaniem nazwy kwalifikowanej (tak jak dla zwykłych metod). Przykład: public ref class Okno { public: property int Szer { void set(int); int get(void); // podobnie dla właściwości Wys private: void Rysuj() {... int szer, wys; ; // Okno void Okno::Szer::set(int nowa_szer) { szer = nowa_szer; Rysuj(); int Okno::Szer::get(void) { return szer; // ustaw szerokość i odrysuj // pobierz szerokość 20
Właściwości proste Deklaracja właściwości, dla której setter kopiuje wartość do pomocniczej zmiennej prywatnej, a getter pobiera wartość z tej samej zmiennej, może być uproszczona do postaci: modyfikator property typ nazwa; Przykład: public ref class Vector { public: property double x; property double y; property double z; ; Kompilator tworzy zmienną pomocniczą oraz domyślne akcesory. Nie ma bezpośredniego dostępu do automatycznie utworzonej zmiennej pomocniczej. 21
Właściwości indeksowane Właściwość indeksowana reprezentuje strukturę danych, w której dane dostępne są poprzez operator indeksowania [ ]. Właściwości indeksowanych używa się zwykle do udostępniania elementów tablicy zadeklarowanej wewnątrz klasy lub składowych wewnętrznych kontenerów. Składnia: modyfikator property typ_właściwości nazwa[typ_indeksu] { modyfikator void set(typ_indeksu, typ_właściwości); modyfikator typ_właściwości get(typ_indeksu); // brak średnika W kodzie klienta, właściwość z indeksem jest używana jak zwykła tablica. 22
Zdarzenia Delegacyjny model obsługi zdarzeń Deklarowanie delegata Deklaracja zdarzenia Delegaty w bibliotekach.net Zdarzenia w bibliotekach.net 23
Delegacyjny model obsługi zdarzeo delegat Subskrybenci Subskrybent 1 funkcja Sub1A źródło zdarzenia zgłoszenie zdarzenia invocation list Subskrybent 2 funkcja Sub2B Subskrybent3 funkcja Sub3C 24
Deklaracja delegata Delegat jest typem obiektowym. Do deklaracji delegata nie używa się jednak żadnej klasy bibliotecznej, ale słowa kluczowego delegate, według składni: prawa_dostępu delegate deklaracja_funkcji; Przykład: using namespace System; void Drukuj(char* tekst) { Console::WriteLine("Wydruk - {0\n", gcnew String(tekst)); void Zapisz(char* tekst) { Console::WriteLine("Zapis - {0\n", gcnew String(tekst)); delegate void Operacja(char*); // deklaracja delegata void main() { Operacja^ opr; opr += gcnew Operacja(&Drukuj); opr += gcnew Operacja(&Zapisz); opr("wywołanie z delegata"); Wynik działania programu: Wydruk wywołanie z delegata Zapis wywołanie z delegata Typ argumentów i wyniku w deklaracji delegata i deklaracji dodawanych funkcji muszą być zgodne. 25
Rejestrowanie metod klasy using namespace System; ref class Dokument { public: void Czytaj(String^ nazwa) { Console::WriteLine("Czytaj dokument: {0\n", nazwa); void Drukuj(String^ nazwa) { Console::WriteLine("Drukuj dokument: {0\n", nazwa); void Zapisz(String^ nazwa) { Console::WriteLine("Zapisz dokument: {0\n", nazwa); ; // Dokument ref class Obraz { public: static void Rysuj(String^ nazwa) { Console::WriteLine("Rysuj obraz: {0\n", nazwa); ; // Obraz delegate void Operacja(String^); void main() { Dokument^ dok = gcnew Dokument(); Operacja^ opr = gcnew Operacja(dok, &Dokument::Czytaj); opr += gcnew Operacja(dok, &Dokument::Drukuj); opr += gcnew Operacja(dok, &Dokument::Zapisz); opr += gcnew Operacja(&Obraz::Rysuj); // funkcja statyczna Console::WriteLine("Operacje:\n"); opr(gcnew String("Rysunek")); 26
Deklaracja zdarzenia W C++/CLI koncepcja delegacyjnego modelu obsługi zdarzeń została zrealizowana poprzez deklarację zdarzenia (słowo kluczowe event) i powiązanie go z delegatem. modyfikator event nazwa_delegatu ^ nazwa_zdarzenia; Wersja rozszerzona deklaracji umożliwia zdefiniowanie sposobu realizacji metod add, remove i raise. modyfikator event nazwa_delegatu ^ nazwa_zdarzenia { modyfikator typ_zwracany add (nazwa_delegatu ^ nazwa); modyfikator typ_zwracany remove(nazwa_delegatu ^ nazwa); modyfikator typ_zwracany raise(parametry); // event block 27
Przykład using namespace System; // deklaracja delegatów delegate void ZoomEventHandler(double); delegate void PrintEventHandler(String^); // klasa źródło zdarzeń ref class EventSource { public: event ZoomEventHandler^ OnZoom; event PrintEventHandler^ OnPrint; void FireEvents() { // symulacja zdarzeń OnZoom(1.5); OnPrint("diagram"); ; // klasa subskrybent ref class EventReceiver { public: void OnZoomClick(double scale) { Console::WriteLine( Skala {0", scale); void OnPrintClick(String^ document) { Console::WriteLine( Drukuj {0", document); ; int main() { // utworzenie żródła i subskrybenta EventSource ^ EventSourceObject = gcnew EventSource(); EventReceiver^ EventReceiverObject = gcnew EventReceiver(); // rejestracja funkcji obsługujących EventSourceObject->OnZoom += gcnew ZoomEventHandler(EventReceiverObject, &EventReceiver::OnZoomClick); EventSourceObject->OnPrint += gcnew PrintEventHandler(EventReceiverObject, &EventReceiver::OnPrintClick); // wygeneruj zdarzenia EventSourceObject->FireEvents(); Wynik na ekranie: Skala 1.5 Drukuj diagram 28
Przykład: ExcelIntegration Przykład demonstruje następujące konstrukcje programowe: Definiowanie klasy wartości Wynik do przechowywania dwóch pól typu double. Definiowanie klasy referencyjnej Pomiar symulującej przeprowadzanie pomiarów. Wynik każdego pomiaru przechowywany jest w obiekcie klasy Wynik. Po każdym pomiarze generowane jest zdarzenie PomiarEvent, obsługiwane przez delegata PomiarEventHeandler. Tworzenie graficznego interfejsu użytkownika klasa Form1. W obsłudze zdarzenia Click przycisku pomiarbtn, tworzony jest obiekt klasy Pomiar, rejestrowana jest funkcja OnPomiar obsługująca zdarzenie PomiarEvent i wywoływana jest funkcja wykonująca symulację pomiarów. Definiowanie funkcji odpowiedzi OnPomiar, która kolejne wyniki umieszcza na liście przewijanej pomiarlistbox. Wywołanie funkcji transfer z klasy Pomiar, powoduje utworzenie obiektu komponentu Excel i transfer wyników do arkusza kalkulacyjnego. 29
Delegaty w bibliotekach.net Dane przekazywane przez zdarzenie opisane są przez klasę <nazwa zd arzenia>eventargs, np.: PaintEventArgs, MouseEventArgs. Przykładowo klasa KeyEventArgs ma zdefiniowane właściwości: Alt, Control, Shift, Modifiers, Handled, KeyCode, KeyData, KeyValue, SuppressKeyPress, opisujące różne warianty użycia klawisza). Standardowa sygnatura delegata zdefiniowanego w bibliotece.net zawiera dwa parametry: public delegate void EventHandler ( Object^ sender, // źródło zdarzenia EventArgs^ e // dane związane z zderzeniem ); W System.Windows.Forms, zdefiniowano ok. 130 delegatów, m.in.: public delegate void PaintEventHandler(Object^ sender, PaintEventArgs^ e); public delegate void MouseEventHandler(Object^ sender, MouseEventArgs^ e); public delegate void KeyEventHandler(Object^ sender, KeyEventArgs^ e); 30
Zdarzenia w bibliotekach.net Zdarzenia powiązane z delegatami zadeklarowane zostały w klasach definiujących kontrolki (głównie w klasie Control - ok.70), m.in.: event EventHandler^ Click; event PaintEventHandler^ Paint; Funkcje odpowiadające, muszą być zdefiniowane z odpowiednimi parametrami, a także muszą być zarejestrowane w obiekcie delegata, np.: public ref class Form1 : public Form { public: Form1() { button1 = gcnew Button(); button1->click += gcnew EventHandler(this, &Form1::button1_Click); private: ; Button^ button1; void button1_click(object^ sender, EventArgs^ e) { // tu należy zdefiniować reakcję na naciśnięcie przycisku 31
Implementacja komponentów Infrastruktura komponentowa w.net Definiowanie komponentu Kontrolki Komponent w trybie projektowania Atrybuty Konwertery typu
Infrastruktura komponentowa w.net Przestrzeń nazw System.ComponentModel zawiera klasy i interfejsy składające się na technologię komponentową w.net. Komponent może być wprawdzie zdefiniowany z pominięciem tej infrastruktury wspomagającej, ale traci się w ten sposób wiele z funkcjonalności komponentu, a przede wszystkim wsparcie w trybie projektowania (desigened-time mode). Podstawową koncepcją modelu komponentowego w.net, jest użycie kontenerów do przechowywania komponentów. Koncepcja ta z kolei narzuca wymaganie, aby obiekty umieszczone w kontenerze miały wspólny typ bazowy. Dla komponentów jest to interfejs System.ComponentModel.IComponent. 33
Definiowanie komponentu Komponent w.net jest klasą, która w sposób bezpośredni lub pośredni implementuje interfejs System.ComponentModel.IComponent Interfejs IComponent definiuje standard komponentu.net. Nowy komponent definiuje się zwykle w oparciu o klasę Component, która implementuje interfejs IComponent, a ponadto definiuje typową dla komponentu funkcjonalność (np.: metodę Dispose zwalniającą przydzielone komponentowi zasoby). Schemat definicji komponentu: public ref class NowyKomponent : public System::ComponentModel::Component { 34
Komponenty a kontenery Komponenty zwykle umieszcza się w kontenerze (w obiekcie klasy Container (System::ComponentModel::Container). Użycie kontenera (chociaż nie jest obowiązkowe) ułatwia wzajemną komunikację pomiędzy komponentami, zarządzanie zasobami przydzielonymi komponentom oraz umożliwia integrację komponentu ze środowiskiem programistycznym (design-time environment). Kreator aplikacji typu Windows Forms Applications tworzy klasę formularza w oparciu o klasę biblioteczną Form. W klasie tej tworzony jest kontener components, przeznaczony do przechowywania komponentów. 35
Szkielet aplikacji typu Windows Forms public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); protected: ~Form1() { if (components) delete components; // usunięcie kontenere private: System::ComponentModel::Container ^components; // deklaracja odniesienia void InitializeComponent(void) { this->components = gcnew System::ComponentModel::Container(); ; 36
Kontener w komponencie Szkielet klasy definiującej komponent najłatwiej można wygenerować przy użyciu odpowiedniego kreatora (Project/Add Class /Component Class). Konstruktor klasy zawiera operację dodania komponentu do kontenera zewnętrznego: NowyKomponent(System::ComponentModel::IContainer ^container) { container->add(this); // inne operacje do wykonania przez konstruktor W klasie jest również definiowany kontener wewnętrzny do przechowania innych komponentów. 37
Szkielet definicji komponentu public ref class NowyKomponent : public System::ComponentModel::Component { public: NowyKomponent(void) { InitializeComponent(); NowyKomponent(System::ComponentModel::IContainer ^container) { container->add(this); InitializeComponent(); protected: ~NowyKomponent() { if (components) delete components; private: System::ComponentModel::Container ^components; void InitializeComponent(void) { components = gcnew System::ComponentModel::Container(); ; 38
Przykład: komponent HiResTimer Zadaniem komponentu jest pomiar czasu pomiędzy wywołaniem funkcji składowych Start() i Stop(). Właściwości statyczne Counter i TicksPerSecond zwracają odpowiednio stan i częstotliwość licznika procesora, udostępniane przez funkcje API QueryPerformanceCounter i QueryPerformanceFrequency. Częstotliwość licznika jest dla danego procesora stała i dlatego zostaje odczytana tylko jeden raz i zapamiętana w zmiennej statycznej frequency. Właściwości Seconds, Milliseconds i Microseconds zwracają w odpowiednich jednostkach interwał czasu jaki upłynął pomiędzy Start () i Stop(), a właściwość Ticks liczbę tyknięć licznika. Solution PerformanceTesting zawiera projekt Timer (definicja komponentu HiResTimer) oraz projekty TableTesting, PerformanceTesting i CSharpTesting (aplikacje testujące komponent HiResTimer). 39
Kontrolki Kontrolka jest komponentem wyposażonym w graficzny interfejs użytkownika. Kontrolki biblioteczne dzieli się na dwie grupy o różnym przeznaczeniu: kontrolki występujące w przestrzeni nazw System.Windows.Forms służą do budowania GUI aplikacji klienckich - klasą bazową tych kontrolek jest klasa System.Windows.Forms.Control wyprowadzona z klasy Component, kontrolki występujące w przestrzeni nazw System.Web.UI służą do budowania aplikacji internetowych w technologii ASP.NET - klasą bazową tych kontrolek jest klasa System.Web.UI.Control implementująca interfejs IComponent. 40
Definiowanie kontrolki W zależności od przyjętej strategii budowy, można wyróżnić trzy grupy: kontrolki rozszerzone (ang. extended control), które są modyfikacjami wcześniej zdefiniowanych kontrolek (są zwykle definiowane przez klasy pochodne wyprowadzone z klas definiujących kontrolki biblioteczne). kontrolki składane (ang. composite control), powstające przez umieszczenie zdefiniowanych już kontrolek we wspólnym kontenerze. Tym wspólnym kontenerem dla kontrolek Windows Forms jest zwykle klasa System.Windows.Forms.UserControl, udostępniająca funkcjonalność związaną z zarządzaniem wewnętrznymi kontrolkami (np.: rozmieszczanie kontrolek). Kontener dla kontrolek Web Forms buduje się natomiast w oparciu o klasę abstrakcyjną System.Web.UI.WebControls.CompositeControl. kontrolki adaptowane (ang. custom control), budowane niemal od podstaw w oparciu o klasę bazową Control. 41
Przykład: kontrolka Keyboard1 Kontrolka udostępniająca prostą klawiaturę ekranową, zdefiniowana na bazie klasy bibliotecznej UserControl. zawiera dwie właściwości: KeyCount typu całkowitego, która służy do odczytu i ustawienia liczby przycisków na klawiaturze ekranowej, CharSet typu "tablica znaków Unicode", która służy do odczytu i ustawienia znaków przypisanych poszczególnym przyciskom. Przyciski są ułożone w wiersze i kolumny za pomocą kontrolki bibliotecznej TableLayoutPanel. Kliknięcie dowolnego przycisku, uruchamia funkcję odpowiedzi buttonclick. Funkcja ta identyfikuje znak przypisany do przycisku i generuje zdarzenie CharEvent. Subskrybenci tego zdarzenia, zarejestrowani w delegacie CharEventHandler, otrzymują informację o wybranym z klawiatury znaku. 42
Komponent w trybie projektowania Właściwości i zdarzenia zdefiniowane w komponencie można w trybie projektowania (design-time) odczytywać i ustawiać w przeglądarce właściwości (property browser). Procesem tym można sterować poprzez: atrybuty (attributes), designery (designers), konwertery typów (type converters), wizualne edytory (UI type editors), typy projektowe (design-related types), usługi projektowe (design-time services). Przy realizacji komponentów dla celów komercyjnych, znaczna część kodu dotyczy zadań realizowanych tylko w trybie projektowania. Dla użytkownika końcowego komponentu, funkcjonalność w trybie projektowania jest często najważniejszym kryterium oceny przydatności komponentu czy kontrolki. 43
Atrybuty Atrybuty definiowane są przez klasy biblioteczne (np.: CategoryAttribute) wyprowadzone z klasy System.Attribute. Atrybut Category określa przynależność do grupy atrybutów w przeglądarce. Atrybut Description definiuje tekst wyświetlany na dole okienka przeglądarki. Atrybut Editor umożliwia przypisanie do właściwości odpowiedniego edytora (np.: do wyboru koloru). Atrybut TypeConverter określa sposób konwersji typu właściwości na inny typ. W bibliotekach.net zdefiniowanych zostało kilkaset klas opisujących atrybuty przeznaczone do różnych zastosowań. Wszystkie zostały wyprowadzone bezpośrednio lub pośrednio z klasy bazowej System.Attribute. Na bazie tej klasy można też definiować własne atrybuty. 44
Deklaracja atrybutu W definicji komponentu atrybuty umieszcza się według składni: [nazwa_atrybutu(argumenty), nazwa_atrybutu(argumenty), ] Przykład: // liczba przycisków na klawiaturze ekranowej [ Description("The number of keys on keyboard") ] property int KeyCount{ int get(); void set(int newcount); Atrybuty mogą być wykorzystane zarówno w trybie projektowania (design-time), jak i w trybie uruchomieniowym (run-time). Przykłady: komponent Keyboard1 i projekt DescriptionTest komponent HiResTimer i projekt Atrybuty.
Konwertery typu Konwertery typu, są stosowane do przekształcenia jednego typu danych na inny. W trybie projektowania, znajdują zastosowanie przede wszystkim do konwersji typu właściwości na postać tekstową i odwrotnie, ponieważ przeglądarka właściwości operuje na tekstach. Dla bibliotecznych typów danych (Int32, String, Color itd.), odpowiednie konwertery typu zostały zdefiniowane w przestrzeni System.ComponentModel. Wszystkie klasy definiujące konwertery zostały wyprowadzone ze wspólnej klasy bazowej TypeConverter. Dla ułatwienia identyfikacji, nazwy ich zostały skonstruowane według schematu TypDanychConverter, np.: Int32Converter, StringConverter, ColorConverter. Dla własnego typu danych, konwerter należy zdefiniować jako klasę wyprowadzoną z klasy TypeConverter lub jej pochodnej. 46
Przykład: KonwerterPunkt3D Klasa Punkt3D definiuje punkt w przestrzeni 3-wymiarowej. Klasa została udekorowana atrybutem TypeConverter specyfikującym konwerter dla tego typu danych - KonwerterPunkt3D. [TypeConverter(KonwerterPunkt3D::typeid)] // klasa definiuje punkt w przestrzeni 3-wymiarowej public value class Punkt3D {...; // Punkt3D ref class KonwerterPunkt3D : TypeConverter {... ; // KonwerterPunk3D W klasie definiującej konwerter należy przedefiniować cztery funkcje odziedziczone z klasy bazowej TypeConverter. Funkcje CanConvertFrom i ConvertFrom są wykorzystywane w procesie konwersji z typu String na typ Punkt3D, a funkcje CanConvertTo i ConvertTo przy konwersji typu Punkt3D do typu String, a także w procesie generowania kodu utrwalającego obiekt. Przykład: Solution KonwerteryTypu, Project KomponentPunkt. 47