3 20 Układ aplikacji MDI (2)


Rozdział 20.
Układ aplikacji MDI


W tym rozdziale:

Ponowny przegląd modelu dokument-widok
Ponowny przegląd klasy CDocument
Zarządzanie złożonymi kombinacjami dokumentów, widoków i ramek
Program PaintObj
Dynamicznie dzielone okna
Łączenie widoków z dokumentami
Tworzenie statycznych okien dzielonych
Program Dynasplit
Subclassing okien potomnych


W rozdziale 19. poznałeś stosowany w MFC model dokument-widok. Następnie użyłeś go do zbudowania prostej jednodokumentowej (SDI) aplikacji. Choć tworzenie aplikacji SDI jest oczywiście przydatnym ćwiczeniem, jednak interfejs jednodokumentowy nadaje się głównie dla niewielkich i prostych aplikacji. W przypadku większości rzeczywistych aplikacji konieczna staje się możliwość korzystania z wielu widoków oraz wielu dokumentów, dzięki którym można lepiej zorganizować informację. Gdy w pojedynczej aplikacji możesz otworzyć kilka dokumentów, oznacza to, że w modelu dokument-widok stosujesz strukturę interfejsu wielodokumentowego (MDI). Układ aplikacji MDI jest nieco bardziej złożony niż układ aplikacji SDI. Podstawową strukturę takiej wielodo-kumentowej aplikacji przedstawia rysunek 20.1.
Jak widać na rysunku, aplikacje MDI wciąż korzystają z głównej ramki zawierającej menu, pasek narzędzi oraz pasek stanu. Jednak w aplikacji MDI klasa CMainFrame jest wyprowadzona Z klasy CMDIFrameWnd, a nie Z klasy CFrameWnd. Okno klasy CMDIFrameWnd wygląda tak samo jak okno klasy CFrameWnd, ale oprócz tego obsługuje protokół MDI oczekiwany przez Windows od aplikacji tego typu.



Okna potomne występujące na rysunku są także oknami ramek, lecz w tym przypadku są także egzemplarzami klasy MFC CMDichildWnd. Ta klasa zapewnia okna potomne, których aplikacja MDI w Windows używa w swoim obszarze roboczym do przechowywania poszczególnych egzemplarzy widoków. MFC tworzy po jednym obiekcie CMDichildWnd dla każdego z widoków, podobnie jak obiekt CMainFrame jest tworzony dla pojedynczego widoku w aplikacji SDI. Widok zawarty w ramce może być dowolnego typu i może odnosić się do dowolnego z dokumentów aktualnie otwartych w aplikacji.
Jako twórca aplikacji MFC sam odpowiadasz za podjęcie decyzji, jakie rodzaje dokumentów i widoków zaimplementujesz i w jaki sposób będą one współdziałać z podstawowym szkieletem aplikacji MDI lub SDI utworzonym przez MFC. Twój kod zmienia i rozszerza sposób, w jaki ogólne dokumenty i widoki współdziałają ze sobą. Dostosowując to współdziałanie do własnych potrzeb, zamieniasz szkielet dostarczony przez MFC w aplikację wykonującą dokładnie to, o co Ci chodzi. W trakcie czytania tego rozdziału dowiesz się dużo więcej na temat komponentów aplikacji MDI, a także spróbujesz stworzyć taką aplikację, aby jeszcze lepiej móc wybrać strukturę wymaganą dla swojej aplikacji.
Ponowny przegląd modelu dokument-widok
Jak wiesz z rozdziału 19., aplikacje MFC domyślnie wykorzystują model programowy oddzielający dane programu od wyświetlającego je kodu oraz od kodu przyjmującego polecenia użytkownika. (Z takiego modelu rezygnujesz wtedy, gdy nakazujesz AppWizardowi utworzenie aplikacji opartej na oknie dialogowym lub gdy stworzysz niestandardowy
projekt, na przykład taki jak serwer automatyzacji ActiveX). W modelu dokument-widok obiekt dokumentu MFC odczytuje i zapisuje dane z i do miejsca ich składowania. Dokument może także dostarczyć interfejsu do danych występujących w jakimś miejscu (na przykład w bazie danych). Oddzielny obiekt widoku zajmuje się wyświetlaniem danych - od rysowania reprezentacji danych w oknie aż po zaznaczenie i edycję danych przez użytkownika. Widok pobiera dane przeznaczone do wyświetlenia od obiektu dokumentu i informuje zwrotnie dokument o konieczności zmiany danych.
Choć możesz łatwo przesłonić lub zignorować ten podział pomiędzy dokumentem a widokiem, istnieje wiele przyczyn, dla których zwykle nie powinieneś tego robić. Najlepszym przykładem może być konieczność posiadania kilku widoków tego samego dokumentu, na przykład arkusza kalkulacyjnego i wykresu. W modelu dokument-widok każdy z obiektów widoków reprezentuje osobny widok danych, podczas gdy kod wspólny dla wszystkich widoków (na przykład procedury obliczeniowe) może występować w obiekcie dokumentu. Dokument także bierze na siebie zadanie aktualizowania wszystkich widoków w momencie zmiany danych.
Architektura dokument-widok w MFC bardzo ułatwia projektowanie aplikacji obsługujących wiele widoków, wiele typów dokumentów, dzielone okna i inne wizualne elementy interfejsu użytkownika. Wiesz już, jak w swojej aplikacji zaimplementować wiele widoków; w tym rozdziale dowiesz się także, jak w swojej aplikacji obsłużyć kilka rodzajów dokumentów oraz dzielone okna. W sercu architektury dokument-widok leżą cztery kluczowe klasy, z których każdą poznałeś już w rozdziale 19., i które tutaj tylko pokrótce przypomnimy:
CDocument (lub bardziej wydajna, wyprowadzona z niej klasa coleDocument), obiekt używany w aplikacji do przechowywania lub zarządzania danymi aplikacji, zapewniający także dane dla widoków powiązanych z tą klasą dokumentu

CView (lub jedna z wielu wyprowadzonych z niej klas, takich jak cscroliview, CEditview itd.), obiekt używany w aplikacji do wyświetlania jej danych oraz do obsługi współpracy użytkownika z danymi - zarówno przeglądania danych, jak też ich zaznaczania i modyfikowania.

CFrameWnd (lub jedna z jej odmian, takich jak CMDichildWnd), obiekt zapewniający okno ramki dookoła jednego lub kilku widoków dokumentu. W aplikacji MDI program posiada co najmniej dwa okna ramek - jedno zawierające całą aplikację (i w związku z tym nie posiadające powiązanej ze sobą klasy dokumentu) oraz jedno lub więcej okien wyświetlających dane aplikacji przechowywane w jednej lub kilku klasach dokumentów.

CDocTemplate (lub jedna z wyprowadzonych z niej klas, takich jak csingleDoc-Template czy CMultiDocTempiate), obiekt koordynujący jeden lub więcej dokumentów danego typu i zarządzający ukrytymi zadaniami, jakie MFC musi wykonać w celu stworzenia poprawnego dokumentu, widoku oraz okna ramki dla tego typu dokumentu.
Ponowny przegląd klasy CDocument
Jak już wiesz, klasa CDocument zapewnia podstawowe funkcje dla zdefiniowanych przez użytkownika klas dokumentów. Dokument reprezentuje jednostkę danych zwykle otwieraną przez użytkownika za pomocą polecenia Otwórz w menu Plik i zapisywaną za pomocą polecenia Zapisz w tym samym menu. Klasa CDocument obsługuje standardowe operacje, takie jak tworzenie dokumentu, ładowanie do niego danych oraz zapisywanie na dysk danych zawartych w dokumencie. MFC manipuluje dokumentami poprzez interfejs zdefiniowany w klasie CDocument.
Aplikacja może obsłużyć więcej niż jeden rodzaj dokumentu. Na przykład, aplikacja może obsługiwać zarówno arkusze kalkulacyjne, jak i dokumenty tekstowe (choć w większości tworzonych dzisiaj aplikacji naturalnym dokumentem byłby dokument tekstowy, zaś arkusz kalkulacyjny byłby obsługiwany poprzez osadzanie i łączenie obiektów, czyli OLE). Każdy typ dokumentu posiada powiązany ze sobą wzorzec dokumentu, tworzony w funkcji składowej initinstance () obiektu aplikacji. Wzorzec dokumentu określa, jakie zasoby (na przykład menu, dialog, ikonę lub tablicę akceleratorów) wykorzystuje dany rodzaj dokumentu. Każdy dokument zawiera wskaźnik do powiązanego z nim obiektu CDocTemplate (wzorca dokumentu). W rozdziale 19. przy tworzeniu swojego programu SDI używałeś obiektu csingleDocTemplate wyprowadzonego z klasy CDocTemplate. Podczas tworzenia aplikacji MDI użyjesz zamiast niego obiektu CMulti-DocTemplate.
Użytkownicy współpracują z dokumentem poprzez powiązane z nim obiekty cview. Widok tworzy w oknie ramki obraz danych zawartych w dokumencie, a także interpretuje działania użytkownika odnoszące się do zawartości tego dokumentu. Z pojedynczym dokumentem możesz powiązać kilka widoków. Gdy użytkownik otwiera okno dla dokumentu, MFC tworzy widok i powiązuje go z tym dokumentem. Użyty typ widoku oraz klasę okna ramki określa wzorzec dokumentu.
Dokument otrzymuje polecenia kierowane do niego przez aktywny widok. Jeśli dokument nie obsłuży danego polecenia, przekazuje je do wzorca dokumentu zarządzającego tym dokumentem (który z kolei może przekazać polecenie do okna ramki dokumentu, okna ramki aplikacji itd.).
Gdy użytkownik modyfikuje dane dokumentu, każdy z powiązanych z nim widoków musi odzwierciedlić te modyfikacje. Klasa CDocument dostarcza funkcji składowej UpdateAllviews (), której możesz użyć do poinformowania widoków o takich zmianach, dzięki czemu widoki będą mogły automatycznie odświeżyć swoją zawartość. Oprócz tego, jeśli dane dokumentu ulegną zmianie, przed zamknięciem nie zapisanego dokumentu MFC samo poprosi użytkownika o potwierdzenie tej decyzji. W typowej aplikacji, w celu zaimplementowania dokumentów musisz wykonać następujące operacje:
Dla każdego rodzaju dokumentu, z jakiego będziesz korzystał w aplikacji, wyprowadź jego klasę z klasy CDocument (lub COleDocument).
Dodaj do klasy dokumentu zmienne składowe, w których będą przechowywane dane dokumentu (albo jako proste zmienne w prostych aplikacjach, albo jako bardziej złożone struktury, takie jak obiekty ccmdTarget w złożonych aplikacjach, na przykład w aplikacjach OLE).
Zaimplementuj funkcje składowe dla odczytu i modyfikacji danych dokumentu. Widoki powiązane z dokumentem są najważniejszymi użytkownikami tych funkcji. Różne widoki dokumentu zaimplementujesz na podstawie sposobów, na jakie użytkownik musi mieć dostęp do danych zawartych w dokumencie.
Przesłoń funkcję składową CDocument: :Serialize () w klasie dokumentu, aby móc zapisywać dane na dysk oraz odczytywać je z pliku na dysku. Pamiętaj, że powinieneś odczytywać informacje z pliku w tej samej kolejności, w jakiej zostały one w nim zapisane.
Klasa coieDocument, także pochodząca od klasy CDocument, umożliwia obsługę technologii "przeciągnij i upuść" OLE, a także operacje osadzania i łączenia obiektów w oknie dokumentu.
Zarządzanie bardziej złożonymi kombinacjami dokumentów, widoków i ramek
Jak już wiesz, standardowa relacja pomiędzy dokumentem, jego widokiem (lub widokami) oraz oknem ramki (lub oknami) jest względnie prosta: relacja typu jeden do wielu pomiędzy dokumentami a widokami oraz relacja jeden do jednego pomiędzy każdym widokiem a oknem ramki. Wiele aplikacji musi obsłużyć tylko jeden rodzaj dokumentu (lecz umożliwiając użytkownikowi otwarcie kilku dokumentów tego samego typu) w pojedynczym rodzaju widoku oraz tylko z jednym oknem ramki na dokument. Jednak niektóre aplikacje mogą wymagać zmian w tym domyślnym układzie - tworząc kilka widoków dla pojedynczego rodzaju dokumentu, pojedyncze widoki dla różnych typów dokumentów lub kilka widoków dla różnych typów dokumentów. Warto poświęcić chwilę na rozważenie różnych sytuacji, w których możesz się znaleźć, pracując z dokumentami i widokami, i zastanowić się, w jaki sposób powinieneś zaprojektować swoją aplikację, aby odpowiednio się zachowywała.
Praca z kilkoma rodzajami dokumentów
Bez względu na to czy tworzysz aplikację MDI, czy SDI, AppWizard tworzy jedynie pojedynczą klasę dokumentu. W pewnych przypadkach możesz jednak zechcieć obsłużyć więcej niż jeden rodzaj dokumentu. Na przykład, Twoja aplikacja może potrzebować zarówno arkuszy kalkulacyjnych, jak i wykresów. Najprawdopodobniej w takim przypadku dla obu rodzajów dokumentów zastosujesz osobne klasy dokumentów, a także osobne klasy widoków. Gdy użytkownik wybierze w menu Plik polecenie Nowy, MFC wyświetli okno dialogowe zawierające listę rodzajów dokumentów obsługiwanych przez aplikację. Gdy użytkownik wybierze rodzaj dokumentu, aplikacja stworzy dokument odpowiedniego typu. Następnie każdy z rodzajów dokumentów będzie zarządzany poprzez własny obiekt wzorca dokumentu.
Aby stworzyć w aplikacji dodatkowe klasy dokumentów, użyj przycisku Add Class w oknie dialogowym ClassWizarda. Jako rodzaj klasy bazowej (Class Type) wybierz CDocument (lub COleDOcument) oraz podaj wymagane informacje o dokumencie. Następnie zaim-plementuj struktury danych nowej klasy.
Aby poinformować MFC o swojej dodatkowej klasie dokumentu, musisz następnie dodać drugie wywołanie funkcji AddDocTempiateO wewnątrz funkcji initinstance () klasy aplikacji. Na przykład, aplikacja zawierająca dwa rodzaje dokumentów będzie w funkcji initinstance () zawierała kod podobny do poniższego:
CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(
IDR_OPAINTTYPE,
RUNTIME_CLASS(CSamplelDoc),
RUNTIME_CLASS(CMDIChildWnd) // standard MDI child frame

RUNTIME_CLASS(CSamplelView)); AddDocTemplate(pDocTemplate); pDocTemplate = new CMultiDocTemplate
IDR_OPAINTTYPE,
RUNTIME_CLASS(CSample2Doc),
RUNTIME_CLASS(CMDIChildWnd),// standard MDI child frame

