15 Rysowanie w kontekście urządzenia


CzęŚĆ IV
Tworzenie grafiki


Rozdział 15
Rysowanie w kontekście urządzenia
Kontekst urządzenia i jego użycie
Ustalanie i stosowanie trybów rysowania i możliwości urządzeń
Stosowanie trybów odwzorowywania podczas rysowania przy użyciu cali
lub centymetrów
Wprowadzenie do pojęcia kontekstu urządzenia
Nie dajmy się zwieść określeniu kontekst urządzenia. Jest to jeden z technicznie
brzmiących terminów, określających prosty mechanizm, stanowiący centralny punkt systemu
Windows. Jak wyglądałby ten system, gdyby nie kontekst urządzenia? Nie wiadomo. Kontekst
to swego rodzaju płótno, na którym rysowane są wszystkie punkty, linie, kwadraty, czcionki,
kolory oraz wszystko to, co widzimy. Urządzenie oznacza zaś, że możemy rysować na ekranie,
drukarce, ploterze lub jakimkolwiek innym urządzeniu wytwarzającym dwuwymiarowy obraz,
bez głębszej wiedzy na temat sprzętu, z którego korzystamy.
Struktury kontekstu urządzenia
Kontekst urządzenia jest strukturą Windows, w której przechowywane są atrybuty domyślnych
ustawień używanych podczas wykonywania operacji graficznych, takich jak chociażby rysowanie linii.
Odmiennie niż w przypadku większości struktur Windows, program może nie mieć bezpośredniego
dostępu do struktury kontekstu urządzenia. Ustawienia mogą być zmieniane jedynie poprzez zestaw
standardowych funkcji dostępu.
Jedną z najważniejszych rzeczy, jakie Microsoft dał przemysłowi programistycznemu, jest
standaryzacja wsparcia dla urządzeń różnego typu, zawarta w systemie Windows. Wraz z
rozpędem, którego nabrał przemysł informatyczny, aplikacje graficzne powstają


360_____________________________________Poznaj Visual C++ 6
w wielkiej obfitości. Jest to najprawdopodobniej najważniejszy i najbardziej niedoceniany rezultat
powstania systemu Windows.
Możemy pójść do sklepu z komputerami, kupić najnowszą drukarkę firmy XYZ, zainstalować w
systemie jeden jedyny sterownik dostarczony przez producenta, a każdy punkt utworzony w aplikacji,
zostanie natychmiast przeniesiony na drukarkę. Kontekst urządzenia jest tym, co łączy i wspiera proces
rysowania.
PATRZ TAKŻE
O rysowaniu w kontekście urządzenia czytaj w rozdziale 16. 4 Jak używać kontekstu
urządzenia dla drukarki dowiesz się w rozdziale 22.
Graficzne biblioteki Windows
Kod systemu operacyjnego odpowiedzialny za przeprowadzanie operacji graficznych
zaimplementowany jest w wyniku interakcji dwóch bibliotek DLL. Pierwszą z nich jest GDI.DLL,
która dostarcza programom zestawu funkcji graficznych, niezależnych od urządzenia. Drugą biblioteką
jest biblioteka DLL zależna od urządzenia, która użyta jest w odniesieniu do monitora, drukarki lub
plotera. Na przykład, podczas rysowania na zwykłym monitorze VGA, używana jest biblioteka
VGA.DLL dostarczająca kodu implementacyjnego właściwych funkcji rysujących.
Typy kontekstów urządzenia
Istnieją konteksty urządzeń dla specjalnych zastosowań i określonych zadań. Są one obiektami GDI
(Graphical Device Interface, interfejs urządzeń graficznych) i stanowią zesiaw funkcji umieszczonych w
bibliotece DLL, w samym sercu systemu. Funkcje te zapewniają łączność pomiędzy wywoływanymi
funkcjami graficznymi, a sterownikami urządzeń, które powodują wyświetlenie obrazu lub jego wydruk.
Biblioteka MFC dostarcza klas osłonowych dla kontekstów urządzeń, które upraszczają interakcje z
obiektami GDI. Zasadniczą klasą kontekstu urządzenia jest klasa CDC. Klasa ta dostarcza ogromną
liczbę funkcji rysujących, odwzorowujących współrzędne i wycinających. Wszystkie pozostałe, bardziej
wyspecjalizowane klasy są z niej wyprowadzane i ją poszerzają. Kolejne punkty rozdziału będą
poświęcone właśnie tym klasom.
Stosowanie klasy CDC
Klasą kontekstu urządzenia, którą będziemy stosować zawsze jako klasę bazową, jest klasa CDC.
Klasa ta przechowuje uchwyty dwóch obiektów GDI; m_hDC oraz m_hAttri-bDC. m_hoc jest uchwytem
kontekstu urządzenia łączącym klasę z obiektem GDI w celu obsługi danych wyjściowych z funkcji
rysujących. m_hAttribDC natomiast jest uchwy-


Rysowanie w kontekście urządzenia 361
tem, który łączy wszelkie informacje o atrybutach, takie jak kolor czy tryb rysowania. Nie musimy
zanadto zawracać sobie głowy tymi atrybutami. Musimy jednak pamiętać, iż korzystając z funkcji
GetDCO lub ReleaseDCO (służących do pobierania i usuwania kontekstu urządzenia z okna), uchwyty te
są przyłączane i odłączane od klasy CDC.
Zdolność klasy CDC do przyłączania i rysowania w kontekście urządzenia może zostać
zademonstrowana na prostym przykładzie. Każde okno posiada własny kontekst pokrywający jego obszar
roboczy, włączając w to pulpit pokrywający cały ekran. Do obecnej chwili zajmowaliśmy się
aplikacjami, które zachowywały się bardzo zdyscyplinowanie. Teraz jednak postąpimy bardziej
brawurowo, by zaprezentować moc i łatwość użycia kontekstu urządzenia. Uchwycimy kontekst
urządzenia pulpitu i będziemy na nim bezpośrednio rysować. Jedyną szkodą, jaką możemy w ten sposób
wyrządzić, jest chwilowe zaśmiecenie pulpitu, który jednakże zostanie odświeżony po zamknięciu
programu.
Przeprowadzając poniższą procedurę, utworzymy aplikację opartą o okno dialogowe, chwytającą
kontekst urządzenia.
Funkcje rysujące CDC
Klasa CDC implementuje wszystkie dostępne w Windows funkcje rysujące. Każda z tych implementacji
wywołuje korespondującą funkcję rysującą Win32 niższego poziomu (w GDI.DLL), przekazując
obiektowi CDC uchwyt osadzonego kontekstu urządzenia (m_hDC lub m_hAttribDC).
Tworzenie aplikacji chwytającej kontekst urządzenia
1. Kliknij pozycję New z menu File.
2. W oknie dialogowym New wybierz kartę Projects, wyświetlającą typy projektów.
3. Wybierz MFC AppWizard i wpisz nazwę projektu DCDraw.
4. Kliknij OK, otwierając w ten sposób okno dialogowe l kroku AppWizard.
5. Spośród typów aplikacji wybierz Dialog Based Application.
6. Kliknij Finish, po czym wyświetlone zostaną informacje dotyczące szkieletu aplikacji.
7. Kliknij OK, a AppWizard wygeneruje konieczne pliki należące do projektu.
Mamy zatem szkielet aplikacji utworzonej w oparciu o architekturę okna dialogowego. Dodamy
teraz zwykły przycisk oraz funkcję obsługi kliknięcia, by zaimplementować kod chwytający kontekst
urządzenia.


362 Poznaj Visual C++ 6
Plik ReadMe.txt generowany przez AppWizard
AppWizard generuje plik ReadMe.txt umieszczony w katalogu nowego projektu. Ten plik
tekstowy objaśnia zadania każdego z utworzonych modułów źródłowych, wykonywane w
aplikacji.
Utworzenie przycisku
1. Otwórz panel ResourceView w widoku Project Workspace.
2. Klinij znak plusa, by rozwinąć drzewo zasobów projektu.
3. Kliknij znak plusa obok pozycji Dialog otwierając okno dialogowe zasobów.
4. Kliknij dwukrotnie IDD_DCDRAW_DIALOG, co rozpocznie edycję głównego okna
dialogowego aplikacji.
5. Kliknij widoczny pośrodku napis TODO: Place Dialog Controls Herę, a następnie usuń
go, używając klawisza Delete.
6. Z paska narzędziowego Controls wybierz przycisk i umieść go na szablonie okna, a
następnie powiększ jego wymiary.
7. Naciśnij Alt+Enter aby wyświetlić okno dialogowe właściwości przycisku. Wpisz w polu
Caption nagłówek: Draw!.
8. Zmień identyfikator przycisku na IDC_DRAWIT.
9. Naciśnij Enter, by zamknąć okno właściwości.
Mając już w oknie umieszczony przycisk, musimy utworzyć funkcję obsługi zdarzenia
kliknięcia tego przycisku.
Utworzenie za pomocą CIassWizard funkcji obsługi BN_CLICKED
1. Kliknij przycisk umieszczony w oknie dialogowym.
2. Naciśnij Ctri+W uruchamiając w ten sposób CIassWizard.
3. Powinna zostać otwarta karta MFC CIassWizard Message Maps. Powinna być również
zaznaczona pozycja IDC_DRAWIT. Jeśli tak nie jest, wybierz tę pozycję z listy Object
IDs.
4. Kliknij dwukrotnie komunikat BN_CLICKED z listy Messages, czym spowodujesz
dodanie nowej funkcji obsługi.
5. Kliknij OK w oknie dialogowym Add Member Function, przez co zatwierdzisz nazwę
funkcji: OnDrawit.
6. Kliknij przycisk Edit Code, po czym możesz przystąpić do edycji kodu obsługującego
kliknięcie przycisku.


