Rozdział 31.
Wprowadzenie do ActiveX i projektowania kontrolek ActiveX
W tym rozdziale:
Różne technologie ActiveX
Wybór komponentów ActiveX
Podstawowe zagadnienia projektowania komponentów ActiveX
Biblioteki wykorzystywane do tworzenia komponentów ActiveX
Tworzenie kontrolek ActiveX z użyciem MFC
Zarządzanie zagadnieniami rejestracji kontrolek
Dodawanie metod i właściwości do kontrolek
Dodawanie zdarzeń do kontrolek
Wykorzystanie trwałych danych w kontrolkach
Rysowanie interfejsu użytkownika dla kontrolki
Jak już wiesz, Visual C++ wykorzystuje wbudowane komponenty, takie jak MFC do reprezentacji różnych obiektów, których możesz użyć podczas pisania programów. Wiele z klas MFC ułatwia samo pisanie programu, oszczędzając konieczności tworzenia tego samego kodu do wykonywania tych samych, standardowych zadań. Wiesz także, że użycie MFC w programach jest spójne z zasadami programowania zorientowanego obiektowo.
W miarę gdy programy stają się coraz bardziej złożone i zaczynasz tworzyć większe aplikacje lub aplikacje, które posiadają komponenty dystrybuowane poprzez sieć lub Internet, z pewnością konieczne stanie się przejście o krok z modelem programowania obiektowego i zastosowanie go do wszystkich modułów aplikacji, które będą traktowane
jako samodzielne obiekty. Przy strukturze projektowania stworzonej przez Microsoft, takie obiekty są najczęściej określane jako "obiekty ActiveX" lub "obiekty COM (Com-ponent Object Model)".
W tym i w następnym rozdziale szczegółowo omówimy tworzenie serwerów automatyzacji ActiveX, kontrolek oraz obiektów COM. Kontrolery automatyzacji, dokumenty ActiveX oraz kontenery posiadają zbyt wiele interfejsów oraz wykorzystuj ą zbyt wiele technologii, aby efektywnie omówić je na ograniczonej przestrzeni tych dwóch rozdziałów.
Pochodzenie i zastosowanie ActiveX
W rozdziale 15. poznałeś mechanizm DDE (Dynamie Data Exchange) Microsoftu -sposób wymiany informacji pomiędzy aplikacjami. W następnych rozdziałach korzystałeś z różnorodnych technologii opartych na OLE (Object Linking and Embedding, łączenie i osadzanie obiektów), rozwijających i rozszerzających DDE.
W miarę jak dane stawały się głównym zagadnieniem przy projektowaniu interfejsów, właśnie dane przyciągnęły uwagę użytkownika, a nie sama aplikacja zarządzająca tymi danymi. Przy takim projekcie aplikacja nie powinna ograniczać tworzenia i edycji danych jedynie do własnego środowiska. Innymi słowy, aplikacje nie powinny ograniczać użytkownika do tworzenia i edycji danych wyłącznie w swoich własnych oknach. Zamiast tego większość aplikacji umożliwi użytkownikowi przesłanie danych do innych rodzajów kontenerów, jednak w taki sposób, że cały czas je widać i można je edytować. Najlepszym przykładem interakcji pomiędzy kontenerami a ich komponentami są dokumenty złożone, jednak nie są one jedynym zastosowaniem powiązań OLE pomiędzy obiektami. Dokument -łożony to każdy rodzaj dokumentu, który zawiera komponenty z kilku różnych źródeł. Na przykład, możesz stworzyć dokument zawierający tekst z procesora tekstów, dane tabelaryczne z arkusza kalkulacyjnego oraz notatki dźwiękowe i obrazki stworzone przez kogoś innego w innych aplikacjach. Obecnie coraz większa część tworzonych dokumentów jest dokumentami złożonymi. Jak wyjaśnialiśmy w rozdziale 19., do obsługi złożonych dokumentów w aplikacji możesz wykorzystać elementy wyprowadzone z klasy MFC CCmdTarget.
Na podstawie jej nazwy możesz łatwo zgadnąć, że technologia OLE obsługuje dwa podstawowe typy obiektów - obiekty połączone (linked object) oraz obiekty osadzone (embedded object). Jak już wiesz, gdy użytkownik przenosi lub kopiuje obiekt do nowego miejsca (takiego jak inna aplikacja kontenera), taki obiekt, zachowujący w pełni swoje własne możliwości działania i edycji, jest nazywany osadzonym obiektem OLE. Osadzony obiekt zachowuje pełny zestaw swoich możliwości, nawet wewnątrz nowego kontenera. Z drugiej strony, użytkownik może połączyć się z informacją zamiast ją osadzać w swoim dokumencie. Połączony obiekt OLE zapewnia dostęp do innego obiektu, będącego w innym miejscu w tym samym kontenerze lub znajdującego się w innym, oddzielnym kontenerze. Ogólnie, kontenery obsługują każdy poziom zagnieżdżenia osadzonych i połączonych obiektów OLE. Na przykład, użytkownik może osadzić wykres na arkuszu kalkulacyjnym, który z kolei może być osadzony w dokumencie procesora tekstów. Model interakcji jest tak samo spójny na każdym poziomie zagnieżdżenia.
Teraz gdy nieco lepiej rozumiesz, co naprawdę oznacza OLA, nadszedł czas, aby przejść do ActiveX. ActiveX i OLE stały się synonimami. To co dawniej nazywano kontrolkami OLE (OCX), obecnie jest nazywane kontrolkami ActiveX. Obiekty OLEDocObjects obecnie są nazywane dokumentami ActiveX. W pewnych przypadkach Microsoft zaktualizował całe dokumenty dotyczące implementacji technologii OLE, które teraz odnoszą się do implementacji technologii ActiveX, zaś główną modyfikacją była zamiana terminu "OLE" na termin "ActiveX".
Choć cały czas postępuje rozwój technologii OLE i ActiveX, można zadać pytanie, w jakim stopniu odnoszą się one bezpośrednio do Intemetu. Potrzeba małych, szybkich, nadających się do ponownego wykorzystania komponentów (obiektów COM) występuje już od lat. Kilka lat temu, na konferencji OLE 2.0 Professional Developer Conference Microsoft zademonstrował komponenty rozproszone (obiekty DCOM). W tamtych czasach najważniejszą rolę w rozwijaniu ActiveX miała grupa Yisual Basica. W rzeczywistości, grupa Yisual Basica opracowała bibliotekę BaseCtl, którą Microsoft dołączył do pakietu ActiveX SDK (Software Developer's Kit), w odpowiedzi na żądania grupy potrzebującej małych kontrolek w celu skrócenia czasu ładowania aplikacji Yisual Basica. Jedynym, co bezpośrednio odnosiło się do Internetu, była potrzeba opracowania sposobu implementacji i publikowania stron WWW. Praktycznie każdy nowy element związany z ActiveX pochodzi z czasów, gdy występowała globalna potrzeba małych, szybkich, nadających się do ponownego wykorzystania komponentów, z których wszystkie byłyby oparte na OLE i COM.
ActiveX nie miało zastąpić OLE, ale raczej rozszerzyć je o zastosowania w Internecie, intranetach, programowaniu własnych i komercyjnych aplikacji oraz w narzędziach używanych do opracowywania nowszych aplikacji o szerszym zakresie. Microsoft opublikował kilka dokumentów dotyczących programowania ActiveX. Specyfikacja OC 96 definiuje sposób tworzenia kontrolek, tak aby szybciej się uruchamiały i posiadały większe możliwości rysunkowe. Ta specyfikacja podawała także, jakie interfejsy musi posiadać kontrolka, aby można ją było zakwalifikować jako kontrolkę ActiveX/OLE, oraz które interfejsy są opcjonalne. Dokument "OLE Control and Control Container Guidelines" zawiera ważne informacje dotyczące interakcji pomiędzy kontrolkami a kontenerami. Strona WWW Microsoftu, z jej obszerną bazą wiedzy, dokumentami i artykułami, wraz z sieciowym wsparciem MSDN, stała się bardzo cennym źródłem informacji dla programistów planujących tworzenie i korzystanie z komponentów ActiveX.
Oprócz specyficznych technologii tworzenia komponentów ActiveX, Microsoft ustanowił standard użycia i integracji tych komponentów. Każdy produkt Microsoftu, od Yisual Basica, przez Microsoft Word aż po Yisual J++, może korzystać z komponentów ActiveX. Parę lat temu prawie niemożliwe było znalezienie więcej niż kilku aplikacji posiadających taką zdolność integracji, jaka jest możliwa dzisiaj.
Różne technologie ActiveX
Jak już wiesz, technologia ActiveX stanowi kontynuację i rozszerzenie technologii OLE. Technologię ActiveX można ogólnie podzielić na sześć podstawowych kategorii:
Serwery automatyzacji
Sterowniki automatyzacji
Kontrolki
Obiekty COM
Dokumenty ActiveX
Kontenery ActiveX
Serwery automatyzacji
Serwery automatyzacji są komponentami, którymi inne aplikacje mogą programowo sterować. Serwer automatyzacji zawiera przynajmniej jeden interfejs oparty na interfejsie IDispatch, który inne aplikacje mogą tworzyć lub się z nim łączyć. Serwer automatyzacji może (lecz nie musi) posiadać interfejs użytkownika, w zależności od swojego przeznaczenia.
Serwery automatyzacji mogą być serwerami in-process (działającymi w przestrzeni adresowej procesu sterującego), lokalnymi (działającymi w przestrzeni adresowej własnego procesu) lub zdalnymi (działającymi w przestrzeni adresowej na innym komputerze). Specyfika implementacji serwera może czasem definiować, jak i gdzie serwer będzie działać, jednak nie jest to regułą. Biblioteka DLL serwera może działać zarówno jako serwer in-process, jak i serwer lokalny lub zdalny, podczas gdy wykonywalny plik EXE ActiveX może działać jedynie lokalnie lub zdalnie.
Projektując serwery automatyzacji, należy brać pod uwagę czas działania. Jednak nie jest to żelazną regułą. Na przykład, najszybciej działają serwery będące serwerami in-process dla korzystających z nich sterowników automatyzacji. Jednak użycie serwera automatyzacji in-process nie gwarantuje wydajności in-process. Jeśli serwer automatyzacji in-process zostanie stworzony w przestrzeni adresowej procesu, a następnie udostępniony sterownikowi automatyzacji w innej przestrzeni adresowej procesu, taki serwer staje się lokalny i jego wydajność spada do poziomu wydajności serwera lokalnego. Określenie spadku wydajności serwera działającego lokalnie staje się szczególnie ważnym zagadnieniem w rozwiązaniach korzystających z rozproszonych komponentów.
Sterowniki automatyzacji
Sterowniki automatyzacji to te aplikacje, które wykorzystują i manipulują serwerami automatyzacji. Przykładem sterownika automatyzacji może być Visual Basic. W języku programowania Visual Basic możesz tworzyć, używać i usuwać serwery automatyzacji, tak jakby były integralną częścią języka.
Sterownikiem automatyzacji może być dowolna aplikacja, w postaci pliku EXE lub biblioteki DLL, i może odwoływać się do serwerów automatyzacji in-process, lokalnie lub zdalnie. Zwykle o rodzaju przestrzeni adresowej używanej przez serwer w stosunku do przestrzeni adresowej sterownika automatyzacji decydują wpisy do Rejestru.
Kontrolki ActiveX
Kontrolki ActiveX to 32-bitowe kontrolki odpowiadające stosowanym dawniej kontrolkom OLE (czyli kontrolkom OCX). Typowa kontrolka składa się z interfejsu użytkownika zarówno dla trybu projektowania, jak i trybu używania kontrolki, pojedynczego interfejsu IDispatch definiującego wszystkie metody i właściwości kontrolki oraz pojedynczego interfejsu iConnectionPoint dla zdarzeń, jakie kontrolka może uruchamiać. Dodatkowo, kontrolka może zachowywać stan danych pomiędzy wywołaniami oraz obsługiwać różnorodne elementy interfejsu użytkownika, takie jak operacje wycinania i wklejania czy mechanizm przeciągania i upuszczania. Ogólnie, kontrolka posiada dużą ilość interfejsów COM, które musi obsłużyć aplikacja pojemnika, aby skorzystać ze wszystkich możliwości kontrolki. Jednak po opublikowaniu przez Microsoft zaleceń dla tworzenia kontrolek OLE i ActiveX, kontrolki nie muszą już być ograniczone do opisanych wcześniej możliwości. Zamiast tego programista może teraz zdecydować się na implementację tylko tych elementów, które są najbardziej użyteczne i interesujące dla użytkowników aplikacji. Zalecenia Microsoftu dotyczące kontenerów i kontrolek określają wszystkie wymagane interfejsy i ich właściwości.
Kontrolki ActiveX zawsze działaj ą in-process w przestrzeni adresowej kontenera, w którym występują. Rozszerzenie pliku wykonywalnego kontrolki to najczęściej .OCX, choć z punktu widzenia systemu kontrolka ActiveX nie różni się niczym od standardowej biblioteki DLL w Windows.
Obiekty COM
Obiekty COM (Component Object Model) są pod względem architektury podobne do serwerów i sterowników automatyzacji. Zawierają jeden lub kilka interfejsów COM i najczęściej nie posiadają interfejsu użytkownika (lub jest on bardzo uproszczony). Jednak obiekty tego typu nie mogą być używane przez typową aplikację sterownika w taki sposób, jak korzysta ona z serwerów automatyzacji. Sterownik musi posiadać określoną wiedzę o interfejsie COM, poprzez który obiekt się porozumiewa, czyli inaczej niż w przypadku interfejsów automatyzacji. Systemy operacyjne Windows 95 i Windows NT zawierają setki obiektów COM i własnych interfejsów stanowiących rozszerzenie systemu operacyjnego. Te obiekty COM kontrolują wszystko, od wyglądu pulpitu aż po wyświetlanie na ekranie trójwymiarowych obrazków. Obiekty COM są efektywnym sposobem zorganizowania powiązanych zestawów funkcji i danych, przy zachowaniu wysokiej wydajności działania bibliotek DLL.
Serwery automatyzacji także mogą odnosić korzyści z interfejsów COM. Serwery obsługujące interfejsy COM są znane jako serwery o podwójnym interfejsie. Interfejs IDispatch serwera automatyzacji posiada także odpowiedni interfejs COM opisujący właściwości i metody obiektu. Sterowniki automatyzacji, takie jak Yisual Basic, mogą
korzystać z takich podwójnych interfejsów w celu zapewnienia jeszcze większej wydajności podczas korzystania z serwera. Jedną z wad serwerów o podwójnym interfejsie jest ich graniczenie zestawu typów danych obsługiwanych przez automatyzację OLE dla definiowanych właściwości i metod.
Dokumenty ActiveX
Dokumenty ActiveX, oryginalnie noszące nazwę DocObject, reprezentują obiekty będące więcej niż prostą kontrolką czy serwerem automatyzacji. Dokument może być wszystkim, od arkusza kalkulacyjnego aż po pełną fakturę w aplikacji księgowej. Dokumenty, podobnie jak kontrolki, posiadają interfejs użytkownika, zaś dokumentami musisz zarządzać także poprzez aplikację kontener. Przykładami serwerów dokumentów ActiveX mogą być Microsoft Word i Microsoft Excel, zaś Microsoft Office Binder oraz Microsoft Internet Explorer mogą być przykładami kontenerów dokumentów ActiveX.
Architektura dokumentów ActiveX stanowi rozszerzenie modelu łączenia i osadzania OLE i daje dokumentom większą kontrolę nad kontenerem, w którym użytkownik korzysta z dokumentu. Najbardziej oczywistą zmianą jest sposób prezentacji menu. Menu standardowego dokumentu OLE łączy się z menu kontenera, udostępniając wspólny zestaw poleceń, podczas gdy menu dokumentu ActiveX zastępuje cały system menu aplikacji kontenera, oferując jedynie zestaw poleceń dokumentu, a nie jednocześnie dokumentu i kontenera. Fakt udostępnienia jedynie elementów i poleceń dokumentu wynika z różnicy pomiędzy dokumentami OLE a dokumentami ActiveX. W tym drugim przypadku kontener jest faktycznie tylko pojemnikiem, podczas gdy całą kontrolę sprawuje dokument.
Kolejną różnicą pomiędzy dokumentami ActiveX a dokumentami OLE jest sposób drukowania i przechowywania. Z projektu dokumentu OLE wynika, że powinien on być częścią dokumentu kontenera, w którym się znajduje. W związku z tym Windows drukuje i przechowuje dokumenty OLE jako części dokumentu kontenera. Z kolei od dokumentów ActiveX system operacyjny oczekuje, że same obsłużą własne drukowanie i przechowywanie i w związku z tym nie integruje ich z dokumentem pojemnika.
Dokumentów ActiveX powinieneś używać raczej wewnątrz jednolitej architektury prezentacji niż wewnątrz architektury osadzonych dokumentów, stanowiącej podstawę dla dokumentów OLE. Doskonałym przykładem jednolitej architektury prezentacji (tzn. architektury obsługującej dokumenty ActiveX) jest Internet Explorer Microsoftu. Internet Explorer jedynie prezentuje strony WWW użytkownikowi, jednak użytkownik postrzega, drukuje i zapisuje te strony jako pojedyncze całości, oddzielne od kontenera. Z drugiej strony, Microsoft Word i Microsoft Excel są przykładami architektury dokumentów OLE. Gdy arkusz Excela zostanie osadzony w dokumencie Worda, ten arkusz zostanie zapisany w dokumencie Worda jako jego integralna część.
Dokumenty ActiveX posiadają możliwość publikowania ich jako stron WWW w Internecie lub intranecie. Wyobraź sobie firmowy system śledzenia zamówień, który użytkownicy mogą uruchamiać w tych samych przeglądarkach, jakich używają do łączenia się z In-ternetem, w pełni integrujący dane dotyczące zamówień i z którym użytkownicy mogą współpracować tak, jakby był programem uruchomionym na pulpicie.
Kontenery ActiveX
Kontenery ActiveX są aplikacjami, które mogą zawierać serwery automatyzacji, kontrolki oraz dokumenty. Przykładami kontenerów mogących zawierać serwery automatyzacji i kontrolki mogą być Visual Basic czy ActiveX Control Pad. Microsoft Office Binder i Microsoft Internet Explorer mogą zawierać serwery automatyzacji, kontrolki oraz dokumenty.
Przy zmniejszających się wymaganiach co do tego, co muszą definiować kontrolki ActiveX i dokumenty, kontener musi być na tyle stabilny, aby obsłużyć przypadki, w których w kontrolce lub dokumencie brakuje któregoś z interfejsów. Aplikacja kontenera może umożliwiać niewielką interakcję lub nawet nie umożliwiać interakcji z dokumentem lub kontrolką, którą zawiera, lub może posiadać rozbudowany system interakcji zarówno do manipulacji, jak i edycji zawartych w niej obiektów. To zależy wyłącznie od kontenera zawierającego te komponenty. Specyfikacja kontrolek i dokumentów ActiveX w tym względzie nie określa wymagań co do aplikacji kontenera.
Do czego możesz wykorzystać ActiveX
Zrozumienie różnych zastosowań i możliwości poszczególnych podtypów technologii ActiveX stanowi ważny pierwszy krok w wyznaczaniu rodzaju obiektu ActiveX, jaki należy stworzyć. To jasne, że komponenty ActiveX stanowią cenne narzędzia umożliwiające stworzenie najlepszych możliwych aplikacji - zaś obsługa OLE jest znaczącą dla wielu rozbudowanych aplikacji. Ogólnie, przed przystąpieniem do projektowania komponentu ActiveX powinieneś wziąć pod uwagę pięć następujących zagadnień:
Wymagania aplikacji Ważne jest, by przed rozpoczęciem tworzenia projektu określić jak najwięcej wymagań co do projektu. Pomaga to w lepszym podjęciu decyzji co do zastosowania w projekcie określonych technologii ActiveX.
Wybór właściwej architektury i rodzaju komponentów Wybrane sposoby w jakie możesz tworzyć i implementować komponenty ActiveX są istotnym dla mającego zakończyć się powodzeniem projektu. Nie chcesz przecież poświęcać komponentowi ani zbyt wiele czasu (przedłużając czas tworzenia projektu i jego objętość) ani zbyt mało (co daje w wyniku kiepski projekt programu lub jego kiepską implementację).
Wybór właściwych narzędzi Narzędzia używane do tworzenia komponentu wpływają także na sukces projektu. Wybór właściwego narzędzia do pracy jest równie ważny jak znajomość tej pracy.
Podstawowa architektura komponentu ActiveX Każdy typ komponentu ActiveX posiada swoją własną architekturę i konstrukcję. Zrozumienie tej architektury jest bardzo ważne - i może zaoszczędzić Ci wielu kłopotów podczas tworzenia aplikacji.
Podstawowe pomocnicze narzędzia dla ActiveX Masz do dyspozycji zestaw narzędzi pomocniczych, które są bezcenne przy tworzeniu komponentów ActiveX -dostępnych nie tylko w Visual Studio, ale także na stronie WWW Microsoftu oraz na innych stronach WWW dedykowanych technologii ActiveX.
W skrócie, nie musisz już tworzyć zestawu prostych samodzielnych aplikacji, które się ze sobą nie komunikują- lub komunikują się w sposób bardzo prymitywny. Dzięki OLE, ActiveX i Internetowi, użytkownicy oczekują od aplikacji, że będą elastyczne, modyfi-kowalne oraz rozszerzalne - wszystko to w sposób łatwy dla użytkownika.
Poświęcenie nieco więcej czasu na opracowanie działania i architektury aplikacji oraz określenie wszystkich komponentów, jakie mają zostać w niej wykorzystane, może stanowić właśnie tę różnicę. Jedynie bardzo niewiele aplikacji, w których nie przemyślano dokładnie architektury - bez względu na to czy wszystkie informacje znajdowały się w głowie pojedynczego programisty, czy były zapisywane na tylnych stronach kopert, czy też w postaci jakiejś bardziej formalnej dokumentacji - doczekało się dystrybucji, a nawet wtedy sprzedaż najczęściej nie przedstawiała się najlepiej. Należy przyjąć ogólną zasadę, że formalna dokumentacja lub jakiś inny rodzaj użytecznego zapisu stanowi najprawdopodobniej najlepszą polisę, gdyż umożliwia łatwiejsze wyjaśnienie innym podstaw Twojej wielkiej idei.
Podstawowe zasady OLE i ActiveX determinują większość specyfiki projektu i architektury komponentów w aplikacji. Na przykład, musisz tworzyć kontrolki i dokumenty ActiveX spełniające określony zestaw parametrów i reguł, gdyż w przeciwnym razie nie będą one poprawnie współpracować z kontenerami. Podobnie, sterowniki i serwery automatyzacji muszą działać zgodnie z regułami automatyzacji OLE, zaś obiekty COM muszą spełniać podstawowe założenia modelu COM. Jednak sama zgodność z zasadami OLE i ActiveX nie uwolni Cię od różnych problemów. Na przykład, zależność pomiędzy stworzonymi przez Ciebie komponentami a samym czasem życia tych komponentów jest inna dla różnych aplikacji. Kluczowym zagadnieniem jest także na przykład określenie, jakie kontenery mogą odwoływać się do określonych interfejsów, jakie zabezpieczenia ma obsługiwać komponent lub czy powinien obsługiwać wielobajtowe znaki.
Jaki rodzaj komponentu ActiveX jest Ci potrzebny?
Zaczynając projektować jeden lub więcej komponentów ActiveX w celu wykorzystania ich w swoich aplikacjach, musisz podjąć kilka decyzji (o których wspominaliśmy ogólnie w poprzedniej sekcji). Po pierwsze, musisz zdecydować, jakiego rodzaju komponent najlepiej będzie spełniał stawiane mu wymagania. Podjęcie takiej decyzji wymaga większej wiedzy o zaletach i ograniczeniach różnych typów komponentów, w następnych sekcjach spróbujemy więc przybliżyć te zagadnienia.
Użycie serwerów i sterowników automatyzacji
Serwery i sterowniki automatyzacji są prawdopodobnie najbardziej elastycznymi spośród różnych technologii ActiveX. Praktycznie każda ważniejsza aplikacja Microsoftu oraz setek innych producentów mogą korzystać z interfejsu IDispatch serwera. Ponieważ ten interfejs nie jest ograniczony wymaganiami co do wersji nakładanymi na interfejsy COM, można łatwo go wykorzystać do modelowania współpracy między komponentami.
Najszybszym typem serwera automatyzacji jest serwer automatyzacji z podwójnym interfejsem stworzony jako serwer in-process i pozostający takim przez cały czas swojego istnienia. Termin "podwójny interfejs" wynika z faktu, że taki serwer posiada dwa interfejsy - jeden oparty na interfejsie IDispatch, zaś drugi na interfejsie COM. Interfejs COM jest szybszym z tych dwóch interfejsów. Serwer in-process działa w tej samej przestrzeni adresowej co aplikacja, która go stworzyła. Dzięki działaniu w pojedynczej przestrzeni adresowej, aplikacja może znacznie szybciej wywoływać metody serwera niż wtedy, gdy serwer działa w osobnej przestrzeni adresowej (gdyż nie ma potrzeby szeregowania argumentów i przesyłania ich pomiędzy granicami procesów). Z tego samego powodu serwer in-process ładuje się szybciej niż działający w osobnej przestrzeni adresowej serwer o podobnych rozmiarach, gdyż system operacyjny musi podjąć w tym celu jedynie minimalne wymagane działania, przekazując wywołującej aplikacji wskaźnik do interfejsu
IDispatch lub COM.
Architektura serwera i sterownik automatyzacji w żaden sposób nie zachęca ani nie zniechęca do zastosowania interfejsu użytkownika dla komponentu. Masz swobodę decyzji co do tego, w jaki sposób program wywołujący implementuje i wykorzystuje Twoje serwery. Twoje serwery automatyzacji mogą potencjalnie zawierać interfejs użytkownika w formie dialogu, klasy widoku CFormView lub jakiegoś innego okna opartego na oknie dialogowym.
Serwery aplikacji coraz częściej są aplikacjami o architekturze wielowarstwowej, coraz bardziej popularnej w ostatnich latach. Oddzielenie interfejsu użytkownika od funkcji jest w przypadku serwerów automatyzacji bardzo pożądane, gdyż dzięki temu masz dużą swobodę w sposobie implementacji i korzystania z serwerów w aplikacjach. Tworzenie serwerów z cienką warstwą interfejsu użytkownika korzystających z innych serwerów bez interfejsów użytkownika stanowi klucz do tworzenia aplikacji wielowarstwowych -techniki, dla której Microsoft zmodyfikował standard COM, tworząc nowy standard COM+. Serwerów automatyzacji powinieneś używać tak jak wszystkich innych bibliotek DLL. Jedyna różnica pomiędzy serwerem automatyzacji a standardową biblioteką DLL w Windows jest to, że serwer używa ograniczonego zestawu typów danych, podczas gdy biblioteka DLL nie jest w tym ograniczona. Tworzenie serwerów automatyzacji działających bez potrzeby interfejsu użytkownika także sprawia, że stają się one podstawowymi kandydatami dla DCOM (Distributed COM) oraz innych rozproszonych technologii (takich jak COM+).
Użycie kontrolek ActiveX
Kontrolek ActiveX powinieneś używać głównie do tego, do czego zostały przeznaczone. Innymi słowy, Twoja kontrolka ActiveX zwykle powinna być komponentem posiadającym interfejs użytkownika wzbogacającym działanie okna dialogowego, formularza czy dokumentu. Kontrolki mogą być kosztowne pod względem ładowania, gdyż potencjalnie mogą wymagać dużej ilości interfejsów, w zależności od pełnionych przez siebie funkcji. Specyfikacja OC 96 dodaje interfejs QuickActivate mający pomóc w szybszym ładowaniu kontrolki, lecz poprawa jest niewielka. Oprócz tego, specyfikacja OC 96 określa interfejsy uważane za opcjonalne lub warunkowe, w zależności od rodzaju tworzonej kontrolki. Powinieneś przejrzeć tę specyfikację, aby dowiedzieć się co możesz usunąć a co pozostawić w kontrolce w celu poprawienia jej wydajności i zmniejszenia objętości kodu.
Podczas tworzenia kontrolek pamiętaj, aby były jak najmniejsze. Jeśli nie zamierzasz rozprowadzać kontrolki komercyjnie, usuń z niej okno dialogowe "About". Dodatkowo, zobacz także co możesz usunąć, aby zamiast stron właściwości korzystać z wbudowanych narzędzi edytora kontrolek. Unikaj dużych ilości trwałych danych i zapisuj dane tylko wtedy, gdy musisz. Innymi słowy (jeśli jeszcze się nie zorientowałeś), zaimplementuj tylko te elementy, które są naprawdę konieczne.
Używanie obiektów COM
Obiekty COM (własne interfejsy) są dużo bardziej elastyczne niż wszystkie inne rodzaje komponentów. Są także najszybsze, jeśli chodzi o działanie, choć najszybszymi obiektami COM są te, które działają w przestrzeni adresowej wywołującej je aplikacji. Obiekty COM działające w osobnych przestrzeniach adresowych są mniej wydajne, podobnie jak inne komponenty działające w osobnych przestrzeniach. Obiekty COM mogą w swoich definicjach interfejsów korzystać z dowolnych typów danych i nie są w tym względzie ograniczone tak jak serwery automatyzacji. Jednak z tym wiąże się pewien problem przy przekraczaniu granicy pomiędzy procesami, gdyż taki obiekt wymaga zastosowania własnego kodu szeregowania argumentów wywołań metod (ponieważ uniwersalny mechanizm szeregujący, obsługujący serwery automatyzacji, działa tylko z prostymi typami danych).
Szeregowanie danych występuje wtedy, gdy aplikacja działa w przestrzeni adresowej innej niż przestrzeń adresowa procesu, z którym się komunikuje. Konieczne jest wtedy przetłumaczenie wywołań funkcji i danych do kontekstu zrozumiałego dla obu aplikacji. Tym tłumaczeniem zajmuje się kod szeregujący, obowiązujący dla wszystkich typów komponentów OLE. Przeniesienie komponentu z przestrzeni adresowej aplikacji powoduje zauważalne zmniejszenie wydajności, ponieważ system operacyjny, aplikacja oraz komponent muszą wykonać dużo więcej pracy w celu osiągnięcia tych samych wyników.
Serwery automatyzacji opierają się na wspólnym kodzie szeregującym (w uniwersalnym programie szeregującym), podczas gdy interfejsy COM muszą zawierać w tym celu własny kod. Choć oczywiście możesz sam stworzyć taki kod, jednak wiąże się to z wydłużeniem czasu tworzenia komponentu, konserwacją kodu, rozprowadzaniem komponentu
oraz ogólną wydajnością aplikacji, więc musisz wziąć to pod uwagę, zanim zdecydujesz się na ten krok. Jeśli planujesz stworzenie obiektu COM w celu uruchamiania go poza przestrzenią adresową procesu wywołującego, powinieneś rozważyć użycie zamiast tego serwera automatyzacji, gdyż w takim przypadku wydajność obu typów serwerów jest porównywalna - chyba że masz zamiar stworzyć własny kod szeregujący, znacznie szybszy od wbudowanych mechanizmów.
Obiekty COM są przydatne w przypadkach, gdy ograniczenie rodzaju typów danych dostępnych dla serwerów automatyzacji ma duży wpływ na rodzaj interfejsu, jaki możesz stworzyć. Przykładem obiektu COM może być prosta implementacja wykonująca obliczenia na dużych zestawach danych użytkownika. Zamiast kopiować dane i przekazywać je do obiektu COM, bardziej użyteczne może być przekazanie obiektowi COM wskaźnika do tych danych i umożliwienie mu bezpośredniej manipulacji na tych danych. Ograniczenia typów danych dla serwerów automatyzacji uniemożliwiają tworzenie tego rodzaju interfejsów. Jednak umożliwiają to obiekty COM. Pamiętaj tylko, że w tym szczególnym przypadku obiekt COM może działać tylko jako serwer in-process, gdyż wymaga bezpośredniego dostępu do danych. Tworząc komponenty ActiveX w Visual C++, masz do wyboru cztery opcje, które omówimy w następnych sekcjach.
Tworzenie komponentów ActiveX z użyciem MFC
Ze wszystkich narzędzi do tworzenia komponentów ActiveX najłatwiejszym w użyciu jest biblioteka MFC. Środowisko IDE pakietu Visual C++ zostało zaprojektowane specjalnie z myślą o MFC i, jak wiesz z poprzednich rozdziałów, posiada bardzo użytecznego AppWizarda i ClassWizarda, które pomagają w tworzeniu aplikacji. MFC jest stabilne i zwykle zaspokaja co najmniej 90 procent potrzeb aplikacji. Niestety, tak jak w większości tworzonych programów lub innych kreatywnych projektów, pozostałe 10 procent jest właśnie tą częścią, której poświęcasz 90 procent swojego czasu.
Wyjście poza granice wyznaczone przez MFC może być trudne lub, w pewnych przypadkach, niemożliwe. Weźmy na przykład wymaganie posiadania obiektu będącego tylko jednym egzemplarzem. Bez względu na to, w jaki sposób obiekt jest tworzony przez aplikację klienta, chciałbyś, aby zawsze został zwracany wskaźnik do tego samego egzemplarza. Niestety, tego rodzaju zachowanie jest w MFC nieosiągalne bez zmodyfikowania wbudowanych klas Class Factory, których Visual Studio zwykle nie udostępnia programistom.
Wsparcie dla podwójnego interfejsu w serwerze automatyzacji tworzonego w MFC nie jest niemożliwe, jednak wymaga wprowadzenia tylu zmian w kodzie, że nie będziesz już mógł korzystać z usług ClassWizarda. MFC udostępnia liczne funkcje i właściwości przydatne przy tworzeniu komponentów ActiveX, lecz musisz być przygotowany na to. że będziesz musiał działać zgodnie z narzuconymi przez tę bibliotekę regułami. Od czasu do czasu będziesz mógł nieco nagiąć reguły, ale prawie nigdy nie uda Ci się ich złamać. W następnym rozdziale dowiesz się właśnie, jak naginać te reguły i implementować serwery zarówno o pojedynczych, jak i podwójnych interfejsach.
Przyjętą regułą podczas pracy z MFC przy tworzeniu komponentów ActiveX jest unikanie, jeśli to tylko możliwe, wbudowanych klas i korzystanie w jak największym stopniu z podstawowego Windows API. Unikanie klas MFC w celu rozwiązania problemów z aplikacją komponentu ma dwie zalety. Po pierwsze, Twoja aplikacja będzie zwykle działać szybciej, zaś po drugie, w przypadku zdecydowania się na przeniesienie kodu aplikacji do alternatywnych narzędzi programistycznych, takich jak ATL czy BaseCti, nie będzie wiązać się z koniecznością przepisania dużych części kodu zależnych od MFC. Duża część klas MFC posiada odpowiadające im funkcje Windows API, szczególnie w dziedzinie GDI i rysowania, zaś samo użycie Windows API nie kłóci się z używaniem MFC. Podstawowe klasy do przechowywania danych, takie jak listy i tablice, mogą być lepiej obsłużone z użyciem biblioteki klas ogólnego przeznaczenia, takich jak STL (Standard Template Library), z których można korzystać w połączeniu ze wszystkimi bibliotekami używanymi do tworzenia komponentów ActiveX (MFC,ATL czy BaseCti).
Użycie biblioteki ATL do tworzenia komponentów ActiveX
Biblioteka ATL (ActiveX Template Library) pojawiła się stosunkowo niedawno. Po raz pierwszy mieliśmy okazję ujrzeć ją w lecie 1996 roku i szybko stała się jednym z ulubionych narzędzi programistów. Biorąc pod uwagę ilość kodu opracowanego z użyciem tej biblioteki oraz fakt, że w odróżnieniu od BaseCti biblioteka ATL jest wspieranym produktem, Microsoft i inni twórcy oprogramowania uważają ją za ważną platformę dla tworzenia komponentów ActiveX. W związku z tym powinna istnieć jeszcze przez dłuższy czas.
Początkowa implementacja, wersje 1.0 oraz 1.1, skupiała się na tworzeniu małych i szybkich serwerów automatyzacji oraz obiektów COM. Wraz z wprowadzeniem wersji 2.0. ATL rozszerzyła swój zakres o kontrolki ActiveX oraz inne komponenty ActiveX. Poziom integracji z Visual C++ IDE początkowo obejmował jedynie pojedynczego AppWizarda używanego do tworzenia podstawowego projektu ATL (zresztą, ironicznie, pełniejszego niż jego odpowiednik w MFC). Dodatkowo, za pomocą ClassWizarda można było manipulować obiektami, metodami i właściwościami tak jak w przypadku MFC. Microsoft w pełni zintegrował ATL w wersji 2.0 z Yisual C++ 5.0 (włącznie z AppWizardem, ObjectWi-zardem i ClassWizardem) i zachował ten sam poziom integracji ATL w Yisual C++ 6.0.
Dodatkową zaletą ATL jest fakt, że możesz połączyć komponenty ATL z istniejącymi aplikacjami MFC bez konieczności wykonywania dużej ilości modyfikacji. Dzięki temu masz pełną wolność w tworzeniu swojego komponentu bez ograniczeń nakładanych przez MFC, wciąż mogąc korzystać z klas i innych obiektów MFC (takich jak między innymi struktury, tablice czy listy). Więcej na temat użycia ATL dowiesz się w rozdziale 33.
Użycie biblioteki BaseControl do tworzenia komponentów ActiveX
Biblioteka BaseControl (Basectl) oraz ActiveX SDK są bez wątpienia najtrudniejszym sposobem tworzenia kontrolek ActiveX. Biblioteka Basectl została opracowana przez grupę twórców Visual Basica 4 pod koniec roku 1995 i na początku roku 1996 w odpowiedzi na rosnące żądania lepszej wydajności kontrolek OCX w Visual Basicu. Basectl miała być szkieletową biblioteką, którą doświadczeni programiści mogli wykorzystywać do tworzenia niewielkich i szybkich kontrolek OLE.
W celu spełnienia żądań co do opracowania narzędzi do tworzenia kontrolek OLE biblioteka została przekazana w ręce różnych twórców kontrolek i producentów pozostających w kontakcie z Microsoftem i grupą Visual Basica. Na konferencji Internet Pro-fessional Deveopers' Conference Microsoft umieścił bibliotekę Basectl jako część ActiveX SDK, zaś reszta, jak to się mówi, jest historią.
Biblioteka Basectl nie jest zintegrowana ze środowiskiem Visual C++. W rzeczywistości, wersja biblioteki Basectl dostarczana wraz z ActiveX SDK stanowi niewiele więcej niż przykładowe programy, na podstawie których możesz tworzyć nowe aplikacje. Basectl opiera się na serii obiektów i plików bibliotek, które musisz zbudować, zanim będziesz mógł z nich skorzystać przy tworzeniu komponentów. Wszystkie pliki źródłowe dostarczane wraz z SDK oraz pliki wygenerowane przez AppWizarda zależą od kompilacji w linii poleceń. Przy niewielkim wysiłku możesz zamienić wszystkie projekty na projekty Visual C++, włącznie z plikami obiektów i bibliotek dostarczanych wraz z SDK -jednak zanim zaczniesz, powinieneś zadać sobie pytanie, czy ten wysiłek jest uzasadniony. Dokumentacja dla Basectl jest skąpa i raczej niejasna.
Także tworzenie podstawowych kontrolek z użyciem Basectl może być utrudnione. Duża ilość funkcji i możliwości, do których przywykłeś, korzystając z MFC, jest w Basectl nieobecna. Co gorsza, różni się także ilość nazw funkcji, zaś architektura dla trwałości danych jest już zupełnie inna. Basectl było tworzone do wykonania zadania z użyciem jak najmniejszej ilości kodu. Jak zapewne sam wiesz, próba wykonania zadania z użyciem jak najmniejszej ilości kodu zwykle oznacza w najlepszym razie równie surowe środowisko działania, co jest prawdą również w przypadku Basectl. W przypadku biblioteki Basectl musisz sam zagłębić się w szczegóły jej implementacji i większość funkcji stworzyć samodzielnie.
Jedną z zalet biblioteki Basectl jest duża ilość przykładów. Instalując tę bibliotekę, powinieneś również zainstalować przykłady. Istnieje duża szansa na to, że gdy zechcesz coś napisać, znajdziesz odpowiedni przykład. Kolejną zaletą tej biblioteki jest możliwość bezpośredniego dostępu do całego kodu źródłowego biblioteki, więc gdy natrafisz na błąd, możesz go samemu naprawić i przejść dalej.
Dodatkowo, użycie biblioteki Basectl daje Ci dużo większą swobodę w modelowaniu kontrolki. Na przykład, przypuśćmy, że masz dwie kontrolki, które chcesz stworzyć. Pierwsza z nich to kontrolka Number, otrzymująca jedynie dane numeryczne, zaś druga to Currency, otrzymująca jedynie kwoty w walucie. Jeśli stworzysz kontrolkę BaseNu-meric i wyprowadzisz z niej kod obu kontrolek, obie z nich mogą w dużym stopniu opierać się na modelu dziedziczenia klas C++.
Jasne jest, że MFC nawet w przybliżeniu nie daje takiej swobody. Jednak nie daj się zwieść swobodzie - i tak możesz spodziewać się konieczności włożenia wiele pracy w tworzenie komponentu z użyciem biblioteki Basectl. Co gorsza, stworzony komponent może w ogóle nie działać. Na przykład, zamiana istniejącej kontrolki MFC na wersję korzystającą z biblioteki Basectl może w pewnych przypadkach dać średnio o 40 procent krótszy czas ładowania kontrolki w stosunku do czasu ładowania oryginalnej kontrolki MFC. Na pierwszy rzut oka poprawa o 40 procent brzmi bardzo zachęcająco. Niestety, przy obecnych szybkościach nowoczesnych komputerów i ilości dostępnej pamięci, przyspieszenie czasu ładowania może być dla użytkownika niezauważalne. Innymi słowy, czas ładowania kontrolek MFC może być i tak na tyle niski, że dopiero umieszczenie setek takich kontrolek w formularzu aplikacji może spowodować powstanie zauważalnej różnicy w czasie ładowania formularza.
Tworzenie własnej biblioteki
Ostatnią metodą tworzenia kontrolek jest po prostu zabranie się do pracy i stworzenie kontrolki. Możesz zaczerpnąć kod z bibliotek klas, przykładów, książek itd., tworząc w końcu własną bibliotekę, narzędzia i co tylko chcesz. Jednak nie oczekuj, że będzie to praca lekka, łatwa i przyjemna. Aby zdać sobie sprawę z ilości pracy, jaka się z tym wiąże, zatrzymaj się na moment i zajrzyj do któregoś z plików źródłowych w bibliotece MFC,ATL czy Basectl. Przez całe miesiące lub nawet lata stworzono w nich dosłownie tysiące linii kodu. Z powodu ciągłych zmian dotyczących OLE i ActiveX, mądrzejszym rozwiązaniem jest skorzystanie z którejś z istniejących platform zamiast podejmowania prób ponownego wynalezienia koła, chyba że masz jakiś niesamowicie ważny powód tworzenia własnej biblioteki. Pamiętaj, kluczem do zakończonego sukcesem tworzenia kontrolek nie jest biblioteka, którą wybierzesz, lecz sposób jej wykorzystania.
Podstawy architektury komponentów ActiveX
Zanim przejdziemy do implementowania komponentów ActiveX, powinieneś poznać podstawy architektury różnych rodzajów tych komponentów. Choć istnieje wiele rodzajów komponentów ActiveX - kontrolki, serwery, dokumenty itd. - jednak w każdym przypadku są one po prostu obiektami o architekturze modelu COM (Component Object Model). Model COM definiuje standardy, dzięki którym każdy komponent ActiveX może współdziałać z innymi komponentami.
Oprócz tego, wszystkie komponenty ActiveX są definiowane i ograniczane przez system operacyjny, w którym są tworzone i wykorzystywane. Dodatkowo, opcje dostępne podczas tworzenia komponentu zależą również od rodzaju tego komponentu. Jako programista masz wiele opcji, więc zrozumienie tego, co mogą one wnieść do projektu, stanowi kluczowe zagadnienie.
Serwery automatyzacji ActiveX
Prawdopodobnie najłatwiejszym w implementacji i najbardziej elastyczną postacią komponentu ActiveX jest serwer automatyzacji. Serwer automatyzacji to aplikacja zawierająca jeden lub kilka interfejsów opartych na interfejsie IDispatch. Interfejs jest kolekcją powiązanych ze sobą metod i właściwości, zaś interfejs IDispatch jest interfejsem COM, poprzez który aplikacje korzystające z komponentów ActiveX odwołują się do ich metod i właściwości. Więcej informacji na temat interfejsów IDispatch oraz ich wykorzystania znajdziesz w plikach pomocy Visual C++ dostarczanych wraz z Visual Studiem. Możliwość definiowania unikalnych metod i właściwości dla każdego serwera i wykorzystywania ich poprzez standardowy mechanizm jest jedną z największych zalet serwerów automatyzacji.
Serwer automatyzacji może mieć możliwość bezpośredniego tworzenia przez inne aplikacje poprzez wywołanie funkcji CreateObject () lub jakiejś podobnej. Istnieje możliwość posiadania zagnieżdżonych obiektów, reprezentujących hierarchię obiektów. W takim środowisku pojedynczy nadający się do bezpośredniego tworzenia obiekt automatyzacji jest odpowiedzialny za tworzenie i dystrybucję innych obiektów automatyzacji. Na przykład, aplikacja może udostępniać interfejs dokumentu automatyzacji, który może być tworzony i manipulowany przez inne aplikacje, lecz który udostępnia jedynie interfejs Page, jako wywoływaną metodę obiektu dokumentu. Czas życia obiektu Page jest nie dłuższy niż obiektu dokumentu, gdyż ten obiekt Page nie może istnieć samodzielnie.
W rozdziale 32. nauczysz się tworzyć serwery automatyzacji za pomocą MFC. MFC doskonale się nadaje do szybkiego tworzenia i modyfikowania komponentów, jednak stworzone w ten sposób serwery automatyzacji są najwolniejsze i największe objętościowo spośród różnych dostępnych typów serwerów (MFC, ATL i Basectl). Jednak jedną z dużych zalet MFC jest ścisła integracja ze środowiskiem IDE w Yisual C++ oraz szybkość, z jaką można przygotować działający serwer. Jeśli tylko programista zna swoje narzędzie, może w kilka minut stworzyć serwer automatyzacji i zaimplementować jego metody i właściwości.
Rodzaje serwerów automatyzacji
Tworząc serwer automatyzacji, musisz zdecydować, jak go zaimplementujesz, w zależności od tego, do czego ma on być przeznaczony. Możesz tworzyć dwa podstawowe rodzaje serwerów: jako biblioteka DLL lub jako plik EXE. Jeśli serwer nie będzie musiał działać jako samodzielna aplikacja i ważna jest jego szybkość działania, powinieneś zaimplementować go jako bibliotekę DLL. Serwer w postaci biblioteki DLL zwykle jest nazywany serwerem in-process, gdyż zwykle działa w przestrzeni adresowej procesu wywołującego. Jeśli serwer ma działać jako samodzielna aplikacja, musisz zaimplementować go w postaci pliku EXE. Serwer automatyzacji w postaci programu EXE zwykle jest nazywany serwerem lokalnym lub serwerem out-of-process, gdyż działa we własnej przestrzeni adresowej, innej niż przestrzeń adresowa aplikacji klienta.
Serwery automatyzacji mają także pewien model działania, niezależny od tego, w jaki sposób zostały napisane. Mogą działać w przestrzeni adresowej procesu, lokalnie lub zdalnie, w zależności od tego, w jaki sposób zostaną wy wołane przez aplikację klienta.
Działanie w przestrzeni adresowej procesu
Serwer in-process działa w tej samej przestrzeni adresowej co przestrzeń aplikacji, która go stworzyła. Jako serwery in-process mogą działać tylko serwery w postaci bibliotek OLE, jednak nie jest to gwarantowane. Należy pamiętać o tym szczególnie, gdy korzysta się z zagnieżdżonych lub wspólnych obiektów. Jeśli stworzysz obiekt w przestrzeni procesu (na przykład procesu A) i następnie przekażesz obiekt innej aplikacji w innej przestrzeni procesu (proces B), dla aplikacji w procesie B serwer z aplikacji A będzie serwerem działającym lokalnie. Proces B będzie traktował go jak serwer działający lokalnie - bez względu na to, że ten serwer w przestrzeni procesu A jest serwerem w postaci biblioteki DLL. Ma to bardzo duże znaczenie, gdyż w większości przypadków serwery in-process są stosowane właśnie w celu poprawienia wydajności działania korzystających z nich aplikacji.
Działanie lokalne
Działanie lokalne występuje wtedy, gdy serwer automatyzacji działa w przestrzeni adresowej innej niż przestrzeń adresowa aplikacji klienta. Jak już wspominaliśmy, serwer w postaci biblioteki DLL może być dla klienta serwerem działającym lokalnie, w zależności od tego, jaka aplikacja go udostępniła. Główną wadą w przypadku serwerów działających lokalnie jest obniżona wydajność, gdyż przy wywoływaniu każdej z metod konieczne jest przekroczenie granicy pomiędzy procesami. Wymaga to stosunkowo dużej ilości kodu związanego z przygotowywaniem argumentów wywołań do "podróży" w jedną i w drugą stronę.
Działanie zdalne
Działanie zdalne występuje wtedy, gdy serwer działa na komputerze innym niż komputer, na którym działa aplikacja korzystająca z tego serwera. Podobnie jak w przypadku serwerów działających lokalnie jedną z wad jest niższa wydajność działania aplikacji.
Kontrolki ActiveX
Kontrolka ActiveX, mimo innej nazwy, jest wciąż tą samą kontrolką OCX lub OLE, którą zdążyłeś poznać i pokochać już kilka lat temu. W rzeczywistości, jedynym, co naprawdę się zmieniło, jest obniżenie wymagań co do warunków, jakie musi spełniać obiekt, aby kwalifikować się jako kontrolką.
Aby móc być uważanym za kontrolkę ActiveX, komponent musi być obiektem COM, implementować interfejs lUnknown oraz obsługiwać rejestrowanie i wyrejestrowywanie poprzez eksportowane funkcje DLLRegisterServer ( ) oraz DLLUnregisterServer () .
Jednak nawet jeśli komponent kwalifikuje się jako kontrolką ActiveX, lecz nie oferuje niczego więcej niż wymienione elementy, nie jest wtedy niczym więcej niż wypełniaczem miejsca na dysku. Jeśli komponent potrzebuje interfejsu użytkownika, trwałości danych, zdarzeń lub innych elementów powszechnie występujących w kontrolkach, musi implementować także inne kategorie interfejsów. Dokładne wymagania są zawarte w opubliko-
wanym przez Microsoft przewodniku OLE Control and Control Container Guidelines, Yersion 1.1. Wszelkie zalecenia dotyczące programowania komponentów ActiveX są dostępne również na stronach WWW Microsoftu oraz na płytce CD-ROM ActiveX SDK.
Jak już wiesz, do tworzenia kontrolek ActiveX możesz skorzystać z trzech narzędzi: MFC, ATL lub Basecti. Podobnie jak w przypadku serwerów automatyzacji ActiveX, każde z tych narzędzi posiada swoje wady i zalety. Jednak w przypadku kontrolek ActiveX masz do wyboru tylko jedną opcję tworzenia i wykorzystywania kontrolki, gdyż musi ona być biblioteką DLL działającą w przestrzeni adresowej procesu klienta. Nawet mimo iż rozszerzeniem pliku wykonywalnego kontrolki jest .OCX, nie zmienia to faktu, że kontrolka jest po prostu biblioteką DLL.
Narzędzia wymagane do tworzenia komponentów ActiveX
Zanim zabierzesz się do tworzenia komponentów ActiveX, musisz zgromadzić kilka potrzebnych narzędzi. Większość z nich jest instalowanych automatycznie wraz z pakietem Visual C++. Jednak wraz z rozwojem technologii ActiveX staje się dostępnych coraz więcej narzędzi. Korzystanie z jak największej ich liczby może znacznie wzbogacić Twoją wiedzę na temat działania komponentów oraz sposobów ich wykorzystywania.
Kompilator MIDL
Kompilator MIDL Microsoftu jest jednym ze standardowych komponentów środowiska Visual C++ (w zintegrowanym środowisku pojawił się w Visual C++ w wersji 5.0). Kompilator MIDL kompiluje definicje interfejsów COM (IDL) na kod języka C, który kompilator Visual C++ zamienia na moduły plików wykonywalnych.
Kompilator MIDL obsługuje również szeregowanie argumentów przed przekazaniem ich poprzez granice procesu. Poczynając od Yisual C++ 4.0, kompilator MIDL zaczął być rozprowadzany jako standardowy element pakietu. Kompilator MIDL jest dostępny również jako część Win32 SDK Microsoftu.
Mktyplib
Mktyplib jest kompilatorem biblioteki typów, przeznaczonym do kompilowania plików ODL. Mktyplib jest poprzednikiem kompilatora MIDL i daje ten sam rodzaj plików wynikowych. W miarę odejścia przez Microsoft od plików ODL do plików IDL zastosowanie kompilatora Mktyplib znacznie zmalało.
GUIDGEN
GUIDGEN jest narzędziem, którego możesz użyć do generowania identyfikatorów GUID (Globally Uniąue Identifier), stosowanych przy tworzeniu identyfikatorów interfejsów, identyfikatorów klas czy jakichkolwiek innych 128-bitowych identyfikatorów UUID (Universally Uniąue Identifier), na przykład takich jak interfejs RPC. Visual Studio instaluje program GUIDGEN tylko wtedy, gdy podczas instalacji Visual C++ włączysz opcję związaną z programowaniem komponentów OLE. Gdy uruchomisz program GUIDGEN stworzony przez niego identyfikator GUID jest umieszczany w schowku Windows. Po zakończeniu działania programu możesz wkleić otrzymany identyfikator bezpośrednio w miejsce kodu, w którym powinien się znaleźć.
RegEdit
RegEdit, program do edycji zawartości Rejestru, jest standardowym elementem zarówno systemu Windows 95, jak i Windows NT (choć w Windows NT zwykle lepiej jest korzystać z podobnego programu RegEdt32). Edytor Rejestru jest używany do przeglądania i modyfikacji ustawień systemu operacyjnego oraz zainstalowanych aplikacji. Możesz go użyć także do instalowania i rejestrowania własnych obiektów COM.
RegEdit jest potężnym narzędziem. Zawsze powinieneś korzystać z niego bardzo uważnie i tylko wtedy, gdy masz duże doświadczenie i dokładnie wiesz, co chcesz zrobić. Jeśli użyjesz programu niewłaściwie, możesz utracić jakieś dane lub nawet doprowadzić do tego, że system przestanie się w ogóle uruchamiać.
Serwer rejestracji
Serwer rejestracji jest aplikacją, której możesz użyć do rejestrowania ustawień obiektu COM w Rejestrze Windows bez potrzeby tworzenia oddzielnego pliku rejestru. Aplikacja ma nazwę regsrv32.exe i jest instalowana automatycznie, jeśli podczas instalacji Visual C++ włączysz opcję dla tworzenia komponentów OLE. Program jest instalowany automatycznie także podczas instalowania ActiveX SDK.
Ole2View
Ole2View jest programem wyświetlającym komponenty ActiveX według ich funkcji i kategorii. Program jest szczególnie przydatny przy testowaniu i wykorzystywaniu własnych komponentów. Za jego pomocą można sprawdzić czy komponent został poprawnie zarejestrowany oraz sprawdzić typ i rodzaj interfejsów, jakie udostępnia. Program pozwala nawet na tworzenie i dostęp do różnych komponentów i interfejsów w aplikacji, a obecnie został rozbudowany o konfigurowanie komponentów do wykorzystania DCOM. Gdy próbujesz stwierdzić, co jest przyczyną błędnego działania komponentu, Ole2View jest bezcennym narzędziem.
Dodawanie narzędzi
do środowiska programowego Visual C++
W celu zmaksymalizowania wydajności programowania narzędzia potrzebne dla tworzenia komponentów COM powinny być zintegrowane ze środowiskiem Visual C++. Każde z potrzebnych narzędzi powinno być dodane do menu ToolsVisual Studia. Poniższa sekcja pokazuje, jak to zrobić.
Dodawanie programu GUIDGEN do środowiska Visuał C++
Dodanie programu GUIDGEN do środowiska Visual C++ pozwala na generowanie identyfikatora UUID poprzez wybranie jednego polecenia w menu. GUIDGEN umieszcza wygenerowany identyfikator w schowku Windows, z którego możesz wkleić go w odpowiednie miejsce swojego kodu. Wykonaj więc poniższe kroki:
1. W menu Tools wybierz polecenie Customize. W oknie dialogowym Customize kliknij zakładkę Tools.
2. Na liście Menu Contents przejdź do ostatniej, pustej linii, zaznacz ją i wpisz
SGeneruj nowy UUID.
3. W polu edycji Command wpisz GUIDGEN . EXE.
4. Usuń wszelki tekst z pola edycji Arguments.
5. Usuń wszelki tekst z pola edycji Initial Directory.
6. Kliknij przycisk Close, dodając nowe polecenie do menu Tools.
Oprócz programu GUIDGEN możesz rozważyć dodanie do menu Tools także Edytora Rejestru, możliwości wyrejestrowania komponentu oraz innych narzędzi, jakie wydadzą Ci się przydatne. Pozostałe programy możesz dodać do menu w taki sam sposób jak w przypadku programu GUIDGEN.
Użycie MFC
do tworzenia prostej kontrolki ActiveX
Visual C++ i MFC są wydajnymi i elastycznymi narzędziami do tworzenia kontrolek ActiveX. Jeśli chodzi o szybkość tworzenia kontrolek i łatwość użycia, oba narzędzia nie mają sobie równych. W pozostałej części rozdziału stworzysz kontrolkę ActiveX ze wszystkimi podstawowymi elementami: właściwościami, metodami, trwałością danych oraz rysowaniem. Dodatkowo, poznasz pewne bardziej zaawansowane i mniej znane zagadnienia związane z tworzeniem kontrolek, takie jak metody z opcjonalnymi parametrami, asynchroniczne właściwości czy zoptymalizowane rysowanie.
Kontrolki MFC są dynamicznie łączone z bibliotekami MFC DLL. Dynamiczne łączenie może wpłynąć na wydłużenie czasu ładowania kontrolki, gdyż oprócz samej kontrolki, do pamięci są ładowane również biblioteki MFC DLL. Nawet jeśli MFC DLL jest już w pamięci, i tak potrzebne jest ustawienie adresów i dostosowanie pamięci. Tworząc
kontrolkę ActiveX, programista powinien usunąć tyle zależności od MFC, ile tylko się da. Służy to dwóm celom: po pierwsze, dzięki temu dużo łatwiejsze staje się ewentualne przeniesienie kontrolki do innej biblioteki, takiej jak ATL czy BaseCti, po drugie poprawia się ogólna wydajność kontrolki, gdyż unikamy w ten sposób narzutu wprowadzanego przez biblioteki MFC DLL. Ogólnie, komponenty, których wydajność działania jest jednym z ważniejszych zagadnień, nie powinny korzystać z bibliotek DLL żadnego rodzaju. Ostatni ą rzeczą, jakiej mógłbyś chcieć, jest zależność komponentu od innego pliku na dysku.
Tworzenie podstawowego projektu kontrolki
Aby stworzyć kontrolkę ActiveX MFC, powinieneś zacząć od wykorzystania kreatora AppWizard specjalnie opracowanego do tego celu. Aby skorzystać z jego usług, uruchom środowisko Visual C++ i w menu File wybierz polecenie New. Gdy pojawi się okno dialogowe New, kliknij zakładkę Projects. Na tej zakładce możesz określić kilka aspektów tworzenia kontrolki. Możesz na przykład zdefiniować typ tworzonej aplikacji, jej nazwę oraz folder, w którym AppWizard umieści pliki projektu. W tym przypadku zaznacz pozycję MFC ActiveX ControlWizard, w polu Project Name wpisz MFCControl, zaś jako folder przeznaczenia wpisz (w polu Location) C: \HELiON\Rozdz31\MFCCon-trol. Rysunek 31.1 przedstawia okno dialogowe New już po ustawieniu opcji projektu.
Kliknij przycisk OK uruchamiając kreator ControlWizard, z którego pomocą zdefiniujesz właściwości kontrolki. W pierwszym kroku kreatora określ ilość kontrolek, jakie znajdą się w projekcie. Za pomocą przycisków oznaczonych strzałkami wskaż, że chcesz stworzyć trzy kontrolki, lecz nie zmieniaj pozostałych opcji w tym oknie dialogowym. Kliknij przycisk Next.
Jeśli musisz stworzyć więcej niż jedną kontrolkę i będziesz często korzystał z tych kontrolek łącznie w tej samej aplikacji, powinieneś zawrzeć te kontrolki w pojedynczym pliku .OCX, a to z powodu czasu ładowania kontrolek. Pojedyncza kontrolka OCX zawsze ładuje się szybciej niż dwie kontrolki o podobnych rozmiarach. Oprócz tego możesz skorzystać w kontrolkach ze wspólnych fragmentów kodu, dzięki czemu będą one mniejsze i będą ładować się jeszcze szybciej, z pewnością szybciej niż dwie lub więcej oddzielnych kontrolek.
W drugim kroku kreatora możesz zmienić nazwy kontrolek, zdefiniować niektóre z ich właściwości oraz określić, czy kontrolka ma być podklasą istniejącej kontrolki okna.
Ponieważ poinformowałeś ControlWizarda, że chcesz, aby zostały stworzone trzy kontrolki, kreator tworzy trzy kontrolki o domyślnych nazwach (MFCControll, MFCControl2 oraz MFCControiS). Dla potrzeb projektu zamień nazwy kolejnych kontrolek na MFC-controiwin, MFCControlNoWin oraz MFCControlSubwin. Aby zmienić nazwę klasy, zaznacz jąna rozwijanej liście, po czym kliknij przycisk Edit Names. W polu Short Name wpisz nową nazwę kontrolki. Nazwy innych plików i klas zmienią się automatycznie, odzwierciedlając zmiany, jakie wprowadziłeś w pliku Short Name. Kliknij przycisk OK, zamykając okno dialogowe Edit Names. Rysunek 31.2 przedstawia to okno dialogowe po zamianie kontrolki MFCControl3 na kontrolkę MFCControlSubwin.
Przycisk Advanced służy do wyświetlenia okna dialogowego umożliwiającego wybór rozszerzeń ActiveX, jakie chcesz zastosować dla kontrolki. Sprawdź, czy na liście jest wybrana klasa MFCControlWin, po czym kliknij przycisk Advanced. Wszystkie zaawansowane właściwości w oknie dialogowym są wynikiem specyfikacji OC 96. Warto się im więc przyjrzeć bliżej. Listę zaawansowanych właściwości kontrolek ActiveX zawiera tabela 31.1.
W oknie dialogowym Advanced ActiveX Features włącz tylko poniższe opcje: Unclipped device context (nie obcinany kontekst urządzenia) Flicker-free activation (aktywacja bez migotania)
Mouse pointer notifications when inactive (wysyłanie komunikatów myszy w stanie nieaktywnym)
Optimized drawing code (zoptymalizowany kod rysunkowy)
Loads properties asynchronously (asynchroniczne ładowanie właściwości)
Kliknij przycisk OK zatwierdzając wybór i zamykając okno.
Wybierz kontrolkę MFCControlNoWin i ponownie kliknij przycisk Advanced. Tę kontrolkę zaimplementujemy jako kontrolkę bezokienkową w celu sprawdzenia różnicy pomiędzy implementacją kontrolek okienkowych a bezokienkowych w MFC. W oknie dialogowym Advanced ActiveX Features włącz tylko poniższe opcje:
Windowless activation (aktywacja bezokienkową)
Mouse pointer notifications when inactive (wysyłanie komunikatów myszy w stanie nieaktywnym)
Tabela 31.1. Zaawansowane właściwości kontrolek ActiveX
Właściwość ActiveX
Opis
Windowless activation (aktywacja bezokienkowa)
Umożliwia programowi wywołującemu stworzenie egzemplarza kontrolki bez konieczności tworzenia okna dla samej kontrolki. Innymi słowy, za stworzenie okna dla kontrolki odpowiada aplikacja kontenera
Unclipped device context
(nie obcinany kontekst urządzenia)
Używane dla kontrolek, o których wiadomo, że nie będą rysowały poza swoim obszarem roboczym. Powoduje wyłączenie testowania przez MFC regionu obcinania. Nie możesz użyć tej opcji dla kontrolki bezokienkowej.
Flicker-free activation (aktywacja bez migotania)
Używane dla kontrolek, które wyglądają tak samo bez względu na to, czy są aktywne czy nieaktywne. Wybranie tej opcji eliminuje w pewnym stopniu migotanie, jakie może wystąpić przy zmianie stanu kontrolki z nieaktywnego do aktywnego. Nie możesz użyć tej opcji dla kontrolki bezokienkowej.
Mouse pointer notifications when inactive (wysyłanie komunikatów myszy w stanie nieaktywnym)
Pozwala, by kontrolka otrzymywała komunikaty myszy nawet wtedy, gdy jest nieaktywna.
Optimized drawing code (zoptymalizowany kod rysunkowy)
Umożliwia kontrolce odrysowywanie swojego obszaru roboczego w zoptymalizowany sposób, o ile kontener obsługuje zoptymalizowane rysowanie. W przeciwnym razie kontrolka musi być odświeżana z użyciem standardowych technik rysowania.
Loads properties asynchronously (asynchroniczne ładowanie właściwości
Właściwości asynchroniczne to właściwości ładowane \v tle i wiążące się z przesyłaniem dużych ilości danych. Właściwości są ładowane w tle w celu zabezpieczenia kontenera i kontrolki przed zbyt długą nieaktywnością w oczekiwaniu na załadowanie danvch.
.
Optimized drawing code (zoptymalizowany kod rysunkowy)
Loads properties asynchronously (asynchroniczne ładowanie właściwości)
Kliknij przycisk OK zatwierdzając wybór i zamykając okno.
Dla trzeciej i ostatniej kontrolki zastosujemy subclassing istniejącej kontrolki okna. Wybierz z listy kontrolkę MFCControlSubWin i z rozwijanej listy w dolnej części okna dialogowego wybierz pozycję BUTTON. Dla tej kontrolki nie definiuj żadnych zaawansowanych właściwości ActiveX.
Drugie okno dialogowe kreatora ActiveX ControlWizard umożliwia określenie także innych właściwości tworzonych kontrolek. W naszym projekcie pozostaw je jednak w oryginalnym, domyślnym stanie. Aby dowiedzieć się czegoś więcej na ich temat możesz zajrzeć do dokumentacjiVisual C++.
Kliknij przycisk Finish w oknie dialogowym kreatora, kończąc proces określania właściwości kontrolek. Możesz sprawdzić dokonany wybór w oknie dialogowym New Project Information, pokazanym na rysunku 31.3.
Po stwierdzeniu, czy poprawnie dokonałeś wyboru, kliknij przycisk OK w celu wygenerowania plików źródłowych i projektu kontrolki MFC. Pierwszym kroku w dowolnym projekcie kontrolki jest zapewnienie, że kontrolka będzie zarejestrowana. Bez zarejestrowania nie będzie mogła z niej skorzystać żadna aplikacja.
Rejestracja kontrolki
Obsługą rejestrowania i wyrejestrowywania kontrolki, zajęło się MFC, nie musisz więc już nic w tym kierunku robić. Listing 31.1 zawiera funkcję UpdateRegistry (i, stworzoną przez AppWizarda dla kontrolki CMFCControlWinCtrl. Jak można się domyślać, AppWizard stworzył funkcję rejestrującą dla każdej z kontrolek.
Listing 31.1. Funkcja UpdateRegistry() automatycznie sf\vorzona dla kontrolki
////////////////////////////////////////////////////////
// CMFCControlWinCtrl::CMFCControlWinCtrlFactory::UpdateRegistry
// - Adds or removes system registry entries for
// CMFCControlWinCtrl
BOOL CMFCControlWinCtrl::CMFCControlWinCtrlFactory::
UpdateRegistry(BOOL bRegister) {
// TODO: Verify that your control follows apartment-model
// threading rules.
// Refer to MFC TechNote 64 for morę information.
// If your control does not conform to the apartment-model
// rules, then you must modify the code below, changing the
// 6th parameter from afxRegApartmentThreading to 0.
if (bRegister)
return Afx01eRegisterControlClass( AfxGetInstanceHandle(), m_clsid, m_lpszProgID, IDS_MFCCONTROLWIN, IDB MFCCONTROLWIN,
afxRegApartmentThreading, _dwMFCControlWin01eMisc,
_wVerMajor, _wVerMinor); else
return Afx01eUnregisterClass(m clsid, m IpszProgID);
}
Szczególnie interesujący w tym kodzie jest komentarz umieszczony w nim przez Microsoft, dotyczący apartamentowego modelu wątków oraz tego, w jaki sposób powinieneś zarejestrować swoją kontrolkę jeśli nie spełnia ona reguł narzucanych przez ten model. Poza bardzo specyficznymi przypadkami, jeśli chodzi o rejestrację kontrolki, nie musisz dokonywać żadnych modyfikacji w kodzie swojej aplikacji i tej funkcji. W rzeczywistości, możesz teraz nawet skompilować i zbudować stworzoną kontrolkę, jednak nie będziesz miał z niej pożytku, gdyż nie zawiera metod, właściwości ani zdarzeń, stanowiących podstawę wszystkich kontrolek ActiveX.
Tworzenie metod
Gdy już stworzysz projekt kontrolki ActiveX, możesz zacząć dodawać do kontrolki metody, stanowiące jeden z podstawowych elementów kontrolki. Metoda jest funkcją udostępnianą przez kontrolkę, którą możesz wywoływać tak jak publicznie udostępnioną funkcję składową klasy.
Dla naszej przykładowej kontrolki stworzymy metodę o nazwie CaptionMethod (). Ta metoda będzie przyjmować dwa parametry, z których drugi będzie opcjonalny. Pierwszym łańcuchem będzie łańcuch, który kontrolka będzie wyświetlać w swoim obszarze roboczym, zaś drugi, opcjonalny argument będzie określał sposób wyrównania tekstu w tym obszarze, do lewej lub prawej krawędzi lub wyśrodkowanie go w centrum obszaru roboczego. Aby stworzyć swoją pierwszą metodę, w menu View wybierz polecenie ClassWizard (lub wciśnij kombinację Ctrl+W). Visual C++ wyświetli okno dialogowe kreatora ClassWizard; kliknij w nim zakładkę Automation.
Z rozwijanej listy Class Name wybierz klasę CMFCControlWinCtrl, po czym kliknij przycisk Add Method w celu stworzenia nowej metody. W pole edycji rozwijanej listy Exter-nal Name wpisz nazwę metody, CaptionMethod. Jako typ zwracany przez metodę (rozwijana lista Return Type) wybierz long. Klikając na odpowiednią kolumnę na liście Parameter List dodaj dwa parametry. Pierwszym parametrem będzie IpctstrCaption typu LPCTSTR, zaś drugim, opcjonalnym, będzie varAlignment typu VARIANT. Kliknij przycisk OK dodając metody do klasy. Następnie kliknij przycisk OK, zamykając okno dialogowe ClassWizarda.
Wszystkie opcjonalne parametry muszą być typuVARIANT i muszą znajdować się na końcu listy parametrów. Parametry opcjonalne w żaden sposób nie są obsługiwane przez OLE. Sprawdzenie, czy opcjonalny parametr zawiera przekazane dane, leży wyłącznie w gestii serwera, który może podjąć jedno z poniższych działań:
Użyć danych przekazanych metodzie i jeśli to możliwe, przekonwertować je do użytecznego typu.
W przypadku błędnych danych zignorować parametr i użyć ewentualnej domyślnej wartości.
X Jeśli nie zostanie spełniony któryś z powyższych warunków, poinformować użytkownika o błędzie.
Aby wspomóc implementację metody CaptionMethodO , musisz dodać wyliczenie wszystkich poprawnych argumentów wyrównania oraz dodać do definicji klasy dwie zmienne składowe. Odwołanie do wyliczenia argumentów wyrównań musisz dodać do pliku nagłówkowego klasy, MFCCONTROLWINCTL.H:
// MFCControlWinCtl.h : Declaration of the CMFCControlWinCtrl // ActiveX Control class.
#tinclude "alignmentenums . h"
/////////////////////////////
// CMFCControlWinCtrl : See MFCControlWinCtl . cpp for // implementation.
class CMFCControlWinCtrl : public COleControl (
// Tutaj inny kod protected :
// zmienna zawierająca napis w oknie
CString m_cstrCaption;
// zmienna zawierająca sposób wyrównania
long m_lAlignment;
};
Klasa kontrolki korzysta z dwóch zmiennych składowych, m_cstrCaption oraz m_lAiignment, w celu przechowania napisu wyświetlanego w oknie oraz ustawienia wyrównania. Zwróć uwagę na typ użyty dla zmiennej m_lAlignment. Zmienna jest zadeklarowana jako typ long, a niejako typ wyliczeniowy z powodu ograniczeń na rodzaje danych nakładanych przez automatyzację ActiveX. Pamiętaj, że w metodach i właściwościach może być użyty jedynie taki typ danych, jaki można przekazać w typie VARIANT. Deklarując zmienną m_lAlignment jako, long, nie musimy już jej jawnie konwertować typ rzutując do typu wyliczeniowego przy pobieraniu parametruVARIANT metody captionMethod (). Z drugiej strony, rzutowanie argumentu do typu wyliczeniowego jest trywialne i jeśli chcesz to zrobić, nie ma ku temu żadnych przeszkód.
Definicję typu wyliczeniowego dodamy jako plik nagłówkowy, dzięki czemu będziesz mógł odwołać się do tego typu wyliczeniowego także z innych plików, co będzie konieczne, gdy przejdziemy do dalszego tworzenia kodu kontrolki. Definicję typu wyliczeniowego powinieneś umieścić w pliku nagłówkowym Alignmentenums.h:
#ifndef
_ALIGNMENTENUMS_H #define _ALIGNMENTENUMS_ H
// wyliczenie rodzaju wyrównania
typedef enum tagAlignmentEnum {
EALIGN_LEFT = O;
EALIGN_CENTER = l ;
EALIGN_RIGHT = 2 ; } EALIGNMENT;
ttendif // #ifdef _ALIGNMENTENUMS_H
Gdy skończysz tworzyć plik nagłówkowy, otwórz plik MFCControlWinCtl.cpp \ w konstruktorze klasy zainicjuj zmienną składową m_lAlignment domyślną wartością EALIGN_
LEFT:
///////
// CMFCControlWinCtrl::CMFCControlWinCtrl - Constructor
CMFCControlWinCtrl::CMFCControlWinCtrl() {
InitializelIDs(&IID_DMFCControlWin, &IID_DMFCControlWinEvents);
m_lReadyState = READYSTATE_LOADING;
// TODO: Cali InternalSetReadyState when the readystate // changes.
// Domyślnie ustaw wyrównanie do lewej krawędzi m_lAlignment = EALIGN_LEFT;
Metoda CaptionMethod () zawiera cały kod związany z ustawieniem tytułu i rodzaju wyrównania oraz robi parę innych rzeczy, które musisz wziąć pod uwagę. Funkcja CaptionMethod () została przedstawiona na listingu 31.2.
Listing 31.2. Funkcja CaptionMethodp__________________________________
iong CMFCControlWinCtrl::CaptionMethod(LPCTSTR IpctstrCaption,
const YARIANT FAR& varAlignment) {
// inicjujemy zwracaną wartość wartością błędu
Iong IResult = FALSE;
// jeśli wariant jest typu Iong, po prostu użyjemy wartości if(VT_I4 == varAlignment.vt){
// ustawiamy zwracana, wartość
IResult = TRUE;
}
// jeśli użytkownik nie dostarczył parametru wyrównania, // użyjemy tego co już mamy
else if(VT_ERROR == varAlignment.vt ||VT_EMPTY==
varAlignment.vt)
// ustawiamy zwracaną wartość
IResult = TRUE;
}
else{
// pobieramy wariant w celu dokonania konwersji VARIANT varConvertedValue;
// inicjalizujemy wariant
:rVariantlnit(&varConvertedValue);
// próbujemy zamienić typ danych na coś użytecznego -// możesz także użyć VariantChangeTypeEx() if(S_OK == ::YariantChangeType(&varConvertedValue, (YARIANT*) &varAlignment, O, VT_I4))
{
// przypisanie wartości naszej zmiennej składowej switch(varConvertedValue.1Val){ case EALIGN_CENTER:
m_lAlignment = EALIGN_CENTER;
break; case EALIGN_RIGHT:
m_lAlignment = EALIGN_RIGHT;
break; case EALIGN_LEFT:
m_lAlignment = EALIGN_LEFT;
break; }
// ustawiamy zwracaną wartość IResult = TRUE;
else {
/*
W tym punkcie możesz albo zgłosić problem związany z konwersja danych lub zmienić typ zwracany przez metodę i zwrócić wartość HRESULT z wywołania YariantChangeType ( ) . */
}
}
// jeśli wartość wyrównania jest niewłaściwa if (m_lAlignment < EALIGN_LEFT ||
m_lAlignment > EALIGN_RIGHT)
// ustaw domyślną wartość
m_lAlignment = EALIGN_LEFT;
// jeśli wszystko jest w porządku
if(TRUE == IResult)
{
// jeśli mamy łańcuch
if (IpctstrCaption != NULL)
// przypisujemy łańcuch naszej zmiennej składowej m_cstrCaption = IpctstrCaption;
// czy otrzymaliśmy błędne dane? if (m_lAlignment < EALIGN_LEFT ||
m_lAlignment > EALIGN_RIGHT)
// tak, ustaw domyślną wartość
m lAlignment = EALIGN LEFT;
// wymuszamy odmalowanie kontrolki this->InvalidateControl() ;
}
// zwracamy wynik wywołania funkcji return IResult;
Najpierw metoda próbuje obsłużyć domyślny parametr sprawdzając, czy wariant zawiera rzeczywiście to, czego chcemy, VT_I4, co odpowiada typowi long. Jeśli wariant nie zawiera typu long, metoda sprawdza, czy w ogóle zostały przekazane jakieś dane, sprawdzając typy VT_ERROR i VT_EMPTY. Sprawdzenie danych jest konieczne, ponieważ ten parametr jest opcjonalny. Parametr VARIANT może zawierać dane, ale nie musi, trzeba więc nie tylko sprawdzać poprawność danych, ale także ich obecność. Jeśli nie zostaną dostarczone żadne dane, metoda skorzysta z wartości już wcześniej umieszczonej w zmiennej składowej m_iAiignment.
Pewne sterowniki automatyzacji, takie jak Yisual Basic, nie wymagają, by przy wywoływaniu metody był dostarczany którykolwiek z opcjonalnych parametrów. Na przykład, z Visual Basica metodę captionMethod () możesz wywołać następująco:
MyObject.CaptionMethod "Hello"
lub ewentualnie tak:
MyObject.CaptionMethod "Hello", True
Yisual Basic automatycznie przekazuje w zmiennej VARIANT typ VT_ERROR dla każdego z parametrów, które zostały pominięte w wywołaniu metody. Dla sterowników automatyzacji wywołujących standardową metodę OLE invoke (), takich jak C++, wywołujący sterownik musi zdefiniować wszystkie parametry, nawet jeśli kontrolka lub kontener z nich nie korzystają. W tym konkretnym przypadku, gdy wywołujemy metodę, mamy do wyboru ustawienie typu wariantu na VT_ERROR lub VT_EMPTY.
Podczas przetwarzania opcjonalnych parametrów oparcie się na wbudowanych w OLE procedurach konwersji typów wariantów w poszukiwaniu określonego typu danych gwarantuje, że kontrolka będzie mogła obsłużyć każdą przekazaną j ej daną.
Jeśli wariant zawiera inny poprawny typ danych inny niż VT_I4, metoda próbuje skon-wertować go na typ VT_I4. Dzięki temu możemy obsłużyć przypadki, gdy użytkownik przekazuje poprawną lecz innego typu wartość, na przykład short lub łańcuch.
Zwróć uwagę na użycie funkcji Variantinit (). Bardzo ważne jest, by Twoje programy inicjowały przed użyciem wszystkie zmienne typuVARIANT. Dzięki temu będziesz miał pewność, że te zmienne nie będą zawierały błędnych informacji o typie ani błędnych wartości. Wywołanie Variantinit () w celu zainicjalizowania zmiennych VARIANT jest zgodne z obowiązującą w C++ tendencją do inicjalizowania zmiennych składowych w celu zapewnienia, że nie będą one zawierać błędnych informacji.
Jeśli wymagania co do kontrolki określają użycie tylko specyficznych typów danych, możesz także dodać kod (komunikaty błędów, wyjątki itd.) reagujący na fakt nieotrzymania przez kontrolkę odpowiedniego typu danych. W przypadku naszej metody caption-Method (), jeśli funkcja YariantchangeType (} nie będzie w stanie dokonać konwersji danych, metoda kończy działanie, zwracając wartość FALSE. Zwrócenie wartości FALSE informuje funkcję wywołującą, że wykonanie metody się nie powiodło. Także w tym przypadku możesz zastosować dodatkowy kod obsługi błędów w celu przekazania użytkownikowi jakichś dodatkowych informacji o błędzie.
Przed przejściem dalej, metoda sprawdza, czy zmienna składowa m_lAlign zawiera poprawne dane. Jeśli otrzymane dane były poprawne lub udało sieje przekonwertować na poprawną wartość, zmiennej iResult przypisujemy wartość TRUE. Kod programu sprawdza wartość tej zmiennej i jeśli ma wartość TRUE, łańcuch znaków i ustawienie wyrównania są umieszczane w zmiennych składowych klasy, kontrolka jest unieważniana w celu odrysowania się, po czym następuje powrót do programu wywołującego.
Aby uzupełnić implementację funkcji CaptionMethodO, musisz także zmodyfikować plik ODL. Gdy metody, zdarzenia lub właściwości są dodawane do klasy za pomocą ClassWizarda, do pliku ODL automatycznie dopisywane są nowe pozycje. Plik ODL jest kompilowany do biblioteki typów, stanowiącej po prostu opis komponentu i jego interfejsów, które mogą być użyte przez inne aplikacje przy odwoływaniu się do komponentu. Musisz dodać słowo kluczowe [optional] do ostatniego parametru metody Caption-Method () w pliku ODL. W ten sposób informujesz aplikację kontenera, że ostatni parametr jest opcjonalny i powinien zostać odpowiednio potraktowany. Poniższy kod pokazuje, w jaki sposób wprowadzić zmianę słowa kluczowego pliku MFCCONTROLODL:
// ...
methods :
// NOTĘ - ClassWizard will maintain method Information here.
// Use extreme caution when editing this section.
//{ {AFX_ODL_METHOD(CMFCControlWinCtrl)
[id(l)] long CaptionMethod (BSTR IpctstrCaption,
[optional] VARIANT varAlignment ) ; //} }AFX_ODL_METHOD
// ...
Musisz bardzo uważać modyfikując ręcznie plik ODL. Plik ODL musi dokładnie odpowiadać implementacji. Jeśli go zmodyfikujesz w błędny sposób, mogą wystąpić różne problemy w implementacji i działaniu komponentu. ClassWizard automatycznie aktualizuje plik ODL w momentach dodawania i usuwania z kontrolki metod, właściwości oraz zdarzeń. Dodanie lub usunięcie informacji z pliku ODL może uniemożliwić kreatorowi poprawne zarządzanie plikiem. Słowa kluczowe takie jak [optional] nie wpływają na kreatora i jego możliwość automatycznego zarządzania plikiem podczas dokonywania zmian w klasie.
Właściwości
Właściwości (ang. properties) można podzielić na właściwości definiowane przez użytkownika (user defmed), magazynowe (stock) lub otaczające (ambient). Właściwości definiowane przez użytkownika są właściwościami specyficznymi dla danej implementacji i mają znaczenie jedynie dla komponentu, który je zawiera. Właściwości definiowane przez użytkownika można podzielić dalej na właściwości definiowane jedynie poprzez swój typ danych (zwykłe właściwości) oraz te z dodatkowymi parametrami (właściwości parametryczne). Właściwości magazynowe to zestaw podstawowych właściwości, które zostały już zdefiniowane w OLE. Domyślnie, właściwości magazynowe nie są implementowane w kontrolce, lecz wymagają zaimplementowania przez programistę. Są pre-defmiowane tylko w celu zapewnienia pewnego poziomu jednorodności pomiędzy różnymi implementacjami kontrolek. Z drugiej strony, właściwości otaczające są właściwościami udostępnianymi przez kontener w celu dostarczenia domyślnych wartości korzystającym z nich kontrolkom. W pozostałej części rozdziału stworzymy trzy rodzaje kontrolek: zwykłą, parametryczną oraz magazynową. Oprócz tego nauczysz się korzystać z właściwości otaczających.
Tworzenie zwykłych właściwości definiowanych przez użytkownika
Zwykła właściwość to właściwość zadeklarowana jako pojedynczy typ, na przykład long lub BSTR, nie posiadająca parametrów. Zmienną składową Alignment naszej kontrolki udostępnimy właśnie jako właściwość. Właściwości są dodawane do kontrolki podobnie jak metody. Aby dodać właściwość, przywołaj ClassWizarda, wybierz klasę CMFCControlWinCtrl, przejdź na zakładkę Automation, po czym kliknij przycisk Add Pro-perty. W oknie dialogowym Add Property w polu External name (nazwa zewnętrzna) wpisz Alignment, z rozwijanej listy Type wybierz typ long, zaś jako rodzaj implementacji wybierz opcję Get/Set methods. Kliknij przycisk OK, zatwierdzając zmiany i zamykając dialog.
Następnie dwukrotnie kliknij pozycję Alignment na rozwijanej liście Bcternal names w celu przejścia do pliku źródłowego i dodania kodu do metod Get () i Set (). Jak widać, kod wewnątrz funkcji GetAlignment (} wykorzystuje dodaną wcześniej zmienną składową m_lAlignment. Funkcję GetAlignment () powinieneś zaimplementować następująco:
long CMFCControlWinCtrl::GetAlignment() {
// zwracamy bieżące ustawienie
return m lAlignment;
}
Funkcja GetAlignment () jest bardzo prosta, gdyż jedynie zwraca wartość zawartą w zmiennej składowej m_lAlignment. Funkcja SetAlignment () działa podobnie, z tym że pobiera otrzymaną wartość long i zapisuje ją do zmiennej składowej m_lAlignment. Funkcję SetAlignment () powinieneś zaimplementować następująco:
void CMFCControlWinCtrl::SetAlignment(long nNewValue)
{
// sprawdzamy, czy wartość mieści się w dozwolonym zakresie if(nNewValue >= EALIGN LEFT && nNewYalue <= EALIGN_RIGHT)
{
// ustawiamy nową wartość właściwości m_lAlignment = nNewValue;
// informujemy kontrolkę o modyfikacji właściwości this->SetModifiedFlag();
// odświeżamy przeglądarkę właściwości this->BoundPropertyChanged(dispidAlignment);
// odrysowujemy kontrolkę this->InvalidateControl() ;
}
}
Jak widać, funkcja SetAlignment () robi nieco więcej niż robiła funkcja GetAlignment (). Po pierwsze, sprawdza, czy wartość jest w dozwolonym zakresie i jeśli tak, przechowuje jaw zmiennej składowej m_lAlignment. Następnie wywołuje funkcje SetModifiedFlag () oraz BoundPropertyChangedO w celu poinformowania odpowiednio kontrolki oraz kontenera o zmianie wartości właściwości. Funkcja BoundPropertyChan-ged () w efekcie wymusza na kontenerze odświeżenie jego przeglądarki właściwości w celu odzwierciedlenia nowej wartości. Poinformowanie kontenera jest bardzo ważne, ponieważ wartość właściwości może się zmienić bez wiedzy kontenera, czy to poprzez arkusz właściwości kontrolki czy też, w pewnych przypadkach, w odpowiedzi na inne wywołanie funkcji. Nie dodawaliśmy wywołania funkcji BoundPropertyChanged() do metody cąptionMethod (), gdyż mimo że to wywołanie w niczym by nie zaszkodziło, również niczemu by nie służyło, gdyż kontener nie może wywołać metody CąptionMethod() w trybie projektowania kontrolki, a taki jest właśnie cel istnienia funkcji BoundProperty-Changed() .
Na koniec, metoda SetAlignment () unieważnia obszar interfejs użytkownika kontrolki, wymuszając odświeżenie jej z użyciem nowych informacji.
Nawet mimo że właściwość wydaje się być zwykłą zmienną składową obiektu, w rzeczywistości stanowi parę powiązanych ze sobą funkcji używanych do operowania na pojedynczym fragmencie danych. Pozycje ODL dla właściwości są różne w zależności od jej implementacji.
Dla interfejsów przejętych od klasy dispinterface wystarczy posiadanie pojedynczej pozycji w sekcji klasy związanej z właściwościami. Interfejsy wyprowadzone z IDispatch definiują dwie osobne funkcje: jedną zadeklarowaną jako propertyget, zaś drugą jako propertyput, obie korzystające z tego samego dispid. Parametryzowane właściwości zawsze są definiowane z użyciem drugiej metody, ponieważ nie ma możliwości definiowania dodatkowych parametrów bez deklaracji metody.
Słowo kluczowe dispinterface jest specyficzną konwencją definiowania interfejsów opartych na IDispatch w sposób bardziej zbliżony do C++, w przeciwieństwie do standardowego stylu IDispatch, który został wprowadzony jako pojedyncza pozycja, a nie dwie pozycje metod. Więcej informacji na temat użycia dispinterface i innych słów
kluczowych ODL znajdziesz w dokumentacji dostarczanej przez Microsoft wraz z kompilatorem Visual C++.
MFC i ClassWizard udostępniają kolejnej opcji deklarowania właściwości - tzn. poprzez zmienną składową. MFC dodaje zmienną składową odpowiedniego typu do deklaracji klasy i dostarcza funkcji informującej o zmianie wartości tej zmiennej. Funkcja obsługi komunikatu powiadamiającego nosi nazwę OnNazwa_zmiennejChanged (} i jest dodawana do pliku źródłowego kontrolki. Jeśli wybrałbyś tę metodę dla właściwości Alignment, funkcja nosiłaby nazwę OnAlignmentchanged() i byłaby zaimplementowana w pliku MFCControlWinCtl.cpp. Użycie tej metody wymaga napisania mniejszej ilości kodu, jednak wiąże się także z mniejszą elastycznością, gdyż MFC samo zajmuje się zarządzaniem całym kodem funkcji Get/Set dla właściwości. Spróbuj poeksperymentować z oboma rodzajami tworzenia właściwości, aby przekonać się, który z nich bardziej Ci odpowiada - i pamiętaj, im bardziej twoja kontrolka będzie zależna od MFC, tym trudniej będzie ją ewentualnie przekonwertować do innej biblioteki, na przykład ATL.
Tworzenie parametrycznych właściwości definiowanych przez użytkownika
Właściwość parametryczna to właściwość, która oprócz bycia zmienną określonego typu (na przykład BSTR lub long) akceptuje jeden lub więcej dodatkowych parametrów dokładniej definiujących dane we właściwości. Właściwości parametryczne mogą być użyteczne w przypadku właściwości reprezentujących kolekcje danych, w których dodatkowy parametr stanowi indeks do obiektu w kolekcji. W naszej kontrolce jako właściwość parametryczną udostępnimy zmienną składowąm_cstrCaption kontrolki.
W oknie dialogowym Add Property ClassWizarda zdefiniuj nazwę zewnętrzną (External Name) jako CaptionProp, typ właściwości jako BSTR oraz wybierz implementację jako parę metod Get/Set. Oprócz tego dodaj parametr o nazwie varAlignment o typie VARIANT. Kliknij przycisk OK, dodając właściwość do klasy. Następnie zamknij ClassWizarda, aby móc dopisać do właściwości nieco kodu. Implementacja właściwości Caption-Prop składa się z dwóch metod: GetCaptionProp () oraz SetCaptionProp ().
GetCaptionProp () jest metodą, którą kontrolka wywołuje w celu przekazania danych z właściwości do wywołującego kontenera. W swojej implementacji powinieneś zignorować parametr varAiignment, gdyż w tym kontekście jest bezużyteczny. Zamiast tego powinieneś po prostu zwrócić napis z kontrolki. Metoda GetCaptionProp () powinna zostać zdefiniowana w pliku MFCCONTROLWINCTL.CPP następująco:
BSTR CMFCControlWinCtrl::
GetCaptionProp(const VARIANT FAR& varAlignment)
{
// zwracamy tytuł jako BSTR
return m_cstrCaption.AllocSysString();
}
Metoda GetCaptionProp () używa funkcji CString: :AllocSysString() do stworzenia łańcucha BSTR, będącego w zasadzie łańcuchem UNICODE, jaki zwraca funkcja. Funkcja
SetCaptionPropO odwołuje się po prostu do metody CaptionMethod(), gdyż ta funkcja zawiera już cały kod potrzebny do implementacji metody SetCaptionProp ():
voicł CMFCControlWinCtrl: : SetCaptionProp
(const YARIANT FAR& varAlignment, LPCTSTR IpszNewYalue) {
// użyjemy implementacji CaptionMethod() if(TRUE - CaptionMethod(IpszNewYalue, varAlignment)) // poinformujmy kontener o zmianie właściwości
SetModifiedFlag();
}
Ostatnim szczegółem jest zmodyfikowanie właściwości w pliku ODL. Mamy zamiar korzystać z parametru VARIANT varAiignment jako z opcjonalnego parametru implementacji metod GetCaptionProp () oraz SetCaptionPropO. Mimo że w metodzie SetCaptionProp ( ) parametr varAlignment nie znajduje się na końcu listy argumentów, i tak możemy uczynić go opcjonalnym, gdyż zmienna BSTR ipszNewYalue jest w rzeczywistości typem danych pary funkcji właściwości i jako taka nie wpływa na reguły definiowania parametrów. Plik MFCCONTROL.ODL powinieneś zmodyfikować tak jak w przykładzie poniżej:
// ...
methods :
// NOTĘ - ClassWizard will maintain method information here.
// Use extreme caution when editing this section.
// { {AFX_ODL_METHOD (CMFCControlWinCtrl)
[id(2)] long CaptionMethod (BSTR IpctstrCaption,
[optional] VARIANT varAlignment ) ; [id(3), propget] BSTR CaptionProp ( [optional] VARIANT
varAlignment ) ; [id(3), propput] void CaptionProp ( [optional] VARIANT
varAlignment, BSTR IpszNewYalue) ; //} }AFX ODL METHOD
// ...
Z powodu zawartych parametrów właściwości parametryczne są definiowane w sekcji metod deklaracji dispinterface obiektu. W przypadku interfejsów opartych na IDispatch wszystkie właściwości są implementowane z użyciem metod Get/Set, bez względu na to, czy są parametryczne czy nie. Jedynie dispinterface zawiera oddzielne sekcje dla właściwości i dla metod w pliku ODL. I tylko dispinterface umożliwia deklarowanie właściwości jako pojedynczych pozycji w pliku ODL.
Tworzenie właściwości magazynowych
Właściwość magazynowa jest właściwością zrozumiałą zarówno dla kontrolki, jak i jej kontenera i ma dla obu predefiniowane znaczenie. Właściwości magazynowe mają na celu zapewnienie pewnej podstawowej zgodności pomiędzy wszystkimi implementującymi je kontrolkami i kontenerami. Właściwości magazynowe nie wymagają tworzenia z Twojej strony dużych ilości kodu. Zamiast tego podłączasz po prostu właściwość magazynową
pod istniejącą właściwość. Dodawanie właściwości magazynowych wygląda podobnie jak dodawanie właściwości definiowanych przez użytkownika. Otwórz ClassWizarda i przejdź na zakładkę Automation. Na tej zakładce wybierz klasę CMFCControlWinCtrl i kliknij przycisk Add Property. Jednak tym razem zamiast wpisywać zewnętrzną nazwę, wybierz ją z rozwijanej listy nazw (External name) dostarczonej przez ClassWizarda. Naszą właściwością magazynową będzie właściwość Backcolor. Wybierz opcję Stock w grupie Implementation i kliknij przycisk OK w celu dodania właściwości do klasy.
Właściwość Backcolor pojawi się na liście właściwości razem ze wszystkimi innymi właściwościami. Kiedykolwiek będziesz potrzebował wartości właściwości magazynowej, możesz odpytać kontener o tę wartość, używając jednej z funkcji właściwości magazynowych. W tym przypadku powinieneś użyć funkcji GetBackColor (). Wszystkie właściwości magazynowe mają powiązaną ze sobą parę metod Get/Set. Oprócz domyślnego stylu magazynowej implementacji możesz obsłużyć implementację zmiennej składowej. Ta opcja eliminuje potrzebę odpytywania kontenera o właściwość, ale jednocześnie zmusza cię do przechowywania tej wartości w kontrolce, co powoduje zwiększenie objętości jej egzemplarzy.
Możesz także zaimplementować metody Get/Set i przechowywać i korzystać z właściwości tak jak Ci się podoba, lecz ta opcja także wymaga dodatkowego kodu. Domyślna implementacja magazynowa jest oczywiście najłatwiejsza, gdyż wszystkim zajmuje się MFC.
Używanie właściwości otaczających
Właściwości otaczające to właściwości zaimplementowane w kontenerze, w którym znajduje się kontrolka, w przeciwieństwie do właściwości magazynowych, które są zaimplementowane w kontrolce, a nie w kontenerze. Właściwości magazynowe korzystają z tego samego zestawu predefiniowanych znaczeń i identyfikatorów dispid co właściwości magazynowe. Dispid jest unikalnym identyfikatorem używanym do identyfikowania właściwości wewnątrz interfejsu. Aby skorzystać z właściwości otaczającej, kontrolka musi jedynie zażądać od kontenera przekazania wartości tej właściwości, a następnie wykorzystać tę wartość zgodnie z typem właściwości. Użycie właściwości otaczających pozwala kontrolce na dostosowanie się do ustawień kontenera, w którym się znajduje. Dzięki temu możliwa jest dużo lepsza integracja kontrolki z kontenerem.
Weźmy poprzedni przykład, w którym do naszej przykładowej kontrolki dodaliśmy właściwość magazynową Backcolor. Ponieważ Backcolor jest zdefiniowana jako właściwość magazynowa, użytkownik kontrolki może zmienić kolor tła kontrolki lub pozostawić go bez zmian. Jeśli kolor jest inny niż kolor kontenera lub jeśli z jakiegoś powodu kolor tła kontenera ulegnie zmianie, kolory nie będą sobie odpowiadać, co spowoduje, że aplikacja będzie wyglądała na kiepsko i niedbale napisaną. Jeśli jednak kontrolka będzie korzystać z otaczającego koloru tła swojego kontenera, kolor jej tła zawsze będzie odpowiedni. Jednak to, czy kontrolka będzie korzystać z właściwości otaczających, zależy tylko od Ciebie i od wymagań co do aplikacji.
Aby odwołać się do właściwości otaczającej, możesz wywołać jedną z wielu funkcji właściwości otaczających zdefiniowanych w klasie coieProperty, takich jak na przykład
AmbientBackColor (), czy też po prostu Użyć funkcji GetAmbientProperty () W celu odczytania wartości:
this->GetAmbientProperty(DISPID_BACKCOLOR,VT_COLOR, &varBackColor);
Funkcja GetAmbientProperty () otrzymuje jako pierwszy parametr identyfikator dispid. Ten identyfikator musi być jednym z identyfikatorów zdefiniowanych w MFC. Pełną listę identyfikatorów dispid znajdziesz w plikach źródłowych MFC. Drugim parametrem jest typ danych dla struktury VARIANT żądanej przez program, zaś trzecim parametrem jest referencja do samej struktury VARIANT, w której funkcja GetAmbientProperty () ma umieścić dane.
Tworzenie arkuszy właściwości
Arkusze właściwości pozwalają kontrolce na wyświetlanie jej właściwości w celu przeglądania i edycji i zwykle są implementowane w postaci okien dialogowych z zakładkami. Z arkuszami właściwości mieliśmy już do czynienia w rozdziale 14. Oryginalnie arkusze właściwości zostały opracowane do użycia w przypadkach, gdy kontener nie posiadał możliwości przeglądania właściwości kontrolek. Tak więc mimo że arkusze właściwości mają swoje zalety, jednak nie w każdym przypadku są konieczne. Usunięcie arkusza właściwości z projektu kontrolki powoduje znaczącą redukcję objętości jej pliku wykonywalnego, nie powodując przy tym utraty funkcjonalności.
Ponieważ arkusze właściwości są oknami dialogowymi z zakładkami, większość pracy będziesz wykonywał za pomocą edytora dialogów i Class Wizarda. W oknie projektu wybierz zakładkę widoku zasobów, po czym z listy okien dialogowych wybierz pozycję IDD_PROPPAGE_MFCCONTROLWIN i dwukrotnie ją kliknij, otwierając wzorzec dialogu w edytorze.
Za pomocą edytora zasobów usuń statyczny tekst TODO, po czym umieść w dialogu własny statyczny napis oraz rozwijaną listę. Kliknij prawym przyciskiem myszy statyczny napis, przywołując jego menu podręczne; wybierz polecenie Properties (właściwości). Na zakładce General (ogólne) ustaw ID kontrolki na IDC_ALIGNMENTLABEL, zaś jako treść napisu wpisz Wyrównanie. Na zakładce Styles (style) ustaw opcję Align Text (wyrównaj tekst) na Right (do prawej). Zamknij okno dialogowe zapisując informacje.
Następnie, ponownie prawym przyciskiem myszy, kliknij kontrolkę rozwijaną listy. Także tym razem z menu podręcznego wybierz polecenie Properties. Na zakładce General ustaw ID kontrolki na IDC_ALIGNMENTCOMBO. Na zakładce Styles wybierz z listy Type (typ) pozycję Drop List (rozwijana lista) oraz wyłącz opcję Sort (sortowana). Zamknij okno dialogowe zapisując informacje.
W ten sposób umieściłeś na arkuszu właściwości dwie kontrolki o zmodyfikowałeś ich opcje. Musisz jeszcze tylko dopisać trochę kodu. Zamknij edytor zasobów i przywołaj ClassWizarda. Wybierz klasę CMFCControlWinPropPage, po czym kliknij zakładkę Member Variables. Z listy Control ID's wybierz pozycję IDC_ALIGNMENTCOMBO i kliknij przycisk Add Variable. Pojawi się okno dialogowe Add Member Variable. W tym oknie
ustaw nazwę zmiennej na m_AlignmentCombo, zaś jako jej kategorię wybierz z listy pozycję Control. Kliknij przycisk OK, zatwierdzając decyzję i zamykając okno.
Dodałeś zmienną składową dla kontrolki w dialogu. Teraz musisz jeszcze dodać jedną zmienną dla wartości w tej kontrolce. Ponownie kliknij przycisk Add Variable, lecz tym razem jako nazwę zmiennej wpisz m_AlignmentValue, jako jej kategorię wybierz Value, zaś jako jej rodzaj wybierz typ int. Kliknij przycisk OK, zatwierdzając decyzję i zamykając okno. Zamknij okno ClassWizarda, zatwierdzając utworzenie dwóch nowych zmiennych składowych.
Kod zarządzający wymianą danych pomiędzy kontrolką a arkuszem właściwości umieścimy wewnątrz funkcji DoDataExchange (). Otwórz plik źródłowy MFCControlWinPpg.cpp i zlokalizuj funkcję DoDataExchange (). Zwróć uwagę, że ClassWizard dodał dwie linie kodu do oryginalnej implementacji tej funkcji:
void CMFCControlWinPropPage::DoDataExchange(CDataExchange* pDX) {
//{{AFX_DATA_MAP(CMFCControlWinPropPage)
DDX_Control(pDX, IDC_ALIGNMENTCOMBO, m_AlignmentCombo) ;
DDX_CBIndex(pDX, IDC_ALIGNMENTCOMBO, m_AlignmentValue);
//}}AFX_DATA_MAP
DDP_PostProcessing(pDX);
}
DDX_control to standardowe makro MFC do ładowania wartości kontrolki do zmiennej składowej klasy MFC. DDX_CBindex to standardowa funkcja MFC do pobierania i ustawiania bieżącego indeksu pozycji na rozwijanej liście na podstawie dostarczonej zmiennej (w tym przypadku m_AlignmentValue). Aby w pełni powiązać kontrolkę z arkuszem właściwości, musisz nieco zmodyfikować ten kod:
void CMFCControlWinPropPage::DoDataExchange(CDataExchange* pDX)
{
//{{AFX_DATA_MAP(CMFCControlWinPropPage)
DDX_Control(pDX, IDC_ALIGNMENTCOMBO, m_AlignmentCombo);
//}}AFX_DATA_MAP
if(!pDX->m_bSaveAndValidate)
{
// upewnij się, że wyczyściliśmy listę m_AlignmentCombo.ResetContent(); m_AlignmentCombo.AddString(_T("Do lewej"}); m_AlignmentCombo.AddString(_T("Do środka")); m_AlignmentCombo.AddString(_T("Do prawej"));
}
DDP_CBIndex(pDX, IDC_ALIGNMENTCOMBO, m_AlignmentValue,
_T("Alignment"));
DDX_CBIndex(pDX, IDC_ALIGNMENTCOMBO, m_AlignmentValue) DDP_PostProcessing(pDX);
this->SetModifiedFlag();
}
Zmodyfikowana funkcja robi znacznie więcej niż oryginał. Po pierwsze, wypełnia rozwijaną listę poprawnymi wartościami, dzięki czemu arkusz właściwości odpowiada
wartości właściwości w kontrolce oraz w przeglądarce właściwości. Poprzez sprawdzenie zmiennej m_bSaveAndValidate zapewniamy, że te wartości są ładowane tylko raz, podczas pierwszego wywołania arkusza. Innymi słowy, kod zapewnia, że arkusz właściwości nie zachowuje i nie zatwierdza zawartych w nim danych.
W celu zapewnienia komunikacji pomiędzy kontrolką a arkuszem właściwości musisz dodać przed wywołaniem funkcji DDX_CBindex () wywołanie funkcji DDp_CBindex {). Funkcja DDP_CBindex () instruuje MFC, by podczas ładowania dialogu pobrało od kontrolki wartość właściwości Alignment i umieściło ją w zmiennej składowej m_Aiignmentvalue arkusza właściwości. Podczas zamykania okna dialogowego funkcja DDP_CBindex () pobiera bieżący indeks zaznaczonej pozycji rozwijanej listy i przypisuje tę wartość właściwości Alignment kontrolki. Funkcja DDP_CBindex{) musi być wywołana przed funkcją DDX_CBindex (), gdyż w przeciwnym razie arkusz właściwości nie będzie poprawnie odzwierciedlał wartości właściwości kontrolki.
Warto zapamiętać, że ten arkusz właściwości wymaga specyficznej implementacji. Innymi słowy, klasa ładuje kontrolkę rozwijanej listy do zmiennej składowej m_Alignment-combo klasy arkusza właściwości oraz że konieczne jest załadowanie pozycji wybranego elementu listy przed ustawieniem bieżącej pozycji na liście. Z powodu specjalnej implementacji arkusza właściwości konieczne jest przesunięcie linii DDP_CBindex {) oraz DDX_CBindex () z wnętrza makra AFX_DATA_MAP na zewnątrz w celu dokonania modyfikacji. Dzięki temu można samemu modyfikować te pozycje. Oddzielanie linii w celu zaimple-mentowania funkcji DDP_CBindex () oraz DDX_CBindex () zwykle nie jest konieczne, jednak było jedynym rozwiązaniem w naszym konkretnym przypadku.
Gdy usuwasz linie DDP_CBindex {} i DDX_CBindex () z wnętrza makr MFC, reprezentowane przez nie zmienne składowe nie pojawią się w oknie dialogowym ClassWizarda, a ClassWizard nie będzie mógł nimi automatycznie zarządzać.
Jak wiesz z wcześniejszej części rozdziału, aby przeglądarka właściwości (taka jak okno listy właściwości w Visual Basicu) mogła poprawnie wyświetlać wartość właściwości po zmianie jej przez użytkownika na arkuszu, wewnątrz funkcji SetAlignment () musisz umieścić wywołanie funkcji BoundPropertyChangedO. W ten sposób poinformujesz przeglądarkę właściwości o zmianie wartości i że powinna ona zostać ponownie pobrana. Funkcję SetAlignment (} powinieneś zaimplementować następująco:
void CMFCControlWinCtrl::SetAlignment(long nNewValue) {
// sprawdzamy, czy wartość mieści się w dozwolonym zakresie if(nNewValue >= EALIGN_LEFT && nNewValue <= EALIGN_RIGHT) {
// ustawiamy nową wartość właściwości m_lAlignment = nNewValue;
// informujemy kontrolkę o modyfikacji właściwości this->SetModifiedFlag();
// odświeżamy przeglądarkę właściwości this->BoundPropertyChanged(dispidAlignment);
// odrysowujemy kontrolkę
this->InvalidateControl() ;
}
}
Jedyną czynnością, jaka pozostała do zrobienia, jest ustawienie jakiejś sensownej domyślnej wartości zmiennej składowej m_Alignmentvalue. Najpierw do pliku MFCCONTROL-WINPPG.CPP dołącz kolejny plik nagłówkowy:
ttinclude "MFCControlWinCtl .h"
Dołączenie tego pliku nagłówkowego pozwala na korzystanie ze stałej EALIGN_LEFT. Następnie musisz zainicjalizować zmienną składową m_Al i gnmentYalue w konstruktorze klasy:
// MFCControlWinPpg . cpp : Implementation of the // CMFCControlWinPropPage property page class.
#include "stdafx.h" ttinclude "MFCControl .h" ttinclude "MFCControlWinCtl .h" ttinclude "MFCControlWinPpg .h"
// ...
CMFCControlWinPropPage : : CMFCControlWinPropPage ( ) :
COlePropertyPage (IDD, IDS_MFCCONTROLWIN_PPG_CAPTION) {
//{ {AFX_DATA_INIT (CMFCControlWinPropPage)
m_AlignmentValue = EALIGN_LEFT;
//} }AFX_DATA_INIT
}
Dodawanie zdarzeń
Właściwości i metody służą programiście do komunikowania się z kontrolką z aplikacji kontenera. Zdarzenia służą kontrolce do komunikowania się z kontenerem. W przypadku kontrolek ActiveX zdarzenia nie są niczym innym niż interfejsami IDispatch zaimple-mentowanymi po stronie kontenera w powiązaniu typu kontener-kontrolka. Mechanizm, na którym opierają się zdarzenia, jest nazywany punktem połączenia (ang. connection point). Punkt połączenia jest po prostu opisem typu interfejsu wymaganego do skomunikowania się z kontenerem. Punkty połączeń nie są ograniczone jedynie do interfejsów IDispatch, lecz mogą być dowolnymi interfejsami COM zrozumiałymi dla obu komponentów. Pod tym względem punkty połączeń nie są specyficzne wyłącznie dla kontrolek, lecz mogą być stosowane w każdej implementacji COM. Kontrolki były po prostu pierwszymi komponentami korzystającymi z ich zalet. Więcej informacji na temat punktów połączeń znajdziesz w dokumentacji OLE dostarczanej wraz z Yisual Studio.
Tak jak w przypadku metod i właściwości, także zdarzenia są dodawane i usuwane za pomocą ClassWizarda. Aby dodać zdarzenie, otwórz ClassWizarda i kliknij zakładkę ActiveX Events (zdarzenia ActiveX). Wybierz klasę CMFCControlWinCtrl i kliknij przycisk Add Event (dodaj zdarzenie). Tak jak w przypadku właściwości możesz wybrać zdarzenie z listy predefiniowanych zdarzeń lub stworzyć własne. W przypadku naszej
klasy dodamy zdarzenie o nazwie Change, które będzie posiadać dwa parametry. Pierwszym z nich będzie łańcuch, przekazywany przez referencję (BSTR *), o nazwie cstrCaption. Drugim będzie typ long, także przekazywany przez referencję (long *), o nazwie lAiign-ment. Parametry przekazujemy poprzez referencję w celu umożliwienia kontenerowi ewentualnej zmiany ich wartości.
Kliknij przycisk OK w celu dodania zdarzenia do klasy. Zanim jednak użyjesz zdarzenia, musisz stworzyć standardową funkcję FireChange () zawierającą kod obsługujący przypadki, gdy program zmienił dane przekazane zdarzeniu. Zamknij okno dialogowe ClassWizarda, zatwierdzając dodanie zdarzenia do klasy.
Prototyp funkcji musimy umieścić w definicji klasy w pliku nagłówkowym. Dodatkowo, w pliku źródłowym musimy dopisać implementację funkcji. Oto prototyp funkcji, jaki powinieneś dopisać w pliku nagłówkowym:
// ...
protected:
// miejsce na przechowanie tytułu CString m_cstrCaption;
// miejsce na przechowanie wartości wyrównania long m_lAlignment; void FireChange(void);
};
Po dopisaniu prototypu musisz dodać implementację do pliku źródłowego. Dopisz do niego poniższą funkcję:
void CMFCControlWinCtrl::FireChange(void)
{
// pobranie BSTR, który może zostać przekazany
// poprzez zdarzenie
BSTR bstrCaption =m_cstrCaption.AllocSysStringi()
// odpalenie zdarzenia Change this->FireChange(SbstrCaption, &m_lAlignment);
// zobaczmy, co otrzymaliśmy od użytkownika m_cstrCaption = bstrCaption;
// zwolnienie przekazanych danych ::SysFreeString(bstrCaption);
}
Kontrolka jest odpowiedzialna za dane przekazywane poprzez zdarzenie, więc przed powrotem z funkcji konieczne jest zwolnienie przekazywanego łańcucha BSTR.
Standardowa funkcja FireChange () pozwala na oddzielenie szczegółów związanych ze zdarzeniem Change od reszty programu. Jeśli w przyszłości zdecydujesz się na zmianę implementacji tego zdarzenia, będziesz musiał zmodyfikować tylko jedną funkcję, a nie kilka.
Metoda captionMethodO wymaga, by w momencie zmiany danych zostało odpalone zdarzenie Change, musisz więc umieścić w niej odwołanie do nowego zdarzenia, tuż przed wywołaniem funkcji invalidateControl (). Musisz dodać je także do funkcji SetAlignment (). Pamiętaj jednak, by nie dodawać wywołania FireChangeO do funkcji setCaptionPropO, gdyż ona i tak wywołuje funkcję CaptionMethodO. Oprócz tego, nie zapomnij dodać tej funkcji do każdej nowej funkcji dodawanej do kontrolki w trakcie jej rozbudowy.
Trwałość
Trwałość (ang. persistance) odnosi się do zdolności komponentu do utrzymywania swojego stanu pomiędzy uruchomieniami. Innymi słowy, bez względu na ilość uruchomień i zatrzymań działania komponentu, komponent "pamięta", że, na przykład, zmieniłeś jego tło z białego na zielone. Implementacja trwałości w MFC jest bardzo prosta i wymaga dosłownie minimalnej ilości kodu. Wystarczy po prostu dodać do funkcji DoPropEx-change () po jednej linii dla każdej właściwości, którą chcesz uczynić trwałą. Możesz jednak zastosować bardziej wymyślny mechanizm, utrwalając właściwości niezależnie, w zależności od tego, czy je zachowujesz czy ładujesz, jednak to również nie jest skomplikowane.
Aby utrwalić właściwości naszej kontrolki, zmodyfikujemy funkcję DoPropExchange () w celu zachowania wartości dwóch zmiennych składowych: m_lAlignment oraz m_cstr-Caption. Trwałość można zaimplementować na dwa sposoby. Pierwszy, najprostszy, polega na dodaniu po jednej linii dla każdej z właściwości, bez zwracania uwagi na kolejność lub zależności. Zależności odnoszą się do faktu, że w miarę rozbudowy implementacji kontrolki może okazać się, że pewne trwałe właściwości mogą być zależne w jakiś sposób od innych właściwości.
Określając domyślną wartość w liniach utrwalających właściwości, nigdy nie powinieneś używać tej samej zmiennej, w której mają znaleźć się odczytane dane. Funkcje obsługujące trwałość w MFC są zoptymalizowane w ten sposób, że jeśli trwała właściwość nie uległa zmianie w stosunku do wartości domyślnej, funkcje nie utrwalą tej właściwości. W ten sposób jest oszczędzany czas i miejsce podczas ładowania lub zapisywania trwałych informacji. Jeśli użyjesz tej samej zmiennej, MFC będzie uważać, że jej wartość nie uległa zmianie, gdyż procedura porównawcza sprawdza jedynie adresy i zawartość pamięci. Tak więc jako wartości domyślnych dla trwałych właściwości zawsze powinieneś używać stałych, gdyż tego właśnie oczekują funkcje MFC.
Najprostszym sposobem utrwalenia właściwości jest użycie funkcji PX_ odpowiedniej dla typu tej właściwości. Możesz użyć tej samej metody bez względu na kierunek utrwalania. Innymi słowy, gdy ładujesz lub zapisujesz właściwość, MFC automatycznie obsługuje wszystkie szczegóły - inaczej niż w przypadku serializacji danych w dokumentach. Funkcję DoPropExchange () powinieneś zaimplementować następująco:
void CMFCControlWinCtrl::DoPropExchange(CPropExchange* pPX)
{
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor)}; COleControl::DoPropExchange(pPX) ;
// TODO: Cali PX_ functions for each persistent // custom property
PX_Long(pPx, _T("Alignment"), m_lAlignment, EALIGN_LEFT); PX_String(pPX, _T("CaptionProp")/ m_cstrCaption, _T(""};
}
Z drugiej strony, jeśli chcesz najpierw załadować właściwości, a następnie wykonać jakieś operacje specyficzne dla swojej implementacji (oraz zastosować podobny proces podczas zapisywania właściwości), musisz zaimplementować trwałość nieco inaczej. Wymagania co do określonej kolejności utrwalania właściwości są wysoce subiektywne i zależą od danej implementacji. W takich przypadkach możesz stworzyć kod podobny do przedstawionego na listingu 31.3.
Listing 31.3. Przykładowy kod funkcji DoPropExchangeQ obsługujący trwałość właściwości
_________w kontrolce___________________________________________
void CMFCControlWinCtrl::DoPropExchange(CPropExchange* pPX)
{
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor)};
COleControl: :DoPropExchange(pPX);
// TODO: Cali PX_ functions for each persistent
// custom property.
// Gdy ładujesz właściwości if(pPX->IsLoading())
{
PX_Long(pPx, _T("Alignment"), m_lAlignment,
EALIGN_LEFT); PX_String(pPX, _T("CaptionProp"),
m_cstrCaption, _T (""));
}
// Gdy zapisujesz właściwości if(!pPX->IsLoading(})
{
PX_Long(pPx, _T("Alignment"), m_lAlignment,
EALIGN_LEFT); PX_String(pPX, _T("CaptionProp"),
m_cstrCaption, _T(""));
}
}
Gdy kontrolka zawiera właściwości magazynowe, w celu ich utrwalenia musisz wywołać funkcję klasy bazowej, COleControl: :DoPropExchange (). Implementacja w klasie bazowej nie daje zbyt dużej elastyczności i czasem może się zdarzyć, że będziesz potrzebował nieco większej kontroli.
Jednym z rozwiązań jest samodzielne utrwalanie właściwości magazynowych i zignorowanie implementacji w klasie bazowej. Główną wadą tej metody jest konieczność ręcznego zarządzania kodem odpowiedzialnym za trwałość i brak możliwości zrzucenia tego obowiązku na MFC.
Rysowanie kontrolki
Większość kontrolek posiada pewien interfejs użytkownika. Jednak jak już wiesz, od czasu opublikowania specyfikacji OC 96 oraz pojawienia się ActiveX istnienie interfejsu użytkownika nie jest już koniecznym warunkiem. Rysowanie interfejsu użytkownika już od dawna było uważane za jeden z najbardziej krytycznych aspektów kontrolek, zarówno pod względem wyglądu, jak i wydajności. Kontrolka, która często migocze, sprawia wrażenie kiepsko napisanej, bez względu na jej wewnętrzną implementację. To samo sprawdza się także w stosunku do szybkości odrysowywania się kontrolki. Z rozwojem Internetu i ActiveX jeszcze bardziej konieczne jest, by kontrolka rysowała się szybko i elegancko. Rysowanie kontrolki można rozbić na dwie główne kategorie: rysowanie standardowe oraz rysowanie zoptymalizowane. Rysowanie zoptymalizowane jest wysoce zaawansowanym tematem, więc nie będziemy się nim w tej książce zajmować.
Rysowanie standardowe
Rysowanie standardowe jest po prostu standardowe. Masz całkowitą swobodę w rysowaniu kontrolki tak jak ci się żywnie podoba, korzystając przy tym z dowolnych metod. Możesz używać piór i pędzli, prostokątów i elips. Pamiętaj jednak, że dla każdej aplikacji z interfejsem użytkownika zasadnicze znaczenie ma rysowanie inteligentne.
Chyba największym źródłem migotania kontrolki jest nadmiarowe malowanie i niepotrzebne rysowanie - rysowanie i odrysowywanie obszarów kontrolki, które nie muszą być rysowane. Staraj się odrysowywać tylko te obszary kontrolki, które zostały unieważnione. W ten sposób zaoszczędzisz czas procesora i unikniesz irytującego migotania. Na przykład, jeśli kontrolka posiada białe tło i czarną obwódkę, białe tło rysuj tylko tam, gdzie ma być widoczne. Nie rysuj tła na ramce, gdyż w ten sposób spowodujesz jej migotanie za każdym razem, gdy kontrolka otrzyma komunikat odrysowania.
Zanim dodamy implementację funkcji OnDraw () do pliku źródłowego, do sekcji protected klasy CMFCControiwinCtri dopisz deklarację zmiennej składowej typu CBrush. Ta nowa zmienna posłuży nam do przechowywania obiektu pędzla GDI. W standardowej implementacji kontrolka odtwarzałaby obiekt CBrush przy każdym wywołaniu funkcji
OnDraw() .
Implementacja funkcji onDrawO jest oczywista. Wybieramy czcionkę w kontekście urządzenia, pobieramy wszystkie kolory, jakich mamy zamiar używać, rysujemy tło, rysujemy tekst i rysujemy ramkę. Następnie zwalniamy wszystkie zaalokowane obiekty rysunkowe. Przykład implementacji funkcji OnDraw () został przedstawiony na listingu 31.4.
Listing 31.4. Funkcja OnDrawO odpowiadająca za odświeżanie obszaru kontrolki_______
void CMFCControiwinCtri::OnDraw(
CDC* pdc, const CRect& rcBounds, const CRects rclnvalid) {
// **** Pobieramy czcionkę dla napisu
CFont *p01dFont;
// Pobieramy czcionkę z "magazynka" pOldFont = this->SelectStockFont(pdc);
// Jako kolor tlą czcionki stosujemy kolor tła okna COLORREF clrTextBackgroundColor =
this->TranslateColor(this->GetBackColor ());
// jako koloru tekstu użyjemy zwykłego koloru dla Windows COLORREF clrTextForegroundColor =
this->TranslateColor(this->GetForeColor()};
// pobieramy kolor systemowy COLORREF clrEdgeBackgroundColor =
::GetSysColor(COLOR_3DFACE); COLORREF clrEdgeForegroundColor =
::GetSysColor(COLOR_3DFACE);
// ustawiamy kolor tła i tekstu COLORREF clrOldBackgroundColor =
this->SetBkColor(clrTextBackgroundColor}; COLORREF clrOldForegroundColor =
this->SetTextColor(clrTextForegroundColor);
// jeśli nie mamy pędzla
if(m_Brush.m_hObject == NULL)
// tworzymy pędzel
m_Brush.CreateSolidBrush(clrTextBackgroundColor);
// wybieramy pędzel i zapamiętujemy stary CBrush *p01dBrush = pdc->SelectObject(&m_Brush);
// rysujemy tło pdc->Rectangle(&rcBounds);
// ***** Rysujemy tekst int iHor, iVer;
// pobieramy rozmiar tekstu dla tego kontekstu urządzenia
CSize oSize = pdc->GetTextExtent(m_cstrCaption);
switch(m_lAlignment)
{
case EALIGN_CENTER:
iHor = (rcBounds.right - oSize.cx) / 2;
iVer = rcBounds.top + 3;
break; case EALIGN_RIGHT:
iHor = rcBounds.right - oSize.cx - 3;
iVer = rcBounds.top + 3;
break; case EALIGN_LEFT:
iHor = rcBounds.left + 3;
iVer = rcBounds.top + 3;
break;
}
// wypisujemy tekst
pdc->ExtTextOut(iHor, iVer, ETO_CLIPPED | ETO_OPAQUE, rcBounds, m_cstrCaption, m_cstrCaption.GetLength(),NULL);
// ***** Rysujemy ramkę
// ustawiamy styl i znaczniki krawędzi UINT uiBorderStyle = EDGE_SUNKEN; UINT uiBorderFlags = BF_RECT;
// ustawiamy kolor krawędzi pdc->SetBkColor(clrEdgeBackgroundColor) ; pdc->SetTextColor(clrEdgeForegroundColor);
// rysujemy trójwymiarową krawędź
pdc->DrawEdge((LPRECT)(LPCRECT) rcBounds, uiBorderStyle, uiBorderFlags);
// ***** Zerujemy kolory
// odtwarzamy oryginalne kolory pdc->SetBkColor(clrOldBackgroundColor) ; pdc->SetTextColor(clrOldForegroundColor) ;
// ***** Zwalniamy obiekt czcionki
// odtwarzamy oryginalną czcionkę pdc->SelectObject(pOldFont);
// odtwarzamy pędzel pdc->SelectObject(pOldBrush) ;
}
Podsumowanie
Wszystkie biblioteki opisane w tym rozdziale mają swoje mocne i słabe strony. MFC ułatwia szybkie tworzenie i implementowanie komponentów, zaś wsparcie ze strony Visual C++ dla MFC nie ma sobie równych w stosunku do wsparcia ze strony ATL czy Basectl. MFC oferuje bardzo bogatą i stabilną bibliotekę klas wystarczającą do rozwiązania większości (jeśli nie wszystkich) problemów związanych z tworzeniem kon-trolek. Jednak korzystanie z MFC wiąże się z syndromem "uniwersalnych narzędzi do wszystkiego", dających wolniejsze aplikacje o większej ilości kodu lub takie, których nie da się łatwo dostosować do własnych potrzeb.
ATL zapewnia mniejszą i zwartą bibliotekę dla tworzenia komponentów ActiveX. Jednak w tym przypadku nie mamy wsparcia w postaci standardowych klas i narzędzi, stanowiących mocną stronę MFC. Oprócz tego, integracja ATL ze środowiskiem Yisual C++ także pozostawia sporo do życzenia.
Basectl jest podobne do ATL w tym, że zostało opracowane do tworzenia małych, szybkich komponentów. Podobnie jak w przypadku ATL, brakuje tej bibliotece standardowych klas oraz narzędzi, które sprawiają, że MFC jest tak atrakcyjne. Oprócz tego, Basectl jest obciążone opinią próbnego produktu, nie wspieranego już przez Microsoft.
O wyborze narzędzia do tworzenia komponentów ActiveX decyduje także poziom doświadczenia zespołu programistów oraz przewidziany czas życia komponentów. Zanim podejmiesz decyzję co do stosowanej platformy, znajdź nieco czasu na rozważenie wszystkich za i przeciw.
Po opisaniu możliwych opcji implementacji w drugiej części rozdziału skupiliśmy się na stworzeniu prostej implementacji kontroIki MFC. Podstawę każdej kontroIki ActiveX stanowią metody, właściwości oraz zdarzenia, a dzięki MFC dodawanie ich do kontrolki jest niezwykle proste. Pod koniec rozdziału omówiliśmy także zagadnienia związane z trwałością i rysowaniem interfejsu użytkownika, czyli elementy niezbędne dla pełnej implementacji kontrolki.
MFC jest pierwszym narzędziem Microsoftu dostępnym dla tworzenia komponentów ActiveX (nazywanych dawniej komponentami OLE). MFC ma na celu ukrycie wszystkich szczegółów implementacji komponentów ActiveX/COM, tak abyś nie musiał uczyć się tworzenia zupełnie nowych aplikacji MFC tylko po to, aby móc stworzyć prostą kontrolkę ActiveX. To ukrywanie szczegółów przez MFC jest zarówno zaletą, jak i wadą. Możesz co prawda bardzo szybko stworzyć kontrolkę, lecz jej objętość i wydajność mogą nie być tak optymalne jak w przypadku kontrolki stworzonej z użyciem ATL. Jednak MFC posiada ogromną zaletę polegającą na ścisłej integracji z Visual C++ IDE, które zdaje się być zbudowane z myślą o jak najpełniejszym zespoleniu z MFC.
Teraz, gdy poznałeś już podstawy tworzenia kontrolek, powinieneś poświęcić pewien czas na zastosowanie poznanych technik w dwóch innych kontrolkach, dla których stworzyłeś projekt (w kontrolce bezokienkowej oraz kontrolce subklasowanej). Z kolei w następnym rozdziale dowiesz się, jak w MFC możesz stworzyć serwer automatyzacji.
Wyszukiwarka
Podobne podstrony:
Wprowadzenie do projektowaniaWYKŁAD 1 Wprowadzenie do biotechnologii farmaceutycznejMedycyna manualna Wprowadzenie do teorii, rozpoznawanie i leczenie01 Wprowadzenie do programowania w jezyku Cwprowadzenie do buddyzmu z islamskiego punktu widzenia1 wprowadzenie do statystyki statystyka opisowaInformatyka Wprowadzenie Do Informatyki Ver 0 95Wprowadzenie do psychologii wykł UG645 Informacja dodatkowa wprowadzenie do sprawozdania finasowegowięcej podobnych podstron