RUNTIME_CLASS(CSample2View)); AddDocTemplate(pDocTemplate);
Użycie kilku rodzajów widoków dokumentu
Jak już wiesz, wiele z dokumentów wymaga tylko pojedynczego widoku. Jednak, co także już wiesz, model dokument-widok w MFC umożliwia aplikacji obsługę więcej niż jednego widoku dla danego dokumentu - w rzeczywistości, możesz zastosować tyle różnych rodzajów widoków, ilu wymaga aplikacja. Aby pomóc Ci w implementacji wielu widoków, obiekt dokumentu utrzymuje listę swoich widoków i zapewnia funkcje składowe do dodawania i usuwania widoków oraz funkcję UpdateAllViews o, przeznaczoną do informowania wszystkich widoków o zmianach, które wystąpiły w powiązanym z nimi dokumencie. MFC obsługuje trzy standardowe interfejsy użytkownika, które możesz zaprojektować w swojej aplikacji, umożliwiające zastosowanie kilku różnych widoków dla tego samego dokumentu. Oto owe trzy modele:
Twoja aplikacja może umożliwić użytkownikowi przeglądanie obiektów tej samej klasy, każdego w oddzielnym oknie ramki MFI (a konkretnie oknie potomnym MDI). W celu otwarcia kolejnego widoku tego samego dokumentu użytkownik może użyć polecenia New Window (nowe okno) w menu Window (to polecenie jest automatycznie dodawane do utworzonego przez AppWizarda standardowego zasobu menu IDR_MAINFRAME). Następnie użytkownik może użyć kilku okien do równoczesnego przeglądania różnych części dokumentu. Domyślna implementacja polecenia New Window w MFC powoduje powielenie początkowego okna ramki oraz widoku powiązanego z dokumentem (czyli okna ramki i widoku związanego z dokumentem poprzez wzorzec dokumentu, obiekt CDocTemplate). Kilka widoków tego samego typu dla tego samego dokumentu przedstawia rysunek 20.2.
Twoja aplikacja może pozwolić użytkownikowi na oglądanie obiektów tej samej klasy w tym samym oknie ramki dokumentu. Aplikacja używa dzielonego okna (którym zajmiemy się dokładniej w dalszej części rozdziału) w celu podzielenia obszaru widoku na kilka oddzielnych części, zawierających widoki dokumentu. MFC, w połączeniu z dzielonym oknem, tworzy kilka obiektów widoków tej samej klasy. Więcej na temat procesu stosowanego przez dzielone okno do tworzenia kilku widoków oraz o sposobie implementacji klasy CSplitterwnd dowiesz się w dalszej części rozdziału. Rysunek 20.3 przedstawia model dzielonego okna, zawierający pojedynczy rodzaj widoku.
Twoja aplikacja może pozwalać użytkownikowi na przeglądanie obiektów różnych klas w pojedynczym oknie ramki. W tym trzecim modelu (stanowiącym odmianę okna dzielonego), kilka widoków dokumentu korzysta ze wspólnego okna ramki. Aplikacja i MFC tworzą widoki różnych klas (wyprowadzonych jednak z klasy cview lub którejś z jej klas pochodnych). W tym modelu każdy konstruowany widok przedstawia dane tego samego dokumentu w różny sposób. Na przykład, jeden z widoków może przedstawiać zwykłą postać arkusza kalkulacyjnego, podczas gdy drugi może wyświetlać zawarte na nim formuły. Za pomocą belki podziału (tworzonej wraz z klasą csplitterwnd) użytkownik może dostosować względne rozmiary widoków. Model dzielonego okna z kilkoma różnymi widokami tych samych danych przedstawia rysunek 20.4.
MFC udostępnia te trzy modele do wykorzystania w aplikacjach. Pierwszy z nich korzysta z polecenia New Window w menu Window, a pozostałe dwa wykorzystuj ą klasę csplitterwnd. Więcej na temat dzielonych okien dowiesz się w dalszej części rozdziału. Jasne jest, że te trzy modele są najprostszymi z możliwych implementacji. Korzystając ze stworzonego przez nie punktu wyjścia możesz implementować dalsze, bardziej złożone modele.
Klasa CMDIFrameWnd
Klasa CMDIFrameWnd reprezentuje funkcje okna ramki Windows dla wielodokumento-wej aplikacji MDI. Udostępnia także funkcje składowe służące do zarządzania tym oknem. Aby stworzyć użyteczne okno ramki MDI dla aplikacji wielodokumentowej, musisz wyprowadzić własną klasę dla głównego okna ramki z klasy CMDIFrameWnd, a następnie dodać do niej zmienne składowe w celu przechowania danych specyficznych dla całej aplikacji (ale nie danych specyficznych dla konkretnego dokumentu). Oprócz tego, musisz zaimplementować funkcje obsługi komunikatów oraz mapę komunikatów, określające, jakie komunikaty zostaną skierowane do okna, czy to przez system operacyjny, czy to przez wzorzec dokumentu.
Okno ramki MDI możesz skonstruować na dwa sposoby - wywołując funkcję składową Create () lub wywołując funkcję składową LoadFrame (), obie należące do klasy CFrameWnd. Jednak zanim wywołasz którąś z tych funkcji, musisz użyć operatora new, konstruując na stercie obiekt okna ramki. Zanim wywołasz funkcję Create (), możesz użyć globalnej funkcji AfxRegisterWnddass () do zarejestrowania klasy okna oraz stylu klasy dla ramki. Parametry tworzenia ramki powinieneś przekazać jako natychmiastowe parametry funkcji Create ().
Z drugiej strony, funkcja składowa LoadFrame () wymaga mniejszej liczby argumentów, przyjmując większość domyślnych wartości z zasobów utworzonych dla projektu, takich jak tytuł okna ramki, ikonę, tablicę akceleratorów oraz menu. Aby były dostępne dla funkcji LoadFrame () (i ładowane z definicji dla nowego okna), wszystkie te zasoby muszą mieć ten sam identyfikator zasobu (na przykład domyślny identyfikator zasobów w MFC, IDR_MAINFRAME, lub każdy inny identyfikator zasobów, taki jak IDR_PARENTFRAME).
Klasa CMDIFrameWnd dziedziczy większość z domyślnej implementacji klasy CFrameWnd. Klasa CMDIFrameWnd posiada następujące dodatkowe elementy, które nie pochodzą od klasy CFrameWnd:
Okno ramki MDI zarządza oknami potomnymi w aplikacji, układając je zgodnie z działaniami użytkownika związanymi z paskami przewijania okna potomnego oraz paskami przewijania okna aplikacji. Okno klienta MDI jest bezpośrednim oknem nadrzędnym dla każdego potomnego okna ramki MDI w aplikacji. Style okna WS_HSCROLL i WS_VSCROLL określone dla okna CMDiFrameWnd odnoszą się do okna klienta MDI, a nie do głównego okna ramki, dzięki czemu użytkownik może przewijać obszar roboczy MDI (innymi słowy, paski przewijania przesuwają okna potomne w ramach obszaru roboczego MDI, a nie same obszary robocze okien potomnych).
Okno ramki MDI posiada domyślne menu, używane jako pasek menu, gdy nie jest aktywne żadne okno potomne MDI. Gdy okno potomne MDI staje się aktywne, MFC automatycznie zastępuje pasek menu okna ramki paskiem menu okna potomnego.
Okno ramki MDI działa we współpracy z aktualnie aktywnym potomnym oknem MDI, jeśli takie istnieje. Na przykład, aktualnie aktywne okno potomne MDI otrzymuje komunikaty poleceń przed oknem ramki MDI.
Okno ramki MDI zawiera domyślną implementację polecenia ID_WINDOW_NEW, które tworzy nową ramkę i widok dla bieżącego dokumentu. Aplikacja może przesłonić tę domyślną implementację polecenia w celu dostosowania obsługi okien MDI.
Do niszczenia obiektu okna CMDiFrameWnd nie używaj operatora delete -jeśli to zrobisz, możesz naruszyć licznik odwołań lub doprowadzić do wycieku pamięci w aplikacji. Zamiast tego powinieneś używać funkcji składowej CWnd:: PostNcDestroy (). Implementacja tej funkcji w klasie CFrameWnd w momencie niszczenia okna usuwa także obiekt C++ Gdy użytkownik zamyka obiekt okna CMDIFrameWnd, domyślna funkcja obsługi OnClose () wywołuje funkcję DestroyWindow () (która z kolei wywołuje funkcję składową DestroyWindow o każdego aktualnie otwartego obiektu okna potomnego MDI, CMDIChildWnd).
Mimo że MFC wyprowadza klasę CMDiFrameWnd z klasy CFrameWnd, podczas deklarowania klasy okna ramki wyprowadzonej z CMDiFrameWnd nie musisz używać makra
DECLARE_ DYNCREATE.
Klasa CMDIChildWnd
Klasa CMDIChildWnd reprezentuje funkcje okna potomnego Windows dla wielodoku-mentowej aplikacji MDI. Udostępnia także funkcje składowe służące do zarządzania tym oknem. Okno potomne MDI wygląda podobnie do zwykłego okna ramki, z tym że występuje wewnątrz okna ramki MDI, a nie na pulpicie. Okno potomne MDI nie ma własnego paska menu, lecz zamiast niego korzysta z paska menu okna ramki MDI. MFC automatycznie zmienia pasek menu okna ramki MDI na pasek menu reprezentujący aktualnie aktywne okno potomne.
Aby stworzyć użyteczne okno potomne MDI, musisz wyprowadzić własną klasę z klasy CMDichildWnd (jeśli nie chcesz dostosowywać działania tego okna, możesz użyć po prostu klasy CMDichildWnd). Następnie dodaj zmienne składowe reprezentujące dane specyficzne dla dokumentu, z którym to okno potomne będzie powiązane. Co więcej, w wyprowadzonej klasie musisz zaimplementować funkcje obsługi komunikatów oraz mapę komunikatów w celu określenia, co stanie się z komunikatami przekazanymi do okna. (W przeciwnym razie MFC do obsługi tych komunikatów użyje domyślnych funkcji obsługi klasy CMDichildWnd). Istnieją trzy sposoby konstruowania okna potomnego MDI:
Bezpośrednio, z użyciem funkcji składowej Create (}.
Bezpośrednio, z użyciem funkcji składowej LoadFrame ().
Pośrednio, poprzez wzorzec dokumentu.
Tak jak w przypadku klasy CMDiFrameWnd, przed wywołaniem funkcji Create () lub LoadFrame (} musisz użyć operatora new, konstruując na stercie obiekt okna potomnego. Zanim wywołasz funkcję Create (), możesz użyć globalnej funkcji AfxRegi-sterWndciass () do zarejestrowania klasy okna oraz określenia ikony i stylu klasy dla ramki potomnej. Tak jak w przypadku klasy CMDiFrameWnd parametry tworzenia ramki potomnej powinieneś przekazać jako natychmiastowe parametry funkcji Create ().
Także podobnie jak w klasie CMDiFrameWnd, funkcja składowa LoadFrame () wymaga mniejszej liczby argumentów, przyjmując większość domyślnych wartości z zasobów utworzonych dla projektu, takich jak tytuł okna, ikonę, tablicę akceleratorów oraz menu. Aby były dostępne dla funkcji LoadFrame () (i ładowane z definicji dla nowego okna), wszystkie te zasoby muszą mieć ten sam identyfikator zasobu (taki jak IDR_MAINFRAME).
Gdy obiekty CMDichildWnd zawierają widoki i dokumenty, są one tworzone pośrednio przez MFC zamiast bezpośrednio przez programistę. Obiekt CDocTemplate zarządza tworzeniem ramki, tworzeniem zawartego w niej widoku oraz połączeniem tego widoku z odpowiednim dokumentem. Parametry konstruktora obiektu CDocTemplate określają klasę CRuntimeClass dla trzech klas związanych z tworzeniem okna (dokumentu, ramki i widoku). Obiekt CRuntimeClass jest używany przez MFC do dynamicznego tworzenia nowych ramek na żądanie użytkownika (na przykład poprzez wybranie w menu Rle polecenia New lub wybranie polecenia New Window w menu Window).
Klasa CMDichildWnd dziedziczy większość z domyślnej implementacji klasy CFrameWnd. Jednak oprócz tego posiada następujące dodatkowe właściwości, których nie posiada ta klasa:
W połączeniu z klasą CMultiDocTemplate kilka obiektów CMDichildWnd z tego samego wzorca dokumentu dzieli między siebie to samo menu, oszczędzając zasoby systemowe Windows.
Jak dowiedziałeś się z poprzedniej sekcji, pasek menu aktualnie aktywnego okna potomnego MDI całkowicie zastępuje pasek menu okna ramki MDI, zaś tytuł aktualnie aktywnego okna potomnego MDI jest dopisywany do tytułu okna ramki MDI.
Do niszczenia obiektu okna CMDiFChildWnd nie używaj operatora delete -jeśli to zrobisz, możesz naruszyć licznik odwołań lub doprowadzić do wycieku pamięci w aplikacji. Zamiast tego powinieneś używać funkcji składowej cwnd: :PostNcDestroy (). Implementacja tej funkcji w klasie CFrameWnd w momencie niszczenia okna usuwa także obiekt C++. Gdy użytkownik zamyka okno potomne, domyślna funkcja obsługi Onciose () wywołuje funkcję składową DestroyWindow ().
W odróżnieniu od klasy okna ramki wyprowadzanej z klasy CMDiFrameWnd, klasa okna ramki wyprowadzona z klasy CMDichildWnd musi być zadeklarowana z użyciem makra DECLARE_DYNCREATE, tak aby mógł poprawnie działać mechanizm tworzenia RUNTIME_
CLASS.
Klasa CMultiDocTemplate
Podobnie jak klasa csingleDocTemplate definiuje wzorzec dokumentu dla aplikacji SDI, tak klasa CMultiDocTemplate definiuje wzorzec dokumentu dla aplikacji MDI. Aplikacje MDI używają głównego okna ramki jako obszaru roboczego, w którym użytkownik może otwierać dowolną liczbę okien ramek dokumentów, z których każda zawiera dokument. Jak wiesz z rozdziału 19., wzorzec dokumentu definiuje relacje pomiędzy trzema rodzajami klas:
Klasą dokumentu, wyprowadzoną z klasy CDocument lub coleDocument.
Klasą widoku, wyświetlającą dane z klasy dokumentu, powiązaną z nią poprzez wzorzec. Możesz wyprowadzić tę klasę z klasy cview, cscroliview, CFormView, CEditview lub innych klas pochodzących od klasy cview. Możesz także bezpośrednio użyć klasy CEditview.
Klasą okna ramki, zawierającą widok. W przypadku wzorca dokumentu MDI możesz wyprowadzić tę klasę z klasy CMDichildWnd lub, jeśli nie musisz dostosowywać działania okna ramki dokumentu, możesz użyć tej klasy bezpośrednio.
Aplikacja MDI może obsłużyć więcej niż jeden rodzaj dokumentu, zaś w tym samym czasie mogą być otwarte dokumenty różnych typów. Aplikacja posiada po jednym wzorcu dokumentu dla każdego obsługiwanego rodzaju dokumentu. Na przykład, jeśli aplikacja MDI obsługuje zarówno arkusze kalkulacyjne, jak i dokumenty tekstowe, będzie posiadała dwa obiekty CMultiDocTemplate.
Aplikacja używa wzorców dokumentów, gdy użytkownik tworzy nowy dokument. Jeśli aplikacja obsługuje więcej niż jeden rodzaj dokumentu, wtedy MFC odczytuje nazwy obsługiwanych typów dokumentów z wzorców dokumentów i wyświetla je na liście w oknie dialogowym File New. Gdy użytkownik wybierze rodzaj dokumentu, aplikacja tworzy obiekt klasy dokumentu, obiekt okna ramki oraz obiekt widoku, po czym wzajemnie je łączy.
Poza konstruktorem nie musisz wywoływać żadnych funkcji składowych klasy CMultiDocTemplate. Obiekty tego typu są wewnętrznie obsługiwane przez MFC.
Narzut związany z użyciem klasy CDocument
Jak już wiesz, przy tworzeniu aplikacji będziesz najczęściej korzystał z modelu dokument-widok. W większości przypadków będziesz rzeczywiście używał jakiejś kombinacji dokumentów i dialogów tworzących interfejs aplikacji. Jedną z najbardziej znaczących zalet korzystania z modelu dokument-widok jest to, że klasa CDocument (oraz jej klasy pochodne) jest z natury bardzo niewielka. Pojedynczy obiekt klasy CDocument sam z siebie stanowi bardzo niewielki narzut, lecz do którego należy dodać także narzut jego klas podstawowych. Na szczęście obie klasy podstawowe klasy CDocument także są bardzo niewielkie.
W rzeczywistości, klasa CDocument, pomijając własne dane, które do niej dołożysz dla każdego obiektu dokumentu, zawiera jedynie siedem własnych zmiennych składowych. Klasa CDocument zawiera następujące zmienne składowe (wykorzystywane wewnętrznie przez MFC):
Dwa obiekty Cstring.
Trzy zmienne typu BOOL .
Jeden wskaźnik do obiektu CDocTemplate.
Jeden obiekt typu CPtrList, zawierający listę widoków dokumentu.
Dodatkowo, podczas działania programu, dokument wymaga pewnej ilości czasu (która w przypadku dzisiejszych szybkich komputerów jest prawie pomijalna) do stworzenia obiektu dokumentu, jego obiektów widoku, okna ramki oraz obiektu wzorca dokumentu.
Dalsze właściwości aplikacji MDI
Teraz, gdy poznałeś komponenty aplikacji MDI i wiesz już, czym różnią się one od komponentów aplikacji SDI, prawdopodobnie zdajesz sobie sprawę, że praca z aplikacją MDI niewiele się różni od pracy z aplikacją SDI opisaną w rozdziale 19. W rzeczywistości jedynymi znaczącymi różnicami pomiędzy tymi aplikacjami są różnice w oknach ramek, konieczność definiowania dodatkowych zasobów dla każdego z rodzajów okien potomnych oraz większa elastyczność i siła aplikacji MDI. Zarządzanie różnymi dokumentami i widokami pozostaje jednak takie samo jak w przypadku, gdy miałeś do czynienia z jednym tylko dokumentem i widokiem. Na przykład, aby zapisać dane w konkretnym dokumencie, w dalszym ciągu korzystasz z funkcji składowej SerializeO, tak jak w przypadku aplikacji SDI:
void CSampleDoc::Serialize(CArchive &ar)
{
m_0bjects.Serialize(ar); if( ar.IsStoring() )
{
ar m_size;
} else
{
ar m size;
}
}
Oczywiście, w przypadku aplikacji MDI musisz stworzyć osobne funkcje składowe Seriali ze o dla każdej z różnych klas dokumentów obsługiwanych przez aplikację, z których każda funkcja będzie serializować dane specyficzne dla własnej klasy.
Program PaintObj
Program demonstracyjny znajduje się na dołączonej do książki płytce CD-ROM, w kartotece Rozdz20\PaintObj. Ten przykładowy program został zbudowany z użyciem pojedynczej klasy dokumentu, CPaintobjDoc, jednak skorzystano z klas CMultiDoc-Template oraz CMDiChildWnd w celu stworzenia aplikacji MDI umożliwiającej użytkownikowi tworzenie prostych rysunków w kilku oknach naraz.
Możesz zbudować program po załadowaniu do Visual Studia pliku projektu PaintObj.dsw lub możesz po prostu uruchomić program PaintObj.exe w Eksploratorze Windows. Wygląd programu w trakcie działania przedstawia rysunek 20.5. Na listingu 20.1 znajdują się fragmenty plików źródłowych Paintobj.cpp oraz Paintv\v.cpp.
PaintObj
Położenie na płytce: Rozdz20\PaintObj
Nazwa programu: PaintObj.exe
Moduły kodu źródłowego w tekście: Paintobj.cpp, Paintvw.cpp
Listing 20.1. Fragmenty plików źródłowych Paintobj.cpp i Paintyw.cpp
// paintobj.cpp : Defines the class behaviors for the application.
//////////////////////////////////////////////////
// CPaintobjApp initialization BOOL CPaintobjApp::Initlnstance()
{
// Standard initialization
// If you arę not using these features and wish to reduce the size
// of your finał executable, you should remove from the following
// the specific initialization routines you do not need. Enable3dControls();
// Load standard INI file options (including MRU) LoadStdProfileSettings();
// Register the application's document templates.
// Document templates serve as the connection between
// documents, frame windows and views.
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate IDR_OPAINTTYPE, RUNTIME_CLASS(CPaintobjDoc) , RUNTIME_CLASS(CMDIChildWnd),// standard MDI child frame
RUNTIME_CLASS(CPaintobjView)
AddDocTemplate(pDocTemplate);
// create main MDI Frame window CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame->LoadFrame(IDR_MAINFRAME)
return FALSE; m_pMainWnd = pMainFrame;
// Enable DDE Execute open EnableShellOpen(); RegisterShellFileTypes();
// simple command linę parsing if (m_lpCmdLine[0] == '\0') {
// create a new (empty) document
OnFileNew();
}
else
{
// open an existing document OpenDocumentFile(m_lpCmdLine);
}
// Enable drag/drop open m_pMainWnd->DragAcceptFiles{);
// The main window has been initialized, so show and update it. pMainFrame->ShowWindow(m_nCmdShow); pMainFrame->UpdateWindow();
return TRUE;
}
////////////////////////////////////////////// // paintvw.cpp : implementacja klasy CPaintobjView
IMPLEMENT_DYNCREATE (CPaintobjView, CScrollView)
BEGIN_MESSAGE_MAP (CPaintobj View, CScrollYiew)
// { { AFX_MSG_MAP (CPaintobj View)
ON_WM_LBUTTONDOWN ( )
ON_WM_LBUTTONUP ( )
ON_WM_MOUSEMOVE ( )
ON_COMMAND(ID_VIEW_SCROLL, OnYiewScroll)
ON_UPDATE_COMMAND_UI ( ID_VIEW_SCROLL, OnUpdateViewScroll) )
ON_UPDATE_COMMAND_UI ( ID_VIEW_ZOOMFIT, OnUpdateFiewZoomfit )
ON_COMMAND(ID_VIEW_ZOOMFIT, OnYiewZoomfit )
ON_UPDATE_COMMAND_UI ( ID_EDIT_COPY, OnUpdateEditCopy)
ON_UPDATE_COMMAND_UI ( ID_EDIT_CUT, OnUpdateEditCut )
ON_COMMAND(ID_EDIT_CUT, OnEditCut)
//} }AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CScrollView: :OnFilePrint )
ON_COMMAND ( ID_FILE_PRINT_PREVIEW, CScrollView: : OnFilePrintPreview) ENDMESSAGEMAP ( )
//////////////////////////
// CPaintobjView construction/destruction
CPaintobjView: :CPaintobjView ( ) {
CWinApp* pApp = Af xGetApp ( ) ;
m_bZoomMode = FALSE;
m_bTracking = FALSE;
m_hcurArrow = pApp->LoadStandardCursor (IDC_ARROW) ;
m_hcurCross = pApp->LoadStandardCursor (IDC_CROSS) ;
m_pActive = NULL;
ASSERT (m_hcurArrow != NULL) ; ASSERT (m_hcurCross != NULL ) ;
// Win95 ma IDC_SIZEALL, WinNT 3.51 ma IDC_SIZE m_hcurSize = pApp->LoadStandardCursor (IDC_SIZEALL) if (m_hcurSize == NULL)
pApp->LoadStandardCursor (IDC_SIZE) ; ASSERT (m hcurSize != NULL) ;
}
CPaintobjYiew::-CPaintobjView
{
}
//////////////////////////////////////////////////////////////////////
//CPaintobjView drawing
void CPaintobjView::OnDraw(CDC* pDC) {
CPaintobjDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
POSITION pos;
pos = pDoc->m_Objects.GetHeadPosition();
while (pos != NULL) {
CPainted* pPainter = (CPainted*) pDoc->m_Objects.GetNext(pos) ;
ASSERT(pPainter->IsKindOf(RUNTIME_CLASS(CPainted)));
if (m_pActive != pPainter)
pPainter->Draw(pDC); else
pPainter->DrawSelected(pDC);
}
}
//////////////////////////
//CPaintobjView diagnostic
$ifdef _DEBUG
void CPaintobjView::AssertValid() const
{
CScrol1Fiew::AssertValid();
void CPaintobjYiew: : Dump (CDumpContext& dc) const {
CScrollView: : Dump (dc) ;
}
CPaintobj Doc* CPaintobjYiew: : GetDocument ()
// non-debug version is inline
{
ASSERT (m_pDocument->IsKindOf (RUNTIME_CLASS (CPaintobjDoc)
return (CPaintobjDoc* ) m_pDocument ; } #endif // DEBUG
////////////CPaintobjView mesage handlers
void CPaintobjYiew::OnLButtonDown(UINT nFlags, CPoint point; {
CPaintobjDoc* pDoc = (CPaintobjDoc*) GetDocument();
CClientDC ClientDC(this); OnPrepareDC(&ClientDC); ClientDC.DPtoLP(Spoint);
if (pDoc->m_pSelectedTool == NULL)
POSITION pos;
CPainted* pPainted;
CPainted* pHit;
pos = pDoc->m_Objects.GetHeadPosition();
pHit = NULL;
while (pos != NULL)
{
pPainted = (CPainted*) pDoc->m_Objects.GetNext(pos); if (pPainted->IsHit(point))
pHit = pPainted;
{ break;
}
if (pHit != NULL) {
if (m_pActive != NULL)
{
CRect rectInvalid;; rectInvalid.InflateRect(1, 1); InvalidateRect(&rectInvalid);
}
m_pActive = pHit;
m pActive->DrawSelected(&ClientDC);
}
retrun;
}
SetCaprure (); m_bTracking = TRUE;
pDoc->m_pSelectedTool->OnDown(point) ; CScrollView::OnLButtonDown(nFlags, point);
}
void CPaintobjView::OnLButtonUp(UINT nFlags, CPoint point) {
CPaintobjDoc* pDoc = (CPaintobjDoc*) GetDocument();
ASSERT(pDoc != NULL);
if (m_bTracking) {
ASSERT(pDoc->m_pSelectedTool != NULL); pDoc->m_pSelectedTool->OnUp(point, this);
CRect rect;
pDoc->m_pSelectedTool->GetBoundingRect(&rect); pDoc->ExpandBounds(&rect) ;
pDoc->m_Objects.AddTail(pDoc->m_pSelectedTool); pDoc->m_pSelectedTool = NULL; pDoc->m_nSelectedTool = ID_TOOL_SELECTOR;
m_bTracking = FALSE; ReleaseCapture();
CMainFrame* pMain = (CMainFrame*) AfxGetApp()->m_pMainWnd; pMain->ClearPositionText ();
}
CScrollYiew::OnLButtonUp(nFlags, point);
}
void CPaintobjView::OnMouseMove(UINT nFlags, CPoint point {
CClientDC ClientDC(this);
OnPrepareDC(&ClientDC) ;
ClientDC.DPtoLP(&point) ;
CPaintobjDoc* pDoc = (CPaintobjDoc*) GetDocument(); ASSERT(pDoc != NULL);
if (pDoc->m_pSelectedTool == NULL)
{
::SetCursor(m_hcurArrow); return;
}
::SetCursor(m_hcurCross);
if (!m_bTracking) return;
CMainFrame* pMain = (CMainFrame*) AfxGetApp()->m_pMainWnd, pMain->SetPositionText(point);
pDoc->m_pSelectedTool->DragDraw(SClientDC, point); CScrollView::OnMouseMove(nFlags, point);
}
void CPaintobjView::OnlnitialUpdate() {
CScrollView::OnInitialUpdate();
CPaintobjDoc* pDoc = GetDocument() ASSERT_VALID(pDoc);
CSize siz; pDoc->GetBounds(&siz) ;
m_bZoomMode = FALSE; SetScrollSizes(MM_TEXT, siz); SetScrollSizes(MM TEXT, siz);
}
void CPaintobjView::OnYiewScroll()
{
CPaintobjDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);
CSize siz; pDoc->GetBounds(&siz);
m_bZoomMode = FALSE; SetScrollSizes(MM TEXT, siz);
}
void CPaintobjView::OnUpdateViewScroll(CCmdUI* pCmdUI)
{ pCmdUI->SetCheck(!m_bZoomMode);
}
void CPaintobjView::OnUpdateViewZoomfit(CCmdUI* pCmdUI)
{
CPaintobjDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc) ;
POSITION pos;
pos = pDoc->m_Objects.GetHeadPosition();
pCmdUI->SetCheck(m_bZoomMode); pCmdUI->Enable(pos !=NULL);
}
void CPaintobjView::OnViewZoomfit()
{
CPaintobjDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc) ;
CSize siz;
pDoc->GetBounds(&siz) ; m_bZoomMode = TRUE; SetScaleToFitSize(siz);
}
void CPaintobjView::OnUpdateEditCopy(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_pActive != NULL); }
void CPaintobjView::OnUpdateEditCut(CCmdUI* pCmdUI)
{ pCmdUI->Enable(m pActive != NULL);
}
void CPaintobjView::OnEditCut() ASSERT(m_pActive != NULL);
CPaintobjDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);
POSITION pos;
CRect rectUpdate;
CPainted* pPainted;
BOOL bFoundlt = FALSE;
pos = pDoc->m_Objects.GetHeadPosition() ;
while (pos != NULL) {
pPainted = (CPainted*) pDoc->m_Objects.GetAt(pos);
if (pPainted == m_pActive)
{
bFoundlt = TRUE; break; } pDoc->m_Objects.GetNext(pos) ;
}
ASSERT(bFoundlt == TRUE); if (bFoundlt == TRUE) {
pDoc->m_Objects.RemoveAt(pos) ;
m_pActive->GetBoundingRect(&rectUpdate);
rectUpdate.InflateRect(l, ł);
delete m_pActive;
m_pActive = NULL;
InvalidateRect(&rectUpdate);
}
pDoc->RecalcBoundar()
if (m_bZoomMode)
OnViewZoomfit() ;
else
OnYiewScroll();
return;
}
Dzielone okna
W dzielonych oknach (ang. splitter \vindo\v) okno jest (lub może być) podzielone na dwa lub więcej przewijalne panele. Kontrolka podziału (przycisk podziału, split box) na ramce okna, tuż obok paska przewijania, umożliwia użytkownikowi dobieranie względnych rozmiarów paneli. Każdy panel jest widokiem tego samego dokumentu. W oknach dzielonych dynamicznie widoki zwykle należą do tej samej klasy. W oknach dzielonych statycznie widoki częściej należą do różnych klas. Do implementacji obu rodzajów dzielonych okien używa się klasy csplitterWnd.
Dynamiczne okna dzielone umożliwiają użytkownikowi dowolny podział okna na kilka paneli, a następnie przewijanie osobnych paneli w celu przeglądania różnych części dokumentu. Użytkownik może także usunąć podział okna, rezygnując z dodatkowych widoków.
Statyczne dzielone okna są tworzone już jako podzielone na kilka paneli, z których każdy ma swoje własne przeznaczenie. Na przykład, w edytorze bitmap w Visual C++, okno obrazka zawiera dwa położone obok siebie panele. Lewy panel wyświetla bitmapę w aktualnym rozmiarze, zaś w prawym panelu znajduje się powiększenie tej samej bitmapy. Te panele są rozdzielone pionową belką podziału (ang. splitter bar), którą użytkownik może przeciągać w prawo lub w lewo, zmieniając rozmiary paneli.
Aż do tej chwili uczyliśmy się o aplikacjach prezentujących w swoim interfejsie użytkownika tylko jedno główne okno. W pewnych aplikacjach przydatna staje się możliwość jednoczesnego przeglądania różnych części tego samego dokumentu. Najczęstszymi przedstawicielami tego gatunku są aplikacje mogące potencjalnie przedstawiać użytkownikowi szeroki zakres danych. Na przykład Microsoft Excel umożliwia podział okna arkusza kalkulacyjnego na cztery panele i niezależne przewijanie zawartości każdego z nich.
Wiele z aplikacji, które będziesz tworzył, na przykład takich jak zaprezentowany przed chwilą program PaintObj, może zawierać więcej informacji niż da się pomieścić na pojedynczym ekranie. Choć taka aplikacja pozwala użytkownikowi na przewijanie zawartości okna, jednak użytkownik może być zainteresowany możliwością przeglądania dwóch części dokumentu jednocześnie, nawet części znajdujących się tak daleko od siebie, że żadną miarą nie da się ich zmieścić w pojedynczym oknie na ekranie. Pozwalając użytkownikowi na podział okna, możesz upakować więcej informacji w tym samym obszarze ekranu.
Niestety, rysowanie w tego typu oknach bez wsparcia ze strony MFC jest raczej dość żmudne. Musisz dwukrotnie wywoływać kod rysunkowy, oszukując go przy tym, że okno ma obszar mniejszy niż faktycznie, i w obu przypadkach odpowiednio przekształcając współrzędne rysowania. Na szczęście, MFC dostarcza prostego rozwiązania - klasy csplitterwnd. Ta klasa jest specjalną klasą okna przeznaczoną do istnienia wewnątrz okna ramki aplikacji. Zanim jednak nauczysz się wykorzystywać ją we własnych aplikacjach, powinieneś poznać różne rodzaje dostępnych dzielonych okien.
Różnice pomiędzy dzielonymi oknami
Programiści zwykle nazywają klasę csplitterwnd oraz tworzone przez nią okna oknami dzielonymi, powinieneś więc zwrócić uwagę na drobne różnice w terminologii. Zanim zaimplementujesz klasę csplitterwnd, warto poświęcić chwilę na sposób użycia tej klasy w swojej aplikacji oraz na zasady, które muszą być spełnione, aby użycie tej klasy miało sens i działało poprawnie.
Gdy użytkownik dzieli okno, może dodać nowy panel w poziomie lub w pionie. Innymi słowy, okno dzielone może zażądać utworzenia by kolejny widok wypełnił obszar na prawo lub poniżej belki podziału. Użytkownik może także dalej dzielić okno, żądając utworzenia dwóch kolejnych widoków. Powoduje to wypełnienie widokami dwóch obszarów w pionie lub w poziomie, powodując podział okna na cztery części.
Klasa CSplitterwnd może podjąć to zadanie, gdyż podczas jej tworzenia rejestruje informacje kontekstowe o wzorcu dokumentu. Dzięki temu dzielone okno wie, do jakiej klasy dokumentu i widoku będzie odnosił się nowy panel widoku. Możesz stworzyć kod generujący różne widoki dla każdego z paneli lub możesz dla każdego z paneli generować nowy egzemplarz tego samego typu widoku co w oryginalnym oknie. Tak więc najpierw musisz zdecydować, w jaki sposób użytkownik może skorzystać z dzielonego okna. Jak wiesz z wcześniejszej części rozdziału, masz ogólnie do wyboru dwie możliwości: dynamiczne okno dzielone oraz statyczne okno dzielone.
Specyfika klasy CSplitterWnd
Jak już wiesz, klasa CSplitterwnd jest używana w aplikacjach MFC w celu umożliwienia użytkownikowi podziału okna na kilka osobnych paneli. Panel jest zwykle obiektem specyficznym dla aplikacji, wyprowadzonym z klasy CView, lecz może być dowolnym obiektem CWnd posiadającym odpowiedni identyfikator okna potomnego.
Zwykle obiekt CSplitterWnd jest osadzany w nadrzędnym obiekcie CFrameWnd lub CMDichildWnd. Do tworzenia obiektu csplitterwnd użyj następujących kroków:
1. Osadź zmienną składową klasy CSplitterWnd w klasie nadrzędnej ramki.
2. Przesłoń funkcję CFrameWnd: :OnCreateClient () nadrzędnej ramki.
3. W nowej wersji funkcji OnCreateClient () wywołaj funkcję CreateO lub CreateStaticO klasy CSplitterWnd (w zależności od rodzaju dzielonego okna, jakie chcesz utworzyć).
Aby utworzyć dynamiczne okno dzielone, wywołaj funkcję składową create (). Dynamiczne okna dzielone są zwykle używane do tworzenia i przewijania kilku poszczególnych paneli (widoków) tego samego dokumentu. MFC automatycznie tworzy początkowy panel w dzielonym oknie; następnie zgodnie z działaniami użytkownika MFC tworzy, zmienia rozmiar lub usuwa dodatkowe panele. Podczas wywoływania funkcji create () podaje się minimalną wysokość wiersza i minimalną szerokość kolumny, określające, kiedy panel staje się zbyt mały, by mógł zostać wyświetlony. Po wywołaniu funkcji Create () możesz dostosować te wartości, wywołując funkcje składowe SetColumninf o () (dla szerokości kolumny) oraz SetRowinf o () (dla wysokości wiersza).
Oprócz tego, funkcji składowych SetColumninfoO i SetRowinfoO możesz użyć do ustawienia "idealnej" szerokości kolumny oraz "idealnej" wysokości wiersza. Gdy MFC wyświetla dzielone okno, najpierw wyświetla okno ramki, a potem okno dzielone. Następnie układa panele w kolumnach i wierszach zgodnie z ich idealnymi wymiarami, zaczynając od lewego górnego rogu, a kończąc na prawym dolnym rogu obszaru roboczego okna dzielonego.
Aby stworzyć statyczne okno dzielone, użyj funkcji składowej Createstatic (). W takim przypadku użytkownik może zmienić jedynie rozmiary paneli, a nie ich ilość czy kolejność. Podczas tworzenia statycznego okna dzielonego musisz sam utworzyć wszystkie statyczne panele. Pamiętaj, by utworzyć je przed powrotem z funkcji składowej OnCreate-Client() okna nadrzędnej ramki, gdyż w przeciwnym razie MFC nie będzie mogło poprawnie wyświetlić okna.
Funkcja składowa CreateStatico automatycznie inicjalizuje statyczne okno dzielone z minimalną szerokością kolumny i minimalną wysokością wiersza równymi 0. Po wywołaniu tej funkcji możesz zmienić te minimalne wartości (tak jak w przypadku dynamicznych dzielonych okien), wywołując funkcje składowe Setcolumninf o () oraz SetRowinf o ().
Poszczególne panele w statycznych oknach dzielonych często należą do różnych klas. Okno dzielone obsługuje specjalne paski przewijania (niezależne od pasków przewijania, które mogą występować w panelach). Paski te są potomkami obiektu CSplitterWnd i są wspólnie dzielone pomiędzy oba panele. Te specjalne paski przewijania tworzysz w momencie tworzenia okna dzielonego. Na przykład, okno csplitterWnd posiadające jeden wiersz, dwie kolumny oraz styl WS_VSCROLL wyświetla pionowy pasek przewijania wspólny dla obu paneli. Gdy użytkownik użyje tego paska, komunikaty WM_VSCROLL są wysyłane do obu paneli. Gdy któryś z paneli ustawia pozycję paska przewijania, ustawiana jest pozycja wspólnego paska.
Podczas tworzenia obu rodzajów okna dzielonego musisz podać maksymalną ilość wierszy i kolumn, jaka może pojawić się w tym oknie. W przypadku statycznych okien dzielonych konieczne jest utworzenie tylu paneli, aby wypełnić wszystkie wiersze i kolumny. W przypadku dynamicznych okien dzielonych, w momencie gdy aplikacja tworzy obiekt csplitterWnd, MFC automatycznie tworzy pierwszy panel.
Maksymalna ilość paneli, jaką możesz określić dla statycznych okien dzielonych, to 16 wierszy na 16 kolumn. Jednak należy pamiętać, że podział okna na więcej niż dwa panele zwykle może wprowadzić użytkownika w zakłopotanie i w takich przypadkach lepiej jest użyć kilku różnych widoków w osobnych oknach ramek, pomiędzy którymi użytkownik może się dowolnie przełączać. Zalecana przez Microsoft konfiguracja dla statycznych dzielonych okien jest następująca:
l wiersz x 2 kolumny dla wyświetlania dwóch odmiennych paneli poziomo obok siebie (każdy panel jest wyższy niż szerszy).
2 wiersze x l kolumna dla wyświetlania dwóch odmiennych paneli pionowo ponad sobą (każdy panel jest szerszy niż wyższy).
2 wiersze x 2 kolumny dla wyświetlania podobnych paneli, zwykle dzielących okno na cztery części (najlepsze do wyświetlania danych graficznych).
Maksymalna ilość paneli, jaką możesz określić dla dynamicznych okien dzielonych, to dwa wiersze na dwie kolumny. Zalecana przez Microsoft konfiguracja dla dynamicznych dzielonych okien jest następująca:
l wiersz x 2 kolumny dla wyświetlania danych kolumnowych.
2 wiersze x l kolumna dla wyświetlania danych tekstowych lub innych.
2 wiersze x 2 kolumny dla wyświetlania tabel lub danych tabelarycznych.
Dodatkowe uwagi
na temat dynamicznych okien dzielonych
Jak wiesz z wcześniejszej części rozdziału, dynamiczne okna dzielone pozwalają użytkownikom na dowolny podział okna. Aplikacja z dynamicznymi oknami dzielonymi posiada małe przyciski, jeden ponad pionowym paskiem przewijania i jeden na lewo od poziomego paska przewijania. Przez przeciągnięcie któregoś z tych przycisków użytkownik może dokonać podziału okna na panele. Aplikację posiadającą dynamiczne okno dzielone, podzielone na cztery osobne panele, przedstawia rysunek 20.6.
Po przeciągnięciu nieco w dół przycisku znajdującego się powyżej paska pionowego okno dzieli się i automatycznie tworzy kolejny widok. Aby przygotować ten rodzaj okna dzielonego, musisz w swojej aplikacji, w oknie ramki, zadeklarować egzemplarz obiektu klasy CSplitterwnd. W przypadku aplikacji SDI będzie to klasa CMainFrame, zaś w aplikacjach MDI będzie to klasa CMDichildwnd dla każdego widoku, który ma implementować dynamiczne okna dzielone.
Aby zainicjować dynamiczne okno dzielone, utwórz je w momencie, gdy okno ramki chce utworzyć swój obszar roboczy. Zwykle okno ramki po prostu tworzy widok i wstawia go w swój obszar roboczy, lecz możesz utworzyć okno dzielone i sam wstawić je do ramki. Okno dzielone samo stworzy wypełniający je widok, tworząc później także dodatkowe widoki, gdy użytkownik dokona podziału okna.
Aby ramka utworzyła okno dzielone, przesłoń funkcję OnCreateClient () okna ramki. W przypadku dynamicznego okna dzielonego w aplikacji SDI ta funkcja wymaga jedynie kodu podobnego do poniższego:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT 1pcs, CCreateContext* pContext)
{
return m_wndSplitter.Create(this, 2, 2, CSize(1,1), pContext);
Funkcja CSplitterWnd: :Create () przyjmuje kilka parametrów. Pierwszym z nich jest wskaźnik do okna nadrzędnego, które musi być oknem ramki. Dwa następne parametry to maksymalna ilość wierszy i kolumn obsługiwanych przez to dzielone okno. Możesz zabronić podziału w pionie, przekazując maksymalną ilość wierszy równą l, lub możesz zabronić podziału w poziomie, przekazując wartość l jako maksymalną ilość kolumn. Takie okno dzielone na jednej z krawędzi nie będzie posiadało odpowiedniego przycisku podziału.
Dynamiczne okna dzielone w MFC nie są w stanie obsłużyć więcej niż dwóch wierszy lub dwóch kolumn. Jeśli w funkcji Create () spróbujesz przekazać wartość większą niż 2, MFC w wersji dla debuggowania wyświetli odpowiedni komunikat asercji i zakończy działanie aplikacji.
Wartość CSize () przekazana do funkcji określa najmniejsze dozwolone rozmiary paneli. Użyty w przykładzie rozmiar l na l sprawia, że panele mogą przyjąć dowolne rozmiary. Jeśli jednak z powodu małych rozmiarów Twój widok miałby problemy z rysowaniem w panelu, możesz wymusić większy minimalny rozmiar panelu, przekazując większą wartość CSize ().
MFC nie pozwoli użytkownikowi na stworzenie panelu o rozmiarze mniejszym niż podany w wartości csize o . Gdy użytkownik, przeciągając belkę podziału, zmniejszy panel poniżej tego rozmiaru i zwolni przycisk myszy, MFC po prostu zamknie panel. Oprócz tego, w wersjach programów przeznaczonych do debuggowania zostanie wyświetlone odpowiednie ostrzeżenie, na przykład takie:
Warning: split too smali to create new pane (Ostrzeżenie: podział zbyt mały do stworzenia nowego panela)
Biorąc pod uwagę sposób działania okna dzielonego oraz tworzenia wszystkich zawartych w nim widoków, jasne jest, że musi istnieć jakiś mechanizm, dzięki któremu okno dzielone wie, jaki widok ma stworzyć - oraz z jakim dokumentem ma go powiązać. Służy do tego parametr pContext, otrzymany jako parametr funkcji OnCreateClient () i przekazywany funkcji Create () klasy csplitterWnd. Parametr pContext wskazuje informacje kontekstowe informujące kod klasy csplitterWnd o tym, kto ma obsłużyć tworzenie nowego widoku oraz jego powiązanie z odpowiednim dokumentem.
Użycie różnych widoków w dynamicznych panelach
Fragment kodu z funkcji CMainFrame: :OnCreateSplitter () w poprzedniej sekcji dawał w rezultacie okno dzielone zawierające dwa egzemplarze klasy cview, zarejestrowane we wzorcu dokumentu tworzącego ramkę. W dodatkowych panelach możesz jednak użyć innych klas widoków, dzięki czemu możesz wyświetlać informacje w inny sposób - wyświetlając w różny sposób dane tego samego dokumentu lub nawet w różny sposób dane z różnych dokumentów.
Gdy w dynamicznym oknie dzielonym użytkownik tworzy nowy panel, MFC do stworzenia tego panelu wywołuje funkcję składową CreateView() klasy csplitterWnd. Normalnie, funkcja CreateView() po prostu tworzy żądany widok, oparty na informacji
kontekstowej przekazanej funkcji CreateO w parametrze pContext. Jeśli parametr pContext ma wartość NULL, funkcja sprawdza, jaki widok jest akurat aktywny, i podejmuje próbę utworzenia takiego samego.
Jeśli chcesz użyć w różnych panelach dynamicznego okna dzielonego różnych klas widoków, musisz wyprowadzić własną klasę z klasy CSplitterWnd. Musisz potem przesłonić funkcję CreateView (), tworząc widok według własnego wyboru. Na szczęście, kod przesłaniający jest prosty - wszystko co musisz zrobić to przekazać w wywołaniu funkcji csplitterWnd:: CreateView o makro RUNTiME_CLASS z nazwą klasy widoku, jaki chcesz utworzyć:
BOOL CMySplitterWnd::CreateView(int rów, int col, CRuntimeClass* pViewClass, SIZE sizelnit, CCreateContext* pContext)
{
if(row== O && col == 0)
{
return CSplitterWnd::CreateView(rów, col, pViewClass, sizelnit, pContext);
}
else {
return CSplitterWnd::CreateView(rów, col,
RUNTIME_CLASS(CSecondView) , sizelnit, pContext);
};
}

Kod najpierw sprawdza, czy widok jest tworzony w wierszu O i w kolumnie 0. Jeśli tak, oznacza to, że okno dzielone dopiero powstaje i musisz stworzyć obiekt widoku żądanej klasy. Jeśli kod rzeczywiście tworzy początkowy panel, zostaje stworzony taki widok, jakiego żąda okno dzielone. Jeśli jednak tworzony jest panel inny niż początkowy, kod zwraca klasę RUNTIME_CLASS () klasy csecondview.
Użycie obiektu CRuntimeClass
Kod pokazany w poprzedniej sekcji może nie być do końca oczywisty, a to z powodu przekazania w wywołaniu funkcji CreateView () wskaźnika do obiektu CRuntimeClass. Obiekt klasy CRuntimeClass zawiera informacje czasu wykonywania o typie danej klasy. Mając ten wskaźnik, kod wewnątrz funkcji CreateView () może podjąć się zadania konstrukcji dowolnego obiektu opisywanego przez informacje czasu wykonania.
Jeśli w funkcji CMySplitterWnd: :CreateView() ustawisz punkt wstrzymania i sprawdzisz przebieg wykonania aplikacji korzystającej z kodu z poprzedniej sekcji, poznasz pewne ważne fakty dotyczące klasy okna dzielonego. Co najważniejsze, dowiesz się, że okno dzielone niszczy widoki, które nie są już widoczne i odtwarza je dopiero na żądanie. To oznacza, że cykl życia okna dzielonego i jego widoków można przedstawić w tabeli, takiej jak tabela 20.1.
Tabela 20.1. Fazy życia okna dzielonego i jego widoków
Działanie użytkownika
Odpowiedź okna dzielonego
Uruchomienie aplikacji (klasa CFrameWnd tworzy zawarty w niej egzemplarz obiektu
CSplitterWnd).
Tworzy widok w pozycji 0, 0.
Przeciąga w dół przycisk podziału pionowego.
Tworzy widok w pozycji l, 0.

Przeciąga w prawo przycisk podziału poziomego.
Tworzy widok w pozycji 0, l, a następnie widok w pozycji 1,1, gdyż w tym momencie okno jest dzielone na cztery panele.

Przeciąga przycisk podziału pionowego w górę, usuwając podział.
Niszczy panele w pozycjach O, O oraz l, 0.

Przeciąga w dół przycisk podziału pionowego, ponownie tworząc podział.
Ponownie tworzy widok w pozycji 1, O, a następnie w pozycji 1,1, gdyż teraz znów mamy cztery panele.

Jeśli spróbujesz tego z aplikacją posiadającą dynamiczne okno dzielone, otrzymasz w wyniku cztery widoki drugiej klasy widoku. Możesz naprawić to zapewniając, że będą usuwane jedynie widoki w kolumnie lub wierszu 1. Możesz przekonać się, że okno dzielone ma mnóstwo pracy z żonglowaniem logicznymi pozycjami wierszy i kolumn dla różnych widoków w oknie. Poza tym w tle dokonywane są intensywne obliczenia zapewniające poprawne ułożenie każdego okna widoku w oknie roboczym okna dzielonego.
Użycie okien dzielonych z widokami powiązanymi z więcej niż jednym dokumentem
Proces opisywany w poprzedniej sekcji działa poprawnie pod warunkiem, że wszystkie nowe widoki odwołują się do tego samego dokumentu co widoki istniejące. Jeśli jednak chcesz, by kolejny widok był powiązany z innym dokumentem, musisz obsłużyć tworzenie okna dzielonego w nieco inny sposób. Należy zatem stworzyć okno dzielone i nadać mu inny kontekst tworzenia. Musisz poinformować je, że ma tworzyć nowe dokumenty i widoki, a także przesunąć okno widoku w odpowiednie współrzędne, tak aby pasowało do reszty okna. Możesz wierzyć lub nie, ale to właśnie jest najtrudniejszą częścią całego procesu.
Możesz uniknąć tej pracy, eliminując wywołanie funkcji CSplitterWnd: :CreateView(). Zamiast tego stwórz własny kontekst tworzenia w celu przekazania go funkcji Create-view (), który dokładnie poinformuje ją, co ma zrobić.
Parametr pContext jest wskaźnikiem do obiektu CCreateContext. Obiekt Ccreate-Context rejestruje, która ramka, widok oraz dokument powinny zostać użyte dla nowo tworzonej pary dokument-widok. Poniższy fragment kodu buduje własny obiekt klasy CCreateContext o nazwie ctxSamplel. Ten obiekt zostaje zainicjowany tak, aby zawierał informacje o widoku, dokumencie i wzorcu, potrzebne aplikacji do stworzenia nowego panelu:
BOOL CVourSplitterWnd::CreateView(int rów, int col,
CRuntimeClass* pViewClass, SIZE sizeInit, CCreateContext* pContext) {
CCreateContext ctxSamplel;
// jeśli nie ma aktywnego widoku, wywołaj asercje
CView* pOldYiew = (CView*)GetActivePane() ;
ASSERT(p01dView == NULL);
// Powinieneś tutaj sprawdzić pOldView
// i zrobić z nim coś sensownego.
// W tym fragmencie po prostu sprawdzimy, gdzie jest
// stary widok.
ctxSample1.m_pLastView -p01dView;
ctxSample1.m_pCurrentDoc =p01dView->GetDocument();
ctxSample1.m_pNewDocTemplate =ctxSample1.m_pCurrentDoc->GetDocTemplate(); // Przekazujemy wywołanie return CSplitterWnd::CreateView(rów, col,
p01dView->GetRuntimeClass(), sizelnit, &ctxSample1);
}
Użycie statycznych okien dzielonych
Statyczne okna dzielone są używane w aplikacjach, w których dynamiczne okna dzielone nie spełniają swojej roli. Statyczne okna dzielone mogą być użyte wtedy, gdy aplikacja chce pokazać więcej niż dwa wiersze lub dwie kolumny paneli. Jeśli interesuje Cię możliwość podziału okna (bez względu na ilość wierszy i kolumn), lecz nie chcesz pozwolić użytkownikowi na zmianę tego podziału, także powinieneś użyć statycznego okna dzielonego, gdyż łatwiej jest osiągnąć to w przypadku statycznych okien dzielonych niż pisać kod negujący domyślne działania MFC.
Statyczne okna dzielone także opierają się na klasie CSplitterWnd, z tym że wymagają nieco innego mechanizmu tworzenia. Także w tym przypadku egzemplarz tej klasy powinien zostać umieszczony w aplikacji w klasie wyprowadzonej z CFrameWnd lub CMDiChildWnd, lecz w nowej wersji funkcji przysłaniającej OnCreateClient () znajdzie się zupełnie odmienny kod.
Tworzenie statycznego okna dzielonego
Na początek, zamiast funkcji CSplitterWnd: -.Create () powinieneś wywołać funkcję CSplitterWnd: :Createstatic (). Ta funkcja także tworzy i przygotowuje okno dzielone, lecz w tym przypadku sam musisz stworzyć zawartość poszczególnych paneli. Jeśli tego nie zrobisz, MFC zgłosi asercje i natychmiast przerwie działanie aplikacji. Aby stworzyć panel, wywołaj funkcję CreateView () obiektu CSplitterWnd, z którego korzystasz. Funkcję CreateView () musisz wywołać dla każdego tworzonego panelu. Na przykład, kod tworzący statyczne okno dzielone z pięcioma wierszami i trzema kolumnami paneli może wyglądać następująco:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT*1pcs,
CCreateContext* pContext) {
BOOL bRet;
int nRow;
int nCol;
if ( !m_wncłSplitter.CreateStatic (this, 5, 3}}
return FALSE; for{ nRow = 0; nRow < 5; nRow++)
for( nCol = 0; nCol < 3; nCol++) {
bRet = m_wndSplitter.CreateView(nRow, nCol, RUNTIME_CLASS(CStaticSplitView), CSize(50, 30), pContext); if(bRet == FALSE)
} return FALSE; } return TRUE;
Jeśli chcesz mieć różne widoki w każdym z paneli, powinieneś tak poprawić kod, aby w każdym wywołaniu funkcji CreateView () przekazać różne informacje RUNTIME_CLASS ().
W tym rozdziale podajemy sposoby ręcznego dodawania okna dzielonego do aplikacji, a to głównie dlatego, że takie okno najczęściej jest dodawane do aplikacji dopiero po pewnych przemyśleniach. Jeśli zaczynasz od początku, możesz włączyć opcję Use split window na zakładce zaawansowanych ustawień aplikacji. To okno dialogowe (pokazane na rysunku 20.7) pojawia się po kliknięciu przycisku Advanced w czwartym kroku kreatora AppWizard.
Wspólne paski przewijania
Jak wiesz z poprzedniej części rozdziału, klasa CSplitterWnd obsługuje wspólne paski przewijania. Te paski przewijania są potomkami klasy csplitterWnd i są wspólnie wykorzystywane przez różne panele w oknie dzielonym. Na przykład, w oknie o jednym wierszu i dwóch kolumnach, podczas tworzenia okna csplitterWnd, możesz użyć stylu WS_VSCROLL. Zostanie wtedy utworzony specjalny pasek przewijania, wspólny dla obu paneli, taki jak pokazany na rysunku 20.8.
Gdy użytkownik przesunie pasek przewijania, MFC wyśle komunikaty WM_VSCROLL do obu widoków. Gdy widoki ustawią pozycję paska przewijania, zostanie ustawiona pozycja wspólnego paska.
Wspólne paski przewijania są najbardziej przydatne w sytuacjach, gdy w statycznym oknie dzielonym w różnych panelach występują dwa obiekty widoku należące do tych samych klas. Jeśli w oknie dzielonym pomieszasz widoki różnych typów, możesz być zmuszony do napisania odpowiedniego kodu koordynującego ich pozycje przewijania. Każda klasa, wyprowadzona z cview korzystająca z mechanizmu przewijania opartego na użytych w klasie CWnd odwołaniach do API pasków przewijania, korzysta (jeśli tylko istnieje) ze wspólnego paska przewijania. Jedną z implementacji klasyCView korzystającej ze wspólnego paska przewijania jest klasa CScroi1View. Klasy nie wyprowadzone z klasy CView, klasy korzystające z pasków przewijania nie opierających się na kontrolkach lub klasy używające standardowej implementacji Windows (na przykład CEditView) nie będą działać ze wspólnym paskiem przewijania klasy csplitterWnd.
Wyznaczanie rozmiaru aktualnego i idealnego
Układ paneli w oknie dzielonym zależy od rozmiaru ramki okna (która określa rozmiar okna csplitterWnd). Klasa csplitterWnd rozmiesza i dostosowuje wymiary paneli tak, aby pasowały najlepiej jak to możliwe.
Wysokość wiersza i szerokość kolumny ustawione przez użytkownika lub ustawione przez aplikację w wyniku wywołań metod klasy csplitterWnd reprezentują rozmiary idealne. Rzeczywisty rozmiar może być mniejszy od rozmiaru idealnego (na przykład jeśli brakuje miejsca) lub większy od niego Gęśli panel musi być powiększony w celu wypełnienia obszaru okna dzielonego).
Wydajność okien dzielonych
Dzięki oknom dzielonym łatwo jest podzielić obszar okna ramki lub okna potomnego MDI tak, aby zawierało ono więcej niż jeden widok. Jednak oznacza to także, że kod rysunkowy widoku zostanie wywołany więcej razy niż przed podziałem. Okno widoku będzie po podziale naturalnie mniejsze, więc musisz zapewnić, by widok nie rysował niczego, co nie jest potrzebne. W szczególności, nie powinien rysować niczego poza aktualnymi granicami swojego obszaru. Gdy to zapewnisz, osiągniesz najwyższą wydajność dostępną dla swojej aplikacji.
Ograniczenie ilości odrysowywania, jakie musi wykonać widok, jest najważniejszym warunkiem szybkiego działania aplikacji kolejno odświeżającej różne panele w oknie dzielonym.
Prawdopodobieństwo, że jeden widok zmieni się, gdy inny widoczny widok będzie odświeżał swoją zawartość dla tego samego dokumentu, jest w przypadku okien dzielonych dużo większe niż w przypadku zwykłych okien. Powinieneś rozważyć różne widoki w swojej aplikacji i zapewnić, że w wywołaniach funkcji UpdateAllViews() oraz UpdateView() są przekazywane informacje wystarczające do przeprowadzenia jak najbardziej precyzyjnego odświeżenia okna.
Program Dynsplit
Program demonstracyjny Dynsplit został zbudowany z użyciem pojedynczej klasy dokumentu, CDynsplitDoc, zaś do tworzenia dynamicznych okien dzielonych korzysta z klas
CSingleDocTemplate oraz CSplitterWnd.
Program znajduje się na dołączonej do książki płytce CD-ROM, w kartotece Rozdz20\Dynsplit. Możesz zbudować program po załadowaniu pliku projektu Dynsplit. ds\v lub po prostu za pomocą Eksploratora Windows uruchomić program Dynsplit.exe.
Listing 20.2 zawiera fragmenty kodu z plików źródłowych Dynsplit. cpp oraz dyns-pvw. cpp programu Dynsplit. exe.
Dynsplit
Położenie na płytce: Rozdz20\Dynsplit
Nazwa programu: Dynsplit.exe
Moduły kodu źródłowego w tekście: Dynsplit. cpp oraz dynspv\v.cpp
Listing 20.2. Fragmenty plików Dynsplit. cpp oraz dynspv\v. cpp programu Dynsplit. exe
// dynsplit.cpp : Defines the class behaviors for the application.
//
#include "stdafx.h"
#include "dynsplit.h"
#include "mainfrm.h"
#include "dynspdoc.h"
#include "dynspvw.h"
#ifdef _DEBUG
ftundef THIS_FILE
static char BASED_CODE THIS_FILE[] = _FILE_;
#endif
///////////////////////////////
///CDynamicSplitApp
BEGIN_MESSAGE_MAP (CDynamicSplitApp, CWinApp) //{ {AFX_MSG_MAP( CDynamicSplitApp) ON_COMMAND ( ID_APP_ABOUT, OnAppAbout }
// NOTĘ - the ClassWizard will add and remove
// mapping macros here.
//DO NOT EDIT what you see in these blocks of generated code! //} }AFX_MSG_MAP
// Standard file based document commands ON_COMMAND(ID_FILE_NEW, CWinApp: :OnFileNew) ON_COMMAND(ID_FILE_OPEN, CWinApp: :OnFileOpen) // Standard print setup command
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp: : OnFilePrintSetup) END_MESSAGE_MAP ( )
////////////
// CDynamicSplitApp construction CDynamicSplitApp : :CDynamicSplitApp()
{
// TODO: add construction code here,
// Place all significant initialization inInitInstance
}
//////////////////////////////////////////////////////
// The one and only CDynamicSplitApp object CDynamicSplitApp theApp;
////////////////////////////////////////////////////////// // CDynamicSplitApp initialization
BOOL CDynamicSplitApp::Initlnstance()
{
// Standard initialization
// If you arę not using these features and wish to reduce the size // of your finał executable, you should remove from the following // the specific initialization routines you do not need.
Enable3dControls();

// Load standard INI file options(including MRU); LoadStdProfileSettings();

// 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(CDynamicSplitDoc) ,
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CDynamicSplitView)); AddDocTemplate(pDocTemplate);
// create a new (empty) document OnFileNew();
if (m_lpCmdLine[0] != '\0')
{ // TODO: add command linę processing here
}
return TRUE;
}
///////////////////////////////////////////////////////// // CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
// Dialog Data
//{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA
// Implementation protected:
virtual void DoDataExchange(CDataExchange* pDX) ;
//{{AFX_MSG(CAboutDlg)
// No message handlers
//}}AFX_MSG
DECLARE MESSAGE MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) {
//{(AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX); //{(AFX_DATA_MAP(CAboutDlg) //}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //{{AFX_MSG_MAP(CAboutDlg) // No message handlers //}}AFX_MSG_MAP
END_MESSAGE_MAP()
// App command to run the dialog void CDynamicSplitApp::OnAppAbout() {
CAboutDlg aboutDlg;
aboutDlg.DoModal();
}
// dynspvw.cpp : implementation of the CDynamicSplitView class
//
#include "stdafx.h"
#include "dynsplit.h"
ttinclude "dynspdoc.h" ttinclude "dynspvw.h"
ttifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] +_FILE_;
#endif
///////////////////////////////////////////////////////////////
//CDynamicSplitView
IMPLEMENT_DYNCREATE (CDynamicSplitView,CView)
BEGIN_MESSAGE_MAP (CDynamicSplitView, CView) // { { AFX_MSG_MAP (CDynamicSplitView)
// NOTĘ - the ClassWizard will add and remove // mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//} }AFX_MSG_MAP // Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView: :OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CYiew: :OnFilePrintPreview) ENDMESSAGEMAP ( )
/////////////////////////////////////
// CDynamicSplitView construction/destruction CDynamicSplitYiew: :CDynamicSplitView ( )
{ // TODO: add construction code here
}
CDynamicSplitView: : -CDynamicSplitView ()
{
}
////////////////////////////////////////////////////////////////////
// CDynamicSplitYiew drawing
void CDynamicSplitYiew::OnDraw(CDC* pDC) {
CDynamicSplitDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
}
///////////////////////////////////////
/
//CDynamicSplitView printing
BOOL CDynamicSplitView::OnPreparePrinting(CPrintlnfo*plnfo)
{
// default preparation
return DoPreparePrinting(plnfo);
}
void CDynamicSplitView::OnBeginPrinting(CDC*/*pDC*/,
CPrintlnfo* /*plnfo*/) {
// TODO: add extra initialization before printing
}
void CDynamicSplitView::OnEndPrinting(CDC*/*pDC*/,
CPrintlnfo* /*plnfo*/) {
// TODO: add cleanup after printing
}
/////////////////////////////////
// CDynamicSplitView diagnostics
#ifdef_DEBUG
void CDynamicSplitView::AssertValid()const
{
CView::AssertValid();
}
void CDynamicSplitView::Dump(CDumpContext& dc) const {
CView::Dump(dc;
}
CDynamicSplitDoc* CDynamicSplitYiew::GetDocument() // non-debug
version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDynamicSplitDoc)));
return (CDynamicSplitDoc*)m_pDocument; }
#endif //DEBUG
Subclassing okien potomnych
Aby przeprowadzić subclassing egzemplarza okna, używając Windows API, musisz wywołać funkcję API setwindowLong () i podać jej uchwyt subklasowanego okna, indeks GWL_WNDPROC oraz wskaźnik do procedury subklasującej. Funkcja SetwindowLong () zwraca wskaźnik do oryginalnej procedury okna; możesz go użyć w celu przekazania komunikatów do oryginalnego przetworzenia. Procedura subklasująca musi następnie użyć funkcji callwindowProc () w celu wywołania oryginalnej procedury okna.
Listing 20.3 przedstawia subklasing egzemplarza kontrolki pola edycji w oknie dialogowym. Subklasująca procedura okna umożliwia kontrolce otrzymywanie wszystkich komunikatów klawiatury, łącznie z wciskaniem klawiszy Enter i Tab, jeśli tylko ta kontrolka znajduje się w ognisku wprowadzania. Listing 20.3 przedstawia sposób przeprowadzenia subclassingu wyłącznie z użyciem Windows API.
Listing 20.3. Procedura okna wykorzystująca do subclassingu jedynie Windows API
WNDPROC wpOrigEditProc;
LRESULT APIENTRY EditBoxProc(HWND hwndDlg, UINT uMsg,
WPARAM wParam, LPARAM1Param)
{
HWND hwndEdit; switch(uMsg)
{
case WM_INITDIALOG:
// Pobiera uchwyt kontrolki pola edycji. hwndEdit = GetDlgltem(hwndDlg, ID_EDIT);
// Subklasuje kontrolkę pola edycji. wpOrigEditProc = (WNDPROC) SetwindowLong(hwndEdit, GWL_WNDPROC, (LONG) EditSubclassProc);
// Kontynuuje procedurę inicjalizacji. return TRUE;
case WM_DESTROY:
// Usuwa subclassing z kontrolki pola edycji. SetwindowLong(hwndEdit, GWL_WNDPROC, (LONG) wpOrigEditProc);
// Kontynuuje procedurę czyszczenia
break; } return FALSE;
UNREFERENCED PARAMETER(IParam);
}
// Procedura subklasująca
LRESULT APIENTRY EditSubclassProc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM IParam)
{
iffuMsg == WM_GETDLGCODE)
return DLGC_WANTALLKEYS;
return CallWindowProc(wpOrigEditProc, hwnd, uMsg, wParam, 1Param);
}
Subclassing okien z pomocą MFC
Jak możesz oczekiwać, MFC zapewnia łatwiejszy sposób subclassingu okien wyprowadzonych z klasy cwnd. Zamiast wywoływać funkcję SetWwindowLong (), możesz po prostu wywołać funkcję składową SubclassWindowO. Tak jak w przypadku funkcji Setwin-dowLong () w metodzie korzystającej z API funkcję SubclasswindowO wywołujesz w celu dynamicznego subclassingu okna i podłączenia go do obiektu cwnd wywołującego tę funkcję składową. Funkcji SubciassWindow () musisz przekazać uchwyt okna (HWND), które ma być subklasowane, a ta funkcja z kolei zwróci wartość BOOL określającą, czy subclas-sing się powiódł. Gdy dynamicznie subklasujesz okno, komunikaty Windows przechodzą przez mapę komunikatów obiektu cwnd i najpierw wywołują funkcje obsługi w klasie CWnd. Komunikaty przekazane do klasy bazowej trafią do domyślnych funkcji obsługi w oknie.
Po stronie subklasowanego okna funkcja składowa Subclasswindow() łączy okno lub kontrolkę Windows z obiektem cwnd i zastępuje funkcje wndProc () oraz AfxWndProc () subklasowanego okna. Funkcja przechowuje starą funkcję WndProc () w miejscu zwróconym przez funkcję składową GetSuperWndProcAddr o . Musisz przesłonić funkcję składową GetSuperWndProcAddr () dla każdej osobnej klasy okna, aby móc przechować stary wskaźnik do funkcji WndProc (}. Istniejącą kontrolkę pola edycji w oknie dialogowym możesz subklasować za pomocą kodu przedstawionego na listingu 20.4.
Listing 20.4. Użycie MFC do subclassingu kontrolki istniejącego pola edycji___________
BOOL CSubbedDlg::InlnitDialog()
{
... Inne funkcje inicjalizujące
// Pobranie wskaźnika do kontrolki pola edycji
CWnd *pEdit;
pEdit = GetDlgltem(IDC_NIP);
ASSERT(pEdit != NULL);
// Niech kontrolka korzysta z systemowej czcionki o stałej
// szerokości znaków, gdyż w przypadku liczb i znaku minus
// wygląda to zdecydowanie lepiej.
HFONT hFont = (HFONT) ::GetStockObject(SYSTEM_FIXED_FONT); CFont* pFont = CFont::FromHandle(hFont); pEdit->SetFont(pFont);
// Subclassing kontrolki pola edycji, tak by była // podłączona do naszej klasy CSubbedEdit. m_Subbed.SubclassWindów(pEdit->m_hWnd);
return TRUE;
}
Od momentu wykonania tego kodu komunikaty wysyłane do kontrolki IDC_NIP są przekazywane najpierw do obiektu m_Subbed, czyli egzemplarza naszej klasy csubbe-dEdit. Klasa csubbedEdit jest klasą subklasującą (w znaczeniu C++) klasę MFC CEdit. Ponieważ, po wywołaniu funkcji subclassWindow (), rzeczywistym widokiem MFC jest teraz klasa csubbedEdit, zaczyna otrzymywać komunikaty poprzez egzemplarz obiektu ccmdTarget wewnątrz klasy CEdit. Możesz dokonać edycji, używając Class-Wizarda do stworzenia pozycji mapy komunikatów dla komunikatów WM_CHAR oraz WM_KEYUP, zatwierdzając kod w taki sposób, w jaki kontrolka ma reagować na wciskane klawisze.
Klasa csubbedEdit może być składową klasy dialogowej w aplikacji. Gdy dochodzi do inicjalizacji dialogu, dla kontrolki pola edycji numeru NIP (IDC_NIP) następuje wywołanie funkcji SubclassWindow (). Kod programu nigdy nie usuwa tego subclassingu, gdyż jest on automatycznie odłączany w momencie zamknięcia okna dialogowego.
Jedną z kolejnych możliwości jest użycie lokalnego egzemplarza klasy csubbedEdit i wywołanie dla niego funkcji SubclassWindow (). Jednak w większości przypadków nie można zastosować tego rozwiązania, gdyż egzemplarz obiektu csubbedEdit musi żyć dłużej niż kontrolka, którą subklasuje. Lokalne zadeklarowanie w funkcji subkla-sującej klasy MFC jest praktycznie bezcelowe, gdyż bardzo niewiele funkcji kontynuuje działanie tak długo, by nastąpiło rozprowadzenie komunikatów. Kod subklasujący nie zostałby zainstalowany na wystarczający okres czasu, by system zdążył rozesłać komunikaty.
Gdy chcesz przywrócić subklasowane okno do oryginalnego stanu, powinieneś wywołać funkcję składową CWwnd:UnsubclassWindow(). Ta funkcja zwraca uchwyt odłączonego okna.
Alternatywy dla architektury dokument-widok
Choć model dokument-widok w większości przypadków jest bardzo przydatny, jednak zdarzają się aplikacje, w których powinien zostać pominięty. Celem tego modelu jest oddzielenie danych od wyświetlającego je kodu. W większości przypadków powoduje to uproszczenie struktury aplikacji i redukcję nadmiarowego kodu. Jednak nie jest to już prawdą w przypadku przenoszenia do Windows programów napisanych w C. Jeśli oryginalny kod już łączy zarządzanie danymi z ich wyświetlaniem, przepisywanie go zgodnie z modelem dokument-widok wiąże się z dodatkową pracą, gdyż trzeba oddzielić od siebie obie części. W takim przypadku możesz zechcieć pozostawić kod niezmienionym. Istnieje wiele sposobów podejścia do pomijania architektury dokument-widok, lecz tu przedstawimy jedynie kilka z nich:
Potraktuj dokument jako nieużywany dodatek, zaś kod zarządzający danymi zaimplementuj w klasie widoku. Narzut związany z posiadaniem dokumentu jest stosunkowo niewielki.
Potraktuj zarówno dokument, jak i widok jak nieużywane dodatki. Umieść kod zarządzający danymi i wyświetlający je w oknie ramki. Takie podejście jest zbliżone do modelu programowania w C.
Przesłoń części szkieletu MFC odpowiedzialne za tworzenie dokumentów i widoku, całkowicie eliminując ich tworzenie. Jak już wiesz, proces tworzenia dokumentu zaczyna się od wywołania funkcji cwinApp: :AddDocTemplate (). Wyeliminuj to wywołanie z funkcji składowej initlnstance () w klasie swojej aplikacji i zamiast niego sam stwórz w tej funkcji okno ramki. Kod zarządzający danymi umieść w klasie okna ramki. Wymaga to nieco większej pracy i głębszego zrozumienia działania szkieletu MFC, lecz uwalnia Cię całkowicie od narzutu związanego z modelem dokument-widok.
Podsumowanie
W tym rozdziale poznałeś mnóstwo dodatkowych informacji na temat modelu dokument-widok. Nauczyłeś się tworzyć i wykorzystywać interfejs wielodokumentowy, i to nie tylko dla kilku widoków tego samego dokumentu, ale także dla różnych widoków różnych dokumentów. Uzyskane wiadomości wykorzystałeś do stworzenia demonstracyjnego wielodokumentowego programu PaintObj, umożliwiającego tworzenie i rysowanie w tylu oknach, na ile tylko pozwala pamięć.
Poznałeś także okna dzielone, użyteczne narzędzie do efektywnego wykorzystania "przestrzeni życiowej", jaką użytkownik przeznaczy Twojej aplikacji na pulpicie. Efektywne użycie dzielonych okien umożliwia nie tylko zwiększenie ilości informacji przedstawianych jednocześnie, ale także poprawienie samego sposobu ich przedstawiania. Gdy ludzie bardziej przywykną do zalet płynących z użycia komputerów, projektowanie aplikacji umożliwiających użytkownikom przeglądanie danych tekstowych w jednym panelu i danych graficznych w drugim, bez zmuszania ich do przełączania się pomiędzy aplikacjami, będzie miało coraz większe znaczenie.
Na koniec dowiedziałeś się o subclassingu okien, pozwalającym na poinstruowanie systemu operacyjnego i aplikacji, jakie okna powinny przetwarzać komunikaty oraz w jaki sposób maj ą reagować, gdy użytkownik kliknie w którymś z okien. Dowiedziałeś się, że subclassing okien, i tak względnie łatwy z użyciem Windows API, jest naprawdę bardzo prosty w MFC, zaś sama klasa cwnd, od której pochodzi większość okien, zawiera pomocnicze funkcje SubclassWindow () oraz UnsubclassWindow {) .
W następnym rozdziale przejdziesz krok dalej z dokumentami i widokami i nauczysz się uzupełniać aplikacje o obsługę wydruku. Dowiesz się także, jak zarządzać procesem drukowania w MFC oraz jak generować dobrze wyglądający i ciekawy wydruk.

Wyszukiwarka

Podobne podstrony:
tworzenie aplikacji w jezyku java na platforme android
20 Organizacja usług dodatkowych w zakładzie hotelarskim
20 rad jak inwestowac w zloto
20 3SH~1
24#5901 dydaktyk aplikacji multimedialnych
51 20
Układ Regulacji Kaskadowej 2
39 20 Listopad 2001 Zachód jest wart tej mszy

więcej podobnych podstron