Rysowanie w kontekście urządzenia 363
Możemy teraz zaimplementować kod chwytający kontekst urządzenia i rysujący w nim,
wewnątrz funkcji obsługi kliknięcia przycisku, wzorując się na listingu 15.1.
Skrót dodający funkcję obsługi komunikatu BN_CLICKED
Szybszym sposobem dodania tej funkcji obsługi jest dwukrotne kliknięcie przycisku w
edytorze zasobów. Powoduje to dodanie kodu funkcji i pozycji mapy komunikatów lecz
pomija pierwsze cztery kroki kreatora CIassWizard i otwiera kod w oknie edytora.
Listing 15.1. LST16_1.CPP - rysowanie w kontekście urządzenia pulpitu
1 void CDCDrawDlg::OnDrawit()
2 {
3 // TODO: Dodaj kod obsługi ...
4
5 // ** Pobranie wskaźnika pulpitu
6 'CWnd* pDeskTop = GetDesktopWindow(); O
7
8 // ** Pobranie wskaźnika do kontekstu urządzenia
9 CDC* pDC = pDeskTop->GetWindowDC();
10
11 // ** Pętla poprzez 300 pikseli w poziomie
12 for(int x=0;x<300;x++)
13 (
14 // ** Pętla przez 300 pikseli w pionie
15 for(int y=0;y<300;y++)
16 {
17 // ** Ustalenie odmiennego koloru dla każdego piksela
18 pDC->SetPixel(x,y,x*y);
19 )
20 }
21 pDeskTop->ReleaseDC(pDC);
22 }
O W tej linii ustalone zostaje okno pulpitu (lub cały ekran). Ta
linia odnajduje kontekst urządzenia pulpitu.
Każdy piksel wewnątrz wyznaczonego prostokąta przyjmuje inny kolor, wyliczony w
oparciu o współrzędne.


364 Poznaj Visual C++ 6
Na powyższym listingu w linii 6 pobraliśmy wskaźnik do okna głównego pulpitu Windows, stosując
w tym celu funkcję GetDesktopWindow(). Z tego wskaźnika cwnd możemy uchwycić kontekst
urządzenia okna głównego używając funkcji GetWindowDC (), co widać w linii 9. Linie od 12 do 20 są
odpowiedzialne za utworzenie efektu artystycznego, widocznego na rysunku 15.1. Linie od 9 do 12
powtórzone są w pętli for przez 300 pikseli w poprzek oraz 300 pikseli w dół dla współrzędnych x oraz y.
W wywoływanej dla każdego piksela funkcji SetPixel () pierwsze dwa parametry stanowią jego
współrzędne (piksel O, O leży w lewym górnym rogu ekranu). Trzecim parametrem jest kolor piksela
wyrażony jako odwołanie (omówione szerzej w rozdziale 16 "Stosowanie piór i pędzli"). Kolor ten
zależy od współrzędnych x i y danego piksela, w wyniku czego otrzymujemy zaskakująco piękny,
kolorowy efekt określany mianem moire (mora). Na koniec musimy zwolnić kontekst urządzenia,
stosując funkcję ReleaseDC (), wywołaną linii 21.
Zjawisko mory
Mora powstaje w wyniku nakładania się dwóch wzorów o podobnym układzie i miejscach
przezroczystych. Zostało to zaobserwowane w bardzo cienkiej, mokrej tkaninie jedwabnej o nazwie
moire, stąd nazwa zjawiska. Podobny efekt można uzyskać obracając względem siebie dwa kawałki
tkaniny. Interferencja taka stanowi podstawę holografii.
:jgTx|
^^^^JliBiŁ. ^ . , . ^
|lS S? qit|GelDesktopWindow~^ m w aplfiPIDlg::OnDrawit()


