Rozdział 3 Drukowanie
Programy wygenerowane przez kreatora App Wizard potrafią automatycznie drukować. Oczywiście, każdy program, który potrafi wyświetlać informacje na ekranie, potrafi także wydrukować te informacje na drukarce. Jednakże czasami otrzymane rezultaty nie są takie, jakich można by oczekiwać. Różnice w skalowaniu, dzielenie wydruku na strony oraz drukowanie dodatkowych informacji (jak na przykład czas wydruku lub nazwa drukowanego pliku), wszystkie te czynniki dodatkowo utrudniają drukowanie. Podgląd wydruku jest kolejnym miejscem, gdzie bardzo często dokonywane są modyfikacje, jednakże i tym razem MFC ukrywa szczegóły zastosowanych rozwiązań. W tym rozdziale dowiesz się, jak można dodać do programu możliwość edycji danych podczas wyświetlania podglądu wydruku.
Jeśli przeczytałeś ostatni rozdział, to nie będziesz zaskoczony tym, że jestem wielkim fanem serialu „Star Trek". Sądząc z wypowiedzi niektórych osób w telewizji, nie zaliczam się jednak do największych fanów tego serialu. Mój samochód nie przypomina statku kosmicznego i nie ubieram się w uniformy Floty Gwiezdnej. Z drugiej strony, posiadam kopie „Star Trek Technical Manuals" oraz publikacje autorstwa Leonarda Nimoya. Miałem nawet zaszczyt porozmawiać z Genem Roddenberym zaraz po emisji jednego z odcinków serialu. Tak więc może i nie jestem fanatykiem, ale na pewno godnym szacunku fanem.
Zawsze starałem się zainteresować dzieci serialem „Star Trek". Jestem przekonany, że można znaleźć znacznie gorszych bohaterów do Kirka i jego towarzyszy. Sądzę, że bohaterowie niektórych nowych filmów także mogą zasługiwać na odrobinę szacunku. Ale i tak moje dzieci z założenia nie interesują się rzeczami, które lubię, dlatego też rzadko kiedy mam okazję oglądać „Star Trek" w towarzystwie.
Kiedyś, w rocznicę odkrycia Ameryki, postanowiliśmy pojechać na krótką wycieczkę -coś w stylu mini wakacji na koniec lata. Po spędzeniu całego dnia w muzeum Sea World wpadło nam do głowy przejechać na chwilę granicę meksykańską- tylko i wyłącznie po to, żebyśmy mogli powiedzieć, że byliśmy w Meksyku. Powiedziałem mojemu najmłodszemu synowi, że jest to dla niego okazja, aby z dumnie podniesioną głową wkroczyć na obszary, w których nikt jeszcze nie przebywał (a przynajmniej, w których ja jeszcze nie przebywałem). Oczywiście, usłyszawszy tekst zaczerpnięty ze „Star Treka", syn przewrócił jedynie oczami i zrobił stosownie znudzoną minę.
Cóż, potwierdziło to jedynie fakt, iż jestem całkowicie niepodobny do wielkiego kapitana Kirka. Gdy dojechaliśmy do granicy uświadomiliśmy sobie, że nie mamy żadnego dokumentu identyfikacyjnego dla naszego siedmioletniego syna, Patryka. Pomyśl tylko, czy wozisz przy sobie dokumenty siedmioletniego dziecka? Jeśli wycieczka byłaby zaplanowana, to niewątpliwie nie zapomnielibyśmy wziąć odpowiednich dokumentów. Szkoda tylko, że nie pomyśleliśmy o tym przed dojechaniem do granicy meksykańskiej. Bardzo poważnie zastanawialiśmy, się, czy mimo wszystko nie przejechać granicy, ale gdybyśmy to zrobili, Patryk mógłby nie wrócić do kraju!
Po rozmowie z amerykańskim Patrolem Granicznym (mili goście) zdecydowaliśmy się jednak zaryzykować i przekroczyć granicę. Muszę przyznać, że czułem dziwny dreszczyk emocji, przekraczając granicę ze świadomością, że podejmujemy takie ryzyko. Oczywiście, wiedzieliśmy, że w każdej chwili możemy dowieść obywatelstwa Patryka, niemniej jednak narażaliśmy się na konieczność pozostania w Meksyku przez kilka dni lub nawet tygodni, gdyby coś poszło nie tak.
Wszystko oczywiście skończyło się dobrze -jak w filmie „Star Trek Następne Pokolenia", gdzie wszystkie problemy same się rozwiązują wraz ze zbliżaniem się do końca filmu. Patryk dostał na macado (targu) blaszany bębenek, a kiedy wracaliśmy, na granicy zapytano nas, czy jesteśmy obywatelami amerykańskimi, odpowiedzieliśmy, że jesteśmy i mogliśmy jechać dalej - nawet nie musiałem pokazywać swojego prawa jazdy. Żadnych problemów, żadnych silnych wrażeń.
Sądzę, że czasami programowanie jest bardzo podobne do opisanych powyżej zdarzeń. Jest naturalne, że jesteśmy pełni obaw i ostrożnie wkraczamy na nowe, nieznane terytoria (jak na przykład nowy system operacyjny, nowy język czy technologia). Odkrywanie czegoś nowego również przysparza miłego dreszczyku emocji.
Gdy rozmawiam z programistami używającymi MFC, odkrywam, że wielu z nich obawia się drukowania. Oczywiście każdy może coś wydrukować. Ale jak wydrukować poprawnie wyskalowany, wielostronicowy dokument? Co z nagłówkami, stopkami, marginesami albo numerami stron?
Na dobry początek pokażę prosty program do gry w kółko i krzyżyk. Sam program jest całkowicie nieinteresujący (patrz rysunek 3.1.), będzie jednak potrafił poprawnie drukować. W dalszej części rozdziału pokażę Ci, w jaki sposób można przejąć kontrolę nad sposobem sporządzania podglądu wydruku i dostosować go do swoich potrzeb. Zanim jednak zaczniemy, to porozmawiamy trochę o narzędziach wspomagających drukowanie, dostępnych w MFC.
Drukowanie w MFC - wielkie kłamstwo?
MFC twierdzi, że możesz rysować i drukować za pomocą tego samego kodu. Jednak czy rozwiązanie takie jest w rzeczywistości praktyczne? Czasami tak. W większości wypadków będziesz jednak musiał stworzyć odrębny kod służący tylko do drukowania. Jedyną pocieszającą wiadomością jest to, że kod, który będziesz musiał napisać, jest znacznie prostszy od tego, który wykorzystywany jest przez sam system Windows.
Kiedy użytkownik wybiera opcję drukowania z menu Plik, w aktywnym widoku wywoływana jest metoda OnPreparePrinting. Metoda ta posiada jeden argument -wskaźnik do struktury CPrintlnfo (patrz Tabela 3.1.). Określenie wartości składowych tej struktury pozwoli Ci na dokładne opisanie sposobu drukowania (zwanego także zadaniem drukowania). Jeśli już na tym etapie znasz ilość stron, które będziesz chciał wydrukować, to możesz je podać. Kiedy przekażesz strukturę w wywołaniu metody DoPrepare-Printing, to ustawione w niej dane zostaną odzwierciedlone w standardowym dialogu służącym do wybierania opcji drukowania. Standardowy kod generowany przez kreatora App Wizard zakłada, że nie wiesz niczego na temat zadania drukowania i w związku z tym przekazuje strukturę CPrintlnfo bez wprowadzenia jakichkolwiek modyfikacji.
Aby uruchomić zadanie drukowania, MFC wywołuje metodę OnBeginPrinting. Metoda ta wymaga podania dwóch argumentów: wskaźnika na obiekt klasy CDC oraz wskaźnika na strukturę CPrintlnfo. Obiektu CDC możesz użyć do określenia cech drukarki (wystarczy w tym celu wywołać metodę GetDeviceCaps). Jest to Twoja ostatnia szansa określenia ilości drukowanych stron; możesz to zrobić za pomocą metody CPrintlnfo::SetMaxPage. Jeśli ilość stron wydruku zależna jest od cech charakterystycznych drukarki, to właśnie teraz powinieneś ją określić. Dzięki temu MFC będzie mogło wywołać metody służące do drukowania tylko raz dla każdej strony wydruku. Jeśli jednak wciąż nie jesteś w stanie określić ilości stron, które chcesz wydrukować, to będziesz musiał ręcznie określać wartość składowej m_bContinuePrinting struktury CPrintlnfo. Modyfikacji tej będziesz musiał dokonywać w metodzie OnPrepareDC (jak się już wkrótce dowiesz, metoda ta wywoływana jest podczas drukowania).
Metoda OnBeginPrinting najlepiej nadaje się do przydzielenia zasobów, które będą Ci potrzebne podczas wykonywania zadania drukowania. Dla przykładu, specyficzne czcionki używane podczas drukowania przygotowywane są zazwyczaj właśnie w tej metodzie.
Tabela 3.1. Struktura CPrintlnfo.
Składowa
Typ
m_bDocObj ec t
m_dwFlags
m_nOffsetPage
m_pPD
m_bDirect
m_bPreview
m_bContinuePrinting zmienna
m_nCurPage zmienna
m_nNumPreviewPages zmienna
m_lpUserData m_rectDraw
m_strPageDesc
SetMinPage SetMaxPage GetMinPage GetMaxPage GetOffsetPage
GetFromPage GetToPage
Opis
zmienna Określa, czy drukowany jest dokument klasy CDocument.
zmienna Określa operacje wykonywane przez obiekt DocObject.
zmienna Określa przesunięcie pierwszej strony konkretnego obiektu DocObject w połączonym zadaniu drukowania.
zmienna Wskaźnik na okno dialogowe Drukuj skojarzone z konkretnym zadaniem drukowania.
zmienna Określa ,czy dokument ma zostać wydrukowany bez wyświetlania okna dialogowego Drukuj.
zmienna Określa, czy program działa aktualnie w trybie podglądu wydruku.
Określ, czy aktualna strona powinna zostać wydrukowana (patrz tekst).
Numer aktualnej strony.
Ilość stron wyświetlanych w podglądzie wydruku (l lub 2).
zmienna Wskaźnik do struktury danych dostarczanej przez użytkownika.
zmienna Prostokąt określający wielkość obszaru strony
dostępnego do drukowania (składowa ta może być używana dopiero po rozpoczęciu drukowania).
zmienna Zawiera łańcuch znaków określający postać numeracji stron.
funkcja Określa numer pierwszej strony wydruku,
funkcja Określa numer ostatniej strony wydruku,
funkcja Zwraca numer pierwszej strony wydruku,
funkcja Zwraca numer ostatniej strony wydruku.
funkcja Zwraca ilość stron poprzedzających pierwszą stron? aktualnie drukowanego obiektu DocObject, wchodzącego w skład złożonego zadania drukowania.
funkcja Zwraca numer pierwszej drukowanej strony, funkcja Zwraca numer ostatniej drukowanej strony.
Podczas drukowania MFC wywołuje następujące trzy metody:
OnPrepareDC;
2. OnPrint;
3. OnDraw.
Metody te wywoływane są dla każdej drukowanej strony. Zauważ, że metody OnPrepare-DC oraz OnDraw wywoływane są także podczas rysowania na ekranie. Jeśli będziesz chciał określić, czy metoda została wywołana jako część procesu drukowania, to będziesz mógł to zrobić za pomocą metody CDC::IsPrinting.
Jest kilka powodów, dla których mógłbyś chcieć przesłonić standardową definicję metody OnPrepareDC. Po pierwsze możesz w ten sposób zmodyfikować kontekst urządzenia. Możesz to zrobić na przykład po to, aby zmienić początek układu współrzędnych widoku, dzięki czemu będziesz w stanie wydrukować odpowiednią stronę. Możesz zmienić także tryb mapowania w kontekście urządzenia, dzięki czemu wydruk zostanie przeskalo-wany inaczej niż zawartość ekranu.
Możesz także podejmować decyzje, czy chcesz kontynuować drukowanie, czy też je przerwać. Jest to szczególnie przydatne w wypadkach, gdy nie jesteś w stanie dokładnie określić ilości stron, które chcesz wydrukować. Aby móc postąpić w ten sposób, będziesz musiał upewnić się, że argument plnfo ma wartość różną od NULL. Pamiętaj, że metoda OnPrepareDC wywoływana jest zarówno w przypadku drukowania, jak i rysowania na ekranie. Jeśli argument plnfo nie ma wartości NULL, to będziesz mógł określić, czy chcesz dalej drukować, czy nie (badając wartość składowej m_nCurPage). Jeśli będziesz chciał kontynuować drukowanie, to po wywołaniu metody OnPrepareDC klasy bazowej będziesz musiał przypisać składowej m_bContinuePrinting wartość TRUE. Metoda OnPrepareDC klasy bazowej zawsze przypisuje tej składowej wartość FALSE, a więc pamiętaj, aby odpowiednio ją zmodyfikować. Pamiętaj także, iż zawsze będziesz musiał wywołać tę metodę klasy bazowej, gdyż wiele rodzajów widoków używa jej do specjalnych celów.
Drukowanie
Proste programy zazwyczaj nie przejmują się takimi szczegółami. Dzieje się tak dlatego, iż metoda OnDraw (używana do drukowania widoku i wyświetlania go na ekranie), potrafi automatycznie zadbać o wszystkie sprawy związane z drukowaniem. Jednakże musisz poznać wszystkie tajniki procesu drukowania, aby móc go zmodyfikować - odpowiednio przeskalować widok, dodać nagłówki i stopki wydruku lub w jakikolwiek inny sposób zmodyfikować proces drukowania.
Jeśli chcesz, aby kod metody OnDraw obsługiwał zarówno wyświetlanie danych na ekranie, jak i ich drukowanie, to nie będziesz musiał przesłaniać metody OnPrint. Domyślna implementacja tej metody wywołuje metodę OnDraw za Ciebie. Jednakże jeśli będziesz chciał dodać specjalny kod, który modyfikuje postać wydruku i powoduje, że różni się ona od wyglądu informacji wyświetlanych na ekranie, to będziesz mógł dokonać odpowiednich modyfikacji właśnie w tej metodzie. Dla przykładu załóżmy, że tworzysz program wyświetlający na ekranie schemat sieci, jednakże podczas drukowania zamiast schematu tworzona jest tabela zawierająca zestawienie połączeń pomiędzy poszczególnymi węzłami sieci. Schemat sieci będzie w takim przypadku tworzony w metodzie OnDraw, tabela z zestawieniem połączeń - w metodzie OnPrint. Innym zastosowaniem metody OnPrint jest wykorzystanie jej do wydrukowania elementów, które mają się pojawić tylko i wyłącznie na wydruku. Dla przykładu bardzo częstym rozwiązaniem jest tworzenie w tej metodzie nagłówków i stopek wydruku. Po ich stworzeniu wystarczy zmienić początek układu współrzędnych widoku, aby zapobiec przesłonięciu nagłówka przez kod używany przy wyświetlaniu informacji na ekranie. Możesz także zmodyfikować wielkość obszaru przycinania, aby zapobiec przesłonięciu stopki wydruku. Kiedy skończysz wprowadzać modyfikacje, będziesz mógł wywołać metodę OnDraw, aby wydrukowała ona resztę zawartości strony.
Oczywiście istnieje także alternatywne rozwiązanie, które polega na umieszczeniu kodu odpowiadającego za tworzenie nagłówków i stopek w kodzie metody OnDraw. Kod ten mógłby być wykonywany tylko wtedy, gdy metoda IsPrinting zwróci wartość TRUE. Wybór jednego z tych dwóch rozwiązań jest jedynie kwestią osobistych preferencji.
Metoda OnDraw odpowiada za wyświetlanie informacji na ekranie. Jeśli w odpowiedni sposób obsłużysz kontekst urządzenia w wywołaniach wcześniejszych metod, to w metodzie OnDraw nigdy nie będziesz musiał się interesować tym, czy prezentowane przez nią informacje kierowane są na ekran, czy na drukarkę. Zawsze jednak możesz skorzystać z metody IsPrinting, aby rozróżnić obie te sytuacje.
MFC cyklicznie wywołuje przedstawione powyżej trzy metody dla każdej strony wydruku. Ilość stron wydruku określana jest na podstawie informacji zapisanych w strukturze CPrintlnfo (lub też na podstawie Twoich operacji na składowej m_bContinuePrinting). Przed zakończeniem drukowania wywoływana jest metoda OnEndPrinting. W metodzie tej będziesz mógł zwolnić wszystkie zasoby przydzielone na potrzeby drukowania (w metodzie OnBeginPrinting).
Tak wygląda zarys procesu drukowania stosowanego przez MFC. Cały proces przedstawiony został w Tabeli 3.2. Najciekawsze jest jednak to, że MFC używa dokładnie tego samego procesu do tworzenia podglądu wydruku. Jeśli drukowanie będzie działało poprawnie, to automatycznie uzyskasz poprawnie działający podgląd wydruku.
Tabela 3.2. Proces drukowania.
MFC wywołuje Powód Przesłoń gdy
CView::OnFilePrint wybór opcji z menu wszystko chcesz zrobić samemu
CView::OnPreparePrinting rozpoczęcie procesu drukowania chcesz wpisać odpowiednie informacje w okno dialogowe Drukuj
CView::DoPreparePrinting wyświetlenie okna dialogowego Drukuj chcesz wyświetlić własne okno dialogowe Drukuj
CView::OnBeginPrinting przydzielenie zasobów chcesz jeden raz przydzielić zasoby GDI potrzebne do sporządzenia wydruku
CView::OnPrepareDC określenie kontekstu urządzenia chcesz przydzielić zasoby lub zmodyfikować kontekst urządzenia
CView::OnPrint wykonanie drukowania chcesz wydrukować widok różniący się od widokuwyświetlanego na ekranie lub jeśli chcesz wydrukować dodatkowe informacje
CView::OnDraw odświeżenie ekranu chcesz coś wydrukować (prawie zawsze)
Cview::OnEndPrinting zakończenie drukowania musisz zwolnić przydzielone wcześniej zasoby GDI
Dylemat
W idealnej sytuacji powinieneś być w stanie użyć do drukowania dokładnie tego samego kodu, którego używasz do wyświetlania informacji na ekranie. W praktyce jednak istnieje kilka czynników, które poważnie utrudniają takie postępowanie. Najważniejszym z tych powodów jest skalowanie. Rozdzielczość ekranu oraz rozdzielczość drukarki są zazwyczaj zupełnie inne. Na ekranie linia o długości 100 pikseli będzie całkiem długa. Jednakże ta sama linia będzie bardzo krótka na drukarce o rozdzielczości 600 DPI (punktów na cal). Coś, co na ekranie ma rozsądne rozmiary, po wydrukowaniu będzie znacznie mniejsze, no chyba, że podejmiesz odpowiednie środki zaradcze.
Istnieją dwa sposoby rozwiązania tego problemu. Jednym z nich jest zastosowanie odpowiedniego trybu mapowania. Aby zastosować to rozwiązanie, wywołaj metodę CDC::SetMapMode, która pozwala Ci na wybranie trybu mapowania wykorzystującego jednostki logiczne. Dla przykładu, jeśli wybierzesz tryb MM_LOMETRIC, to podstawową jednostką będzie 0,1 mm (patrz Tabela 3.3.). Oczywiście wielkość ekranu nie będzie w takim trybie bardzo precyzyjna, gdyż sterownik nie zna jego dokładnej rozdzielczości. Większość sterowników celowo zwiększa wymiary po to, aby małe elementy (na przykład, wielkości 10 pikseli) były łatwiej zauważalne. Jednakże drukarki dysponują precyzyjnie określoną ilością punków na cal, w związku z czym wymiary będą przeliczane bardzo dokładnie.
Tabela 3.3. Tryby mapowania.
Tryb
MM_TEXT
MM_HIENGLISH
MM_LOENGLISH
MM_HIMETRIC
MM_LOMETRIC
MM_TWIPS
MM_ISOTROPIC
MM_ANISOTROPIC
Opis
l jednostka logiczna == l piksel.
l jednostka logiczna == 0,001 cala.
l jednostka logiczna == 0,01 cala.
l jednostka logiczna == 0,01 milimetra.
l jednostka logiczna == 0,1 milimetra.
l jednostka logiczna == 1/1440 cala (lub 1/20 punktu drukarskiego).
Używa funkcji SetWindowExt oraz SetViewportExt do wyskalowania osi X i Y, zachowując przy tym stosunek wielkości krawędzi okna; okrąg w jednostkach logicznych będzie wyświetlony jako okrąg na ekranie.
Używa funkcji SetWindowExt oraz SetViewportExt do nadania podanych wielkości osiom X i Y; okrąg w jednostkach logicznych zostanie wyświetlony na ekranie jako elipsa, chyba że ręcznie przeliczysz współczynnik skalowania, tak aby zachować proporcje okręgu.
Stosowanie trybów mapowania
Wszystkie tryby mapowania, za wyjątkiem MM_TEXT, znacznie ułatwiają drukowanie, dlatego też zawsze warto ich używać. Należy jednak pamiętać, iż we wszystkich tych trybach (prócz MM_TEXT) oś Y jest odwrócona. Innymi słowy, należy używać liczb ujemnych jako współrzędnych tej osi (chyba że samemu przesunąłeś początek układu współrzędnych).
Oczywiście zdarzają się i takie wypadki, gdy zastosowanie pikseli jest wygodniejsze od użycia jednostek logicznych. Dobrym tego przykładem jest przedstawiony w tym rozdziale program do gry w kółko i krzyżyk. Nie jest łatwo myśleć o planszy w milimetrach lub calach dlatego, że wymiary planszy ulegają zmianie wraz ze zmianami wielkości okna.
Domyślnym trybem mapowania jest MM_TEXT, w którym jednej jednostce logicznej odpowiada jeden piksel. W tym trybie kierunkiem poziomym jest oś X, a jednostki rosną od strony prawej do lewej. Współrzędne osi pionowej (Y) rosną od góry ku dołowi. Inne tryby mapowania (takie jak MM_LOENGLISH) odwracają kierunek osi Y. W ten sposób oś Y rozpoczyna się u góry kontekstu urządzenia, a współrzędne rosną od dołu ku górze. Oznacza to, że wszystkie wartości, które będziesz stosował, będą wartościami ujemnymi! Oś X pozostaje w tym trybie niezmieniona. Te domyślne ustawienia możesz zmodyfikować za pomocą metody CDC::SetViewportOrg.
Choć może użycie tych samych układów współrzędnych dla wyświetlania danych na ekranie i drukowania ich może się wydawać bardzo atrakcyjnym rozwiązaniem, to jest jednak wiele przypadków, w których przysparza ono tyle samo kłopotów ile ich rozwiązuje, Czasami nie chcesz wyświetlać danych na ekranie, licząc wszystko w centymetrach i calach. Współrzędne dotyczące manipulacji myszką są zawsze podawane w pikselach. Może się także zdarzyć, że będziesz chciał, aby prezentowane dane zajmowały zawsze cały ekran oraz stronę wydruku, niezależnie od ich wielkości. W takich przypadkach logiczne tryby mapowania nie są zbytnio przydatne.
Jeśli mimo wszystko będziesz chciał pozostać przy stosowaniu pikseli, to najprawdopodobniej przyda Ci się wyskalowanie kontekstu drukarki w taki sposób, aby jednostki na ekranie i drukarce były podobne. Do wykonania tego zadania najlepiej nadają się metody OnPrepareDC oraz OnPrint. Kod odpowiedzialny za skalowanie możesz także umieścić w metodzie OnDraw. W takim wypadku będziesz musiał sprawdzać, czy trwa proces drukowania (za pomocą metody IsPrinting), a jeśli tak, wykonać odpowiednie przeskalowanie. Wszystko zależy od tego, jak wiele metoda OnDraw ma wiedzieć o tym, czy drukuje, czy wyświetla dane na ekranie.
Pełny przykład drukowania
Rozpatrzmy przykład programu przedstawionego na Listingu 3.1. Jest to program do gry w kółko i krzyżyk pozwalający na wydrukowanie planszy z rozgrywką. Jedynymi ciekawszymi elementami jakie zastosowałem w tym programie są: winietka wyświetlana podczas uruchamiania programu oraz umieszczony na pasku stanu komponent przedstawiający aktualny czas (umieszczony za pomocą Galerii Komponentów wywoływanej po wybraniu opcji Insert^Componeni).
Listing 3.1. Drukowanie widoku.
// mfctttView.cpp
ttinclude "mfctttDoc.h" ttinclude "mfctttview.h"
ttifdef _DEBUG
tdefine new DEBUG_NKW
ttundef THIS_FILE
static char THIS_FILE[] =
ttendif
/ / /1111111111111 /1111111111 /1111 II CMfctttYiew
IMPLEMENT_DYNCREATE(CMfctttYiew, CYiew)
BEGIN_MESSAGE_MAP(CMfctttYiew, CYiew)
//{{AFX_MSG_MAP(CMfCtttYiew)
ON_WM_LBUTTONDOWN()
ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
ON_UPDATE_COMMAND_UI (ID_EDIT_UNDO, OnUpdateEdi tUndo)
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CYiew::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CYiew::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CYiew::OnFilePrintPreview) END_MESSAGE_MAP ()
11111111111111111111111111111111
II CMfctttYiew construction/destruction
CMfctttYiew::CMfctttYiew()
// TODO: add construction code here
CMfctttView: :-CMfctttView()
{
}
BOOL CMfctttView: :PreCreateWindow(CREATESTRUCT& es) {
return CView: :PreCreateWindow(cs) ;
/ / 1 1 1 1 1 1 1 1 1 1 / 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 II CMfctttYiew drawing
// Odstęp od krawędzi pola planszy ttdefine OFFSET 15
void CMfctttYiew: :OnDraw(CDC* pDC) {
CMfctttDoc* pDoc = GetDocument ( ) ASSERT_VALID(pDoc) ; // Stwórz panele
// Pióro linii planszy
CPen p(PS_SOLID, 10, RGB (O, O, 0) ) ; // X pióro
CPen xp(PS_SOLID,5,RGB(OxFF,O,0)); //O pióro
CPen op(PS_SOLID, 5,RGB(0, O, OxFF) ) ,-
CPen *old;
// rysuj plansze
CRect r;
GetClientRect(&r);
old=pDC->SelectObject(&p);
// Poniżej podałem przykładowy kod testujący // umożliwiający sprawdzenie czcionek //CFont f,*oldf;
//f.CreateFont(-30,O,O,O,O,O,O,O,O,O,O,O,O,"Arial" ); //oldf=pDC->SelectObject(&f); //pDC->TextOut(0,0,"Hello"); //pDC->SelectObject(oldf);
// Rysuj siatkę
pDC->MoveTo(r.right/3,0);
pDC->LineTo(r.right/3,r.bottom);
pDC->MoveTo(2*r.right/3,0);
pDC->LineTo(2*r.right/3,r.bottom);
pDC->MoveTo(0,r.bottom/3);
pDC->LineTo(r.right,r.bottom/3);
pDC->MoveTo(O,2*r.bottom/3);
pDC->LineTo(r.right,2*r.bottom/3); // Rysuj klocki
for (int x=0;x<3;x++)
for (int y=0;y<3;y++)
CPoint pt(x,y);
CPoint ptl(x+l,y+l);
GridToMouse(pt);
GridToMouse(ptl);
switch (pDoc->GetBoardState(x,y))
case X:
pDC->SelectObject(&xp);
pDC->MoveTo(pt.x+OFFSET,pt.y+OFFSET);
pDC->LineTo(ptl.x-OFFSET,ptl.y-OFFSET);
pDC->MoveTo(ptl.x-OFFSET,pt.y+OFFSET);
pDC->LineTo(pt.x+OFFSET,ptl.y-OFFSET);
break; case O:
pDC->SelectObject(&op);
pDC->SelectStockObject(HOLLOW_BRUSH) ;
pDC->Ellipse(pt.x+OFFSET,pt.y+OFFSET, ptl.x-OFFSET,ptl.y-OFFSET);
break;
pDC->SelectObject(old); }
// CMfctttView printing
BOOL CMfctttYiew::OnPreparePrinting(CPrintlnfo* plnfo) {
p!nfo->SetMaxPage(2);
return DoPreparePrinting(plnfo);
// Ta wersja metody OnPrint pozwala Ci na użycie trybu MM_TEXT w Twoim // widoku, dzięki czemu wydruk może być poprawnie wyskalowany void CMfctttView::OnPrint(CDC* pDC, CPrintlnfo* plnfo) {
CDC *dc=GetDC();
CString s;
int x=dc->GetDeviceCaps(LOGPIXELSX) ,-
int y=dc->GetDeviceCaps(LOGPIXELSY);
int xl=pDC----GetDeviceCaps(LOGPIXELSX) ;
int yl=pDC->GetDeviceCaps(LOGPIXELSY);
// nagłówek wyświetlaj jedynie podczas drukowania
s.Format("Tic Tac Toe gamę: %s",
GetDocument()->GetTitle() ) ; pDC->TextOut(O,O,s); pDC->MoveTo(0,75) ;
pDC->LineTo(pinfo->m_rectDraw.Width(),75); if (pinfo->m_nCurPage==2) {
CMfctttDoc *doc=GetDocument(); s.Format("Games I Won=%d, Games I Lost=%d" " Draw Games=%d",doc->wins,doc->loss, doc->draw);
pDC->TextOut (O, 100, s) ,-return; }
// Zmień tryb mapowania tak, aby piksele były poprawnie wyskalowane pDC->SetMapMode(MM_ISOTROPIC) ; pDC->SetWindowExt(x,y); pDC->SetViewportExt(xl,yl); // Górny margines wielkości 100 pikseli pDC->SetViewportOrg(0,100); CView::OnPrint(pDC, plnfo);
void CMfctttView: :OnBeginPrinting (CDC* /*pDC*/,
CPrintlnfo* /*plnfo*/) {
// TODO: add extra initialization before printing
void CMfctttView: :OnEndPrinting(CDC* /*pDC*/,
CPrintlnfo* /*plnfo*/) {
// TODO: add cleanup after printing
111 /1! 1111111111111111111111111 / II CMfctttView diagnostics
łtifdef _DEBUG
void CMfctttView::AssertValid() const
{
CYiew::AssertValid();
void CMfctttView::Dump(CDumpContext& dc) const {
CView::Dump(dc); } CMfctttDoc* CMfctttView::GetDocumentO
ASSERT(m_pDocument->IsKindOf( RUNTIME_CLASS(CMfctttDoc)));
return (CMfctttDoc*)m_pDocuinent; } #endi£ //_DEBUG
1111111111111111 /111111111111111 II CMfctttView message handlers
// Skonwertuj współrzędne myszy na współrzędne siatki
void CMfctttYiew::MouseToGrid(CPoint &pt>
{
CRect r;
GetClientRect(&r);
pt.x/=r.right/3;
pt.y/=r.bottom/3;
// Skonwertuj współrzędne siatki na współrzędne myszy
void CMfctttView: :GridToMouse (CPoint &pt)
{
CRect r;
GetClientRect (&r) ;
pt .x*=r . right/3, •
pt.y*=r .bottom/3;
void CMfctttView: :OnLButtonDown(UINT nFlags, CPoint point) {
CMfctttDoc *doc=GetDocument ( ) ;
// Skonwertuj do pozycji siatki MouseToGrid (point) ;
// Jeśli pole planszy nie jest puste, to zasygnalizuj i pomiń ruch
if (doc->GetBoardState (point .x, point .y) !=EMPTY) {
MessageBeep(MB_ICONEXCLAMATION) ; return; }
// Pusty - umieść w polu X lub O, w zależności od numeru ruchu // Uwaga: ten kod został umieszczony w celach testowych, // komputer zawsze wyświetla O, dlatego ten ruch zawsze musi // wyświetlać X
doc->SetBoardState (point .x, point .y,
(doc->turn&l)?0:X) ; doc->turn++;
doc->UpdateAHViews(NULL) ; doc->Play(); // Wykonaj ruch
void CMfctttYiew: :OnEditUndo O {
CMfctttDoc *doc=GetDocument ( ) ;
doc->undo (TRUE) ;
void CMfctttYiew: :OnUpdateEditUndo (CCmdUI* pCmdUI) {
CMfctttDoc *doc=GetDocument () ;
pCmdUI->Enable(doc->undo( FALSE) ) ;
Najbardziej interesującą częścią programu jest kod odpowiedzialny za drukowanie. W kodzie przedstawionym na Listingu 3.1. możesz pominąć metodę OnPrint lub wewnątrz niej odwołać się bezpośrednio do tej samej metody klasy bazowej. Jeśli tak zrobisz, to przekonasz się, że pomimo wszystko zarówno drukowanie, jak i podgląd wydruku działają poprawnie. Jednakże wydrukowana plansza będzie bardzo mała w porównaniu z planszą widoczną na ekranie.
Jest kilka możliwości rozwiązania tego problemu. Po pierwsze mógłbyś rysować planszę w trybie mapowania innym niż MM_TEXT. Spowoduje to jednak powstanie nowego problemu związanego z tym, iż plansza powinna zmieniać swoje wymiary wraz ze zmianami wielkości okna programu. Znacznie łatwiej jest modyfikować wielkość planszy w trybie mapowania MM_TEXT. Najprostszym rozwiązaniem jest przeskalowanie kontekstu drukarki w taki sposób, aby linie zajmujące cały ekran przebiegały także przez całą stronę wydruku.
Poniżej przedstawiona została uproszczona wersja kodu realizującego takie przeskalowanie w metodzie OnPrint (wersję pełną znajdziesz na Listingu 3.1.):
void CMfctttYiew::OnPrint(CDC* pDC, CPrintlnfo* plnfo)
CDC *dc=GetDC();
int x=dc->GetDeviceCaps(LOGPIXELSX); int y=dc->GetDeviceCaps(LOGPIXELSY); int xl=pDC->GetDeviceCaps(LOGPIXELSX); int yl=pDC->GetDeviceCaps(LOGPIXELSY); // modyfikacja trybu mapowania pDC->SetMapMode(MM_ISOTROPIC); pDC->SetWindowExt(x,y); pDC->SetViewportExt(xl,yl); CView::OnPrint(pDC,plnfo);
W pierwszej linii metody pobierany jest wskaźnik do kontekstu urządzenia (DC) skojarzonego z widokiem. Kontekst urządzenia, przekazywany jako argument wywołania metody (pDC), odnosi się do drukarki. Kolejne cztery linie kodu określają ilości pikseli przypadające na cal w każdym z kontekstów (zarówno w pionie, jak i w poziomie). Kolejnym krokiem jest przejście do trybu mapowania MM_ISOTROPIC i ustawienie rozmiarów tak, aby cal na ekranie odpowiadał mniej więcej calowi na wydruku. Przy okazji powinieneś zapamiętać, że po przejściu do trybu mapowania MM_ISOTROPIC powinieneś w pierwszej kolejności określić wymiary okna, a dopiero potem wymiary widoku. Po wykonaniu powyższego kodu narysowanie linii o długości x jednostek w kontekście drukarki spowoduje wydrukowanie linii o długości xl jednostek fizycznych.
Stosując ten lub inny, podobny sposób postępowania, musisz wiedzieć o jeszcze jednej istotnej rzeczy. Jeśli zmienisz używany tryb mapowania w opisany powyżej sposób, to nie będziesz mógł korzystać z domyślnych czcionek. Kod odpowiedzialny za rysowanie będzie musiał stworzyć czcionki po zmianie trybu mapowania. W przeciwnym wypadku wyniki drukowania nie będą zbyt atrakcyjne.
Kod przedstawiony na Listingu 3.1., oprócz samej planszy, drukuje także nagłówek (w postaci tekstu i poziomej linii - patrz rysunek 3.2.). W przykładzie użyta została metoda SetYiewportOrg, dzięki czemu metoda OnDraw rozpocznie rysowanie 100 jednostek poniżej początku strony, robiąc w ten sposób odpowiednio dużo miejsca dla nagłówka.
W MFC algorytm używany do drukowania wykorzystywany jest także przy tworzeniu podglądu wydruku, dzięki czemu ten sam kod obsługuje obie te czynności. Przy takim rozwiązaniu jedynym problemem pozostaje podział wydruku na strony. Jeśli wieś/, jaka będzie maksymalna ilość stron już podczas wywoływania metody OnPreparePrinting, to będziesz ją mógł od razu podać. W przeciwnym razie powinieneś ją określić podczas wykonywania metody OnBeginPrinting. Możesz także dokonywać zmian w podziale dokumentu na strony podczas jego drukowania - sprawdzając numer drukowanej strony przechowywany w strukturze CPrintlnfo. Jeśli będziesz chciał wydrukować stronę, to nadaj wartość TRUE składowej m_bContinuePrinting struktury CPrintlnfo. Przykładowy program na drugiej stronie wydruku wyświetla statystyki gry. Zauważ, że zastosowana została tu pierwsza omówiona metoda, a cały kod odpowiedzialny za wydrukowanie statystyki umieszczony jest w metodzie OnPrint.
Dostosowywanie podglądu wydruku do własnych potrzeb
Pamiętam, że gdy byłem dzieckiem fascynowało mnie struganie. Podejrzewam, że dzieci w dzisiejszych czasach już nie strugają (oczywiście nie oznacza to wcale, że nie mają noży, o czym łatwo można się przekonać oglądając informacje w telewizji). Nic zrozum mnie źle - nigdy nie byłem dobry w struganiu, chociaż zawsze chciałem być.
Niezależnie od tego, co zaczynałem strugać, to i tak w końcu wychodził z lego mały totem. W ten sposób udało mi się zrobić wiele całkiem fajnych totemów. Te same problemy miałem na zajęciach praktycznych w szkole średniej. Pewnie bym je oblał, gdyby nie
część poświęcona elektryczności. Trochę gorzej szło mi z materiałami plastycznymi. Zaczynałem robić krzyż, ale w efekcie i tak wychodziło z tego coś przypominającego cyfrę „7". Całe szczęście, że instruktor nie pytał, co robimy, aż do momentu, gdy przychodziło się do niego z ukończonym dziełem.
Doświadczenia te nauczyły mnie dwóch rzeczy:
• Powinienem trzymać się jak najdalej od noży i innych narzędzi;
• Zazwyczaj łatwiej jest zmniejszać rzeczy, niż je powiększać.
To ostatnie stwierdzenie można także z powodzeniem zastosować do podglądu wydruku. W stosunkowo łatwy sposób można bowiem ograniczyć możliwości podglądu wydruku i zmusić go, żeby robił tylko to, co chcemy. Znacznie trudniej jest dodać do niego nowe możliwości. W dalszej części rozdziału pokażę, jak można zrobić obie te rzeczy.
W celu przedstawienia możliwości dostosowywania podglądu wydruku do własnych potrzeb napisałem bardzo prosty program umożliwiający łączenie punktów na ekranie (patrz rysunek 3.3.). Gdy klikniesz lewym przyciskiem myszy, to program narysuje linię prostą łączącą ostatnio narysowany punkt z miejscem, w którym aktualnie umieszczony jest wskaźnik myszy. Gdy klikniesz prawym przyciskiem myszy, to program nie rysuje nowej linii, a jedynie zaznacza na ekranie nowy punkt, od którego zostanie rozpoczęta następna linia. Jak widać, program nie jest zbytnio skomplikowany, a momo to doskonale będzie się nadawał na przykład prezentujący drukowanie.
Gdy kiedyś będziesz miał okazję pisania programu takiego jak len, to pierwszą rzeczą, na którą zwrócisz uwagę, będzie to, że opcja podglądu wydruku dostarczana przez MFC wykonuje za Ciebie „kawał porządnej roboty". Dla przykładu, MFC automatycznie obsługuje wyświetlanie dokumentów zawierających więcej stron, jak również pozwala na wyświetlanie dwóch stron jednocześnie.
Dostosowywanie podglądu wydruku
Dostosowanie podglądu wydruku do własnych potrzeb jest dość prostym zadaniem. Wszystko, co będziesz musiał zrobić, ogranicza się bowiem do usunięcia przycisków, których nie chcesz używać. Oczywiście, kod obsługujący odpowiednie polecenia cały czas jest gdzieś tam wewnątrz MFC. Ale co Ci to przeszkadza? Jeśli użytkownik nie może uruchomić kodu, to tak, jak gdyby w ogóle tego kodu nie było.
Cała sztuczka polega na przejęciu kontroli nad metodą OnFilePrintPreview. Standardowo, kreator App Wizard dodaje makro definiujące procedurę obsługi tego polecenia do mapy obsługi komunikatów widoku. Makro to jest jednak umieszczane poza komentarzami oznaczającymi komunikaty obsługiwane przez kreatora Class Wizard, a do obsługi polecenia używana jest metoda klasy bazowej. Dlatego też nie jesteś w stanie zobaczyć ani kodu obsługi polecenie wyświetlania podgląd wydruku, ani odpowiedniego elementu mapy obsługi komunikatów.
Naszym pierwszym krokiem będzie przeniesienie makra O1SL.COMMAND, zawierającego metodę OnFilePrintPreview, do obszaru makr zarządzanych przez kreatora Class Wizard. Kolejny krok to usunięcie operatora zakresu (często ma on postać: CView::) umieszczonego przed nazwą metody OnFilePrintPreview, dodanie deklaracji tej metody do pliku nagłówkowego i zdefiniowanie jej w pliku CPP. Po zakończeniu tych czynności mapa komunikatów Twojego widoku powinna przypominać tę przedstawioną poniżej.
BEGIN_MESSAGE_MAP(CConndotView, CView)
//{{AFX_MSG_MAP(CConndotYew)
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN ( )
ON_COMMAND(ID_FILE_PRINT_PREVIEW, OnFilePrintPreview)
//}}AFX_MSG_MAP
//Standard printing comments
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) END_MESSAGE_MAP()
Oryginalna wersja kodu metody OnFiIePrintPreview ma następującą postać (możesz ją odnaleźć w pliku VIEWPREV.CPP, w kodzie źródłowym MFC):
void CView::OnFilePrintPreview()
{
// In derived classes, implement special window handling here // Be surę to Unhook Frame Window close if hooked.
// must not create this on the frame. Must outlive this function CPrintPreviewState* pState = new CPrintPreviewState;
// DoPrintPreview's return value does not necessarily indicate that
// Print preview succeeded or failed, but rather what actions
// arę necessary at this point. If DoPrintPreview returns TRUE,
// it means that OnEndPrintPreview will be (or has already been)
// called and the pState structure will be/has been deleted.
// If DoPrintPreview returns FALSE, it means that
// OnEndPrintPreview WILL NOT be called and that cleanup,
// including deleting pState must be done here.
if (!DoPrintPreview(AFX_IDD_PREVIEW_TOOLBAR, this, RUNTIME_CLASS(CPreviewView), pState)) {
// In derived classes, reverse special window handling // here for Preview failure case
TRACEO("Error: DoPrintPreview failed.\n"); AfxMessageBox(AFX_IDP_COMMAND_FAILURE); delete pState; // preview failed to initialize, // delete State nów
Jest oczywiste, że wszystkie najważniejsze czynności muszą być wykonywane w metodzie DoPrintPreview. Jeśli z metody OnFilePrintPreview usuniesz niepotrzebne komentarze i makra używane przy testowaniu, to okaże się, że cała metoda ma niewiele więcej niż 4 linie kodu. Kluczem do dostosowania podglądu wydruku do swoich potrzeb jest metoda DoPrintPreview.
Przyjrzymy się teraz czterem argumentom wywołania tej metody. Pierwszym z nich jest identyfikator zasobu zawierającego pasek narzędzi podglądy wydruku. Zmiana tego zasobu spowoduje automatyczną zmianę paska narzędzi. Proste, prawda? W zasadzie mógłbyś stworzyć zupełnie nowy pasek narzędzi, jednakże znacznie łatwiej jest posłużyć się oryginalnym paskiem, który przechowywany jest w pliku AFXPR1NT.RC (plik ten z niewiadomych powodów umieszczony został w kartotece MFCMNCLUDE}. Po skopiowaniu oryginalnego paska narzędzi i umieszczeniu w Twoim pliku zasobów, wystarczy go odpowiednio zmodyfikować, dostosowując jego postać do własnych potrzeb. Nie zapomnij także zmienić identyfikatora zasobu i użyć tego nowego identyfikatora w wywołaniu metody DoPrintPreview.
Przykład własnego podglądu wydruku
Na Listingu 3.2. przedstawiony został kod modyfikujący postać podglądu wydruku (wygląd zmodyfikowanego podglądu wydruku możesz zobaczyć na Rysunku 3.3.). Jedynym interesującym aspektem tworzenia własnej metody OnFi!ePrintPreview jest wykorzystanie w tej metodzie odwołania do obiektu CPreviewView. Obiekt ten jest używany w MFC do reprezentowania okna podglądu wydruku. Jedyny plik nagłówkowy zawierający deklarację tej klasy nosi nazwę AFXPRIV.H. Jeśli chcesz, aby Twój kod został poprawnie skompilowany, to będziesz musiał dołączyć do niego ten plik.
Używanie pliku nagłówkowego AFXPRIV.H
W zasadzie wszystkie deklaracje umieszczone w pliku AFXPRIV.H mogą się zmieniać w kolejnych wersjach MFC. Jednakże firma Microsoft okazała się na tyle łaskawa, aby nie zmieniać tych deklaracji, które mogą spowodować błędy w kodzie wielu programistów. De facto, wiele elementów umieszczonych początkowo w pliku nagłówkowym AFXPRIV.H (jak na przykład niektóre makra służące do konwersji danych) zostały ostatnio oficjalnie udokumentowane i przeniesione do innych plików nagłówkowych, a to wszystko z tego powodu, że były one tak często wykorzystywane.
Jeśli chcesz napisać profesjonalnie wyglądający program, to będziesz musiał czasami podjąć pewne ryzyko i wykorzystać te części MFC, które nie są oficjalnie udokumentowane. Może się zdarzyć, że po pojawieniu się nowszej wersji MFC będziesz musiał dokonać w swoim kodzie jakichś modyfikacji, jednakże taka już jest cena „życia na krawędzi".
Poniżej przedstawiłem oficjalny komentarz firmy Microsoft zaczerpnięty z pliku nagłówkowego AFXPRIV.H.
„Ten plik nagłówkowy zawiera przydatne klasy, które są udokumentowane jedynie w Notatkach Technicznych MFC. Klasy te mogą się zmieniać w kolejnych wersjach MFC, dlatego w przypadku wykorzystania tego pliku będziesz musiał być przygotowany na konieczność modyfikacji swojego kodu. W przyszłości częściej używane fragmenty tego pliku nagłówkowego mogą zostać oficjalnie udokumentowane i przeniesione w inne miejsca."
Metoda OnDraw przedstawiona na Listingu 3.2. wykonuje specjalne czynności podczas drukowania. W przypadku wykrycia, że program jest w trakcie drukowania (za pomocą metody CDC::IsPrinting), metoda ta zmienia tryb mapowania, dzięki czemu widok wydrukowany będzie wiernym odzwierciedleniem widoku ekranu. Aby móc to zrobić, kod tej metody zmienia tryb mapowania kontekstu drukarki (na MM_ISOTROPIC), pobiera kontekst aktualnego widoku i na podstawie jego wielkości określa parametry kontekstu drukarki.
Listing 3.2. Własny pasek narzędzi okna Podglądu Wydruku.
/ l conndotview.cpp : implementation of the CConndotView class
#include "stdafx.h"
#include "conndot.h"
tfinclude "CustomPreview.h" // dołącz okno podglądu wydruku
ttinclude "conndotDoc .h" tinclude "conndotView.h"
ttifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = _ FILE _ ;
ttendif
// CConndo tVi ew
IMPLEMENT_DYNCREATE(CConndotView, CView)
BEGIN_MESSAGE_MAP(CConndotView, CView) //{{AFX_MSG_MAP(CConndotView) ON_WM_LBUTTONDOWN() ON_WM_RBUTTONDOWN()
ON_COMMAND(ID_FILE_PRINT_PREVIEW, OnFilePrintPreview)
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
END_MESSAGE_MAP()
// CConndotView construction/destruction
CConndotView: :CConndotView( ) {
// TODO: add construction code here
}
CConndotView: : ~CConndotView( )
{ }
BOOL CConndotYiew: :PreCreateWindow(CREATESTRUCT& es) {
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT es
}
return CView: : PreCreateWindow(cs) ;
/ / CConndotView drawing
void CConndotView: :OnDraw(CDC* pDC) {
CConndotDoc* pDoc = GetDocument ( ) ;
ASSERT_VALID(pDoc) ;
CPoint pt;
if (pDC->IsPrinting( ) )
{
CDC *vdc=GetDC() ; int n;
n=vdc->GetDeviceCaps (LOGPIXELSX) ; pDC->SetMapMode(MM_ISOTROPIC) ; pDC->SetWindowExt (n, n) ,-n=pDC->GetDeviceCaps (LOGPIXELSX) ; pDC->SetViewportExt (n, n) ; }
pDC->MoveTo(0, 0) ;
for (int i=0; i<pDoc->points -GetSize ( ) ; i++ {
pt=pDoc->points [ i] ;
if (pt.x<0)
{
pt .x=-pt .x; pt.y=-pt.y; pDC->MoveTo (pt) ;
else
pDC->LineTo (pt) ;
// CConndotYiew printing
BOOL CConndotYiew::OnPreparePrinting(CPrintlnfo* plnfo)
// default preparation
return DoPreparePrinting(plnfo);
void CConndotView: :OnBeginPrinting (CDC* /*pDC*/, CPrintlnfo*
/*plnfo*/)
{
// TODO: add extra initialization before printing
void CConndotView: :OnEndPrinting (CDC* /*pDC*/, CPrintlnfo*
/*plnfo*/)
{
// TODO: add cleanup after printing
// CConndotView diagnostics
łtifdef _DEBUG
void CConndotView::AssertValid() const
{
CView::AssertValid();
void CConndotYiew::Dump(CDumpContext& dc) const CYi ew: : Dump (dc ) ;
CConndotDoc* CConndotView::GetDocument() // non-debug version is
inline
{
ASSERT (m_pDocument->IsKindOf (RUNTIME_CLASS (CConndotDoc) ) ) ,-
return (CConndotDoc*)m_pDocument; } łtendif //_DEBUG
// CConndotView message handlers
void CConndotView::OnLButtonDown(UINT nFlags, CPoint point) {
CConndotDoc *doc=GetDocument();
// Rysuj
doc->points.Add(point);
doc->UpdateAllViews(NULL);
CView::OnLButtonDown(nFlags, point); }
void CConndotView::OnRButtonDown(UINT nFlags, CPoint point)
{
// przesuń tylko aktualny punkt
CPoint mover=point;
mover.x--mover.x;
mover.y=-mover.y;
GetDocument()->points.Add(mover); CView::OnRButtonDown(nFlags, point);
void CConndotView::OnFilePrintPreview()
CPrintPreviewState* pState = new CPrintPreviewState;
if (!DoPrintPreview(IDD_PREVIEW_TOOLBAR, this,
RUNTIME_CLASS(CCustomPreview),pState))
// W klasie potomnej odtwórz tutaj specjalną obsługę okna // gdyż w przeciwnym razie okno podglądu wydruku nie będzie // działało prawidłowo
TRACEO("Error: DoPrintPreview failed.\n");
Af xMessageBox (AFX_IDP_COMMAND_FAILURE) ;
delete pState; // okno podglądu nie zostało
// zainicjalizowane usuń State
Bardziej zaawansowane dostosowywanie podglądu wydruku
Jeśli się dokładniej przyjrzysz pozostałym trzem argumentom wywołania metody DoPrint-Preview, to najprawdopodobniej samemu zorientujesz się, do czego one służą. Drugim argumentem wywołania metody DoPrintPreview jest wskaźnik na widok, który ma zostać wydrukowany (zazwyczaj będzie to this). Trzecim argumentem jest klasa czasu wykonania (otrzymana za pomocą makra RUNTIME_CLASS) okna podglądu wydruku. Zazwyczaj jest to klasa CPrintYiew, jednakże nic nie stoi na przeszkodzie, aby użyć jakiejś innej klasy. Na przykład, można by stworzyć klasę potomną klasy CPrevie-wYiew, dodać do niej nowe możliwości, a następnie podać jej nazwę jako trzeci argument wywołania metody DoPrintPreview.
Ale co mógłbyś robić we własnej klasie widoku podglądu wydruku? Cokolwiek byś sobie wymyślił. Ot chociażby - napisać wielkimi literami „PODGLĄD WYDRUKU" (oczywiście oprócz pozostałej zawartości podglądu). Jednakże rewelacyjną modyfikacją byłoby uaktywnienie okna podglądu wydruku w taki sposób, aby użytkownik mógł modyfikować dane podczas oglądania podglądu (takie rozwiązanie mogłoby troszkę przypominać postać ukladu strony dostępny w wielu popularnych edytorach).
Podczas tworzenia własnej klasy potomnej klasy CPrintYiew możesz odkryć, że potrzebne Ci będą pewne dodatkowe elementy. Po pierwsze, bez większych trudności będziesz mógł dodawać nowe przyciski do paska narzędzi swojego widoku. Cała modyfikacja będzie się sprowadzała do zmienienia szablonu paska narzędzi przechowywanego w pliku zasobów (tak jak w poprzednim przykładzie). Możesz także stworzyć swoją klasę jako klasę potomną klasy CPrintPreviewState i dodać do niej dowolne dane, jakie będą Ci potrzebne. Jedyną rzeczą, o jakiej nie możesz zapomnieć, to umieszczenie nazwy Twojej nowej klasy w wywołaniu operatora new w metodzie OnFHePrintPreview.
Wyprowadzanie klasy
Niestety, podczas tworzenia nowej klasy kreator Class Wizard nie umożliwia wybrania klasy CPreviewView jako klasy bazowej. Klasa CPreviewView jest klasą potomną klasy CScroIlYiew; nie polecałbym Ci jednak wyprowadzania swojej nowej klasy z klasy CPreviewView ze względu na to, iż obsługuje ona wszystkie czynności charakterystyczne dla przewijalnych widoków (widoków wyprowadzonych z klasy CScrollYiew). Najlepszym wyjściem z sytuacji jest wyprowadzanie klasy widoku z klasy CYiew. Po stworzeniu klasy będziesz musiał zamienić wszystkie wystąpienia łańcucha znaków „CYiew" na „CPreviewView" (zarówno w pliku nagłówkowym - .H, jak i pliku źródłowym - .CPP). Upewnij się, że wszystkie wystąpienia łańcucha znaków „CYiew" zostały poprawnie zamienione. Będziesz także musiał dołączyć do pliku źródłowego plik nagłówkowy AFXPRIV.H, aby uzyskać definicję klasy CPreviewView.
Kolejnym etapem będzie zmodyfikowanie istniejącego widoku. Czynności, jakie będziesz musiał w tym celu wykonać, są bardzo podobne do czynności, które wykonywałeś podczas modyfikowania paska narzędzi w oknie podglądu wydruku. Jedyna różnica polega na tym, iż zamiast klasy CPreviewView, w wywołaniu metody DoPrintPreview użyjesz swojej, stworzonej przed chwilą klasy; oprócz tego, zamiast pliku AFXPR1V.H, dołączysz do widoku plik nagłówkowy Twojej klasy.
Jeśli będziesz chciał, to możesz przesłonić metodę OnDraw, aby samemu narysować coś na stronie (lub w obszarze poza stroną). Problem jednak polega na tym, że nie wiadomo gdzie rysować. Podobny problem pojawi się, kiedy będziesz chciał obsługiwać kliknięcia myszką- współrzędne kliknięcia będziesz musiał przeliczać na współrzędne, które mają jakikolwiek sens dla Twojego programu.
Wewnętrzne tajniki sporządzania podglądu wydruku
Kod odpowiadający za sporządzanie podglądu wydruku umieszczony jest w trzech plikach źródłowych MFC. Kod obsługujący okno podglądu wydruku umieszczony jest w pliku V1EWPREV.CPP. Specjalny kontekst urządzenia odpowiadający za poprawne drukowanie znajduje się w pliku DCPREY.CPP. I wreszcie wszystkie odpowiednie definicje umieszczone zostały w pliku AFXPRIV.H. Przestudiowanie tych trzech plików w znacznej mierze może wyjaśnić Ci szczegóły sposobu sporządzania podglądu wydruku.
Podstawowym źródłem informacji dla podglądu widoku jest składowa m_pPage!nfo. Jest to tablica struktur typu PAGE_INFO (patrz Tabela 3.4.). W tablicy tej umieszczane są informacje określające każdą stronę sporządzanego wydruku. Jeśli cały wydruk składa się tylko z jednej strony, to oznacza to, że w tablicy tej umieszczony będzie tylko jeden element (zostanie on umieszczony w komórce tablicy o indeksie 0). Jeśli na wydruk składają się dwie strony, to odpowiednie informacje zostaną umieszczone w komórkach i indeksach O i 1.
W składowej rectScreen przechowywane są współrzędne obszaru (prostokątnego), jaki aktualna strona zajmuje na ekranie. Pozostałe trzy składowe tej struktury są obiektami klasy CSize. Jednakże MFC nie używa ich do określania wielkości. Zamiast tego są one używane do zapamiętania ułamka'(cx/cy); będziesz musiał o tym pamiętać, analizując oryginalny kod MFC.
Tabela 3.4. Postać struktury PAQE_INFO.
Składowa
Definicja.
rectScreen
sizeUnscaled
sizeScaleRatio
sizeZoomOutRatio
Współrzędne tej strony na ekranie.
Prostokąt określający niewyskalowany obszar ekranu.
Współczynnik (cx/cy) pomiędzy jednostkami drukarki a jednostkami ekranu.
Współczynnik skalowania używany podczas zmniejszania powiększonego podglądu wydruku.
Poprzez manipulowanie dostępnymi współczynnikami skalowania będziesz mógł przekształcać punkty na drukarce na punkty na ekranie (i na odwrót). Praktyczny sposób przekształcania będziesz mógł zobaczyć w przykładowym programie przedstawionym w dalszej części rozdziału. Niestety, nie udało mi się znaleźć łatwego sposobu przekształcania punków na ekranie na punkty, którymi możesz się posługiwać w programie. Na szczęście, jeśli raz napiszesz odpowiedni kod, to już nigdy więcej nie będziesz musiał tego robić powtórnie. Oczywiście, zamiast pisać własny kod, równie dobrze możesz skopiować kod przedstawiony w przykładowym programie.
Klasa CPreviewView dysponuje wieloma innymi składowymi, jednakże nie są one dla nas interesujące. Numer aktualnie drukowanej strony możesz określić za pomocą składowej m_nCurrentPage. Kontekst urządzenia podglądu wydruku jest przechowywany w składowej m_pPreviewDC.
Tworzenie podglądu wydruku umożliwiającego edycję
Na Rysunku 3.4. przedstawiona została kolejna wersja programu umożliwiającego rysowanie i łączenie punktów. Ta wersja programu dysponuje własnym obiektem klasy CPreviewView. Okno podglądu wydruku wygląda niemalże identycznie z oknem dostępnym w poprzedniej wersji programu; jedyną różnicą jest to, że na pasku narzędzi umieszczony został dodatkowy przycisk o nazwie Edit. Kliknięcie na tym przycisku powoduje zmianę kształtu kursora (będzie on przypominał strzałkę) i umożliwienie rysowania bezpośrednio w oknie podglądu wydruku. Powtórne kliknięcie przycisku (którego nazwa została zmieniona na Zoom) spowoduje przejście do normalnego trybu podglądu wydruku i zmianę kształtu kursora.
W jaki sposób można to wszystko zrobić? Otóż klasa podglądu wydruku stworzona na potrzeby tego przykładu zawiera składową typu Boolean (editmode) używaną do przechowywania informacji o trybie, w jakim aktualnie znajduje się okno podglądu wydruku (dostępne są dwa tryby: tryb edycji oraz tryb powiększania). Oprócz tego w mapie komunikatów dostępnych w tej klasie umieszczone zostały makra określające procedury obsługi kliknięć prawym i lewym przyciskiem myszy, procedurę obsługi komunikatu WM_SETCURSOR oraz procedurę obsługi przycisku ID_EDITBUTTON (używanego do przełączania trybu pracy okna podglądu wydruku).
Kiedy użytkownik naciska przycisk zmiany trybu pracy, wartość zmiennej editmode jest zamieniana na przeciwną. Dodatkowo zdefiniowana została procedura obstugi UPDATE_COMMAND_UI odpowiadająca za modyfikowanie nazwy przycisku (więcej informacji na temat procedur używanych do aktualizacji opcji menu i innych elementów interfejsu użytkownika). Gdy program otrzymuje komunikat WM_SETCURSOR, to sprawdza czy podgląd wydruku znajduje się w trybie edycji (czy składowa editmode posiada wartość TRUE) oraz czy wskaźnik myszy znajduje się w obszarze roboczym okna podglądu wydruku. Jeśli oba te warunki zostaną spełnione, to wskaźnikowi myszy nadawany jest kształt strzałki. W przeciwnym razie, wywoływane są odpowiednie metody klasy bazowej, dzięki czemu wykonane zostaną standardowe czynności.
Listing 3.3. Edycja danych w oknie podglądu wydruku.______
// CustomPreview.cpp : implementation file
_FILE_
łtinclude "stdafx.h" ttinclude "conndot.h" ttinclude "CustomPreview.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] =
#endif
// CCustomPreview
IMPLEMENT_DYNCREATE(CCustomPreview, CPreviewView)
CCustomPreview::CCustomPreview() {
editmode=FALSE; }
CCustomPreview::~CCustomPreview()
BEGIN_MESSAGE_MAP(CCustomPreview, CPreviewView)
//{{AFX_MSG_MAP(CCustomPreview)
ON_WM_RBUTTONDOWN()
ON_WM_LBUTTONDOWN ( )
ON_COMMAND(ID_EDITMODE, OnEditMode)
ON_UPDATE_COMMAND_UI(ID_EDITMODE, OnUpdateEditMode)
ON_WM_SETCURSOR()
//} }AFX_MSG_MAP END_MESSAGE_MAP()
// CCustomPreview drawing
// CCustomPreview diagnostics
łłifdef _DEBUG
void CCustomPreview::AssertValid() const
{
CYiew::AssertValid();
void CCustomPreview::Dump(CDumpContext& dc) const
CView::Dump(dc); #endif //_DEBUG
11111111 /11111111111111111111 /11111111111111111111111 /1111 /11111111 II CCustomPreview message handlers
void CCustomPreview::OnRButtonDown(UINT nFlags, CPoint point) if (editmode)
ConvertPoint(point);
// przesuń tylko aktualny punkt
CPoint mover=point;
mover.x=-mover.x;
mover.y=-mover.y;
GetDocument()->points.Add(mover);
else
CPreviewView::OnRButtonDown(nFlags, point);
void CCustomPreview::OnLBUttonDown(UINT nFlags, CPoint point)
i f (editmode)
CConndotDoc *doc=GetDocument ( ) ; ConvertPoint (point) ; doc->points .Add (point) ; doc->UpdateAHViews (NULL) ;
else
CPreviewView: .-OnLButtonDown (nFlags, point)
CConndotDoc *CCustomPreview: :GetDocument ( ) {
return (CConndotDoc * ) CPreviewView: :GetDocument ( ) ,-
void CCustomPreview: : ConvertPoint (CPoint & point)
{
// zakładamy, że widoczna jest tylko l strona,
// jeśli to nie jest dobre założenie, to będziesz musiał sprawdzić
// numer wyświetlanej strony - możesz to zrobić, badając wartość
// każdej składowej rectScreen elementu tablicy m_pPage!nfo.
// Nie zapomnij, że podgląd wydruku używa sizeScaleRatio jako ułamka, // nie są stosowane czynniki skalujące x, y. CPoint ViewportOrg; if (nunZoomState != ZOOM_OUT)
ViewportOrg = -GetDeviceScrollPosition ( ) ; else
ViewportOrg=GetDC ( ) ->GetViewportOrg ( ) ; m_pPreviewDC->SetScaleRatio (m_pPage!nf o [
m_nCurrentPage-l ] . sizeScaleRatio . ex,
m_pPage!nfo [m_nCurrentPage-l] . sizeScaleRatio . cy) ,-
// określ wielkość marginesu CSize PrintOffset; m_pPreviewDC->Escape(GETPRINTINGOFFSET, O, NULL,
(LPVOID)&PrintOffset) ;
m_pPreviewDC->PrinterDPtoScreenDP( (LPPOINT) &PrintOf f set ) ; PrintOffset += (CSize) m_pPage!nf o [
m_nCurrentPage-l] . rectScreen. TopLef t () ; PrintOffset += CSized, 1);
PrintOffset += (CSize) ViewportOrg; // na potrzeby przewijania point- =PrintOf f set ;
// dostosuj punkt do pozycji strony
point. x = MulDiv(point .x, m_pPage!nfo[
m_nCurrentPage-l] . sizeScaleRatio . cy, m_pPage!nf o [m_nCurrentPage-l ] . sizeScaleRatio. ex) ; point. y = MulDiv(point .y, m„pPageInf o [
m_nCurrentPage-l] . sizeScaleRatio . cy,
m_pPage!nfo [m_nCurrentPage-l ] . sizeScaleRatio. ex) ;
void CCustomPreview: : OnEdi tMode ( ) {
ed t tmode- i editmode ;
void CCustomPreview::OnUpdateEditMode(CCmdUI* pCmdUI) {
pCmdUI->SetText(editmode?"Zoom":"Edit"};
pCmdUI->Enable();
BOOL CCustomPreview: :OnSetCursor (CWnd* pWnd, UINT nHitTest, UINT
message) {
if (editmode && nHitTest==HTCLIENT) {
: : SetCursor (Af xGetApp ( ) ->LoadStandardCursor ( IDC_ARROW) ) ; return TRUE;
else
return CPreviewView: :OnSetCursor (pWnd, nHitTest,
message) ;
Najważniejsza część pracy wykonywana jest w procedurach obsługi kliknięć prawym i lewym przyciskiem myszy. Kod umieszczony w tych procedurach musi w pierwszej kolejności sprawdzić wartość składowej editmode. Jeśli wartość tej składowej wynosi FALSE, to program nie wykonuje żadnych dodatkowych czynności i wywołuje metodę klasy bazowej umożliwiającą standardową obsługę obydwu komunikatów. Jeśli jednak wartość tej składowej wynosi TRUE, to program pobiera współrzędne położenia wskaźnika myszy w momencie kliknięcia i przekształca je na współrzędne stosowane w programie. Następnie współrzędne te zapisywane są w dokumencie.
Aby maksymalnie uprościć program, konwersja punktów realizowana jest w specjalnej metodzie o nazwie ConvertPoint. Funkcja ta wykonuje za Ciebie całą „brudną robotę", o czym łatwo możesz się przekonać, przeglądając jej kod. Wbrew pozorom to nie przeliczenie współrzędnych za pomocą funkcji MulDiv oraz odpowiednich współczynników skalujących stwarza najwięcej problemów. Prawdziwych kłopotów przysparza określenie wielkości marginesów strony wydruku oraz przeliczenie tych marginesów na pik-sele. Co więcej, musisz także wziąć po uwagę możliwość przewinięcia okna. Na szczęście będziesz mógł skopiować tę metodę bezpośrednio ż przykładu do swojego kodu.
W przykładowym programie wykorzystana została jeszcze jedna chytra sztuczka, dotyczy ona pobierania wskaźnika do obiektu dokumentu. W Rozdziale 1. miałeś okazję zobaczyć, że kreator App Wizard przesłania metodę GetDocument obiektu widoku w taki sposób, iż zwraca ona wskaźnik na dokument typu wykorzystywanego w Twoim programie, a nie wskaźnik na ogólny typ dokumentu - CDocument. Jednakże gdy tworzysz nową klasę widoku za pomocą kreatora Class Wizard, to nie ma on żadnego pojęcia o tym, z jakim dokumentem nowy widok będzie współpracował. Dlatego też podczas tworzenia nowego widoku za pomocą tego kreatora metoda GetDocument nie jest przesłaniana. Jeśli wywołasz tę metodę w nowej klasie widoku, to zostanie zwrócony wskaźnik do obiektu CDocument. W takim wypadku, aby móc skorzystać z metod charakterystycznych dla Twojego dokumentu, będziesz musiał odpowiednio rzutować otrzymany wskaźnik. Oczywiście możesz samemu przesłonić standardową definicję metody GetDocument, takie rozwiązanie zostało zastosowane w przedstawionym przykładzie.
Podsumowanie
Jeśli jesteś podobny do wielu innych programistów, to będziesz odkładał implementowanie drukowania aż do ostatnich etapów powstawania programu. Na szczęście MFC zachęca Cię i umożliwia takie postępowanie, gdyż drukowanie i rysowanie na ekranie są w MFC bardzo ściśle ze sobą związane. Czasami bez obaw możesz powierzyć drukowanie MFC, a samemu zająć się rozwiązywaniem bardziej złożonych problemów.
Jednakże jeśli wymagania stawiane sposobowi drukowanie lub działania podglądu wydruku w Twoim programie są bardziej wymagające, to będziesz musiał dokładnie zrozumieć, jak MFC realizuje drukowanie i tworzy podgląd wydruku. Dzięki informacjom przedstawionym w tym rozdziale będziesz w stanie zmodyfikować sposób drukowania tak, aby dokładnie odpowiadał Twoim wymaganiom.
Praktyczny przewodnik Drukowanie
Zarządzanie oknem dialogowym Drukuj
Skalowanie wydruku
Drukowanie innych elementów
Drukowanie nagłówków i stopek
Modyfikowanie paska narzędzi okna podglądu wydruku
Modyfikowanie działania podglądu wydruku
W przypadku wielu aplikacji jedyną rzeczą, jaką będziesz musiał zrobić, aby opcje drukowania zaczęły działać poprawnie, będzie użycie innego trybu mapowania niż MM_TEXT. Kreator App Wizard dołącza do tworzonych aplikacji prostą obsługę drukowania i wybór jakiegoś „normalnego" trybu mapowania powinien spowodować, że wydruki sporządzane przez program będą automatycznie i poprawnie skalowane.
Zarządzanie oknem dialogowym Drukuj
Pierwszą metodą wywoływaną po rozpoczęciu procesu drukowania jest metoda On-PreparePrinting zdefiniowana w klasie widoku. Jej działanie ogranicza się do wywołania kolejnej metody - DoPreparePrinting. Do metody OnPreparePrinting przekazywany jest obiekt klasy CPrintlnfo, dzięki któremu będziesz w stanie przejąć kontrolę nad informacjami wyświetlanymi w oknie dialogowym Drukuj (czyli, jednocześnie, także nad samym procesem drukowania).
Dwiema najbardziej interesującymi składowymi dostępnymi w klasie CPrintlnfo są: m_pPD (w której przechowywany jest wskaźnik do okna dialogowego CprintDialog wyświetlanego przez MFC) oraz SetMaxPages, która to metoda pozwala Ci na określenie
ilości stron wydruku. . •
Jeśli w ogóle nie będziesz chciał wyświetlać okna dialogowego Drukuj, to przed wywołaniem metody DoPreparePrinting przypisz składowej m_bDirect (klasy CPrintlnfo)
wartość TRUE. W takim wypadku wykonane zostanie zadanie drukowania, podczas którego, bez żadnych interwencji ze strony użytkownika, wydrukowane zostaną wszystkie strony. Podczas drukowania wykorzystana zostanie domyślna drukarka.
Skalowanie wydruku
Jeśli w programie wykorzystujesz tryb mapowania MM_TEXT lub któryś inny jednorodny (isotropic) tryb, to sporządzony wydruk będzie się zazwyczaj różnił od wyglądu ekranu. Po zastanowieniu przyznasz, że nie jest to pozbawione sensu. Rozdzielczość monitora wynosi bowiem około 72 punktów na cal. Dlatego też, jeśli narysujesz linię składającą się z 75 punktów, to będzie ona miała długość około jednego cala (około - gdyż rozdzielczości ekranów nie są dokładne). Jednakże na drukarce o rozdzielczości 300 punktów na cal (dpi), ta sama linia będzie miała długość dokładnie 1/4 cala; na drukarce o rozdzielczości 600 dpi - ta sama linia będzie już miała tylko 1/8 cala długości.
Istnieje kilka sposobów przezwyciężenia tego problemu. Najprostszym z nich jest użycie trybu mapowania niezależnego od wykorzystywanego sprzętu. Zamiast tego można użyć takich trybów mapowania jak: MM_LOENGLISH lub MM_HIMETRIC (patrz Tabela 3.3).
Chociaż nie zawsze jest to możliwe do wykonania, to jednak czasami będziesz musia) przeskalować wydruk, używając przy tym jednego z bardziej popularnych trybów mapowania, Pomysł tego rozwiązania polega na tym, żeby używając trybu MM_ISOTROPIC tak ustawić parametry kontekstu drukarki, aby narysowanie na drukarce linii prostej składającej się z 75 punków spowodowało wydrukowanie linii o długości nieco powyżej jednego cala.
Sposób przeskalowania kontekstu drukarki, tak aby był on zgodny z parametrami ekranu, przedstawiony został na Listingu 3.1. (metoda OnPrint). Oczywiście, może się zdarzyć, że będziesz chciał postąpić w nieco inny sposób. Na przykład mógłbyś chcieć, aby element na wydruku był dokładnie dwa razy większy od tego samego elementu na ekranie. Mo i żesz to osiągnąć bez jakichkolwiek większych problemów poprzez odpowiedni współczynników skalowania.
Drukowanie innych elementów
Generalnie rzecz biorąc, MFC zakłada, że będziesz chciał drukować te same elementy, które są widoczne na ekranie. Zazwyczaj jest to całkiem słuszne założenie; mogą się jednak zdarzyć takie przypadki, kiedy na wydruku będziesz chciał umieścić zupełnie co innego. Załóżmy, że częścią Twojego widoku jest formularz służący do wprowadzania danych. Formularz ten prezentuje pojedynczy rekord z bazy danych. Podczas drukowania danych takiej aplikacji nie będziesz chciał wydrukować tylko jednego - aktualnego rekordu; zapewne będziesz chciał sporządzić tabelaryczny wydruk wszystkich rekordów zapisanych w bazie.
Taka modyfikacja sposobu drukowania jest wyjątkowo prosta. Wystarczy przesłonić standardową definicję metody OnPrint. Domyślna definicja tej metody powoduje wywołanie metody OnDraw, jednakże możesz utworzyć swoją własną wersję tej metody, która umożliwi Ci wydrukowanie dowolnych informacji pod dowolną postacią. Rozwiązanie to jcsi szczególnie istotne w przypadku tworzenia programów, w których widok jest klasą potomną klasy CFormYiew. Wynika to z faktu, iż widoki takie nie mają możliwości wydrukowania swojej zawartości.
Drukowanie nagłówków i stopek
Kolejnym powodem, dla którego mógłbyś chcieć przesłonić standardową definicję metody OnPrint, jest konieczność lub chęć umieszczenia na wydruku dodatkowych elementów. Doskonałym przykładem takiego wykorzystania metody OnPrint jest drukowanie nagłówków i stopek, które nie są widoczne na ekranie.
Przykład takiego zastosowania metody OnPrint możesz znaleźć na Listingu 3.1. Bardzo istotną rzeczą, o której nie można zapomnieć podczas drukowania nagłówków i stopek, jest konieczność określenia odpowiedniej wielkości regionu przycinania (w celu ochronienia stopki) oraz współrzędnych początku układu współrzędnych (w celu ochronienia nagłówka). Jeśli uważasz, że powyższe rozwiązanie wymaga zbyt wiele pracy, to zawsze będziesz mógł umieścić kod tworzący nagłówki i stopki wydruku wewnątrz metody OnDraw.
Modyfikowanie paska narzędzi okna podglądu wydruku
Jeśli będziesz chciał zmodyfikować postać standardowego paska narzędzi wyświetlanego w oknie podglądu wydruku, to będziesz musiał przesłonić standardową definicję metody OnFilePrintPreview. Poniżej przedstawione zostały czynności, który będziesz musiał wykonać:
1. Umieścić makro ON_COMMAND zawierające metodę OnFilePrintPreview
wewnątrz części mapy komunikatów zarządzanej przez kreatora Class Wizard.
2. Z wywołania metody OnFilePrintPreyiew usunąć modyfikator zawierający nazwę klasy bazowej (CView).
3. Dodać własną definicję metody OnFilePrintPreview (do pliku nagłówkowego - H i źródłowego - CPP).
4. Wewnątrz metody OnFilePrintPreview stworzyć na stercie (za pomocą operatora New) nowy obiekt klasy CPrintPreviewState.
5. Wywołać metodę DoPrintPreyiew, przekazując jako argumenty jej wywołania następujące dane: identyfikator zasobu określającego postać paska narzędzi, wskaźnik RUNTIME_CLASS(CPrintView) oraz obiekt, który stworzyłeś w poprzednim kroku.
6. Jeśli metoda DoPrintPreview zwróci wartość FALSE, to powinieneś wyświetlić komunikat o błędzie i usunąć obiekt stworzony w kroku 4.
7. Upewnić się, że do pliku źródłowego Twojego nowego widoku dołączyłeś plik nagłówkowy AFXPRIV.H.
8. Stworzyć zasób określający postać paska narzędzi i nadać mu ten sam identyfikator, którego użyłeś w kroku 5. (określanie postaci paska narzędzi możesz rozpocząć od skopiowania oryginalnego paska, który możesz znaleźć w pliku AFXPRINT.RC znajdującym się w kartotece MFCMNCLUDE).
Gdy zakończysz pracę, to Twoja mapa komunikatów powinna mieć następującą postać:
BEGIN_MESSAGE_MAP (CConndotYiew, CView)
// { {AFX_MSG_MAP (CConndotView)
ON_WM_LBUTTONDOWN ( )
ON_WM_RBUTTONDOWN ( )
ON_COMMAND ( ID_FILE_PRINT_PREVIEW, OnFilePrintPreview)
//}}AFX_MSG_MAP
//Standard printing corranents
ON_COMMAND(ID„FILE_PRINT, CView: : OnFilePrint )
ON_COMMAND(ID„FILE_PRINT_DIRECT, CView: : OnFilePrint ) END_MESSAGE_MAP ( )
Poniżej przedstawiona została postać Twojej wersji metody OnFilePrintPreview:
void CMyYiew: : OnFilePrintPreview ( ) {
CPrintPreviewState* pState = new CPrintPreviewState; if ( !DoPrintPreview(AFX_IDD_PREVIEW_TOOLBAR, this,
RUNTIME_CLASS ( CPreviewView) , pState) ) {
// domyślny komunikat o błędzie Af xMessageBox (AFX_IDP_COMMAND_FAILURE) ; delete pState;
Kompletny przykład prezentujący opisaną tutaj metodę modyfikowania paska narzędzi okna podglądu wydruku możesz znaleźć na Listingu 3.2.
Modyfikowanie działania podglądu wydruku
Opisane powyżej czynności służące do zmodyfikowania postaci paska narzędzi okna podglądu wydruku, mogą zostać użyte także do całkowitego zmienienia wyglądu oraz sposobu działania okna podglądu. Jednakże w takim przypadku będziesz musiał utworzyć swoją własną klasę, wyprowadzoną z klasy CPrevłewView i użyć jej nazwy jako trzeciego argumentu wywołania metody DoPrintPreview. Przykład zastosowania tej metody przedstawiony został na Listingu 3.3.