Rozdział 19.
Dokumenty, widoki i SDI
W tym rozdziale:
Obiekt dokumentu
Obiekt Widoku
Klasa CDocument
Przeznaczenie funkcji Initinstance()
Obiekt okna ramki
Klasa cview
Dynamiczne tworzenie obiektów
Wzorzec dokumentu SDI
Rejestrowanie dokumentów w systemie operacyjnym
Tworzenie rzeczywistej aplikacji typu dokument-widok
W sercu aplikacji MFC leży koncepcja obiektu dokumentu i odpowiadającego mu okna widoku. Obiekt dokumentu zwykle reprezentuje plik otwarty przez aplikację, podczas gdy okno widoku zapewnia wizualną prezentację danych w dokumencie oraz przyjmuje polecenia użytkownika. Relacja pomiędzy dokumentami a widokami jest relacją typu jeden do wielu. Innymi słowy, dokument może mieć wiele widoków, lecz dany widok może być powiązany tylko z jednym dokumentem.
W swoich aplikacjach obiekty dokumentów będziesz reprezentował poprzez klasy wyprowadzone z bazowej klasy MFC, CDocument. Z kolei klasy widoków będą wyprowadzone z klasy MFC cview. W tym rozdziale zajmiemy się właśnie klasami CDocument oraz cview, a także pokażemy ich zastosowanie w prostej, jednodokumentowej (SDI, Single Document Interface) aplikacji. Bardziej złożonymi manipulacjami dokumentami i widokami zajmiemy się dopiero w rozdziale 20.
Projekt typu dokument-widok
Każda aplikacja MFC obsługująca interfejs jednodokumentowy (SDI, Single Document Interface) lub wielodokumentowy (MDI, Multiple Document Interface) korzysta z architektury typu dokument-widok. AppWizard projektuje aplikację w taki sposób, że dla każdego ładowanego przez aplikację pliku jest tworzony egzemplarz klasy dokumentu aplikacji. Następnie aplikacja używa egzemplarza klasy widoku w celu umożliwienia interakcji użytkownika z aplikacją i danymi w dokumencie.
W swojej aplikacji możesz powiązać więcej niż jeden egzemplarz konkretnego widoku z danym dokumentem, a możesz nawet powiązać z danym dokumentem egzemplarze zupełnie różnych widoków.
Tak więc wbudowaną potęgą architektury typu dokument-widok jest to, że użytkownicy pracując z aplikacją, tworzą i niszczą egzemplarze obiektów odpowiedzialnych za zarządzanie plikiem i interfejsem użytkownika (oraz danymi), definiujących sposób, w jaki postrzegane są dane, na których pracują ci użytkownicy.
Przyjazna dla użytkownika aplikacja oferuje takie widoki na dane aplikacji, jakie są jak najbardziej sensowne z punktu widzenia użytkownika. Na przykład, większości czytelników raczej trudno wyobrazić sobie, jak zimno jest w Dakocie Południowej, gdy mają do dyspozycji jedynie tabelę temperatur dla wszystkich stanów USA. Jednak przy zastosowaniu standardowych kolorów w mapie temperatur wgazecie - niebieskiego dla zimna i czerwieni dla gorąca - powoduje żepodróżnik może bardzo łatwo zorientować się co go czeka, jeśli tylko rzuci okiem na odpowiedni fragment mapy. Jednak dane tabelaryczne także mają swoje zastosowania. Po pierwsze, do tabeli można łatwo wprowadzać dane, a poza tym, jeśli nie masz pojęcia, gdzie leży Dakota Południowa, jest to jedyny sposób przekonania się, jaka tam panuje temperatura.
Tworzone przez AppWizarda aplikacje jednodokumentowe wykorzystują tylko pojedynczy rodzaj dokumentu oraz pojedynczy rodzaj widoku, tworząc po jednym tylko egzemplarzu obu tych klas. Jednak jest to regułą tylko w przypadku kodu tworzonego przez AppWizarda - gdy już stworzy szkielet aplikacji, możesz do woli zmieniać kompozycję swojego projektu, na przykład gdy uznasz, że wygodniej będzie korzystać z kilku egzemplarzy widoków jednocześnie.
Aplikacje MDI korzystają z przynajmniej jednej pary dokument-widok, lecz umożliwiają także korzystanie z dodatkowych dokumentów i widoków, w różnych kombinacjach, pozwalając użytkownikowi na pracę z innymi plikami lub na wyświetlanie tych samych danych na różne sposoby. Jak wspominaliśmy, więcej na temat aplikacji wielodokumento-wych dowiesz się w rozdziale 20. Rysunek 19.1 pokazuje, jakie klasy mogą obsługiwać prostą aplikację SDI zaimplementowaną z użyciem obiektów MFC.
W aplikacjach SDI generowanych przez AppWizarda samą ramkę okna reprezentuje klasa CMainFrame. W takich przypadkach AppWizard definiuje dla Ciebie w pliku nagłówkowym MainFrm.h klasę CMainFrame, zaś implementuje ją w pliku źródłowym MainFrm.cpp. Klasa CMainFrame większość swoich funkcji przejmuje od klasy CFra-meWnd, która w MFC reprezentuje proste okienko z ramką.
W przypadku aplikacji SDI klasa CFrameWnd nie robi zbyt wiele. Jedyny wyjątek występuje wtedy, gdy uzupełnisz aplikację o pasek stanu lub dokowalne paski narzędzi. Wtedy za tworzenie i inicjalizację tych obiektów odpowiada klasa CMainFrame.
Konwencjonalne aplikacje Windows napisane w C (oraz korzystające z Windows API) modyfikują sposób działania własnych klas Windows. W ciele aplikacji kod programu maluje, rysuje lub coś przechowuje pod dyktando komunikatów wejściowych (lub kombinacji komunikatów wejściowych) otrzymywanych poprzez okna aplikacji. Na szczęście, korzystając z MFC możesz skupić się bardziej na pracy z klasami zamiast pracy z serią wywołań funkcji systemowych. Te klasy MFC posiadają możliwość przejmowania podstawowych komunikatów Windows oraz, jeśli to możliwe, wykonania większości pracy na dużo niższym, zwykle niewidocznym dla programisty, poziomie.
Klasa CDocument
Dostarczona przez MFC klasa CDocument zapewnia podstawowe funkcje dla obiektów dokumentów w aplikacji. Do podstawowych funkcji klasy CDocument należą możliwość tworzenia nowych dokumentów, serializowanie danych dokumentów, zapewnianie podstawowej współpracy pomiędzy obiektem dokumentu a oknem widoku oraz parę innych rzeczy. MFC dostarcza także serię klas wyprowadzonych z klasy CDocument, implementujących funkcje specyficzne dla pewnych typów aplikacji. Na przykład, MFC posiada klasy CRecordset i CDAORecordset ułatwiające tworzenie widoków dla baz danych. Relacja pomiędzy dokumentami a widokami została przedstawiona na rysunku 19.2.
Rys. 19.2.
Relacja typu jeden do wielu pomiÄ™dzy dokumentem a jego •widokami
CView1 +
CView2
+
CView3
(Wszystkie wskaxnik do
Klasa CDocument
(zawiera dane)
Deklarowanie klasy dokumentu w aplikacji
Gdy do tworzenia aplikacji używasz AppWizarda, zwykle nie musisz sam martwić się o deklarowanie bazowej klasy dokumentu - AppWizard zrobi to za Ciebie. Jednak mimo to dobrze będzie, gdy sam zrozumiesz działanie klasy CDocument - gdyż bardziej złożone programy mogą wymagać zastosowania kilku egzemplarzy obiektów wyprowadzonych z tej klasy. Oprócz tego, zrozumienie działania tej klasy umożliwi Ci łatwe rozszerzenie wygenerowanego przez AppWizarda szkieletu aplikacji.
Bez względu na to, czy tworzysz aplikację SDI czy MDI, AppWizard wyprowadza z bazowej klasy CDocument tylko pojedynczą klasę pochodną.
Budując prostą aplikację MFC, często wystarczy dokonanie tylko niewielkich modyfikacji w dostarczonej przez AppWizarda klasie dokumentu. Zwykle nie musisz robić nic poza dodaniem kilku zmiennych oraz funkcji składowych umożliwiających innym częściom programu dostęp do tych zmiennych.
Na przykład, obiekt dokumentu dla prostego programu komunikacyjnego (takiego jak emulator terminala) mógłby zawierać zmienne składowe dla ustawień modemu. Te zmienne składowe zawierałyby informacje, takie jak numer telefonu, szybkość, parzystość, ilość bitów danych, startu oraz stopu itd. Możesz łatwo przechować ustawienia komunikacji za pomocą kilku zmiennych składowych w wyprowadzonej klasie dokumentu, tak jak pokazano w poniższym fragmencie kodu:
class CSimpleTermDoc : public CDocument
{
protected:
CSimpleTermDoc();
DECLARE_DYNCREATE(CSimpleTermDoc) public:
CString m_sPhoneNum;
DWORD m_dwTransSpeed;
WORD m_nTransParity;
WORD m_nTransBits;
WORD m_dwConnectTime;
Po zadeklarowaniu zmiennych składowych musisz zapewnić, że program zainicjuje je z użyciem pewnych domyślnych wartości w funkcji składowej OnNewDocument () klasy CSimpleTermDoc. Oprócztego w funkcji Serialize () musisz umieścić kod zapewniający, że program będzie właściwie serializował te zmienne. Kod funkcji OnNewDocument (} oraz Serialize () mógłby wyglądać tak jak na listingu 19.1.
Listing 19.1. Standardowy kod dla funkcji składowych OnNe\vDocument() oraz Serializep
BOOL CSimpleTermDoc::OnNewDocument(} (
if( !CDocument::OnNewDocument()) return FALSE;
m_sPhoneNum = _T ("555-1212"};
m_dwTransSpeed = 28800;
m_nTransParity = 0;
m_nTransBits = 8;
m_dwConnectTime = 0;
return TRUE;
}
void CSimpleTermDoc::Serialize(CArchive &ar) {
if (ar.IsStoring())
ar « m_sPhoneNum; ar « m_dwTransSpeed; ar « m_nTransParity; ar « m_nTransBits; ar « m dwConnectTime;
}
else {
ar m_sPhoneNum; ar m_dwTransSpeed; ar m_nTransParity; ar m_nTransBits; ar m dwConnectTime;
}
}
W przypadku prostych aplikacji zwykle naprawdę nie potrzeba niczego więcej poza ini-cjalizacją i serializacją własnych zmiennych składowych, aby otrzymać pełną, całkowicie funkcjonalną klasę dokumentu.
Zmienne składowe klasy CDocument
Klasa CDocument, oprócz często wykorzystywanych funkcji do serializacji i inicjalizacji, posiada także kilka innych funkcji składowych. Pierwszy zestaw funkcji składowych zapewnia dostęp do powiązanych z dokumentem obiektów widoków. Każdy obiekt dokumentu w aplikacji posiada listę powiązanych ze sobą widoków. W celu pobrania iteratora dla tej listy możesz wywołać funkcję składową GetFirstviewPosition (). Otrzymany iterator jest typu POSITION.
W aplikacjach MFC często będziesz korzystał z iteratorów typu POSITION, głównie w przypadku klas kolekcji. Gdy aplikacja musi przejść przez listę, zwykle żądasz iteratora, powiązanego przez klasę kolekcji z pierwszym obiektem na liście, a następnie za pomocą funkcji iteratora odwołujesz się kolejno do elementów na liście. W tym kontekście także klasa CDocument jest klasą kolekcji: przechowuje informację o kolekcji widoków
powiązanych z obiektem tej klasy. Tak więc, po otrzymaniu za pomocą funkcji GetFir-stviewPosition () iteratora dla pierwszego widoku w klasie możesz kolejno wywoływać funkcję składową GetNextview () w celu przejścia przez pozostałe widoki w kolekcji.
Innymi słowy, aby przetworzyć wszystkie widoki, jakie program powiązał z danym obiektem dokumentu, możesz zastosować kod wyglądający mniej więcej tak:
POSITION posYiew = GetFirstViewPosition();
while (posYiew != NULL)
{
CView *pView = GetNextView(posView);
// Teraz możesz wykorzystać wskaźnik do widoku
}
Jeśli jednak chcesz jedynie poinformować wszystkie widoki dokumentu o tym, że zmieniła się zawartość dokumentu, możesz je zaktualizować bez uciekania się do pętli iteratora. Zamiast tego możesz wywołać funkcję składową updateAUViews () klasy dokumentu. Co więcej, możesz przy tym przekazać własne dane, instruujące widoki, by w selektywny sposób odświeżyły jedynie wybrane obszary okien widoków. Więcej na temat selektywnej aktualizacji powiemy w dalszej części rozdziału, gdy zajmiemy się funkcją składową CView : : OnUpdate () .
Do kilku innych funkcji składowych odnoszących się do powiązanych obiektów widoków należą dużo rzadziej wykorzystywane funkcje Addview() oraz RemoveView (). Jak wskazują ich nazwy, te funkcje pozwalają na ręczne dodanie i usunięcie widoków z listy widoków powiązanych z dokumentem. Ogólnie raczej rzadko będziesz z nich korzystał, gdyż większość programistów po prostu korzysta z domyślnej implementacji MFC, z niewielkimi jedynie modyfikacjami.
Kiedykolwiek zmienią się dane dokumentu (czy to w wyniku działań użytkownika czy też w efekcie wewnętrznych obliczeń programu), program powinien wywołać funkcję składową SetModif iedFiag (). Spójne użycie tej funkcji zapewnia, że MFC wyświetli komunikat ostrzegający użytkownika przed zniszczeniem niezapisanego, lecz zmodyfikowanego dokumentu. Jeśli chcesz zmienić to zachowanie, powinieneś przesłonić funkcję składową isModified () w celu otrzymania stanu tego znacznika.
Funkcji składowej SetTitle (} możesz użyć do zmiany tytułu obiektu dokumentu. Z kolei aplikacja wyświetli zmieniony tytuł na belce tytułowej okna ramki (głównej belce okna aplikacji SDI lub okna potomnego aplikacji MDI).
Do ustawienia ścieżki dostępu i nazwy pliku dokumentu służy funkcja SetPathName (), zaś do odczytania tej ścieżki i nazwy służy funkcja GetPathName (). Na koniec, w celu pobrania wzorca dokumentu powiązanego z dokumentem powinieneś wywołać funkcję
GetDocTemplate() .
Dokumenty a przetwarzanie komunikatów
Jedną z najważniejszych cech obiektu dokumentu, którą być może wychwyciłeś z wcześniejszej części rozdziału, jest fakt, że obiekt CDocument nie jest bezpośrednio powiązany z oknem. Jednak obiekt CDocument jest obiektem otrzymującym komunikaty - co oznacza, że może przetwarzać komunikaty systemu operacyjnego. Za przekazywanie komunikatów od systemu operacyjnego do dokumentu odpowiadają powiązane z nim obiekty widoków.
Ponieważ przed przesłaniem komunikatu do dokumentu trafia on najpierw do okna ramki, a następnie do obiektu widoku, możesz precyzyjnie określić, które komunikaty powinny zostać przetworzone przez okno ramki, które przez widok, a które przez dokument. Jednak dobrym punktem wyjścia dla przetwarzania nadchodzących komunikatów mogą być pewne ogólne zasady (oraz chęć ułatwienia sobie zadania).
Gdy rozmawiamy o komunikatach, musimy wziąć pod uwagę podstawową cechę architektury typu dokument-widok, czyli musimy pamiętać, że dokument jest abstrakcyjną reprezentacją danych - reprezentacją, która z założenia jest niezależna od wizualnej reprezentacji danych w oknie widoku. Podobnie ważny jest fakt, że dokument może posiadać jedno lub kilka powiązanych ze sobą okien widoków, lecz może także nie mieć żadnego takiego okna - tak więc obiekt dokumentu powinien odpowiadać jedynie na te komunikaty, które są globalne z natury. Tzn. dokument powinien odpowiadać jedynie na te komunikaty, które mają bezpośredni wpływ na dane dokumentu; efekt takich komunikatów powinien zostać następnie odzwierciedlony we wszystkich widokach powiązanych z dokumentem. Z drugiej strony, widoki powinny odpowiadać na komunikaty specyficzne jedynie dla danego okna widoku.
W praktyce podział pomiędzy zadaniami dokumentów i widoków ułatwia podjęcie decyzji, w jaki sposób należy przetworzyć dany komunikat. Na przykład, jeśli aplikacja posiada polecenie Zapisz, które użytkownik wybiera w celu zapisania danych obiektu, powinno ono zostać obsłużone przez obiekt dokumentu - gdyż to polecenie odnosi się do danych, a nie do tego, jak użytkownik widzi te dane.
Z drugiej strony, jeśli aplikacja obsługuje polecenie Kopiuj, które użytkownik wykorzystuje do skopiowania danych wybranych w konkretnym oknie, prawdopodobnie zechcesz obsłużyć je w danym obiekcie widoku. W rzeczywistości, jeśli dokument obsługuje kilka widoków, dane zaznaczone w każdym z okien mogą być różne - z czego jeszcze jaśniej wynika, że polecenie Kopiuj powinno być przetwarzane przez każdy obiekt widoku oddzielnie.
Oba opisane przypadki były oczywiste - w pierwszym przykładzie chodziło o zapis danych, zaś w drugim o skopiowanie danych reprezentowanych w jednym z widoków. Jednak istnieją także przypadki graniczne. Takim przypadkiem jest polecenie Wklej. Wyznaczenie, czy polecenie powinno zostać obsłużone w klasie dokumentu czy w klasie widoku, jest już bardziej złożone. Polecenie Wklej wpływa na cały dokument (gdyż wstawiasz dane do dokumentu), a nie tylko na pojedynczy widok. Z drugiej strony, bieżący widok może mieć znaczny wpływ na sposób wklejenia danych do dokumentu. Na przykład, polecenie Wklej może spowodować zastąpienie danych zaznaczonych w aktywnym widoku. Innymi słowy, podjęcie decyzji, czy polecenie powinno zostać obsłużone przez dokument czy przez widok, w przypadku takich poleceń zależy od struktury aplikacji i w każdym razie jest czymś, co powinieneś dokładnie przemyśleć.
Co ciekawe, pewnych poleceń nie powinieneś przetwarzać ani w klasie widoku, ani w klasie dokumentu, lecz raczej w klasie okna ramki. Doskonałymi przykładami poleceń, które powinny zostać obsłużone w oknie ramki, są polecenia służące do ukrywania i wyświetlania pasków narzędzi. Obecność lub nieobecność paska narzędzi nie ma szczególnego wpływu na dokument czy widok. Zamiast tego jest elementem konfiguracji, globalnym dla całej aplikacji.
Przesłanianie wirtualnych funkcji dokumentu
Jak dowiedziałeś się z wcześniejszej części rozdziału (przy opisie funkcji OnNewDocu-mento oraz Serialize ()), wiele z funkcji składowych klasy CDocument jest funkcjami wirtualnymi, co oznacza, że możesz przesłonić je we własnej deklaracji klasy. Funkcje wirtualne klasy CDocument zapewniają domyślne przetwarzanie odpowiednie w większości przypadków. Jednak od czasu do czasu zdarzy się sytuacja, w której takie domyślne przetwarzanie Ci nie wystarczy.
Na przykład, klasa CDocument oraz jej klasy pochodne wywołują funkcję składową on-NewDocument () za każdym razem, gdy program inicjalizuje nowy obiekt dokumentu (lub gdy ponownie wykorzystuje obiekt dokumentu w aplikacji SD1). Funkcja onNewDo-cument (} zwykle jest wywoływana w wyniku wybrania polecenia Plik | Nowy. Podobnie, klasa CDocument wywołuje funkcję składową oncloseDocument () za każdym razem gdy aplikacja ma za chwilę zamknąć dokument. Jeśli aplikacja przed zniszczeniem obiektu dokumentu musi przeprowadzić jakieś porządki, powinieneś przesłonić tę funkcję własną funkcją.
Klasa dokumentu wywołuje funkcję OnOpenDocument () w celu odczytania dokumentu z dysku oraz funkcję OnSaveDocument () w celu zapisania dokumentu na dysk. Powinieneś przesłonić te funkcje tylko wtedy, gdy domyślna implementacja (wywołująca funkcję składową Serialize ()) jest niewystarczająca. Doskonałym przykładem takiej sytuacji może być program szyfrujący dane przed zapisem na dysk i odszyfrowujący je po odczytaniu z dysku.
Domyślne implementacje funkcji OnOpenDocument () oraz OncloseDocument () wywołują funkcję składową DeleteContents (). Ta funkcja usuwa zawartość dokumentu bez niszczenia samego obiektu. Użycie funkcji DeleteContents () w momencie otwierania nowego dokumentu jest bardziej efektywne (w kontekście zarówno wykorzystania pamięci, jak i szybkości działania aplikacji) niż zamykanie i niszczenie oryginalnego dokumentu, a następnie tworzenie nowego obiektu.
Funkcja składowa OnFileSendMail () wysyła obiekt dokumentu jako załącznik wiadomości e-mail. Najpierw wywołuje funkcję OnSaveDocument () w celu zapisania kopii dokumentu do tymczasowego pliku na dysku (w kartotece określonej przez zmienną środowiskową TEMP). Następnie kod programu wewnątrz funkcji dołącza ten tymczasowy plik do wiadomości MAPI (Messaging AP1). Funkcja używa innej funkcji składowej, OnUpdateFileSendMail (), w celu włączenia polecenia menu identyfikowanego przez stałą ID_FILE_SEND_MAIL lub usunięcia go, jeśli wsparcie MAPI jest dla programu niedostępne. Obie funkcje, OnFileSendMail () oraz OnUpdateFileSendMail {) są funkcjami przesłanialnymi, dzięki czemu możesz (względnie łatwo) zaimplementować własne wysyłanie wiadomości w swojej aplikacji.
Praca ze złożonymi danymi dokumentu
We wcześniejszej części rozdziału nauczyłeś się wyprowadzania prostych klas dokumentów z klasy CDocument, wewnątrz których mogłeś przechowywać dane dokumentu jako serie prostych zmiennych składowych. Jednak aplikacje tworzone w rzeczywistych warunkach zwykle są bardziej wymagające. Większość tworzonych aplikacji wymaga nieco bardziej zaawansowanych danych niż mógłbyś zaprezentować za pomocą kilku zmiennych o prostych typach.
W obiekcie dokumentu można stosować kilka różnych rozwiązań związanych z operowaniem złożonymi strukturami danych, jednak chyba najlepszym z nich jest użycie zestawu klas wyprowadzonych z klasy CObject. Każda wyprowadzona klasa może posłużyć do przechowania złożonych obiektów danych. Dokument z kolei używa standardowej lub własnej klasy kolekcji w celu przechowania obiektów tych klas. Na przykład, możesz stworzyć definicje danych podobne do następujących:
class CAppObject : public CObject
{
// definicje
}
class CAppObjectl : public CObject
{ // definicje
}
class CAppObject2 : public CObject
{
// definicje
}
Następnie, wewnątrz deklaracji klasy dokumentu, możesz zamieścić składową typu cobList. Klasa cobList obsługuje uporządkowane listy wskaźników do niejednorodnych obiektów cobject, dostępnych sekwencyjnie lub poprzez wartość wskaźnika. Listy cobList zachowują się jak podwójnie połączone listy. Tak więc deklaracja dokumentu mogłaby wyglądać na przykład tak:
class CSampleDoc : public CDocument
{ // deklaracje
public:
CObList m_DataObList;
// deklaracje
}
W złożonych sytuacjach, takich jak opisywana tutaj, samo zadeklarowanie zmiennych składowych jest często niewystarczające. Twoja klasa dokumentu powinna także zawierać funkcje składowe stanowiące metody umożliwiające widokom i innym obiektom dostęp do danych dokumentu. Na przykład, możesz nie pozwolić innym klasom (na przykład
klasie widoku) na bezpośrednie manipulowanie zmienną m_DataObList. Zamiast tego powinieneś dostarczyć funkcję składową, poprzez którą klasa widoku może w razie potrzeby odwołać się do obiektów na tej liście.
Takie funkcje składowe powinny także zapewnić, że za każdym razem gdy zmieniają się dane dokumentu, aplikacja prawidłowo aktualizuje wszystkie widoki tego dokumentu. Tego typu funkcje składowe powinny także wywoływać funkcję setModifiedFiag ( ) w celu poinformowania dokumentu, że jakaś funkcja lub klasa zmieniła zawarte w nim dane. Jeśli Twoja aplikacja ma obsługiwać mechanizm cofania ostatnich poleceń, wewnątrz tych funkcji powinieneś zastosować także kod umieszczający w buforze dane umożliwiające cofnięcie polecenia. Aby to lepiej zrozumieć, przyjrzyj się poniższej funkcji, AddNewObj ( ) , dodającej nowy obiekt do listy obiektów w dokumencie:
BOOL CSampleDoc: : AddNewObj (CAppObject *pObject) {
try {
m_DataObList .AddTail ( (CObject* ) pObject ) ;
SetModifiedFiag (TRUE) ;
UpdateAllYiews (NULL, UPDATE_OBJECT, pObject);
return TRUE; }
catch (CMemoryException *pe) {
TRACĘ ("Doc-AddNewObj : Błąd alokacji pamięci . \n" ) ;
pr->Delete () ;
return FALSE;
}
}
Zrozumienie znaczenia funkcji składowej AddNewObj () jest łatwiejsze, gdy weźmiesz pod uwagę relacje pomiędzy dokumentem a jego widokami oraz sposobem, w jaki program przekazuje pomiędzy nimi sterowanie.
Najpierw użytkownik współpracuje z widokiem, co może w rezultacie doprowadzić do dodania nowego obiektu, usunięcia lub zmodyfikowania istniejącego obiektu czy też jakieś innej akcji. Załóżmy teraz, że w wyniku działań użytkownika wystąpiła potrzeba dodania nowego obiektu do dokumentu. Aby dodać nowy obiekt, obiekt widoku wywołuje funkcję składową AddNewObject ( ) . Gdy ta funkcja składowa z powodzeniem doda obiekt do listy, obiekt dokumentu wywołuje funkcję składową UpdateAllViews ( ) , która z kolei wywołuje funkcję składową onUpdate ( ) każdego z widoków powiązanych z danym obiektem widoku dokumentu. Funkcja AddNewObj () przekazuje funkcji updateAll-views ( ) wskazówkę, poprzez użycie zdefiniowanej dla aplikacji stałej UPDATE_OB JECT oraz wskaźnika do obiektu CObject. Ta wskazówka pomaga wszystkim widokom w efektywnym odświeżeniu własnych okien przez odmalowanie tylko tych regionów, na które bezpośrednio lub pośrednio wpłynęło dodanie nowego obiektu. Mechanizm przekazywania sterowania pomiędzy dokumentem a widokami pokazuje rysunek 19.3.
Kolejną korzyścią płynącą z użycia klas kolekcji MFC w aplikacji jest wsparcie dla se-rializacji takich kolekcji. Na przykład, aby załadować lub odczytać dane dokumentu zawarte wobiektach CObject i dostępne poprzez obiekt listy CObList, jedyne co musisz zrobić to skonstruować funkcję składową Serialize() dokumentu:
515
Rys. 19.3.
Sterowanie
przechodzi
od klasy widoku
do klasy dokumentu
i z powrotem
Klasa widoku
Użytkownik dodaje obiekt: GetDocument()->AddObject():
(strzalka do kl.dok.)
Klasa dokumentu
AddObject()
{
m DataObjList.AddTail(); SetModifiedFIagO; UpdateAIIViews();
(strzalka do OnUp..}
OnUpdate()
{
Następuje aktualizacja widoku
void CSampleDoc::Serialize(CArchive &ar) {
if(ar.IsStoring())
{
// serializacja danych klasy nie należących do kolekcji } else
{
// serializacja danych klasy nie należących do kolekcji
} m DataObList.Serialize(ar);
}
Musisz jednak pamiętać, że aby ta technika zadziałała, musisz zaimplementować funkcje składowe Serialize () we wszystkich klasach swoich obiektów. Klasa wyprowadzona z cobject nie serializuje się sama. Jeśli zdecydujesz się na użycie którejś z klas wzorców kolekcji ogólnego przeznaczenia, serializacja staje się zagadnieniem, któremu należy poświęcić dużą uwagę. Kolekcje CArray, CList oraz CMap przy serializowaniu elementów kolekcji bazują na funkcji składowej serializeElements (). MFC deklaruje tę funkcję następująco:
template
void AFXAPI
SerializeElements(CArchive &ar, TYPE *pElements, int nCount);
Ponieważ wzorzec klasy kolekcji nie wymaga, by klasa TYPE była wyprowadzona z klasy cobject, nie są więc wywoływane funkcje Serialize () dla poszczególnych elementów (ponieważ nie ma żadnej gwarancji, że funkcja Serialize o istnieje w klasie obiektu). Zamiast tego, domyślna implementacja funkcji SerializeElements () przeprowadza akcję bitowego odczytu lub zapisu. Jednak, jak zapewne się domyślasz, w większości przypadków bitowy zapis lub odczyt nie jest tym, co chcesz uzyskać. Zamiast tego powinieneś więc zaimplementować własną funkcję SerializeElements () dla swoich obiektów. Mógłbyś zaimplementować j ą mniej więcej tak:
void SerializeElements(CArchive &ar, CAppObject **pObs, int nCount)
{
for (int i = 0; i < nCount; i++, pObs++) (*pObs) ->Serialize (ar) ;
}
Zalety klas CCmdTarget oraz CDocItem
Jak dowiedziałeś się z poprzednich sekcji tego rozdziału, do przechowywania danych w dokumentach możesz używać obiektów wyprowadzonych z klasy cobject. Niestety, jeśli chcesz, by Twoje dokumenty i aplikacje obsługiwały automatyzację OLE, klasa cobject nie wystarczy. Zamiast tego musisz zadeklarować swoje obiekty jako cele poleceń (ang. command targef). Jeśli chcesz obsłużyć automatyzację OLE, możesz wyprowadzić swoje dane z klasy bazowej MFC CCmdTarget.
Możesz także, co zresztą jest ogólnie lepszym rozwiązaniem, wyprowadzić swoje obiekty danych z klasy MFC CDocItem. Możesz albo samodzielnie stworzyć kolekcję obiektów CDocItem, albo pozostawić to klasie MFC coieDocument. Innymi słowy, zamiast wyprowadzać swoją klasę z klasy CDocument, wyprowadź jaz klasy coieDocument. Klasy coieDocument możesz używać w aplikacjach OLE, w których klasą bazową dokumentu aplikacji jest albo sama klasa coieDocument, albo klasa z niej wyprowadzona. Podobnie jak CDocument, klasa coieDocument jest klasą kolekcji. Klasa coieDocument obsługuje kolekcję obiektów CDocItem, wyprowadzonych z kolei z klasy COleServerltem lub COleClientltem. W każdym razie klasa coieDocument obsługuje klasę CDocItem ogólnie (tzn. nie ma znaczenia fakt czy element odnosi się do klienta, czy do serwera). Ogólna implementacja klasy coieDocument oznacza, że możesz dodawać do kolekcji własne obiekty wyprowadzone z klasy CDocItem, nie martwiąc się, że wpłyniesz na działanie i zachowanie operacji OLE.
Jedną z miłych rzeczy w klasie coieDocument jest to, że składowe CDocItem są dodawane automatycznie. Innymi słowy, jeśli użyjesz funkcji Addltemf), Removeitem(), GetStartPosition() oraz GetNextPosition{), możesz dodawać, usuwać i odwoływać się do obiektów dokumentów bez konieczności wykonywania dodatkowych operacji. Ukryty w klasie kod MFC obsługuje także inne operacje (na przykład serializację), także bez dodatkowego udziału z Twojej strony.
Jednak programowanie z użyciem klasy coieDocument nie jest pozbawione pułapek. Z powodu sposobu wyprowadzania swoich obiektów dokumentów oraz wyprowadzenia obiektów z klas OLE COleClientltem i C01eServerltem, może być konieczne dodanie specjalnego kodu w celu odwołania się do danego obiektu. Na przykład, przypuśćmy, że zadeklarowałeś swoje obiekty danych jako:
class CSampleDocItem : public CDocItem
{
// jakiÅ› inny kod CRect m rect;
}
Oprócz tego, załóżmy, że zmienną składową m_Rect obsługujesz także wewnątrz swoich elementów klienta OLE:
class CSampleClientltem : public COleClientltem
{
// jakiÅ› inny kod CRect m rect;
}
Mając te dwie deklaracje, możesz przypuszczać, że istnieje możliwość napisania funkcji pobierającej element z dokumentu i manipulującej jego składowąm_Reet:
void sampFunc(CDocItem *pltem)
{
samp2Func(p!tem->m_Rect); // BÅ‚Ä…d!
}
Ponieważ klasa CDocItem sama z siebie nie zawiera zmiennej składowej m_Rect, kompilator wstrzymuje kompilację programu z błędem nieznanej składowej. Niestety, użycie wskaźnika do własnej klasy wyprowadzonej z CDocItem także nie rozwiązuje problemu:
void sampFunc(CSampleDocItem *pltem)
{
samp2Func(p!tem->m_Rect); }
Choć takie zadeklarowanie funkcji powoduje obsłużenie wyprowadzonej klasy, jednak nie obsługuje elementów klienta OLE o typie CDocItem - co ma duże znaczenie. Oczywistym rozwiązaniem jest proste utworzenie dwóch przesłoniętych funkcji sampFunc (), lecz operowanie dwoma identycznymi wersjami tej samej funkcji jest nie tylko brakiem elegancji, ale także powoduje duże trudności w konserwacji kodu. Zamiast tego najlepszym rozwiązaniem jest użycie funkcji pośredniej pobierającej wskaźnik do obiektu CDocItem i użycie informacji czasu wykonania MFC w celu uzyskania zmiennej składowej:
CRect GetRect(CDocItem *pDoc!tem) {
if(pDocItem->IsKindOf(RUNTIME_CLASS(CSampleDocItem)))
return ((CSampleDocItem*)pDocItem)->m_Rect; else if (pDodtem->IsKindOf (RUNTIME_CLASS (CSampleClientltem) ))
return ((CSampleClientltem*)pDocItem)->m_Rect; ASSERT(FALSE); return CRect(O, O, O, 0);
}
sampFunc(CDocItem *pItem) {
samp2Func(GetRect(pltem));
};
To rozwiązanie wymaga jednak zadeklarowania i zaimplementowania obu klas, csample-Docitem oraz CSampleClientltem z użyciem makr DECLARE_DYNAMIC oraz IMPLE-MENT_DYNAMIC. Jednak nie powinno to stwarzać problemu, gdyż obiekty dokumentów zwykle obsługują serializację. Gdy deklarujesz i implementujesz klasę w użyciem makr DECLARE_SERIAL i IMPLEMENT_SERIAL, powoduje to także niejawne użycie makr
DECLARE_DYNAMIC OrazIMPLEMENT_DYNAMIC.
Znaczenie funkcji Initlnstance dla dokumentów
Jak dotąd w tym rozdziale uczyłeś się o wyprowadzaniu klas dokumentów w celu użycia we własnych aplikacjach. W następnej sekcji poznasz klasy widoku oraz sposób ich użycia przy odwoływaniu się do danych zawartych w dokumencie. Jednak zanim przejdziemy do widoków, cenne będzie zrozumienie, jak jednodokumentowa aplikacja MFC odwołuje się do początkowego dokumentu.
Gdy do stworzenia nowej aplikacji używasz AppWizarda, zostaje zaimplementowana przesłanialna funkcji Initlnstance () o zawartości zależnej od opcji wybranych w oknach dialogowych AppWizarda. Na przykład, jeśli wybierzesz aplikację jednodokumentowa (SDI), AppWizard utworzy następujący kod:
BOOL CMyApp::Initlnstance()
{
// Standard initialization
SetDialogBkColor(); // Set dialog backgroundcolor to gray
LoadStdProfileSettings(); // Load standard INI file options
// Register the application's document templates.
// Document templates serve as the connection between
// documents, framÄ™ windows and views.
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMainFrame), // main SDI framÄ™ window
RUNTIME_CLASS(CMyView)); AddDocTemplate(pDocTemplate);
OnFileNew(); // create an empty document if (m_lpCmdLine[0] != '\0')
{
// TODO: add command linÄ™ processing here
} return TRUE;
}
Funkcja Initlnstance () stworzona przez AppWizarda najpierw ustawia szary kolor tła dialogu. Następnie ładuje opcje zawarte w standardowym pliku INI. Po zajęciu się tymi podstawowymi przygotowaniami, kod tworzy wskaźnik do wzorca dokumentu, służącego jako połączenie pomiędzy klasą CDocument, klasą okna ramki oraz wszelkimi widokami stworzonymi dla tego dokumentu. W tym przypadku AppWizard zarejestrował klasę dokumentu jako CMyDoc, okno ramki jako CMainFrame, zaś klasę widoku jako CMyView. Następnie program dodaje za pomocą funkcji AddDocTemplate o nowo utworzony wzorzec dokumentu do kolekcji wzorców dokumentów oraz wywołuje funkcję składową OnFiieNewO klasy CMyDoc w celu stworzenia początkowego, pustego dokumentu. Ostatnie linie tej funkcji umożliwiają dodanie kodu przeznaczonego do przetwarzania zawartości linii polecenia (na przykład nazwy pliku przeznaczonego do otwarcia).
W rozdziale 20. dowiesz się czym się, różni funkcja Initlnstance () w przypadku programu wielodokumentowego (MDI). Na razie wystarczy, że zapamiętasz, w jaki sposób MFC używa klasy CSingleDocTemplate do powiązania w programie dokumentu, widoku oraz okna ramki.
W jaki sposób aplikacja zarządza dokumentami i widokami
Teraz, gdy dowiedziałeś się już o dokumentach i zostałeś pobieżnie wprowadzony we wzorce dokumentów, ważne jest, byś zrozumiał, w jaki sposób MFC utrzymuje powiązania pomiędzy dokumentami i widokami składającymi się na aplikację oraz skąd wie, które widoki i dokumenty są ze sobą powiązane. Informacje czasu wykonania dla klasy, z którymi zetknąłeś się w poprzednich sekcjach, pomogą Ci w zrozumieniu tych powiązań.
Ponieważ architektura typu dokument-widok stanowi kamień węgielny każdej aplikacji korzystającej z dokumentów (jak już wiesz, aplikacje oparte na oknie dialogowym zachowują się nieco inaczej), MFC musi być w stanie tworzyć i niszczyć obiekty klas implementujących tę architekturę. Ponieważ aplikacja może obsługiwać więcej niż jeden rodzaj powiązań typu dokument-widok, MFC musi mieć jakiś sposób określenia, jakie klasy dokumentów, widoków i ramek mają być zaimplementowane, jakie są powiązania pomiędzy tymi klasami oraz w jaki sposób mają być tworzone egzemplarze tych klas podczas działania programu. W końcu, o ile pojedynczy dokument może obsłużyć wiele różnych rodzajów widoków, o tyle przypisywanie mu widoków innych rodzajów może być bezcelowe.
Klasa CSingleDocTemplate
Aby dowiedzieć się, jak MFC opisuje i utrzymuje te powiązania, za pomocą AppWi-zarda stwórz prostą jednodokumentową aplikację lub przyjrzyj się przykładowej aplikacji HexView, którą będziemy tworzyć w dalszej części rozdziału. W obu przypadkach znajdziesz kod źródłowy wyglądający podobnie do poniższego fragmentu (identycznego zresztą z tym, który widziałeś w jednej z wcześniejszych sekcji, przy okazji omawiania funkcji składowej initinstance ()):
CSingleDocTemplate* pDocTemplate;
pDocTeraplate = new CSingleDocTemplate(IDR_MAINFRAME,
RUNTIMEJCLASS(CMyDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CMyYiew)); AddDocTemplate(pDocTemplate);
Ten kod dynamicznie alokuje nowy obiekt CSingleDocTemplate. Konstruktor tej klasy przyjmuje cztery parametry. Pierwszym z nich jest identyfikator zasobu; omówimy go w dalszej części rozdziału. Drugim, trzecim oraz czwartym parametrem są wskaźniki do informacji czasu wykonania (runtime) dla kilku klas. Makro RUNTIME_CLASS () generuje wskaźnik do informacji czasu wykonania dla klasy dokumentu, głównej ramki oraz widoku. Wszystkie te wskaźniki są przekazywane do konstruktora klasy CSingleDocTemplate, który przechowuje je w celu umożliwienia tworzenia w razie potrzeby egzemplarzy obiektów składających się na kompletny zespół dokument-widok.
Obiekt CSingleDocTemplate istnieje tak długo, jak długo działa aplikacja. MFC używa go wewnętrznie i sama niszczy wszystkie utworzone wzorce dokumentów (w momencie niszczenia obiektu cwinApp). W swojej aplikacji kod alokujący obiekt csingleDoc-
Template znajdziesz w funkcji CWinApp: : InitIstance (), lecz nigdzie nie znajdziesz w niej kodu usuwającego egzemplarze wzorców dokumentów dodane za pomocą funkcji AddDocTemplate (). Dzieje się tak, ponieważ funkcja destruktora klasy cwinApp automatycznie niszczy wszystkie dodane wzorce dokumentów.
Okna ramek
Jak dotąd, w tym rozdziale zajmowaliśmy się dokumentami i ramkami - i będziemy się nimi zajmować także w dalszej części rozdziału. Jednak prawdopodobnie już zdajesz sobie sprawę ze znaczenia ramek w modelu aplikacji typu dokument-widok - mimo że z klasami ramek nawet w przybliżeniu nie będziesz pracował tak często jak z klasami widoków i dokumentów.
W rzeczywistości, widok implementowany przez aplikację jest oknem, lecz nie jest to okno pop-up ani okno ramki. Zamiast tego jest to pozbawione ramki okno potomne, nie posiadające także własnego menu, przez co musi być zawarte w jakiegoś rodzaju ramce. MFC umieszcza tworzone okno widoku w obszarze roboczym okna ramki wskazanego w konstruktorze wzorca dokumentu. W aplikacji SDI okno ramki jest zawsze głównym oknem aplikacji. Jak zobaczysz w rozdziale 20., okno ramki dla widoku w aplikacji wielodokumentowej jest oknem potomnym MDI.
Tworząc aplikacje Windows, większość programistów nie podejmuje dodatkowego kroku w celu rozdzielenia obszaru roboczego swojej aplikacji od okna ramki. Zamiast tego zwykle tworzone jest okno z użyciem stylu WS_OVERLAPPED, zaś rysowanie odbywa się bezpośrednio w jego obszarze roboczym. Aby uczynić MFC bardziej modularnym, Microsoft zaimplementował je tak, by dokonywało rozróżnienia pomiędzy dwoma rodzajami okien ramek, których możesz używać. To znaczy, MFC dokonuje zarówno wewnętrznego, jak i zewnętrznego rozróżnienia pomiędzy oknem ramki interfejsu jed-nodokumentowego a oknem ramki interfejsu wielodokumentowego. O oknach ramek porozmawiamy nieco później, a na razie wystarczy informacja, że to właśnie okno ramki jest tym oknem, które otrzymuje wszystkie komunikaty menu oraz obszaru ramki.
Komunikaty ramki w Windows są komunikatami, które otrzymują jedynie okna z ramkami. Należą do nich komunikaty o zmianie rozmiaru okna, jego maksymalizacji, minimalizacji itd. Okno ramki otrzymuje także wiele innych komunikatów związanych z obszarem ramki, gdyż to właśnie okno ramki jest w aplikacji odpowiedzialne za obsługę tego obszaru.
Zasoby wzorców dokumentów
Jak wiesz z sekcji zatytułowanej "Klasa CSingleDocTemplate", pierwszym parametrem konstruktora tej klasy jest identyfikator zasobu. Ten parametr informuje ramkę używaną we wzorcu o rodzaju zasobów, jakie ramka musi mieć do dyspozycji, aby można było poprawnie utworzyć powiązanie. Ten identyfikator wskazuje zasoby używane przez ramkę, takie jak tablica akceleratorów, menu oraz ikona. Okno ramki używane w aplikacji powinno stosować ten sam identyfikator zasobu dla każdego rodzaju zasobu, którego chce użyć.
Jeśli przejrzysz plik HexView.rc, znajdziesz w nim tablicę akceleratorów, menu oraz ikonę, wszystkie oznaczone identyfikatorem IDR_MAINFRAME - odpowiadającym identyfikatorowi przekazywanemu przez aplikację konstruktorowi klasy csingleDocTemplate. Korzystanie z tego samego identyfikatora jest o wiele bardziej wygodne niż konieczność przekazywania do konstruktora sześciu czy siedmiu parametrów.
Identyfikator zasobu jest także identyfikatorem pozycji w tablicy łańcuchów aplikacji. Wskazywany łańcuch ma bardzo specyficzny format: jest to w rzeczywistości siedem łańcuchów w jednym, z których każdy jest rozdzielony znakiem nowej linii (\n).
W jaki sposób zasób łańcucha wpływa na wzorzec dokumentu
Jak wspominaliśmy w poprzedniej sekcji, identyfikator zasobu IDR_MAINFRAME w rzeczywistości odnosi się do kilku różnych zasobów w aplikacji. Jednym z nich jest łańcuch znaków zawierający siedem podłańcuchów. Podłańcuchy wewnątrz tego łańcucha określają siedem elementów dotyczących aplikacji, a właściwie dokumentu implementowanego przez aplikację. Pierwszy podłańcuch zawiera tytuł okna dla głównego okna aplikacji, w momencie gdy zostanie uaktywniony rejestrowany typ dokumentu. Drugi podłańcuch zawiera bazową nazwę domyślnego dokumentu - MFC doda do tej nazwy kolejną liczbę dla każdego dokumentu otwartego przez użytkownika. Na przykład, jeśli ten podłańcuch zawiera napis Hexview, trzeci dokument otwarty z użyciem tego wzorca będzie nosił nazwę HexView3. W innych książkach na temat Visual C++ ten podłańcuch może być nazywany nazwą dokumentu. Jeśli drugi podłańcuch jest pusty, jako nazwy domyślnego dokumentu aplikacja użyje napisu Untitied (bez tytułu).
Trzeci podłańcuch jest nazwą typu dokumentu. Jeśli aplikacja obsługuje więcej niż jeden rodzaj dokumentu, po wybraniu przez użytkownika polecenia New w menu File zostaje wyświetlona lista typów dokumentów do wyboru. Jeśli jako trzeci podłańcuch wpiszesz łańcuch pusty, rejestrowany typ dokumentu nie pojawi się na liście w oknie dialogowym New. Możesz użyć tego sposobu w celu ukrycia tych typów dokumentów, których użytkownik nie powinien móc tworzyć bezpośrednio.
Czwarty podłańcuch zawiera opis typu dokumentu oraz filtr (zawierający znaki \vild-card) nazw plików używany przez aplikację przy dopasowywaniu plików dokumentów rejestrowanego typu. Ten łańcuch jest dodawany do listy Pliki typu w oknie dialogowym Otwórz Plik w aplikacji. Na przykład, jeśli posiadasz mapę rozmieszczenia budek telefonicznych z rozszerzeniem .but, możesz w tym podłańcuchu wpisać Budki Telefoniczne (* .but). Tekst zawarty w tym łańcuchu służy jedynie wygodzie użytkownika - innymi słowy, w żaden sposób nie określa faktycznych znaków wildcard używanych do wyszukiwania plików przez okno dialogowe Otwórz Plik.
Takie informacje są zawarte dopiero w piątym podłańcuchu. Jeśli nie podasz w nim rozszerzenia pliku, zostanie wybrane pierwsze z domyślnych rozszerzeń. Nazwa rozszerzenia powinna zawierać wiodącą kropkę, lecz nie powinna zawierać gwiazdki (*). Na przykład, poprawną nazwą jest .xyz, podczas gdy nazwy xyz lub * .xyz są niepoprawne.
Szósty podłańcuch identyfikuje typ dokumentu dla bazy danych rodzajów dokumentów. Ten łańcuch jest używany przez Eksploratora Windows, Rejestr oraz OLE dla celów zarejestrowania typu dokumentu. Nie jest on pokazywany użytkownikom bezpośrednio, lecz można go poznać, wyświetlając okno dialogowe Powiązania plików w Eksploratorze Windows lub bezpośrednio przeszukując Rejestr.
Siódmy i ostatni podłańcuch jest nazwą dokumentu w formie przechowywanej w Rejestrze Windows. Zakładając, że implementujesz obsługę OLE, ten łańcuch jest używany przez Rejestr (i pośrednio przez samo OLE) do identyfikacji typu dokumentu implementowanego przez aplikację. Na przykład, zarówno Word, jak i Excel wyświetlają tę nazwę na liście obiektów, w momencie gdy użytkownik wybierze w menu Wstaw polecenie Obiekt w celu osadzenia w dokumencie obiektu zarządzanego przez Twoją aplikację. Powinieneś starać się utrzymać ten łańcuch krótki i zwięzły, ale na tyle znaczący, by użytkownik nie miał problemów ze zorientowaniem się, czego dotyczy.
Podział zasobu łańcucha
Ponieważ zasób łańcucha ma kluczowe znaczenie w procesie rejestracji dokumentu, zaś poprzez jego modyfikację możesz w subtelny sposób wpływać na właściwości aplikacji, powinieneś uważnie traktować to, co w nim wpisujesz. Domyślny łańcuch stworzony przez AppWizarda dla aplikacji HexView wygląda następująco:
"HexView\n\nHexView\n\n\nHexView.Document\nHexView Document"
Ten domyślny łańcuch sprawia, że tytułem okna aplikacji jest Hexview, typem dokumentu w oknie New aplikacji jest HexView, wewnętrzną nazwą OLE jest Hexview. Document, zaś nazwą zewnętrzną jest HexView Document.
Klasa CDocTemplate, z której MFC wyprowadza klasy csingleDocTemplate oraz CMultiDocTemplate, zawiera funkcję składową GetDocString (). Ta funkcja, która akceptuje zarówno referencję do obiektu cstring, jak i indeks, umożliwia odczytanie wartości zarejestrowanego zasobu łańcucha. CDocTemplate definiuje tę funkcję następująco:
virtual BOOL GetDocString(CString&r
Sting, enum DocStringIndex index) const;
Drugi parametr, index, jest wyrażony jako prywatna wartość wyliczeniowa klasy CDocTemplate. Dozwolone wartości tego parametru zostały zebrane w tabeli 19.1.
Nie istnieje odpowiednia funkcja CDocTemplate: :SetDocString ().Po utworzeniuwzorca dokumentu nie można już zmieniać jego atrybutów.
Jak już wiesz, zasób łańcucha określa wiele subtelnych aspektów aplikacji, zaś domyślny łańcuch stworzony przez AppWizarda tworzy sensowny, lecz raczej prosty interfejs użytkownika. Zmiany wartości w tym łańcuchu możesz dokonać także podczas działania AppWizarda, na zakładce Document Template Strings w oknie dialogowym Advanced Options. Wszystkie pola na tej zakładce odnoszą się bezpośrednio do części łańcucha. Jeśli wiesz, jak ma działać aplikacja po stworzeniu, możesz dokonać zmian już podczas tworzenia jej szkieletu, unikając w ten sposób bezpośredniej modyfikacji łańcucha w zasobach.
Tabela 19.1. Dozwolone wartości parametru index
Wartość
Opis
CDocTemplate::windowTitle
Tekst na belce tytułowej głównego okna aplikacji; ma znaczenie jedynie w przypadku aplikacji SDL
CDocTemplate::docName
Część bazowa domyślnej nazwy dokumentu.
CDocTemplate::fileNewName
Nazwa dokumentu w oknie dialogowym New.
CDocTemplate::filterName
Tekst dla pozycji listy Typ Pliku w oknie dialogowym Otwórz Plik.
CDocTemplate::filterExt Filtr wildcard do dopasowywania plików dla tego typu dokumentów.
CDocTemplate::regFileTypeld
Wewnętrzna nazwa dla bazy danych Rejestru, używana przez OLE oraz przez Eksploratora Windows.
CDocTemplate::regFileTypeName
Nazwa dokumentu dla bazy danych Rejestru, używana przez aplikacje OLE oraz wyświetlana dla użytkownika.
Jednak uzupełniając aplikację o nowe możliwości, z pewnością zostaniesz zmuszony do dokonania zmian w łańcuchu. Aby je wprowadzić, musisz posłużyć się edytorem tablicy łańcuchów. Jeśli się pomylisz, MFC albo na ślepo wstawi błędny łańcuch w błędną część interfejsu użytkownika, albo już w czasie działania programu wyświetli okno dialogowe błędu asercji. Ogólnie, gdy modyfikujesz łańcuch wzorca, powinieneś zaraz po tym sprawdzić działanie aplikacji, aby upewnić się, że nie wyłączyłeś lub nie naruszyłeś jej innych aspektów.
Przegląd standardowych zasobów widoków
Identyfikator zasobu we wzorcu dokumentu określa także wiele innych zasobów w aplikacji. Wszystkie one są powiązane z typem dokumentu, który jest aktualnie edytowany w oknie. W przypadku tworzonych przez AppWizarda aplikacji SDI identyfikatorem zasobu łańcucha wzorca oraz innych powiązanych z dokumentem zasobów jest IDR_MAINFRAME.
AppWizard nadaje głównej ramce aplikacji samodzielne menu, które także jest wskazywane poprzez identyfikator IDR_MAINFRAME. W przypadku aplikacji SDI oznacza to, że menu zależy od rodzaju aktywnego dokumentu. W aplikacjach z interfejsem MDI oznacza to że każdy typ dokumentu posiada własne menu. Jeśli chcesz, by wszystkie typy dokumentów były związane z tym samym menu, możesz po prostu zmienić wzorce, tak by wskazywały na ten sam zasób menu.
Okno ramki posiada również ikonę, także określaną przez ten sam identyfikator co inne zasoby; co wskazuje, że właśnie ta ikona ma zostać użyta do reprezentacji dokumentu SDI. Na przykład, system operacyjny wyświetla tę ikonę wraz z nazwą, w momencie gdy użytkownik zminimalizuje okno aplikacji. W przypadku aplikacji MDI ta ikona będzie wyświetlana jako ikona zminimalizowanego okna potomnego MDI. Ikona używana dla samej aplikacji pochodzi z innego źródła, które opiszemy w rozdziale 20.
W aplikacji może wystąpić także okno dialogowe, budowane na podstawie wzorca również określonego tym samym identyfikatorem zasobu. Takie aplikacje są rzadkie -zwykle w takich przypadkach okno dialogowe stanowi rdzeń interfejsu dla widoku dokumentu. W dalszej części rozdziału zajmiemy się widokami używającymi okna dialogowego jako rdzenia interfejsu, zaś samą opisywaną technikę zastosujemy w rozdziałach 26., 27. i 28., przy okazji pracy z bazami danych.
Przy budowie domyślnego paska narzędzi AppWizard korzysta z bitmapy także wskazywanej przez ten sam identyfikator zasobu. Choć pasek narzędzi wygląda na składany z osobnych przycisków, w rzeczywistości same obrazki przycisków są częściami większej bitmapy. O klasie CToolbar, używanej do tworzenia pasków narzędzi w MFC, dowiesz się w rozdziale 23.
Ponieważ okno ramki jest oknem posiadającym menu, może ono także korzystać z tablicy akceleratorów. Ta tablica zawiera kody tłumaczące kombinacje wciskanych klawiszy na komunikaty WM_COMMAND, wysyłane w momencie gdy system operacyjny wykryje określoną sekwencję klawiszy. Przeszukiwaniem tablicy akceleratorów oraz tłumaczeniem i rozsyłaniem komunikatów zajmuje się system operacyjny. Domyślna tablica akceleratorów utworzona przez AppWizarda zawiera akceleratory dla standardowych elementów interfejsu użytkownika w menu. Nie trzeba chyba dodawać, że także i ten zasób posiada ten sam identyfikator IDR_MAINFRAME.
Możesz do woli modyfikować te zasoby według własnego upodobania. Jednak aby uzyskać żądany efekt, sama modyfikacja zasobu może nie wystarczyć. Pamiętaj by zapoznać się z klasami korzystającymi z tych zasobów, tak aby po modyfikacjach aplikacja wciąż działała poprawnie.
Czas życia wzorca dokumentu
Jak zapewne się domyślasz, klasa csingieDocTempiate należy do klas lekkich, to jest zajmujących niewiele pamięci. Nie musisz się więc martwić, że zajmuje ona niepotrzebnie pamięć, nawet jeśli korzystasz z tuzinów różnych typów dokumentów. Klasa csingieDocTempiate (oraz CMultiDocTemplate, którą zajmiemy się w rozdziale 20.), jest intensywnie wykorzystywana przez aplikację. Po jej ustawieniu i uruchomieniu aplikacja posługuje się nią przy zarządzaniu obiektami dokumentu, widoku oraz okna -lecz Ty nie musisz już zajmować się nią bezpośrednio. Jak widziałeś w poprzedniej części rozdziału, aplikacja powinna zarejestrować wszystkie swoje wzorce dokumentów wewnątrz funkcji składowej CWinApp: : initinstance (). Czyniąc z nich publiczne składowe obiektu aplikacji, możesz odwoływać się do nich później, gdy wystąpi potrzeba żonglowania dokumentami i widokami.
Gdy przychodzi na myśli manipulowanie wzorcami dokumentów, prawdopodobnie najlepszym rozwiązaniem jest pozostawienie całej tej pracy bibliotece MFC. Innymi słowy, o utworzenie potrzebnych dokumentów i widoków oraz powiązanie ich ze sobą powinieneś poprosić kod zawarty we wzorcu dokumentu. W dobrze zaprojektowanej aplikacji prawie nigdy nie występuje konieczność bezpośredniego tworzenia własnych widoków, dokumentów i ramek. Najlepsze aplikacje całą tę pracę pozostawiają wzorcom dokumentów.
Zaawansowane posługiwanie się wzorcami
Teraz, gdy znasz już podstawy stosowania wzorców dokumentów, ważne jest, byś pomyślał o nich z perspektywy struktury aplikacji. Sposób działania aplikacji zależy w dużym stopniu od Twojego punktu widzenia. Jeśli użytkownicy będą musieli wykonać mnóstwo pracy w celu dostania się do widoku danych, którym są zainteresowani, szybko ulegną zniechęceniu. Co gorsza, jeśli widok w aplikacji nie prezentuje danych w sposób postrzegany przez użytkownika jako intuicyjny, Twoja aplikacja będzie postrzegana jako toporna, gdyż użytkownicy będą musieli spędzać zbyt dużo czasu zastanawiając, się jak powinna działać, zamiast z niej korzystać.
Standardowe szkielety aplikacji tworzone przez AppWizarda są zbyt dobre, aby z nich nie korzystać. Nawet po pewnych modyfikacjach MFC reaguje w dalszym ciągu w sposób ogólnie postrzegany przez użytkowników jako intuicyjny i spójny z interfejsem, do którego dotąd przywykli.
Jeśli rejestrujesz kilka różnych wzorców dokumentów, otrzymujesz dodatkowe okno dialogowe, które po wybraniu polecenia New w menu File umożliwia użytkownikowi wybór rodzaju dokumentu. A gdy już skorzystasz z tej prostej możliwości, wkrótce przekonasz się, że występuje także kilka innych miejsc, w których Twoja aplikacja mogłaby się nieco różnić od standardu.
Praca z kilkoma wzorcami
W aplikacji MFC typu dokument-widok zawsze możesz użyć więcej niż jednego wzorca dokumentu. Niezbędne wzorce dokumentów powinny zostać utworzone w funkcji cwinApp: : initinstance (). Jeśli aplikacja pochodzi od AppWizarda, znajdziesz w niej taką funkcję oraz kod rejestrujący parę dokument-widok dla dokumentu domyślnie używanego przez aplikację.
Jeśli kiedykolwiek zechcesz użyć dokumentów lub widoków w jakiejkolwiek innej kombinacji, powinieneś dodać kod w celu stworzenia wzorców dla takich par. Dzięki temu będziesz mógł łatwo stworzyć egzemplarze takich par na żądanie użytkownika. Utworzony obiekt klasy wyprowadzonej z klasy CDocTempiate jest po prostu obiektem i jako taki posiada wskaźnik, który po utworzeniu obiektu (operatorem new) powinieneś przechować. Ogólnie, wskaźniki do wzorców dokumentów powinieneś przechowywać w aplikacji w klasie wyprowadzonej z klasy cwinApp. Gdy tak zrobisz, podczas działania aplikacji będziesz mógł odwołać się do nich prawie w każdej chwili.
Każdy wzorzec zarejestrowany w aplikacji za pomocą funkcji cwinApp: :AddDoc-Template () jest przechowywany na połączonej liście. MFC używa jej do wyszukiwania wzorców, w momencie gdy użytkownik nakazuje utworzenie nowego dokumentu, nowego widoku lub żąda wykonania dowolnej operacji wymagającej odszukania wzorca dokumentu. Na przykład, gdy w momencie wywołania funkcji cwinApp:: OnFileNew () istnieje więcej niż jeden wzorzec dokumentu, MFC wyświetla listę pozwalającą użytkownikowi na wybranie wzorca nowego dokumentu, który chce utworzyć.
Nieudokumentowana klasa CDocManager
Lista jest zarządzana przez wewnętrzny egzemplarz klasy kolekcji MFC zwanej CDocManager. Ta klasa jest nieudokumentowaną właściwością MFC, jednak zrozumienie jej działania może być całkiem użyteczne. Egzemplarz tej klasy tworzy obiekt Twojej aplikacji wyprowadzony z klasy CWinApp. Obiekt CWinApp przechowuje wskaźnik do obiektu CDocManager, który jest niszczony tuż przed zakończeniem działania aplikacji, w destruktorze klasy cwinApp. Sam wskaźnik jest przechowywany w zmiennej składowej m_pDocManager. W rzeczywistości, możesz użyć go w dowolnym momencie w celu uzyskania dostępu do menedżera dokumentów.
Główna zaleta menedżera dokumentów wynika z tego, że zarządza on połączoną listą obiektów wzorców dokumentów. Menedżer dokumentów przechowuje tę listę w swojej publicznej składowej m_TemplateList. Dzięki temu możesz poruszać się po tej liście, stosując kod podobny do poniższego:
void CSampleWinApp::IterateEveryTemplate() {
CDocManager* pManager = AfxGetApp()->m_pDocManager;
if ( pManager == NULL ) return;
POSITION pos = pManager->GetFirstDocTemplatePosition();
while( pos != NULL )
{
// pobranie następnego wzorca
CDocTemplate* pTemplate =
pManager->GetNextDocTemplate(pos) ;
// teraz możesz zrobić coś z otrzymanym wskaźnikiem
DoSomething(pTemplate);
}
}
Jedną z najciekawszych rzeczy, którą możesz zrobić z listą wzorców, jest wyprowadzenie listy wszystkich aktywnych dokumentów. Wymaga to zastosowania zagnieżdżonej pętli; tak by dla każdego znalezionego wzorca móc przejść przez dokumenty utworzone na podstawie tego wzorca. W tym celu możesz użyć kodu podobnego do poniższego:
void CSampleWinApp::IterateEveryDocument()
{
CDocManager* pManager = AfxGetApp()->m_pDocManager; if( pManager == NULL ) return;
POSITION posTemplate = pManager->GetFirstDocTemplatePosition() ; while( posTemplate != NULL )
{
// pobranie następnego wzorca CDocTemplate* pTemplate =
pManager->GetNextDocTemplate(posTemplate); POSITION posDoc = pTemplate->GetFirstDocPosition(); while( posDoc != NULL )
CYourDocument* pThisOne =
(CYourDocument*) pTemplate->GetNextDoc(posDoc);
{
// zrób coś z otrzymanym dokumentem pThisOne->SomeFunctionCall();
}
}
}
W obu fragmentach kodu pobierasz wskaźnik do menedżera dokumentów przez pobranie funkcją AfxGetApp o wskaźnika do obiektu aplikacji, a następnie sprawdzasz, czy zmienna składowa m_pDocManager zawiera wskaźnik do menedżera. W rzeczywistości występuje tu pewien nadmiar, gdyż oba fragmenty kodu stanowią funkcje składowe klasy CSampiewinApp, więc i tak najprawdopodobniej są składowymi każdego obiektu otrzymanego w wyniku wywołania AfxGetApp (). Zamiast tego możesz więc bezpośrednio odwołać się do zmiennej składowej m_pDocManager. Jednak w tym przykładzie celowo zastosowaliśmy takie rozwiązanie, aby podkreślić możliwość odwołania się do obiektu aplikacji już podczas działania programu. Co więcej, wiesz dzięki temu, jak zastosować ten kod w każdej funkcji dowolnego obiektu w swojej aplikacji - gdyż ten kod nie zakłada, że działa wewnątrz obiektu wyprowadzonego z klasy CWinApp.
Fragmenty kodu wykorzystują funkcje składowe klasy CDocManager, GetFirstDoc-TempiatePosition () oraz GetNextDocTempiate (), wyglądające znajomo, gdyż pełniące role podobne do funkcji składowych GetFirstviewPosition () oraz GetNextview (;> klasy dokumentu, o których dowiedziałeś się we wcześniejszej części rozdziału. Rzeczywistą pracę wykonuje jedynie funkcja GetNextDocTemplate () - pobiera ona wskaźnik typu POSITION do następnego wzorca dokumentu. Jak widać, w drugim fragmencie kodu występuje rzutowanie wskaźnika, gdyż funkcja GetNextDoc () zwraca wskaźnik do klasy bazowej CDocument. W tym miejscu właściwą praktyką byłoby zastosowanie funkcji isKindOf o lub użycie makra MFC DYNAMIC_DOWNCAST () w celu upewnienia się, że otrzymujemy naprawdę to, czego chcemy, lecz w tych przykładach zrezygnowaliśmy z tego na rzecz prostoty.
Niszczenie wzorców
dodanych funkcją składową AddDocTemplate()
Jeśli przy dodawaniu wzorców dokumentów do listy użyłeś funkcji MFC AddDocTem-plate (), w momencie zamykania aplikacji nie musisz martwić się o usuwanie wzorca. Jednak w pewnych przypadkach możesz zażyczyć sobie ukrycia wzorca przed użytkownikiem, więc musisz mieć jakiś sposób usunięcia wzorca. Możliwość usunięcia obiektu wzorca jeszcze w trakcie normalnej pracy programu jest zbyt pożyteczna, aby z niej zrezygnować.
Podczas projektowania aplikacji nie martw się przechowywaniem wzorców tak długo, jak długo są potrzebne - to naprawdę bardzo niewielkie objętościowo dane. Jednak tak jak w przypadku każdego innego obiektu można trzymać się pewnych zasad. Tysiąc wzorców byłoby już prawdopodobnie pewnym obciążeniem pamięci (i koszmarem przy kodowaniu), jednak dziesięć czy dwadzieścia wzorców nie stanowi żadnego problemu, oczywiście pod warunkiem, że rzeczywiście ich potrzebujesz.
Wykorzystanie klasy CView
Jak już wiemy, dla każdej klasy wyprowadzonej z coocument stosuje się klasę wyprowadzoną z cview stanowiącą interfejs wizualny. Klasa wyprowadzona z cview stanowi wizualną prezentację danych dokumentu oraz odpowiada za interakcję użytkownika z oknem widoku.
Z kolei okno widoku jest oknem potomnym okna ramki. W aplikacji SDI okno widoku jest oknem potomnym głównego okna ramki. W aplikacji MDI okno widoku jest oknem potomnym potomnego okna MDI. Oprócz tego, okno ramki może być oknem ramki podczas edycji "na miejscu" OLE, jeśli tylko Twoja aplikacja obsługuje taką edycję. Z kolei okno ramki może zawierać kilka okien widoków (na przykład w postaci okna dzielonego, o którym dowiesz się w rozdziale 20.).
Deklarowanie klasy widoku
Jak szczegółowo wyjaśnialiśmy w poprzednich sekcjach, wszelkie dane stanowiące część dokumentu powinieneś zadeklarować jako zmienne składowe klasy dokumentu. Jednak wciąż mając to na uwadze, powinieneś zdawać sobie sprawę także z tego, że wiele elementów danych w aplikacji będzie odnosić się również do danego widoku. Co więcej, większość z tych danych będzie ulotna - co znaczy, że nie trzeba zapisywać ich wraz z dokumentem.
Przypuśćmy na przykład, że tworzysz aplikację zdolną do prezentacji danych dokumentu przy różnych współczynnikach powiększenia. Współczynnik powiększenia jest specyficzny dla poszczególnego widoku - co oznacza, że w różnych widokach mogą wystąpić różne powiększenia, nawet jeśli te widoki odnoszą się do tego samego dokumentu.
Biorąc to pod uwagę, prawdopodobniej najlepiej obsłużysz współczynnik powiększenia, deklarując go jako zmienną składową klasy widoku, a nie jako zmienną składową klasy dokumentu:
class CZoomView : public CView
{
protected:
CZoomView();
DECLARE_DYNCREATE(CZoomView) public:
CZoomableDoc* GetDocument();
WORD m wZoomPercent;
}
Jednak dużo ważniejsza niż jakakolwiek zmienna składowa reprezentująca jakieś ustawienie jest zmienna składowa reprezentująca bieżące zaznaczenie. Bieżące zaznaczenie jest kolekcją obiektów wewnątrz dokumentu zaznaczonych przez użytkownika w celu dokonania na nich manipulacji. Natura ł rodzaj wykonywanej przez użytkownika manipulacji jest całkowicie zależna od aplikacji, może jednak obejmować takie operacje jak wycinanie i kopiowanie do schowka lub operacje "przeciągnij i upuść" OLE.
Najprostszym sposobem zaimplementowania bieżącego zaznaczenia jest użycie klasy kolekcji, tak jak w przypadku klasy dokumentu. Na przykład, kolekcję reprezentującą bieżące zaznaczenie możesz zadeklarować następująco:
class CSelectableView : public CView
{
// tutaj więcej kodu
CList m SelectList;
}
Oprócz zmodyfikowania deklaracji klasy widoku musisz napisać jedną lub kilka funkcji składowych, tak aby klasa widoku mogła odpowiadać na operacje zaznaczania - wypełnianie i opróżnianie listy itd. Jednak w każdym przypadku musisz przesłonić funkcję składową OnDraw (). Domyślna implementacja tej funkcji niczego nie robi - konieczne jest więc napisanie kodu wyświetlającego elementy danych dokumentu (nawet jeśli klasa widoku nie posiada żadnych własnych zmiennych).
Na przykład, jeśli swoją klasę dokumentu wyprowadzisz z coieDocument i użyjesz klasy CDocItem do przechowania danych dokumentu, funkcja składowa OnDraw () dla klasy widoku może wyglądać mniej więcej tak:
void COleCapView::OnDraw(CDC *pDC)
{
COLECapDoc *pDoc = GetDocument(); ASSERT_VALID(pDoc);
POSITION posDoc = pDoc->GetStartPosition(); while (posDoc != NULL)
{
CDocItem *pObject = pDoc->GetNext!tem(posDoc);
if(pObject->IsKindOf(RUNTIME_CLASS(CNormDocItem)))
{
((CNormDocItem *)pObject)->Draw(pDC);
}
else if(pObject->IsKindOf(RUNTIME_CLASS{COleDocItem)))
{ ((COleDocItem *)pObject)->Draw(pDC);
}
else
ASSERT(FALSE);
}
}
Funkcje składowe klasy CView
Podobnie jak klasa CDocument, także klasa cview ma wiele funkcji składowych, których możesz użyć w ich wersjach oryginalnych lub możesz przesłonić w celu uzyskania określonego działania.
Do najczęściej używanych funkcji tej klasy należy funkcja składowa GetDocument (}, zwracająca wskaźnik do obiektu dokumentu uprzednio powiązanego z widokiem. Kolejną powszechnie stosowaną funkcją jest DoPreparePrinting(). Ta funkcja wyświetla okno dialogowe Drukuj i tworzy kontekst urządzenia drukarki oparty na ustawieniach
wybranych przez użytkownika w tym oknie. Więcej informacji na temat tej funkcji podamy w rozdziale 21.
Funkcje GetDocument () i DoPreparePrinting {) sąjedynymi funkcjami klasy cview, które nie są przesłanialne. Możesz za to przesłonić wszystkie inne funkcje składowe tej klasy. Te funkcje składowe stanowią uzupełnienie ogromnej ilości przesłanialnych funkcji klasy CWnd (stanowiącej klasę bazową dla klasy cview). Oprócz tego, funkcje składowe obsługują większość działań ze strony użytkownika. Próba przytoczenia listy wszystkich funkcji składowych byłaby bezcelowa, gdyż jest ich po prostu zbyt dużo. Wystarczy że powiemy, że do funkcji składowych należą funkcję obsługujące komunikaty klawiatury, myszy, timera, systemu i inne, zdarzenia schowka i MDI, a także komunikaty inicjalizacji i zamykania programu. Aplikacja powinna dopiero w miarę potrzeb przesłaniać funkcje składowe klasy widoku. Na przykład, jeśli aplikacja pozwala użytkownikowi na umieszczenie obiektu w dokumencie poprzez kliknięcie i przeciągnięcie myszką, powinieneś w tym celu przesłonić funkcję składową cwnd: :OnLButtonDown (). Ogólnie, do tworzenia własnych wersji funkcji możesz użyć ClassWizarda, wstawiając w nie własny kod w miejscu wskazywanym przez komentarz TODO.
Większość aplikacji zawsze przesłania kilka najważniejszych funkcji klasy cview. Wiesz już o jednej z nich - w przypadku klasy widoku konieczne jest zapewnienie własnej wersji funkcji onDraw (), a można było wyświetlić jakiekolwiek dane. Oprócz tego, w każdej aplikacji obsługującej OLE (czyli, w dzisiejszych czasach, w praktycznie każdej) musisz przesłonić funkcję składową isSelected(). Ta funkcja zwraca wartość TRUE, jeśli obiekt wskazywany przez jej argument stanowi część bieżącego zaznaczenia. Jeśli zaimplementowałeś swoją bieżącą selekcję, używając kolekcji wzorca CList jako listy elementów CDocitem (co pokazywaliśmy wcześniej w tym rozdziale), funkcję isSelec-ted () możesz zaimplementować w poniższej postaci:
BOOL CSampView::IsSelected(const CObject* pDodtem) const {
return (m SelectList. Find ( (CDodtem* ) pDodtem) != NULL);
}
Kolejną ważną funkcją składową przesłanianą w większości aplikacji jest funkcja OnUpdate (). Jak już wiesz z wcześniejszej części rozdziału, funkcja składowa UpdateAllYiews () klasy dokumentu wywołuje funkcję OnUpdate () każdego widoku powiązanego z dokumentem wywołującym funkcję UpdateAllYiews (). Domyślna implementacja funkcji OnUpdate () po prostu unieważnia cały obszar roboczy okna widoku (co z kolei powoduje odrysowanie całego obszaru roboczego). W celu poprawienia wydajności swojej aplikacji możesz zechcieć przesłonić tę funkcję i unieważnić tylko te obszary okna widoku, które tego wymagają. Na przykład, funkcję OnUpdate () możesz zaim-plementować tak jak w następującym przykładzie:
void CSampView::OnUpdate(CView *pView, LPARAM IHint, CObject *pObj)
{
if(IHint == UPDATEJDBJECT) // stalÄ… zdefiniowana w aplikacji
InvalidateRect((CAppObject*)pObj->m_Rect); else
Invalidate();
}
W funkcji OnUpdate () nie powinieneś niczego rysować. Wszelkie rysowanie powinno odbywać się w funkcji OnDraw () klasy widoku.
Jeśli aplikacja obsługuje niestandardowe tryby odwzorowania, takie jak powiększenia czy obroty, funkcja OnPrepareDC () klasy cview zyskuje specjalne znaczenie. W tej funkcji ustawiasz tryb odwzorowania okna widoku przed tym, zanim aplikacja rzeczywiście przystąpi do rysowania na ekranie. Zawsze powinieneś się upewnić, przed utworzeniem kontekstu urządzenia dla okna widoku, że aplikacja wywołuje funkcję OnPrepareDC () w celu zapewnienia poprawnego ustawienia kontekstu urządzenia.
Podobnie, Twoja aplikacja często może potrzebować utworzenia kontekstu urządzenia wyłącznie w celu odczytania bieżącego trybu odwzorowania okna widoku. Możesz na przykład zechcieć w funkcji składowej OnLButtonDown () widoku przeliczyć pozycję kliknięcia myszką ze współrzędnych fizycznych na logiczne:
void CSamYiew::OnLButtonDown(UINT nFlags, CPoint point) {
CClientDC dc(this);
OnPrepareDC(&dc);
dc.DPtoLP(&point);
// dalsze przetwarzanie
}
Widoki a komunikaty
Oprócz komunikatów, dla których MFC zawiera domyślne funkcje obsługi w klasie cview lub jej klasie bazowej CWnd, typowa klasa widoku przetwarza także wiele innych komunikatów systemowych. Zwykle należą do nich komunikaty poleceń wysyłane, gdy użytkownik wybierze polecenie w menu, kliknie przycisk narzędzia lub w jakiś inny sposób skorzysta z interfejsu użytkownika.
Jak wiesz z poprzedniej części rozdziału, to czy dany komunikat ma zostać obsłużony przez widok lub dokument (lub, w pewnych przypadkach, przez okno ramki), zależy wyłącznie od Ciebie. Pamiętaj jednak, że najważniejszym kryterium w podejmowaniu tej decyzji jest zakres, w jaki dany komunikat wpływa na przetwarzanie w aplikacji. Jeśli polecenie odnosi się do całego dokumentu lub zawartych w nim danych, komunikat powinien zostać obsłużony w klasie dokumentu (chyba że polecenie ma ścisły związek z konkretnym widokiem, co zwykle odnosi się do implementacji poleceń Kopiuj i Wklej). Jeśli polecenie wpływa jedynie na konkretny widok (na przykład ustawienie współczynnika powiększenia czy obrotu), komunikat powinien zostać przetworzony przez dany obiekt widoku.
Warianty klasy okna wyprowadzone z klasy CView
Oprócz podstawowej klasy cview MFC udostępnia także kilka wyprowadzonych z niej klas specjalnego przeznaczenia, zaprojektowanych w celu ułatwienia obsługi złożonych zadań. Te klasy zostały zebrane w tabeli 19.2.
Tabela 19.2. Wersje klas MFC wyprowadzone z klasy Cl'iew
Nazwa klasy
Opis
CCtrlView
Obsługuje widoki bezpośrednio oparte na kontrolce (takie jak kontrolka drzewa czy kontrolka pola edycji).
CDaoRecordView
Używa kontrolek dialogu do wyświetlania rekordów bazy danych. Więcej na temat klasy CDaoRecordYiew dowiesz się w rozdziale 27.
CEditView
Używa kontrolki pola edycji, tworząc wielowierszowy edytor tekstu.
CForm View
Wyświetla kontrolki okna dialogowego. Obiekty CFormYiew musisz oprzeć na szablonach okien dialogowych
.
CHtmlView
Zapewnia okno, w którym użytkownik może przeglądać strony WWW, a także foldery w lokalnym systemie plików oraz w sieci. Więcej na temat tej klasy dowiesz się w rozdziale 36.
CListView
Wyświetla kontrolkę listy.
COleDBRecor-dView
Używa kontrolek dialogu do wyświetlania rekordów bazy danych. Więcej na temat klasy COleDBRecordYiew dowiesz się w rozdziale 28.
CRecordView
Używa kontrolek dialogu do wyświetlania rekordów bazy danych. Więcej na temat klasy CRecordYiew dowiesz się w rozdziale 26.
CRichEditView
Wyświetla kontrolkę pola edycji Rich Edit.
CScrollViewUmożliwia wykorzystanie przez użytkownika pasków przewijania w celu poruszania się po danych dokumentu.
CTreeViewWyświetla kontrolkę drzewa.
Innym, rzadko przesłanianym wariantem klasy cview jest klasa cpreviewview. MFC używa jej w celu udostępnienia aplikacji podglądu wydruku. Więcej na jej temat dowiesz się w rozdziale 21.
Wszystkie klasy wyprowadzone z klasy cview posiadają funkcje składowe specyficzne dla swojego zastosowania. Funkcje składowe klas widoków wyprowadzonych z klasy CCtr1View odpowiadają komunikatom okienkowym specyficznym dla kontrolek, które te klasy reprezentują.
CFormView i klasy MFC z niej wyprowadzone (włącznie z CDaoRecordView, COleDBRecordView oraz CRecordYiew) obsługują mechanizm DDE (Dialog Data Exchange). Wszystkich czterech klas możesz użyć w sposób podobny do użycia klasy wyprowadzonej z CDialog (o klasach wyprowadzonych z klasy coiaiog mówiliśmy w rozdziale 13.).
Aplikacje oparte na klasie CForrrView i aplikacje okien dialogowych
Jak już wiesz, aplikacje oparte na oknie dialogowym stanowią wyjątek od standardowego modelu dokument-widok stosowanego w MFC. Jeśli za pomocą AppWizarda stworzysz aplikację opartą na oknie dialogowym, otrzymasz w rezultacie program nie posiadający ani klasy dokumentu, ani klasy widoku (zresztą nie mający także klasy okna ramki). Zamiast tego aplikacja wszystkie swoje funkcje będzie zawierać w pojedynczej klasie okna dialogowego, wyprowadzonej z CDialog.
Choć w wielu prostych aplikacjach wyprowadzenie pojedynczej klasy dialogowej może być w zupełności wystarczające, jednak w ten sposób traci się wsparcie dla wielu użytecznych elementów MFC, występujących w aplikacjach typu dokument-widok. Aplikacja oparta na oknie dialogowym nie posiada menu, paska narzędzi ani paska stanu (wszystko to występuje w oknie ramki), nie obsługuje OLE ani MAPI oraz nie posiada możliwości drukowania (przynajmniej dopóki nie dopiszesz w tym celu własnego, dość obszernego kodu).
Jeśli potrzebujesz interfejs podobny do okna dialogowego, lecz mimo to chcesz korzystać z dobrodziejstw architektury typu dokument-widok, możesz zbudować swoją aplikację z użyciem klasy CFormYiew jako bazowej klasy widoku, korzystając przy tym z modelu aplikacji SDI. W ten sposób zapewnisz sobie dostęp do zalet pełnej aplikacji MFC, pozostając jednocześnie przy standardowym wyglądzie okna dialogowego, korzystającego z zasobu szablonu definiującego postać okna oraz wykorzystując zalety dynamicznej wymiany danych.
Powrót do okien ramki
Jak dotąd w tym rozdziale nauczyłeś się, czym są dokumenty, wzorce dokumentów oraz widoki, dowiedziałeś się także (choćby pobieżnie), że widok prawie zawsze występuje w oknie ramki. Ta koncepcja, mimo że często nie zauważana, stanowi klucz do zarządzania różnymi widokami wewnątrz aplikacji.
Jedyny przypadek, gdy aplikacja nie tworzy widoku wewnątrz rzeczywistego okna ramki, występuje wtedy, gdy widokiem jest osadzony obiekt OLE. Taki widok wciąż posiada ramkę - z tym że różni się ona znacznie od standardowych okien ramek, jakie dotąd poznaliśmy.
Jak staramy się wyjaśnić w tym rozdziale, aplikacja SDI zwykle tworzy egzemplarz obiektu CFrameWnd, podczas gdy aplikacja MDI zwykle tworzy egzemplarz obiektu CMDiFrameWnd. Oczywiście, jeśli piszesz aplikację opartą na oknie dialogowym, głównym oknem będzie okno dialogowe, zaś aplikacja nie ma żadnego okna ramki.
Ze sposobu, w jaki działa rozprowadzenie poleceń, zorientujesz się, że okno ramki pełni czasem funkcję "wychwytywacza" komunikatów skierowanych do aplikacji. Innymi słowy, każde polecenie menu, które nie zostanie obsłużone przez widok, ma szansę zostać obsłużonym przez okno ramki.
Powinieneś implementować funkcje obsługi przygotowane na wszelkie komunikaty ramki, bez względu na to, z którym widokiem użytkownik akurat pracuje. Jeśli posiadasz polecenia menu, na które powinieneś reagować w różny sposób dla różnych widoków, możesz zaimplementować funkcje obsługi zarówno w klasie okna ramki, jak i widoku. Funkcja obsługi w widoku jest wykonywana, gdy jest aktywny obiekt widoku, zaś w przeciwnym razie jest wywoływana funkcja obsługi w oknie ramki.
Znaczenie funkcji AfxGetMainWnd()
Ramki pełnią rolę głównego okna dla wątku sterującego procesem. Jeśli w dowolnym miejscu programu wywołasz funkcję AfxGetMainWnd (), otrzymasz wskaźnik do obiektu klasy cwnd używanego przez aplikację jako główne okno. Jeśli chcesz odwołać się do klasy wyprowadzonej z CFrameWnd lub CMDiFrameWnd, musisz dokonać rzutowania tego wskaźnika.
Okno ramki jest odpowiedzialne za więcej niż tylko zapewnienie, by aplikacja miała menu i skalowalną ramkę. Pełni ona także funkcję zakotwiczenia dla paska narzędzi oraz paska stanu. Jak dotąd nie poznałeś jeszcze odpowiednich klas, lecz ponieważ są one bardzo często łączone z oknem ramki, ważne jest, by wiedzieć, jak działają.
Jeśli aplikacja posiada pasek stanu lub pasek narzędzi, kod tworzący egzemplarze obiektów cstatusBar i CToolBar znajdziesz w klasie głównego okna. Jak się zapewne domyślasz, klasa cstatusBar tworzy pasek stanu, zaś klasa CToolBar tworzy pasek narzędzi. W większości aplikacji tworzenie tych okien odbywa się w funkcji składowej OnCreate () okna ramki aplikacji. Więcej na temat tych klas dowiesz się w rozdziale 23.
Ten kod tworzy rzeczywiste okno. Ponieważ obiekty C-H- są składowymi okna ramki, aplikacja tworzy je w tym samym czasie, w którym tworzy obiekt okna ramki.
Gdy aplikacja została utworzona przez AppWizarda, zmienna reprezentująca pasek stanu nosi nazwę m_wndStatusBar, zaś pierwszy pasek narzędzi jest reprezentowany przez zmienną m_wndToolBar. Zwróć uwagę, że jest to pierwszy pasek narzędzi. Okno ramki może z powodzeniem obsłużyć więcej niż jeden pasek narzędzi - MFC doskonale sobie radzi nawet z dość dużą ilością różnych pasków.
Na marginesie, klasy cstatusBar i CToolBar są zależne od okna ramki. Użycie ich w innych rodzajach okien (takich jak dialogi) wykracza poza zakres tej książki - i nie jest zresztą zagadnieniem, z którym styka się większość programistów. Wystarczy, że będziesz wiedział, że te klasy nie są bezwzględnie zależne od architektury dokument-widok, jednak rozmieszczając się w oknie, opierają się na oknie ramki powiązanym z klasami dokumentu i widoku, dodatkowo informując okno widoku o wielkości obszaru, jaki ma ono do dyspozycji.
Aplikacja jednodokumentowa HexView
Program demonstracyjny dla tego rozdziału znajdziesz na dołączonej do książki płytce CD-ROM, w kartotece Rozdzl9\HexView. Ten program wykorzystuje interfejs SDI z klasą wyprowadzoną z klasy bazowej cscroliview, przedstawiając użytkownikowi szesnastkową reprezentację zawartości pliku. Możesz zbudować ten program, ładując plik projektu HexView.dsw lub po prostu uruchamiając program HexVie\v.exe w Eksploratorze Windows. Gdy uruchomisz program, ujrzysz puste okna. Możesz wtedy załadować plik, używając polecenia Otwórz w menu Plik lub klikając przycisk Otwórz na pasku narzędzi. Rysunek 19.4 przedstawia program, w którym otwarto plik HexView.cpp.
HexView
Położenie na płytce: Rozdzl9\HexView
Nazwa pliku wykonywalnego: HexView.exe
Moduły kodu źródłowego w tekście: HexVie\vDoc.cpp, HexViewView.cpp
Listing 19.2. Fragmenty plików HexViewDoc.cpp i HexVie\vView.cpp
// HexViewView . cpp : implementation of the CHexViewView class
//
/////////////////////////////////////////
// CHexViewView construction/destruction
CHexViewView : : CHexViewView ( ) {
memset ( &m_logf ont , O, sizeof (m_logfont ) ) ;
m_nPointSize = 120; tcscpy(m logfont . lf FaceName, T( "Fixedsys"
// start out with a system font
CWindowDC dc (NULL);
m_logfont.IfHeight = ::MulDiv(m_nPointSize,
dc.GetDeviceCaps(LOGPIKELSY), 720) m_logfont.lfPitchAndFamily = FIXED_PITCH;
m_pFont = new CFont;
m_pFont->CreateFont!ndirect(&m_logfont); m_pPrintFont = NULL; m bPrinting = FALSE;
}
CHexViewView::~CHexViewView(} {
if (m_pFont != NULL)
delete m_pFont; if (m_pPrintFont != NULL) delete m_pPrintFont;
}
///////////////////////// CHexViewView drawing
void CHexViewView::OnDraw(CDC* pDC) {
CHexViewDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CString strRender;
CFont* pOldFont;
CSize ScrolledSize;
int nStartLine;
int nHeight;
CRect ScrollRect;
CPoint ScrolledPos = GetScrollPosition();
if (m_bPrinting) {
// Odszukaj pierwszÄ… liniÄ™ dla tej strony
ScrolledSize = CSize(m_nPageWidth, m_nPageHeight); ScrollRect = CRect(O, ScrolledPos.y,
ScrolledSize.ex,
ScrolledSize.cy + ScrolledPos.y); pOldFont = pDC->SelectObject(m_pPrintFont); nStartLine = m nPrintLine;
}
else {
CRect rectClient; GetClientRect(&rectClient);
// Sprawdź wysokość każdej linii
pOldFont = pDC->SelectObject(m_pFont); nHeight = MeasureFontHeight(m_pFont, pDC);
// Znajdź początkową linię,
// opierając się na przewinięciu pliku
ScrolledSize = CSize(rectClient.Width(), rectClient.Height()); ScrollRect = CRect(rectClient.left, ScrolledPos.y,
rectClient.right,
ScrolledSize.cy + ScrolledPos.y); nStartLine = ScrolledPos.y/16;
// Zacznij rysowanie w odpowiednim miejscu.
ScrollRect.top = nStartLine*nHeight; }
if (pDoc->m_pFile != NULL)
{
int nLine;
for (nLine = nStartLine; ScrollRect.top < ScrollRect.bottom;
nLine++) {
if (!pDoc->ReadLine(strRender, 16, nLine*16)) break;
nHeight = pDC->DrawText(strRender, -l, SScrollRect, DT_TOP | DT_NOPREFIX |DTSINGLELINE); ScrollRect.top += nHeight;
}
}
pDC->SelectObject(pOldFont) ;
}
//////////////////////////////////////////////////////// // CHexViewView printing
BOOL CHexViewView::OnPreparePrinting(CPrintlnfo* plnfo) {
BOOL bResult;
CWinApp* pApp = AfxGetApp();
// Zapytaj aplikację o domyślną drukarkę; jeśli // takiej nie ma, poproś MFC o zgłoszenie błędu.
if (!pApp->GetPrinterDeviceDefaults(&pInfo->m_pPD->m_pd) ||
p!nfo->m_pPD->m_pd.hDevMode == NULL) return DoPreparePrinting(plnfo);
HGLOBAL hDevMode = p!nfo->m_pPD->m_pd.hDevMode; HGLOBAL hDevNames = p!nfo->m_pPD->m_pd.hDevNames;
DEYMODE* pDevMode = (DEYMODE*) ::GlobalLock(hDevMode); DEYNAMES* pDevNames = (DEVNAMES*) ::GlobalLock(hDevNames);
LPCSTR pstrDriverName = ((LPCSTR) pDevNames) +
pDevNames->wDriverOffset ;
LPCSTR pstrDeviceName = ((LPCSTR) pDevNames) +
pDevNames->wDeviceOffset;
LPCSTR pstrOutputPort = ((LPCSTR) pDevNames) +
pDevNames->wOutputOffset;
CDC dcPrinter;
if (dcPrinter.CreateDC(pstrDriverName, pstrDeviceName,
pstrOutputPort, NULL)) {
CalcPageCount(&dcPrinter,pInfo);
dcPrinter.DeleteDC();
bResult = DoPreparePrinting(plnfo);
}
else {
MessageBox("Nie powiodło się stworzenie"
" kontekstu urzÄ…dzenia drukarki"); bResult = FALSE;
}
: :GlobalUnlock(hDevMode); ::GlobalUnlock(hDevNames);
return bResult;
}
//////////////
//CHexViewView mesage handlers
void CHexViewView::OnlnitialUpdate() {
CHexViewDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// Przygotowanie poczÄ…tkowego rozmiaru
CSize sizeTotal(O, pDoc->m_1FileSize); SetScrollSizes(MM TEXT, sizeTotal);
CScrollView::OnlnitialUpdate(;
}
void CHexViewView::OnViewFont() {
// Pozwolenie użytkownikowi na zmianę decyzji co do czcionki
LOGFONT IfCopy;
memcpy(slfCopy, &m_logfont, sizeof(IfCopy));
// Wybranie czcionki w oknie dialogowym.
CFontDialog dlg(slfCopy);
dlg.m_cf.Flags |= CF_FORCEFONTEXIST | CF FIKEDPITCHONLY;
// Jeśli użytkownik wybierze czcionkę, utworzymy ją
// i umieścimy w zmiennej składowej do późniejszego
// użytku.
if (dlg.DoModal() == IDOK) {
CFont* pFontCopy = new CFont;
if (pFontCopy->CreateFont!ndirect(&lfCopy))
{
m_nPointSize = dlg.GetSize(); if (m_pFont != NULL) delete m_pFont;
m_pFont = pFontCopy;
memcpy(&m_logfont, SlfCopy, sizeof(IfCopy) ) , Invalidate();
}
else
{
delete pFontCopy; MessageBox(
_T("Nie powiodło się stworzenie nowej czcionki!"));
}
}
}
//HexViewDoc.cpp : implementation of the CHexViewDoc class
/////////////////////////////////////////////////////
//CHexViewDoc comands
BOOL CHexViewDoc::OnOpenDocument(LPCTSTR IpszPathName)
{
if (ICDocument::OnOpenDocument(IpszPathName)) return FALSE;
if (m_pFile != NULL)
{
m_pFile->Close(); delete m_pFile;
}
try
{
m_pFile =
new CFile(IpszPathName, CFile::modeRead | CFile::typeBinary);
}
catch (CFileException* e)
{ CString strError;
strError.Format(_T("Nie powiodło się otwarcie pliku: %d'
_sys_errlist[e->m_10sError]); AfxMessageBox(strError); return FALSE;
}
m_1FileSize = m_pFile->GetLength( return TRUE;
}
/////////////////////////////// CHexViewDoc implementation
BOOL CHexViewDoc::ReadLine(CString& strLine, int nLength,
LONG lOffset = -1L)
{
LONG IPosition;
if (lOffset != -1L)
1Position = m_pFile->Seek(lOffset, CFile::begin); else
1Position = m_pFile->GetPosition();
if (1Position == -1L)
{
TRACE2("CHexViewDoc::ReadLine zwróciło" " FALSE Seek(%8.81X, %8.81X)\n", lOffset, IPosition); return FALSE;
BYTE* pszBuffer = new BYTE [nLength] ;
int nReturned = m_pFile->Read (pszBuffer, nLength);
if (nReturned <= 0) {
TRACE2 ("CHexViewDoc: :ReadLine zwróciło FALSE Read(%d, %d)\n"; nLength, nReturned) ;
delete pszBuffer;
return FALSE;
}
CString strTemp; CString strCharsIn;
strTemp. Format (_T ("%8.81X - "),1Position); strLine = strTemp;
for (int nlndex = 0; nlndex < nReturned; nlndex++) {
if (nlndex == 0)
strTemp. Format (_T("%2.2X") , pszBuffer [nIndex] ) ; else if (nIndex % 16 == 0)
strTemp. Format (_T ( "=%2 . 2X" ) , pszBuffer [nIndex] ) ; else if (nIndex % 8 == 0)
strTemp. Format (_T ( "-%2 . 2X" ) , pszBuf f er [nIndex] ) ; else
strTemp. Format (_T(" %2.2X"), pszBuffer [nlndex] );
if (_istprint (pszBuffer [nIndex] )
strCharsIn += pszBuffer [nIndex] ; else
strCharsIn += _T {'.'); strLine += strTemp;
}
if (nReturned < nLength)
{
CString strPadding(_T{''}, 3*(nLength-nReturned));
strLine += strPadding;
}
strLine += _T(" "};
strLine += strCharsIn;
delete pszBuffer; return TRUE;
}
Podsumowanie
Większość aplikacji MFC jest opartych na modelu dokument-widok. Dokument, obiekt abstrakcyjny, reprezentuje dane aplikacji i zwykle odpowiada zawartości pliku. Z kolei widok zapewnia prezentację danych i przyjmuje polecenia użytkownika. Powiązanie pomiędzy dokumentami a widokami jest w rodzaju jeden do wielu: dokument może (i zwykle tak jest) obsługiwać kilka powiązanych ze sobą widoków, zaś dany widok jest zawsze powiązany z dokładnie jednym dokumentem.
Aplikacje wyprowadzają swoje klasy dokumentów z bazowej klasy MFC CDocument. Ta klasa zawiera większość funkcji obiektu dokumentu. W najprostszym przypadku aplikacja potrzebuje jedynie zmiennych składowych reprezentujących dane aplikacji, a także przesłoniętych wersji funkcji OnNewDocument () (dla inicjalizacji) oraz Seriali ze () (dla zapisu i odczytu danych), aby otrzymać w pełni funkcjonalną klasę dokumentu.
Bardziej wymyślne aplikacje do przechowywania swoich danych wykorzystują klasy kolekcji, zawierające zestaw obiektów składających się na dokument. W szczególności, aplikacje mogą korzystać z klasy coleDocument, opierając się na jej zdolności do zarządzania listą obiektów CDocitem, które umożliwiają przechowywanie nie tylko obiektów klientów i serwerów OLE.
Klasy widoków są wyprowadzane z bazowej klasy MFC cview. Okna widoków reprezentowane przez obiekty cview są oknami potomnymi. W aplikacji SDI oknem nadrzędnym jest główne okno ramki, zaś w aplikacjach MDI oknami nadrzędnymi są okna potomne MDI.
Obiekty widoku, oprócz posiadania zmiennych składowych reprezentujących ustawienia specyficzne dla widoków (takie jak współczynnik powiększenia czy kąt obrotu), często odpowiadaj ą także za bieżące zaznaczenie. Bieżące zaznaczenie to zestaw obiektów dokumentu, które użytkownik zaznaczył w bieżącym widoku w celu przeprowadzenia na nich jakiejś operacji. Tak jak w przypadku dokumentów, bardziej złożone aplikacje do zarządzania bieżącym zaznaczeniem wykorzystuj ą klasy kolekcji.
Choć klasa widoku może, i generalnie to robi, przesłaniać wiele funkcji składowych klasy cview, z której pochodzi, jednak w każdej klasie widoku konieczne jest przysłonięcie tylko jednej funkcji, OnDraw (). W przypadku aplikacji obsługujących OLE konieczne
jest przysłonięcie jeszcze jednej funkcji składowej, isSelectedO. Oprócz tego, w aplikacjach standardowo przysłania się funkcje OnUpdate () oraz OnPrepareDC ().
Poza implementacjami klasy cview, jakie możesz wyprowadzić, MFC oferuje szereg różnych wersji klasy widoku przeznaczonych do wykorzystania w aplikacji w celu obsługi przewijania widoku, wyświetlania widoku opartego na oknie dialogowym, wyświetlania kontrolek oraz wyświetlania widoków reprezentujących rekordy baz danych. Podczas projektowania aplikacji powinieneś pamiętać, by wybrać klasę najlepiej nadającą się na klasę bazową swojej klasy widoku.
Na koniec, dokumenty, widoki oraz okna ramki obsługują komunikaty. Decyzja, która z tych trzech klas ma obsłużyć dany komunikat, powinna zależeć od efektu tego komunikatu. Jeśli komunikat wpływa na cały dokument, jego obsługą powinna zająć się klasa dokumentu, chyba że komunikat wpływa na dokument w kontekście konkretnego widoku. W takim przypadku, lub w przypadku gdy efekt jest specyficzny wyłącznie dla danego widoku, komunikat powinien zostać obsłużony przez klasę widoku. Na koniec, klasa okna ramki powinna obsługiwać te komunikaty, które mają globalny wpływ na aplikację, lecz nie odnoszą się bezpośrednio do danych dokumentu (na przykład komunikaty ukrywania i wyświetlania pasków narzędzi).
Wyszukiwarka
Podobne podstrony:
12 Dokumenty widoki ramki
19 Widoki list
TI 99 08 19 B M pl(1)
19 Nauka o mózgu
[W] Badania Operacyjne Zagadnienia transportowe (2009 04 19)
hezjod teogonia, dokument elektroniczny
Nowy dokument tekstowy
Nowy Dokument tekstowy
więcej podobnych podstron