^OnDrawitOl OnInitDialogt)
j ; (iOnPaint() j ; t
OnQueryDraglco | : ?
OnSysCommand j \
t/m_h\con l B6i_l Globals
g/ .. ,3,t -. .

.S ,-.t-.;n" r-.-: i-'^'.* rin-.-J.-
;-.-.:- .'.



l/ 3t,:



^^^

CDC pDC



;^!





Cancef







-'' ** l ;'.':





Eoi-(int x.

Draw! l



{

l



/ *.





for (ii





{





pDC-!.SetPixel (!i.y,i:*y);


pDBskTop->ReleaseDC (pDC);
Rysunek 15.1. Uchwycenie i rysowanie w kontekście urządzenia pulpitu


Rysowanie w kontekście urządzenia 365
Ponieważ program DCDraw jest tak nonszalancki w działaniu, rysując bezpośrednio na pulpicie,
musimy naprawić tego skutki i oczyścić pulpit. Za pomocą funkcji obsługi OnDestroyO możemy
przechwycić komunikat Windows WM_DESTROY. Funkcja OnDe-stroyO wywoływana jest w momencie
destrukcji okna dialogowego, w związku z tym jest dobrym miejscem na umieszczenie kodu
czyszczącego.
Dodanie funkcji obsługi komunikatu WM_DESTROY
1. Otwórz panel ClassView w Project Workspace.
2. Rozwiń drzewo klas DCDraw, klikając znak plusa.
3. Kliknij prawym klawiszem pozycję CDCDrawDlg, wywołując w ten sposób menu skrótów.
4. Wybierz opcję Add Windows Message Handler.
5. Wybierz komunikat WM_DESTROY z listy New Windows Messages/Events.
6. Kliknij przycisk Add and Edit, dodając tym samym funkcję obsługi do klasy CDCDrawDlg i
przechodząc do edycji implementacji.
Teraz możemy dodać kod oczyszczający pulpit do funkcji obsługi OnDestroyO. Aby uzyskać
wyczyszczenie pulpitu, musimy nakazać oknu pulpitu odświeżenie oraz wydanie rozkazu odświeżenia
zawartości wszystkim oknom potomnym (aplikacjom). Kod wykonujący to zadanie zawarty jest na
listingu 15.2. W linii 8 funkcja składowa okna pulpitu RedrawWin-dow () (odnalezionego przez
wywołanie funkcji GetDesktopWindow ()) wymusza odświeżenie zawartości pulpitu. Funkcji tej
przekazane są trzy parametry. Pierwsze dwa są wskazaniami odświeżanego prostokąta i obszaru. Na
podstawie przekazanych w obu parametrach wartości NULL, funkcja RedrawWindow () słusznie zakłada,
iż odświeżony ma być cały obszar pulpitu. Trzeci parametr składa się z kilku znaczników: RDW_ERASE
(wymazujący wszystko), RDW_INVALIDATE (nakazujący odświeżenie), RDW_ALLCHILDREN (nakazujący
odświeżenie okien wszystkim aplikacjom) oraz RDW_ERASENOW (nakazujący natychmiastowe
wykonanie).
Listing 15.2. LST16_2.CPP - odświeżanie pulpitu i okien wszystkich aplikacji przez funkcję
RedrawWindow()
1 void CDCDrawDlg::OnDestroy()
2 {
3 CDialog::OnDestroy();
4
5 // TODO: Add your message handler code here
6
7 // ** Odświeżenie pulpitu i wszystkich okien potomnych
8 GetDesktopWindow()->RedrawWindow(NULL,NULL, O
9 RDW_ERASE+RDW_INVALIDATE+
10 RDW_ALLCHILDREN+RDW_ERASENOW) ;
11 }


366 Poznaj Visual C++ 6
O Wymuszenie odświeżenia zawartości całego pulpitu Windows oraz okien wszystkich
aplikacji
Po skompilowaniu i uruchomieniu programu z uwzględnieniem powyższych zmian
otrzymamy kolorowy prostokąt, jak na rysunku 15.1. Oczywiście klasa kontekstu urządzenia
zawiera o wiele więcej funkcji rysujących. Są one dalece bardziej wyrafinowane niż SetPixel
(). Jednak istnieje zbyt wiele złożonych tematów, by opisywać je w tym miejscu. Uczynimy to
w szerokim zakresie w rozdziałach 16 i 17.
PATRZ TAKŻE
O rysowaniu z wykorzystaniem piór i pędzli czytaj w rozdziale 16. O pisaniu
tekstu w kontekście urządzenia czytaj w rozdziale 17.
Stosowanie kontekstu urządzenia Client
Klasa cćlientDC automatyzuje użycie funkcji GetDC oraz ReleaseDCO. Podczas
konstruowania obiektu cćlientDC przekazujemy wskaźnik okna jako parametr, a klasa używa
tego wskaźnika do przyłączenia kontekstu urządzenia okna. Podczas destrukcji klasy
wywołuje ona automatycznie funkcję ReleaseDCO, nie musimy więc zawracać sobie tym
głowy. Client odwołuje się do obszaru roboczego okna. Istnieje natomiast korespondująca
klasa cwindowDC, umożliwiająca dostęp zarówno do paska tytułowego, jak i ramki okna.
Stosuje się ją jednak rzadko, gdyż "przyzwoite" aplikacje działają tylko w obszarze klienckim
(roboczym).
Zmienimy teraz projekt, by zastąpić wskaźnik CDC obiektem cćlientDC. Jeśli klik-niemy
dwukrotnie pozycję funkcji OnDrawit () w panelu ClassView, edytor wykona skok
bezpośrednio do kodu funkcji obsługi przycisku.
Wprowadzimy zmiany w funkcji OnDrawit () według listingu 15.3. Spowoduje to użycie
jako pola rysunkowego kontekstu urządzenia okna dialogowego, zamiast okna pulpitu. Aby to
osiągnąć, przekazujemy obiektowi cćlientDC, dIgDC wskaźnik this (do przechowującej go
klasy) do własnego okna dialogowego. Ponieważ okno dialogowe jest formą cwnd, obiekt
dIgDC zawiera obecnie uchwyt kontekstu urządzenia tego okna. W liniach 9 - 17 następuje
procedura rysowania jak poprzednio, z jedną różnicą; w linii 15 bowiem obiekt dIgDC stosuje
wywołanie obiektu zamiast wskaźnika do SetPixel () .Warto również zauważyć, iż na końcu
funkcji OnDrawit () nie zostaje wywołana ReleaseDC (). Zamiast tego, uchwyt kontekstu
urządzenia będzie automatycznie zwalniany przez destruktor cćlientDC w chwili destrukcji
obiektu dIgDC, na zakończenie działania funkcji.


Rysowanie w kontekście urządzenia 367
Listing 15.3. LST16_3.CPP - zastosowanie klasy CCIientDC do uchwycenia kontekstu urządzenia okna
dialogowego
1 void CDCDrawDlg::OnDrawit()
2 (
3 // TODO: Add your control notification handler code
4
5 // ** Skonstruuj DC klienta z okna dialogowego
6 CCIientDC dlgDC(this); O
7
8 // loop through 300 pixels horizontally
9 for(int x=0;x<300;x++)
10 (
11 // loop through 300 pixels vertically
12 for(int y=0;y<300;y++)
13 (
14 // ** set each pixel to a different color
15 dlgDC.SetPixel(x,y,x*y);
16 }
17 }
18 }
O Skonstruuj kontekst urządzenia, aby użyć prawidłowego obszaru klienta w oknie
dialogowym.
Po skompilowaniu aplikacji z wprowadzonymi zmianami i jej uruchomieniu zobaczymy,
że rysowanie odbywa się tylko w kontekście urządzenia okna dialogowego, kiedy użyty
zostanie przycisk Draw!, co widać na rysunku 15.2.
Wymuszanie odświeżania okna
Komunikat WM_PAINT jest zwykle wysyłany wtedy, gdy pewna część okna wymaga uaktualnienia.
Jednakże stosując funkcję Redrawwindow () klasy CWnd można wymusić wysłanie do okna
komunikatu WM_PAINT. Parametry tej funkcji określają obszar, który ma ulec odświeżeniu, a także
decydują, czy ma nastąpić wymazanie tła.
Stosowanie kontekstu urządzenia Paint
Klasa cpaintDC jest specjalną klasą osłonową kontekstu urządzenia, która wspomaga
obsługę komunikatu WM_PAINT. Komunikat WM_PAINT wysłany zostaje do okien, gdy


368 Poznaj Visual C++ 6
część lub całość ich obszaru jest odsłonięta przez inne okno. Nakazuje on aplikacji odma-
lowanie odsłoniętego obszaru.

Rysunek 15.2. Przechwycenie kontekstu urządzenia okna dialogowego przez CCIientDC
Zamiast odmalowywania całości okna za każdym razem, gdy jego część zostanie od-
słonięta, Windows przekazuje informacje o współrzędnych odsłoniętego kawałka. Możemy
użyć tych informacji do odmalowania tylko tej części okna, oszczędzając w ten sposób cenny
czas procesora, który byłby stracony na wykonywanie czynności dla użytkownika
niezauważalnych. Jeśli próbowalibyśmy rysować poza wyznaczonym prostokątem, nie stałoby
się nic, ponieważ kontekst urządzenia powoduje również okrajanie obszaru roboczego do
obszaru właśnie tego prostokąta. Jednakże całkowite poleganie na procedurze obcinania nie
jest mądrym posunięciem, gdyż cały kod rysujący będzie wykonywany non stop, spowalniając
w znacznym stopniu działanie całego komputera. Czasami jednak jest to nieuniknione -
odrysowując złożony tekst lub diagram, zbyt trudno ocenić, w którym miejscu przerwać tekst
lub linię. W tej sytuacji możemy pozwolić Windows zając się obcinaniem.
Próbną aplikację możemy przekształcić w ten sposób, by rysowanie odbywało się z
funkcji OnPaint () i tylko na odsłoniętym obszarze. Klikamy zatem dwukrotnie pozycję
funkcji OnPaint () na panelu ClassView. Przechodzimy w ten sposób do edycji tej funkcji.
Zauważmy, iż nie musimy tworzyć tej funkcji poprzez CIassWizard. Zawdzięczamy to
kreatorowi AppWizard, który automatycznie umieścił jej kod w szkielecie aplikacji. Treść
funkcji widoczna jest na listingu 15.4. Linię 24 wywołującą funkcję OnDrawit (), musimy
jednak dopisać sami.
Na listingu znajdujemy również inne ciekawostki utworzone przez AppWizard. W linii 3
wywołana zostaje funkcja lslconic() zwracająca TRUE, gdy okno aplikacji jest
zminimalizowane (czyli wyświetlone na pasku zadań). Jeśli tak jest, ikona aplikacji zostaje
umieszczona wewnątrz prostokąta wyświetlonego w pasku zadań poprzez linię 18. Możemy
użyć wartości FALSE w celu zaimplementowania kodu OnPaint () i wywołania OnDrawit () w
linii 24.


Rysowanie w kontekście urządzenia______________________ __ 369
Listing 15.4. LST16_4.CPP - modyfikacja funkcji OnPaint ()
1 void CDCDrawDlg::OnPaint()
2 {
3 if (IsIconicO)
4 {
5 CPaintDC dc(this); // kontekst urządzenia do rysowania
6
7 SendMessage(WM_ICONERASEBKGND, O (WPARAM) dc.GetSafeHdcO, 0) ;
8
9 // Wy środkowa n i e ikony w prostokącie klienta
10 int cxlcon = GetSystemMetrics(SM_CXICON) ;
11 int cylcon = GetSystemMetrics(SM_CYICON);
12 CRect rect;
13 GetCIientRect(Srect);
14 int x = (rect.WidthO - cxlcon + l) / 2;
15 int y = (rect.Height() - cylcon + l) / 2;
16
17 // Narysowanie ikony
18 dc.DrawIcon(x, y, m_hlcon); @
19
20 }
21 else
22 (
23 // ** Wywołanie funkcji rysującej
24 OnDrawit();
25 }
26 }
O Usunięcie tła zminimalizowanego okna
Narysowanie ikony pośrodku zminimalizowanego okna
Dodatkowa linia, wywołująca funkcję wypełniającą
Wprowadźmy teraz zmiany do funkcji OnDrawitO, które spowodują użycie CPa-intDC.
Zmiany te ukazane są na listingu 15.5. Zauważmy, iż w linii 6 klasa CPaintDC użyta jest do
skonstruowania obiektu paintDC z okna dialogowego. Mechanizm pracuje poprawnie jedynie
wtedy, gdy okno odpowiada na komunikat WM_PAINT. W tym przypadku wywołujemy tę
funkcję z OnPaint (), wszystko zatem jest w porządku. Ponieważ jest to wciąż kod obsługi
przycisku, możemy go wywoływać klikając przycisk Drawit, lecz


370_____________________________________Poznaj Visual C++ 6
obecnie nie wywoła to żadnego efektu, gdyż okno nie posiada legalnego obszaru - jest całkowicie odcięte
od funkcji rysującej.
Następna zmiana tkwi w linii 9, zostaje zadeklarowany wskaźnik RECT i ustawiony na adres
prostokąta rcPaint, po czym zapisany w składowej obiektu paintDC. Prostokąt ten zawiera obszar do
odmalowania i użyty zostaje w liniach 12 i 15, w związku z czym pętla wywoływana jest tylko dla
prostokąta nieważnego. Podnosi to znacząco prędkość rysowania, gdyż tylko niewielka część okna
wymaga odmalowania. SetPixel () spowalnia działanie aplikacji, gdy jest wywoływana wielokrotnie.
Obszar o wymiarach 300x300 pikseli wywołuje tę funkcję 90 000 razy! Jeżeli natomiast odsłonięty obszar
ma wymiary 30x50 pikseli, SetPiksel () będzie wywoływana jedynie 1500 razy, co oznacza 60-krotne
skrócenie procesu. Jest więc o co walczyć.
Zauważmy jeszcze jedną wprowadzoną zmianę. Otóż SetPiksel () jest obecnie wywoływana z
obiektu paintDC w linii 18. Funkcja zaimplementowana jest w klasie CDC, w związku z czym wszystkie
funkcje rysujące dostępne są ze wszystkich wyspecjalizowanych wyprowadzanych klas kontekstu
urządzenia.
Listing 15.5. LST16_5.CPP - użycie klasy CPaintDC do obsługi żądania WM_PAINT
l

void CDCDrawDlg::OnDrawit()

2

(

3

// TODO: Add your control notification handler code

4



5

// ** Skonstruowanie kontekstu urządzenia okna dialogowego

6

CPaintDC paintDC(this);

7



8

// ** Utworzenie wskaźnika prostokąta

9

RECT* pRect = SpaintDC.m ps.rcPaint;

10



11

// ** Pętla wykonywana w liniach poziomych

12

for(int x=pRect->left; x < pRect->right ;x++)

13

{

14

// ** Pęt-Za wykonywana w liniach pionowych

15

for(int y=pRect->top; y < pRect->bottom ;y++)

16

(

17

// ** Nadanie każdemu pikselowi innego koloru

18

paintDC.SetPixel(x,y,x*y) ;

19

}

20

l

21

}

Wygenerujemy i uruchomimy aplikację z wprowadzonymi zmianami i zaobserwujmy zachowanie się
kontekstu urządzenia. Najpierw powinniśmy ujrzeć okno dialogowe z wielokolorowym tłem i
widocznymi przyciskami. Podczas wyświetlania okna wysłany


Rysowanie w kontekście urządzenia 371
zostaje komunikat WM_PAINT w celu całkowitego narysowania okna, następnie zaś rysowane są
przyciski, przesłaniające wymalowany pod nimi obszar. By zaobserwować działanie tego komunikatu w
praktyce, przesłonimy połowę okna dialogowego innym oknem (na przykład Kalkulatora Windows).
Uruchomić go możemy klikając przycisk Start, a następnie przechodząc przez Programy do karty
Akcesoria, skąd wybieramy Kalkulator. Efekt działania widać na rysunku 15.3.

Rysunek 15.3. Eksperymentowanie z komunikatem WM_PAINT poprzez odsłanianie okna
Gdy ustawimy okno Kalkulatora tak, by przesłaniało w połowie okno dialogowe DCDraw,
kukniemy wewnątrz okna dialogowego, by wysunąć je na pierwszy plan. Zobaczymy, iż odsłonięta jego
część została wypełniona. Zajmuje to bardzo krótką chwilę. Teraz ponownie klikamy Kalkulator i
przesuwamy go nieco w lewo i w górę. Kolejna odsłonięta część okna dialogowego powinna zostać
wypełniona trochę szybciej. Wykonując kilkakrotnie te czynności zauważymy, iż wypełnianie odbywa
się coraz szybciej. Gdy Kalkulator będzie przesłaniał już tylko niewielki skrawek okna dialogowego w
jego górnym lewym rogu, klikamy ponownie okno dialogowe i próbujemy zaobserwować, jak długo trwa
jego odświeżanie - zdecydowanie szybciej. Dzieje się tak, ponieważ odsłonięty obszar jest bardzo mały i
odświeżaniu ulega Jedynie ten kawałek. Funkcja SetPiksel () wywoływana jest mniej intensywnie,
dlatego cały proces przebiega o wiele szybciej. Możemy eksperymentować dalej przesuwając okna
względem siebie, obserwując działanie komunikatu WM_PAINT. Na komunikat ten reagować muszą
wszystkie aplikacje Windows, lecz ponieważ najczęściej zapewniają to klasy widoku lub obiektu
sterującego, nie musimy się tym przejmować.
Przypomnijmy sobie teraz składową m_ps klasy cpaintDC. Związana jest ze strukturą PAINTSTRUCT
opisującą prostokąt rcPaint i określającą obszar wymagający odmalowania. Struktura ta zawiera kilka
użytecznych składowych.
Definicja struktury PAINTSTRUCT wygląda następująco:
typedef struct tagPAINTSTRUCT { HDC hdc ;


372_____________________________________Poznaj Visual C++ 6
BOOL f E ras e ;
RECT rcPaint ;
BOOL fRestorę ;
BOOL fIncUpdate ;
BYTE rgbReserved[16] ;
} PAINTSTRUCT ;
Składowa hdc jest uchwytem podstawowego kontekstu urządzenia GDI. Znajdujemy tu również
znacznik fErase, którego wartość Windows ustawia jako TRUE, gdy zachodzi potrzeba wymazania tła
przed rozpoczęciem rysowania. Ze składową rcPaint spotkaliśmy się na listingu 15.5. Ostatnie trzy
składowe określone w dokumentacji Microsoftu są jako zarezerwowane przez Windows, w związku z
tym najlepiej zostawić je w spokoju.
Zastanawiać nas może, skąd pochodzą te wszystkie informacje, które przekazywane są obiektowi
CPaintDC. Odpowiedzią jest funkcja BeginPaintO klasy CWnd. Pamiętajmy, iż klasa CDialog, jak
większość klas obiektów sterujących i widoków, wyprowadzona jest właśnie z klasy CWnd. Dlatego też
cała funkcjonalność okna dostępna jest dla tych klas i wszystkie one stosują funkcję BeginPaint () dla
powołania struktury PAINTSTRUCT podczas deklarowania obiektu CPaintDC. Gdy obiekt CPaintDC ulega
destrukcji, wywoływana jest funkcja klasy CWnd EndPaint () w celu powiadomienia Windows o
przetworzeniu komunikatu WM_PAINT i wykonaniu odświeżenia uszkodzonego obszaru okna.
Obsługa komunikatu WM_ERASEBKGND
Inną metodą pozwalającą podjąć decyzję o wymazaniu tła jest dodanie funkcji obsługi komunikatu
Windows WM ERASEBKGND. Nowa funkcja obsługi, OnEraseBkGnd (), wywoływana będzie za każdym
razem, gdy zajdzie potrzeba wymazania tła. Wskaźnik do odpowiedniego obiektu kontekstu urządzenia
(obiektu coc) przekazywany jest funkcji OnEraseBkGnd () jako jej pierwszy parametr.
Stosowanie kontekstu urządzenia pamięci
Kontekst urządzenia pamięci jest kontekstem bez przydzielonego urządzenia. Może wydać się to
dziwne, lecz jest to bardzo pożyteczne rozwiązanie. Normalnie używamy kontekstu przyłączonych
urządzeń, by kopiować i wklejać fragmenty obrazu na ekranie. Możemy jednak utworzyć kontekst
urządzenia pamięci kompatybilny z kontekstem urządzenia wyświetlającego. Można go wykorzystać do
skopiowania obrazu do pamięci, gdzie nie będzie wyświetlany, a następnie przesłać z powrotem do
kontekstu urządzenia wyświetlającego.
Nie istnieje żadna klasa osłonowa dla kontekstu pamięci. Nie jest ona zresztą potrzebna, gdyż CDC
doskonale radzi sobie sama. Ponieważ jest to zwykła klasa kontekstu urządzenia, możemy jej użyć do
rysowania w sposób normalny, z tą różnicą, że rysowanie


Rysowanie w kontekście urządzenia 373
odbędzie się w pamięci, w związku z czym użytkownik nie będzie widział tworzonego obrazu do
momentu przesłania go do kontekstu urządzenia wyświetlającego.
Wymiary bitmapy w kontekście urządzenia pamięci
Bitmapa znajdująca się w kontekście pamięci może być większa lub mniejsza niż w kontekście
określonego urządzenia. Tworząc dużą bitmapę można osiągnąć znacznie wyższą rozdzielczość, niż na
standardowym ekranie. Należy jednak pamiętać, iż duża bitmapa wymaga użycia większej ilości
pamięci systemowej. Jeśli bitmapa przekroczy objętością rozmiary pamięci RAM, komputer znacznie
spowolni działanie poprzez intensywne korzystanie z pamięci wirtualnej dla uzupełnienia braków
RAM. Pamięć wirtualna jest powolna z uwagi na fakt, iż następuje ciągła wymiana danych pomiędzy
dyskiem i pamięcią.
Możemy zatem utworzyć kontekst urządzenia pamięci, dzięki któremu rysunek wykonany zostanie
w pamięci zamiast na ekranie. Po wykonaniu tego rysunku, za pomocą funkcji BitBitO możemy
skopiować go na ekran. Modyfikacje dokonane na listingu 15.6 są obszerne, gdyż utworzyć musimy nie
tylko kontekst urządzenia pamięci, lecz także bitmapę dla niego. Inaczej niż kontekst urządzenia
wyświetlającego, który jest powiązany z oknem, kontekst pamięci nie jest automatycznie wyposażany w
kompatybilną z ekranem bitmapę.
Kontekst urządzenia rysującego zamieniony zostaje z powrotem na kontekst obszaru roboczego, a
kontekst pamięci zadeklarowany jest w linii 9 listingu 15.6 jako obiekt memDC. Funkcja składowa
kontekstu urządzenia CreateCompatibleDC () wywołana jest w linii 10. Funkcji tej przekazano wskaźnik
obiektu clientDC, który posłuży jako wskazówka dotycząca wymiarów oraz innych atrybutów podczas
tworzenia kontekstu urządzenia pamięci, kompatybilnego z kontekstem okna.
W linii 17 zapisana jest deklaracja bitmapy, która użyta będzie przez kontekst pamięci do wykonania
rysunku. Bitmapa owa utworzona zostaje przez funkcję składową Cre-ateCompatibleBitmap () w linii 18.
Także tej funkcji przekazany zostaje obiekt clientDC dla wskazania jej, w którym kontekście użyta
będzie bitmapa. Jest to również wykorzystane do określenia liczby kolorów, jaką bitmapa musi
wykorzystywać. Szerokość i wysokość prostokąta roboczego ustalone poprzez linię 14 przekazane
zostają jako parametry określające wymagane wymiary bitmapy. W linii 22 bitmapa zostaje umieszczona
w kontekście urządzenie przez funkcję składową SelectObjectO. Każda czynność rysowania wykonana w
kontekście urządzenia pamięci odbywać się będzie z wykorzystaniem dołączonej bitmapy.
Możemy teraz utworzyć tło w sposób podobny do dotychczasowych, wszystkie czynności są jednak
wykonywane w pamięci zamiast na ekranie. W linii 36 następuje wywołanie funkcji BitBit (), która
kopiuje bitmapę do kontekstu ekranu. Funkcja ta jest funkcją kopiującą obraz, a nazwę swą przejęła ze
starego terminu bit blitting oznaczającego układ scalony umożliwiający szybkie kopiowanie zawartości
pamięci z jednego miejsca w inne.


374___________________ __ ___ __ __ __ Poznaj Visual C++ 6
Pewne karty graficzne wykonują to zadanie po wywołaniu funkcji BitBit (), jednakże zajmuje się tym
głównie procesor. BitBit () kopiuje dane z memDC do clientDC, gdy przekażemy jej szerokość,
wysokość i punkt początkowy jako parametry.
Interesującym parametrem dostarczanym tej funkcji jest znacznik SRCINVERT, który nakazuje jej
odwrócenie kolorów obrazu. Powstaje w ten sposób odmienny zestaw kolorów w oknie docelowym, co
udowadnia fakt rysowania w pamięci i kopiowania obrazu do kontekstu okna dialogowego. Możemy
stosować zamiennik tego znacznika SRCCOPY, gdy obraz skopiowany z kontekstu pamięci ma pozostać
niezmieniony.
Wprowadźmy zatem zmiany widoczne na listingu 15.6 i usuńmy wywołanie funkcji OnDrawitO z
funkcji obsługi OnPaintO (w przeciwnym wypadku uzyskamy bardzo dziwne efekty). Następnie
skompilujemy i uruchomimy aplikację. Po kliknięciu przycisku Drawit zauważymy krótkie opóźnienie
związane z wykonywaniem rysunku w pamięci, po czym zostanie on skopiowany z użyciem
odwróconych kolorów do kontekstu urządzenia okna dialogowego.
Listing 15.6. LST16_6.CPP - rysowanie w kontekście urządzenia pamięci





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void CDCDrawDlg::OnDrawit() (
// TODO: tu dodaj własny kod obsługi komunikatu
II ** Utworzenie kontekstu paintDC okna dialogowego CCIientDC clientDC(this);
// ** Utworzenie kompatybilnego kontekstu pamięci C DC memDC;
memDC.CreateCompatibleDC(SclientDC) ; O
// ** Ustalenie prostokąta roboczego CRect rcdient;
GetCIientRect(srcCIient) ;
// ** Utworzenie kompatybilnej bitmapy CBitmap memBitmap;
memBitmap.CreateCompatibleBitmap(&clientDC, @ rcCIient.Width () ,
rcdient .Height () ) ;
// ** Wybranie bitmapy dla kontekstu pamięci
memDC.SelectObject(SmemBitmap) ;
// ** Pętla wykonywana przez linie poziome prostokąta roboczego
for(int x=0; x < rcdient .Width () ;x++)
{


Rysowanie w kontekście urządzenia 375
27 // ** Pętla wykonywana przez linie pionowe prostokąta II roboczego
28 for(int y=0; y < rcCIient.Height() ;y++)
29 (
30 // ** Nadanie każdemu pikselowi innego koloru
31 memDC.SetPixel(x,y,x*y);
32 }
33 )
34
35 // ** Skopiowanie obrazu z pamięci do kontekstu urządzenia II okna dialogowego
36 clientDC.BitBlt(0,0, O
37 rcdient.WidthO , rcCIient .Height () ,
38 &memDC,0,0,SRCINVERT);
39 }
O Utworzony zostaje kontekst urządzenia dla pamięci kompatybilny z kontekstem urządzenia
wyświetlającego.
@ Bitmapa musi być również kompatybilna z kontekstem wyświetlania. Rysunek w
pamięci jest już wykonany. Poza tym nie dzieje się nic.
O Na koniec cały obraz zostaje skopiowany z pamięci na ekran, dzięki czemu staje się widoczny dla
użytkownika.
Stosowanie trybów odwzorowania
Tryby odwzorowania są kolejnym użytecznym narzędziem dostarczanym przez konteksty urządzenia.
Ich stosowanie umożliwia rysowanie na ekranie lub drukarce przy użyciu w celach pomiarowych
centymetrów lub cali zamiast pikseli. Zapewniają one również dokładność odwzorowania skali.
Układ współrzędnych i osie
Każda funkcja rysująca kontekstu urządzenia wymaga parametrów określających pozycję w układzie
współrzędnych. Układ współrzędnych jest dwuwymiarową powierzchnią, opartą na geometrii
kartezjańskiej. Każdy punkt posiada określone wartości obu współrzędnych, przekazywane funkcji
rysującej jako odrębne parametry.
Gdy stosowany jest tryb MM_TEXT, niższe wartości współrzędnej pionowej znajdują się u góry ekranu,
a dla osi poziomej po lewej stronie. Dostępne są również inne tryby, które powodują umieszczenie
niskich wartości osi pionowej u dołu ekranu. W przypadku osi poziomej układ pozostaje niezmienny.


376 Poznaj Visual C++ 6
Podczas korzystania z trybów odwzorowania spotkamy się z pojęciami jednostek lo-
gicznych oraz jednostek urządzenia. Mówiąc prościej, jednostką urządzenia jest piksel lub inna
najmniejsza jednostka, jaką dane urządzenie jest w stanie zaprezentować. Jednostkami
logicznymi natomiast mogą być cale, centymetry lub jednostki do wyliczania wielkości
czcionki.
Domyślnie kontekst urządzenia używa trybu odwzorowania zwanego MM_TEXT. Ta
dziwnie brzmiąca nazwa oznacza po prostu, że jedna jednostka logiczna odpowiada jednej
jednostce urządzenia. W związku z tym, nie zachodzi żadna konwersja i współrzędne
określane poleceniami rysowania, będą odwzorowane przez adekwatną liczbę pikseli. Istnieje
kilka trybów odwzorowania, a są one wymienione w tabeli 15.1.
Tabela 15.1. Wspierane tryby odwzorowania Znacznik trybu
Jednostka logiczna równa jest
MM_TEXT jednemu pikselowi
MM_LOMETRIC 0,1 milimetra
MMJHMETRJC 0,01 milimetra
MM_LOENGLISH 0,01 cala
MM_HIENGLISH 0,001 Cala
MM_TW l P S 1/440 cala lub 1/20 punktu (w przypadku czcionek)
MM_ISOTROPIC jednostce ustalonej przez użytkownika, lecz zawsze przy zachowa-
niu proporcji pomiędzy X i Y
MM_ANISOTROPIC jednostce ustalonej przez użytkownika
Powyższe tryby odwzorowania ustawiane są przez funkcję kontekstu urządzenia Set-
MapMode (), która po prostu pobiera jeden spośród tych znaczników. Po dokonaniu ustalenia
trybu, koordynaty, jakie przekażemy funkcjom rysującym, skonwertowane zostaną do postaci
jednostek urządzenia (lub pikseli) dla każdego z trybów, poprzez wewnętrzne odwzorowanie
GDI. Jeśli zatem wybierzemy tryb MM_LOMETRIC i przekażemy funkcji rysującej wartość 100,
tryb odwzorowania wyliczy, że wskazaną wartością jest 100X0,1 milimetra, czyli l centymetr.
Na tej podstawie z kolei, skalkulowana zostanie liczba pikseli ekranu lub drukarki, jaką
urządzenie użyje do odwzorowania l centymetra.
Znaczniki MM_ISOTROPIC oraz MM_ANISOTROPIC umożliwiają ustalenie skali za pomocą
funkcji SetWindowExt () oraz SetViewPortExt (). Do dyspozycji mamy również dwie funkcje
SetwindowOrgO i SetViewPortOrg() pozwalające na zmianę punktu początkowego (lub O, 0)
układu współrzędnych. Ujemne współrzędne są normalną rzeczą, zwłaszcza gdy zmienimy
punkt początkowy.
Wykorzystajmy zatem zdobytą wiedzę do utworzenia małego programu typu SDI.
Utworzymy szkielet aplikacji, korzystając z procedury opisanej w rozdziale 12. Powinni-


Rysowanie w kontekście urządzenia 377
śmy przy tym zaakceptować wszystkie ustawienia domyślne poza wybraniem opcji aplikacji SDI w
kroku l i nadaniem jej nazwy MapMode.
Klasa widoku MapMode posiada funkcję składową OnDraw (), która jest zwykle wykorzystywana
do implementacji kodu rysującego. By przeprowadzić edycję kodu tej funkcji, wykonamy opisaną
poniżej procedurę.
Odnajdywanie i edycja funkcji OnDraw ()
1. Otwórz kartę ClassView w widoku Project Workspace.
2. Kliknij znak plusa, by zobaczyć klasy projektu.
3. Kliknij znak plusa widoczny obok pozycji MapMode View.
4. Kliknij dwukrotnie funkcję OnDrawO klasy widoku i rozpocznij edycję.
Nowe linie, jakie umieścimy w funkcji OnDraw (), znajdziemy na listingu 15.7. Po pierwsze,
zauważmy, że trybem odwzorowania jest MM_LOMETRIC, co znaczy, że wszystkie wartości
współrzędnych podawane są w jednostkach równych 0,1 mm. W linii 10 odnaleziony zostaje prostokąt
roboczy przez funkcję GetCIientRect (). Nie ma znaczenia, jakiego trybu .odwzorowania używamy,
ponieważ współrzędne zawsze podawane są w pikselach, a zatem w linii 16 użyta jest funkcja kontekstu
urządzenia DPtoLP () dla dokonania konwersji na jednostki logiczne. DPtoLp () oznacza Device Points
to Logical Points (jednostki urządzenia na jednostki logiczne) i może konwertować obiekty cpoint lub
CRect, jeśli przekazane jej zostaną ich wskaźniki.
Istnieje również funkcja odwrotna LPtoDP () przeprowadzająca konwersję w przeciwnym kierunku.
Pomiędzy liniami 19 i 21 zmienna x wykonuje pętlę w poziomych liniach prostokąta, od lewej do prawej,
po dokonaniu konwersji jednostek na logiczne. Pętla przeskakuje każdą setną jednostkę logiczną, co daje
nam jeden centymetr przy trybie odwzorowania MM_LOMETRIC. Trzykrotnie wywołana funkcja SetPixel()
umieszcza wewnątrz okna znaki w odstępach l cm.
Listing 15.7. LST16_7.CPP - zastosowanie trybu odwzorowania MMJLOMETRIC
1 void CMapModeView::OnDraw(CDC* pDC)
2 {
3 CMapModeDoc* pDoc = GetDocument() ;
4 ASSERT_VALID(pDoc) ;
5
6 // TODO: tutaj dodaj kod rysowania dla własnych danych
7
8 // ** Ustalenie trybu odwzorowania na 0,1 mm
9 pDC->SetMapMode(MM_LOMETRIC); O
10


378 Poznaj Visual C++ 6
11
12

CRect rcCIient;

13

GetCIientRect(&rcClient) ;

14



15
16

// ** Konwersja koordynat urządzenia na koordynaty logiczne pDC-
>DPtoLP(&rcClient); @

17



18

// ** Pętla dla każdych 100 jednostek logicznych

19

for(int x=rcClient.TopLeft().x;

20

x
21

x+=100)

22

(

23
24

// ** Ustawienie 3 pixeli na współrzędnej y pDC-
>SetPixel(x,rcCIient.CenterPoint().y-1,0);

25

pDC->SetPixel(x,rcCIient.CenterPoint().y,0) ;

26

pDC->SetPixel(x,rcCIient.CenterPoint().y+1,0);

27

}

28

} .

O Ustalony zostaje metryczny tryb odwzorowania, w związku z tym jednostka
współrzędnych równa jest O, l milimetra.
Funkcja DPtoLP () dokonuje konwersji punktów urządzenia na logiczne (aktualnie O, l
mm).
Trzykrotnie wywołana funkcja setpixel () umieszcza małe pionowe znaki.
Zauważmy, iż funkcji OnDraw () przekazano wskaźnik kontekstu urządzenia, który
używany jest podczas wszystkich operacji rysowania. Szkielet aplikacji przyjmuje ten
kontekst automatycznie. W rozdziale 22 zobaczymy jak ten kontekst ustawia ekran lub
drukarkę.
Gdy skompilujemy i uruchomimy aplikację, powinniśmy ujrzeć rząd regularnie roz-
mieszczonych znaków. Jeśli przyłożymy do ekranu miarkę zobaczymy, iż są one w odstę-
pach jednego centymetra.
Spróbujemy teraz zmienić znacznik MM_LOMETRIC na MM_LOENGLISH. Gdy
ponownie skompilujemy i uruchomimy program, znaki będą rozmieszczone w odstępach
równych jednemu calowi.


Rysowanie w kontekście urządzenia 379
Dokładność odwzorowania na ekranie
Odwzorowywanie na ekranie monitora cechuje się matą dokładnością. Na tę sytuację
składają się różnice w wymiarach monitorów i ustawieniach szerokości i wysokości obrazu.
Drukarki są znacznie dokładniejsze i wiarygodniejsze.
Tryby odwzorowania swobodnego skalowania
Możemy ustalać własną skalę odwzorowania, co toruje nam drogę do łatwego tworzenia trybów
powiększania poprzez zastosowanie trybu MM_ANISOTROPIC. Oznacza to możliwość nadania odmiennych
proporcji, w różnych kierunkach. Przykładowo, pies jest istotą anisotropiczną, choć nie zmienia swoich
wymiarów tak łatwo, jak robi to tryb odwzorowania. Ten tryb odwzorowania jest potężnym narzędziem,
umożliwiającym ustalenie wzajemnej konwersji jednostek logicznych i urządzenia.
Listing 15.8 demonstruje taką konwersję, ustalając poprzez funkcję SetViewPort-Ext() wymiary
prostokąta roboczego: szerokość na 500 pikseli wysokości. W linii 19 funkcja SetWindowExt () ustala
szerokość na 10000 jednostek logicznych, przy ciągle podanej w pikselach wysokości. Pętla w liniach
23-30 rysuje dokładnie 100 znaków w poprzek prostokąta, czyli co 100 jednostek. Jednakże w wymiarze
pionowym jednostka logiczna jest równa jednemu pikselowi (MM_TEXT).
Jeśli skompilujemy kod ze zmianami wprowadzonymi do funkcji OnDraw (), które widzimy na
listingu 15.8, ujrzymy 100 równomiernie rozstawionych znaków, rozmieszczonych wszerz okna. Gdy
spróbujemy zmienić jego skalę zauważymy, że znaki są rozciągane lub zwężane, lecz zawsze odstęp
pomiędzy nimi wynosi 100.
Listing 15.8. LST16_8.CPP - zastosowanie trybu odwzorowania MM_AMISOTROPIC
1 void CMapModeView::OnDraw(CDC* pDC)
2 (
3 CMapModeDoc* pDoc = GetDocument() ;
4 ASSERT_VALID(pDoc) ;
5
6 // TODO: add draw code for native data here
7
8 pDC->SetMapMode(MM_ANISOTROPIC) ;
9
10 CRect rcdient;
11 GetClientRect(SrcClient) ;
12
13 // ** Ustalenie szerokości urządzenia równej szerokości
14 // ** obszaru roboczego oraz wysokości na 500 pikseli


380___________________ Poznaj Visual C++ 6





15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
pDC->SetViewportExt(CSize(rcCIient.BottomRight().
x,500));
// ** Ustalenie szerokości logicznej na 10 000 jednostek // **
l 500 pikseli wysokości pDC->SetWindowExt(CSize(10000,500)) ;
pDC->DPtoLP(&rcClient) ;
forfint x=rcClient.TopLeft().
x;xx+=100) (
pDC->SetPixel(x,rcCIient.CenterPoint().y-1,0) ;
pDC->SetPixel(x,rcCIient.CenterPoint() .y,0) ;
pDC->SetPixel(x,rcCIient.CenterPoint().y+1,0) ;





O Tryb MM_ANISOTROPIC pozwala każdej z osi na swobodny wybór sposobu skalowania. @
Funkcja SetViewportExt () ustala szerokość i wysokość urządzenia w pikselach.
Funkcja SetWindowExt() ustala wymiary logiczne okna we współrzędnych logicznych.
Tryb odwzorowania MM_ISOTROPIC oznacza nadanie takich samych właściwości skali we
wszystkich kierunkach. Możemy używać tych samych funkcji SetViewportExt () oraz
SetWindowExt () dla ustalania skali, lecz GDI zawsze dostosowuje odmienne stopnie skali, w
związku z czym jednostka logiczna w osi poziomej równa jest jednostce logicznej w pionie.
Tryb ten przydaje się, gdy chcemy zapobiec zmianom proporcji rysunku.
PATRZ TAKŻE
O tym jak stosować tryby odwzorowania podczas drukowania czytaj w rozdziale 22.
Stosowanie funkcji SetWindowExt () i SetViewportExt ()
Funkcje SetWindowExt () oraz SetViewportExt () działają jedynie w trybach MM_ISOTROPIC i
MM_ANISOTROPIC. W pozostałych trybach są ignorowane.


Rysowanie w kontekście urządzenia 381
Badanie możliwości urządzenia
Kontekst może zostać użyty do zbadania możliwości przyłączonego urządzenia. W większości
przypadków wspiera ono wszystkie standardowe czynności rysowania grafiki oraz czcionek. Jednakże
niektóre urządzenia - na przykład plotery pisakowe - wspierają jedynie rysowanie liniowe. Drukarki
natomiast mogą być czarno-białe lub wykorzystywać ograniczony zestaw kolorów. Dzięki funkcji
GetDeviceCaps () możemy dowiedzieć się wszystkiego, czego potrzebujemy o określonym urządzeniu.
Skoro funkcja ta może dostarczyć tylu informacji, to jak można się domyśleć, istnieje długa lista
znaczników, które możemy jej przekazać w celu zbadania konkretnych aspektów technologicznych.
Funkcja kontekstu urządzenia GetDeviceCaps () pobiera jeden parametr, będący jednym z tych właśnie
znaczników, określający rodzaj żądanej informacji.
Znacznik TECHNOLOGY informuje o rodzaju przyłączonego urządzenia, zwracając jedną spośród
wartości wymienionych w tabeli 15.2.
Specyficzne możliwości graficzne
Istnieje cały zestaw znaczników pozwalających na stwierdzenie posiadania przez urządzenie
możliwości rysowania krzywych (CURYECAPS), linii (LINECAPS), wielo-kątów
(POLYGONCAPS) oraz tekstu (TEXTCAPS). Znaczniki te odnoszą się do ploterów, które mogą
nie posiadać możliwości rysowania pewnych elementów.
Tabela 15.2. Wartości zwracane przez funkcję GetDeviceCaps () dla znacznika TECHNOLOGY
Zwracana wartość Opis
DT_RASDISPLAY Zwykła karta graficzna lub monitor DT_RASPRINTER
Drukarka rastrowa DT_PLOTTER ploter pisakowy DT_RASCAMERA Rastrowe
urządzenie wejściowe DT_CHARSTREAM Sekwencja znaków, na przykład z
klawiatury DT_METAFILE plik zawierający informacje dotyczące rysowania
DT_DISPFILE Plik do wyświetlenia
Wersja sterownika może zostać sprawdzona poprzez użycie znacznika DRIVEVERSION.
Kilka kolejnych znaczników dostarcza informacji o fizycznych wymiarach urządzenia. Znaczniki te
wymienia tabela 15.3.


382 Poznaj Visual C++ 6
Parametry funkcji GetDeviceCaps ()
Oczywiście istnieje o wiele więcej znaczników, niż przedstawione w powyższej tabeli. Znaczniki,
które nie zostały wymienione, służą w większości do sprawdzenia, czy dane urządzenie jest w stanie
wykonać określoną operację. Dla większości zwykłych monitorów i drukarek operacje rysowania są
dostępne i dają dobre wyniki.
Tabela 15.3. Znaczniki przekazywane funkcji GetDeviceCaps () w celu określenia fizycznych wymiarów
urządzenia
Przekazany znacznik Opis zwracanej wartości
HORZRES Szerokość urządzenia podana w pikselach VERTRES Wysokość urządzenia
podana w pikselach HORS IZĘ Szerokość urządzenia podana w milimetrach VERTSI ZE
Wysokość urządzenia podana w milimetrach ASPECTX Relacja wysokości do szerokości
podana w pikselach ASPECTY Relacja szerokości do wysokości podana w pikselach
ASPECTXY Długość przekątnej podana w pikselach
Kolejne znaczniki służą do zdobycia informacji o liczbie odtwarzanych kolorów oraz możliwościach
rysowania przez urządzenia. Wymienia je tabela 15.4.
Tabela 15.4. Znaczniki przekazywane funkcji GetDeviceCaps () do określenia liczby kolorów i
możliwości rysowania przez urządzenie
Przekazywany znacznik Opis zwracanej wartości
NUMCOLORS Liczba kolorów, jaką wykorzystuje urządzenie
PLANES Liczba płaszczyzn kolorów obsługiwanych przez urządzenie
BITSPIXEL Liczba bitów reprezentujących jeden piksel
NUMPENS Liczba piór wykorzystywanych przez urządzenie
NUMBRUSHES Liczba pędzli wykorzystywanych przez urządzenie
NUMFONTS Liczba czcionek wspieranych przez urządzenie
COLORRES Rozdzielczość kolorów urządzenia (jeśli da się zastosować)
SIZEPALETTE Liczba pozycji palety w palecie systemowej (jeśli da się zastoso-
wać)


Rysowanie w kontekście urządzenia 383
Zrobimy teraz użytek z funkcji GetDeviceCaps (), by ustalić właściwości karty graficznej.
Okno dialogowe About będzie świetnym miejscem na umieszczenie tych informacji w postaci
listy.
Dodanie pola listy do okna dialogowego About
1. Wybierz kartę ResourceYiew z widoku Project Workspaces.
2. Odnajdź pozycję IDD_ABOUTBOX należącą do okna dialogowego.
3. Kliknij dwukrotnie tę pozycję, by rozpocząć jej edycję.
4. Rozciągnij okno i umieść w nim pole listy.
5. Naciśnij Alt+Enter, by zmienić właściwości pola listy.
6. Na karcie Generał zmień identyfikator ID na IDC_DEVCAPS.
7. Kliknij kartę Styles i usuń znacznik opcji Sort.
8. Naciśnij Enter, by zamknąć okno właściwości.
9. Dodaj pole Static ponad polem listy i wpisz Device Capabilities.
Szablon okna dialogowego About powinien wyglądać obecnie jak na rysunku 15.4.


sm*^.; v^i^S. '^^w^wS^^ ;::.;tó< :^H]Fiie ^dif View Snsert grojeci
fiuild f"oyoul laota ^indOT/ tiełp
^SfiSSSS * ą l?" a- :.: - ;[aia?i' IftlGetDesktopWindow
ijCAtunrtDlg3|lDC_DEVCAPS^liSi^^OBiST IJChapl6P2
j^|Win32 Debug
"r] S a^"Eia', l "'El
S.^^iOBlAESlitiiia ^aSt
l> t 4- ISl |EnlireC(inter_J |K f ,\






Chap16P2Varsion1
O Copyrighf(C)1998
Devics Capabilities
^, .-^^
>t B A< aM Q
a 9 a Są as S
B!I o- Ę" 3 &
a H
*6 C
la-.a ChapłBPS
$ LJAcceleri B
a Dialog
l
a|iDp_;
iłs iJlcon in
SMenu BO
String Ti EB
OToolbar:
la i_|Version


"^JM-SIU
|l^ i;1' ;" ,: ": iSEBIj", 'J Fteady
^a ^ Generał | Styl&s | Erfended Słyles |
D; [TDCJIEYCAFS3
17 Visible r firoup r aełp 10 r
Disabled 17Tbstop





Rysunek 15.4. Edycja okna dialogowego About aplikacji MapMode
Teraz musimy jeszcze przydzielić polu listy zmienną, dzięki której pole to będzie mogło wyświetlić
łańcuch składający się na informację dla użytkownika.


384 Poznaj Visual C++ 6
Przydzielenie zmiennej do pola listy
1. Kliknij pole listy w oknie edytora.
2. Naciśnij Ctri+W, by uruchomić CIassWizard. Powinien on zostać otwarty z zaznaczoną
pozycją IDC_DEVCAPS w polu Control IDs na karcie Member Yariables.
3. Kliknij przycisk Add Variable, co spowoduje wyświetlenie okna dialogowego Add
Member Variable.
4. Rozwiń listę Category i zastąp domyślną pozycję Value pozycją Control.
5. Wpisz w polu Member Variable nazwę nowej zmiennej, m_listDevCaps.
6. Kliknij OK, by zamknąć okno dialogowe.
7. Kliknij OK, by zamknąć CIassWizard.
Mamy zatem przydzieloną zmienną klasy CListBox do pola listy, wcieloną do klasy
CAboutDig. Teraz musimy wpisać kod wysyłający zapytanie do kontekstu urządzenia, poprzez
funkcję GetDeviceCaps () i wyświetlający otrzymane informacje. Aby ten mechanizm
zadziałał, należy dodać do klasy CAboutDig funkcję obsługi OninitDia-log () inicjalizującą
pole listy podczas jego otwierania.
Dodanie funkcji obsługi OninitDialogO
1. Wybierz panel CIassView z widoku Project Workspace.
2. Kliknij prawym klawiszem myszy pozycję CAboutDig, by otworzyć menu skrótów.
3. Wybierz opcję Add Windows Message Handler.
4. Z listy New Windows Messages/Events wybierz pozycję WM_INITDIALOG.
5. Kliknij przycisk Add and Edit, co pozwoli przejść do edycji treści funkcji.
Wpiszmy teraz treść listingu 15.9, dzięki czemu rezultaty działania funkcji GetDeviceCaps
() zostaną umieszczone w polu listy okna dialogowego About. W linii 8 tego listingu użyto
CdientDC dla uchwycenia kontekstu urządzenia okna dialogowego About. Kontekst ten
przechowuje informacje o urządzeniu wyświetlającym. Poprzez linie 14 i 15 wartość związana
ze znacznikiem rozdzielczości poziomej HORZRES sformatowana zostaje do postaci łańcucha
tekstowego.
W linii 16 łańcuch ten przekazany zostaje do pola listy, po czym wyświetlony. Proces ten
powtórzony jest dla wszystkich żądanych atrybutów urządzenia.
Listing 15.9. LST16_9.CPP wyświetlenie w polu listy informacji pobranych przez Get-
DeviceCaps ()
1 BOOL CAboutDig::OninitDialogO
2 {
3 CDialog: :OnInitDialog.() ;


4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 }
// TODO: Tutaj dodaj dodatkową inicjallzację
II ** Przechwycenie kontekstu okna dialogowego
CCIientDC dcDev(this);
// ** Deklaracja łańcucha
CString strCap;
// ** Pobranie otrzymanych informacji i sformatowanie ich
strCap.Format("Horizontal Resolution = %d pixels",
dcDev.GetDeviceCaps(HORZRES)) ;
m_listDevCaps.AddString(strCap) ;
strCap.Format("Vertical Resolution = %d pixels",
dcDev.GetDeviceCaps(YERTRES)) ;
m_listDevCaps.AddString(strCap) ;
strCap.Format("Horizontal Size = %d cm",
dcDev.GetDeviceCaps(HORZSIZE) / 10);
m_listDevCaps.AddString(strCap) ;
strCap.Format("Vertical Size = %d cm",
dcDev.GetDeviceCaps(VERTSIZE) / 10);
m_listDevCaps.AddString(strCap) ;
strCap.Format("Supported Colors = %d colors",
dcDev.GetDeviceCaps(NUMCOLORS)) ;
m_listDevCaps.AddString(strCap) ;
return TRUE; // Zwraca TRUE do chwili ustawienia fokusu na
obiekcie sterującym
11 WYJĄTEK: Strony właściwości OCX powinny
zwracać TRUE





Po skompilowaniu aplikacji z uwzględnieniem opisanych zmian i jej uruchomieniu oraz
wybraniu pozycji About Map Modę... z menu Hełp powinniśmy ujrzeć informacje o
atrybutach używanego urządzenia wyświetlającego, jak widać na rysunku 15.5. Nie należy się
martwić, że urządzenie powinno wykorzystywać większą liczbę kolorów, niż wynika to z
otrzymanej informacji. Podawana liczba jest liczbą kolorów palety systemowej kontekstu
urządzenia, która domyślnie wynosi 20. Paleta może zostać rozszerzona poprzez przydzielenie
większej palety do kontekstu urządzenia, w ramach możliwości naszego sprzętu.


386 Poznaj Visual C++ 6





Vj
f*
Chapl6P2 Version 1.0






HorizontsI Resolulion - 600 pixels
Vertfcol Resolution 600 pixels
Morilontoi Sile " 16 cm YerticaJ
Size -12 cm Supported Cotere -
EO colors
Rysunek 15.5. Wyświetlone informacje otrzymane są przez funkcję GetDeviceCaps () 4 O
możliwościach drukarki przeczytasz w rozdziale 22.


Rozdział 16
Stosowanie piór i pędzli
Wykorzystanie piór do rysowania linii i kształtów
Malowanie pędzlami wypełnionych kształtów różnymi kolorami i teksturami
Stosowanie funkcji rysujących dla uzyskiwania złożonych kształtów
Tworzenie piór
Pióra to jeden z podstawowych obiektów GDI. Mają swoje miejsce głęboko w otchłani
Windows i są tam od samego początku. Zanim cokolwiek narysujemy, musimy utworzyć lub
wybrać jedno z piór, odpowiednich do wykonania określonego zadania.
Pióra i figury wypełnione
Pióra wykorzystywane są do rysowania linii i krzywych. Podczas rysowania wypełnionej figury, pióro
używane jest do rysowania krawędzi figury, do wypełniania jej zaś służy pędzel. Figura nie musi być
wypełniana, narysowany będzie jedynie jej obrys. W takim przypadku stosuje się pędzel przezroczysty
lub pusty, co omówione jest w dalszej części rozdziału.
Stosowanie klasy CPen
MFC posiada klasę osłonową nazwaną CPen znacznie ułatwiającą korzystanie z piór
(obiektów rysujących). Klasa ta przechowuje podstawowy obiekt GDI, zajmując się jego
dostarczaniem i usuwaniem.
By utworzyć pióro, musimy je zadeklarować, przekazując pewne parametry inicjali-
zujące. Poniższy kod tworzy pióro rysujące czerwoną linię ciągłą:
CPen penRed(PS_SOLID,3,RGB(255,0,0)) ;


Wyszukiwarka

Podobne podstrony:
15 Eksploatowanie maszyn i urządzeń do obróbki termicznej
15 Rysowanie linii i krzywych
Rozdział 15 Pozostałe urządzenia wejścia
1999 02 Radiowy pilot do sterowania 15 urządzeniami
CAD 15 LAB02 Warstwy Rysowanie precyzyjna
CAD 15 LAB01 Rysowanie figur prostych
311[15] Z2 02 Użytkowanie urządzeń transportowych
311[15] Z2 06 Użytkowanie sieci i urządzeń elektrycznych w wyrobiskach
15 Eksploatowanie urządzeń do wzbogacania i przeróbki
311[15] Z2 03 Użytkowanie maszyn i urządzeń do zabezpieczania wyrobisk
urzadz1
04 Prace przy urzadzeniach i instalacjach energetycznych v1 1
15 3

więcej podobnych podstron