Rozdział 9.
Grafika
W tym rozdziale:
Architektura GDI
W jaki sposób GDI reprezentuje sprzęt graficzny
Różne rodzaje grafiki GDI
Kontekst urzÄ…dzenia
W jaki sposób wyświetlać na ekranie tekst i grafikę wektorową
Manipulowanie tekstem
Czcionki
Pióra i pędzle
Operacje rastrowe
Regiony obcinania
Od wczesnych lat osiemdziesiątych systemy operacyjne przeszły długą drogę. W tamtych czasach pracowało się z systemami tekstowych menu, w których polecenia dla programu były przekazywane poprzez klawisze funkcyjne. Jedynie komputery takie jak Atari, Ainiga czy Mac cieszyły się luksusem posiadania graficznego interfejsu użytkownika. Jednak po sukcesie platformy IBM PC i systemu operacyjnego Windows wszystko stało się graficzne.
Programiści nie muszą już martwić się niskopoziomowymi szczegółami wiążącymi się z rysowaniem okna, dialogu czy tworzeniem przycisku (przynajmniej w większości przypadków). Programowanie w środowisku graficznym przynosi całą masę zupełnie nowych zagadnień, szczególnie gdy twoja aplikacja wyświetla dane graficznie, na przykład w postaci wykresów, grafów lub diagramów.
Właśnie tego dotyczy cały bieżący rozdział: programowania graficznego w Windows 98. Poznamy podstawy architektury graficznej, nazywanej GDI, oraz zobaczymy, jak działa każda z części, udostępniając programiście niesamowitą elastyczność i duże możliwości. Omówimy różne klasy GDI i przedstawimy różne elementy związane z GDI, takich jak pędzle, pióra czy czcionki.
Wprowadzenie do interfejsu urządzeń graficznych
Interfejs urządzeń graficznych, w skrócie GDI (Graphics Device Interface), formuje część rdzenia systemu operacyjnego Windows. GDI zarządza wszelkim wyjściem graficznym w programach Windows. Oznacza to, że bez względu na to czy na ekranie jest wyświetlane okna, czy działa wspaniały wygaszacz ekranu, czy też aplikacja tworzy wydruk na drukarce, za wszystkie te operacje odpowiada właśnie GDI.
Windows także wykorzystuje GDI do rysowania elementów interfejsu użytkownika, takich jak okna, menu czy dialogi. Windows wykorzystuje GDI nawet do wyświetlania wskaźnika myszy, mimo że wydaje się, że "unosi" się on ponad innymi obiektami na ekranie.
Microsoft stworzył GDI w celu oddzielenia operacji graficznych od sprzętu. GDI zapewnia wysokopoziomowe funkcje dające ogólnie ten sam efekt bez względu na sprzęt zainstalowany w systemie. Oddzielenie operacji graficznych od sprzętu umożliwia tworzenie interesujących efektów przy niewielkim wysiłku i bez zwracania szczególnej uwagi na zainstalowany sprzęt.
Rodzaje grafiki
Tekst
Funkcje GDI występują w trzech odmianach: tekstowe, rastrowe i wektorowe. Choć każda z tych kategorii może wydawać się samodzielna, jednak występują razem z określonych powodów. Na poziomie sterownika urządzenia każdy rodzaj grafiki posiada własny zestaw punktów wejścia i sposobów działania. Na wyższym, programowym poziomie, program steruje wyglądem każdego z rodzajów, korzystając z nieco odmiennego zestawu atrybutów graficznych. Poznanie każdego z rodzajów grafiki stanowi pierwszy krok na drodze do pełnego opanowania GUI. A ponieważ chcesz zostać ekspertem w dziedzinie grafiki w Windows, przejdźmy do opisu jej poszczególnych rodzajów.
Jak na ironię, mimo że Windows znacznie ułatwia programowanie grafiki, jednak w przypadku tekstu wiąże się to ze zwiększonymi komplikacjami. Jednak przy większej złożoności mamy do dyspozycji także większą elastyczność. Ponieważ tekst jest traktowany jako grafika, możesz kontrolować więcej aspektów niż mógłbyś nawet wymyślić w przypadku środowiska tekstowego. Chcesz stworzyć 18-punktową, czerwoną czcionkę Helvetica? Żaden problem. A co z 12-punktowym, cieniowanym tekstem wypisanym czcionką Arial? Także żaden problem. Aby te same efekty otrzymać w środowisku tekstowym, musiałbyś się naprawdę bardzo nagimnastykować. W Windows wystarczy w tym celu kilka wywołań GDI.
Grafika rastrowa
Drugim rodzajem grafiki GDI jest grafika rastrowa. Grafika rastrowa jest w Windows najczęściej używanym rodzajem grafiki. Funkcje grafiki tego typu operują na danych zawartych w tablicach zwanych bitmapami (mapami bitów). Obraz standardowego monitora VGA 640x480 jest po prostu bitmapą, którą karta graficzna wyświetla na ekranie. Częścią obsługi grafiki rastrowej w Windows jest możliwość tworzenia bitmap poza ekranem. Rysowanie na takich bitmapach i przesyłanie ich w całości na ekran jest bardzo szybkie, dzięki czemu możesz tworzyć grafikę pozbawioną migotania.
Innymi przykładami grafiki rastrowej są ikony i wskaźniki myszy dodawane do Twojej aplikacji w wyniku edycji pliku zasobów (.RC). Kolejnym przykładem wykorzystania grafiki rastrowej jest przemieszczanie okien na ekranie. Gdy okno jest przesuwane w inne miejsce ekranu, funkcje grafiki rastrowej pobieraj ą piksele tworzące okna i umieszczają je w miejscu docelowym.
Ponieważ dane na ekranie są przechowywane w tablicach, bitmapy są przydatne do przechowywania złożonej grafiki w celu szybkiego wyświetlenia na ekranie. Możesz zawczasu tworzyć złożone obiekty podczas kompilacji lub już podczas działania programu, a następnie szybko kopiować je na ekran. Jedynym problemem w przypadku bitmap jest ilość miejsca przez nie zajmowanego. Jedna bitmapą o rozmiarach 640 x 480 pikseli i ośmiobitowym kolorze zajmuje około 300K miejsca w pamięci. Oczywiście, wraz ze wzrostem rozmiarów i ilości kolorów zwiększa się również ilość zajmowanego miejsca.
Grafika wektorowa
W GDI termin grafika wektorowa odnosi się do funkcji rysunkowych tworzących odcinki oraz wypełnione figury. GDI posiada cały zestaw funkcji rysujących odcinki, krzywe, wycinki oraz wielokąty. Możesz w dowolny sposób łączyć i dopasowywać wywołania tych funkcji z funkcjami rastrowymi.
Zwykle myśląc o grafice wektorowej, ludzie myślą o regularnych kształtach geometrycznych. Jednak podczas rysowania interfejsu użytkownika Windows korzysta z bardzo niewielu wywołań grafiki wektorowej. Większość aplikacji także zwykle z niej nie korzysta, oczywiście z wyjątkiem programów rysunkowych i projektowych. Oczywiście, korzystanie z tych funkcji nie jest niczym niewłaściwym, lecz po prostu aplikacje biurowe zwykle mają do czynienia z tekstami, zaś programy wyświetlające obrazki zwykle posługują się głównie bitmapami.
UrzÄ…dzenia GDI
Aby fizyczne urządzenie graficzne, takie jak drukarka czy karta graficzna, mogło być urządzeniem GDI, wcale nie musi obsługiwać wszystkich trzech rodzajów grafiki. Na tym właśnie polega piękno GDI. W oparciu o zainstalowane sterowniki urządzeń, GDI może zaadoptować się do zainstalowanego sprzętu i dostarczać sensownych wyników graficznych bez względu na rodzaj urządzeń. W rzeczywistości bardzo niewiele urządzeń posiada wbudowaną pełną obsługę wszystkich rodzajów grafiki.
Jedynym wymaganiem, jakie musi spełniać urządzenie GDI, jest możliwość zmiany koloru piksela. To wszystko. Dla wszystkich operacji poza tym prostym wymaganiem GDI może samo rozłożyć złożone żądanie rysunkowe na prostsze elementy zrozumiałe dla urządzenia graficznego. Jednak sterownik urządzenia musi dokładnie poinformować GDI, jakie posiada możliwości. Sterownik komunikuje się z GDI, przekazując zestaw bitów możliwości, wykorzystywanych przez GDI do dostosowania własnych żądań rysunkowych.
W przypadku prostych urządzeń z ograniczonymi możliwościami GDI samodzielnie adaptuje i wykonuje większość pracy. GDI może wewnętrznie zamieniać wszystkie wywołania tekstowe i wektorowe na dane rastrowe, które mogą zostać przekazane do urządzenia. Jak już wiemy, mimo że grafika rastrowa wymaga większej ilości miejsca w pamięci, jednak umożliwia wyświetlanie nawet najbardziej skomplikowanej grafiki na najtańszych nawet, podstawowych urządzeniach graficznych.
Jeśli urządzenie poinformuje GDI, że posiada bardziej zaawansowane możliwości, GDI wykonuje mniejszą część pracy. Jeśli sprzęt graficzny potrafi bezpośrednio obsłużyć żądanie rysunkowe, GDI przekazuje to żądanie bezpośrednio do urządzenia. W rezultacie występuje wzrost wydajności związany z dwoma aspektami. Po pierwsze, GDI musi wykonać mniej wewnętrznej pracy i w mniejszym stopniu obciąża procesor. Po drugie, wysokopo-ziomowe żądania rysunkowe zajmują mniej pamięci i miejsca na dysku niż te same żądania rozbite na niskopoziomowe składniki. Mniej przechowywanej informacji oznacza mniejsze obciążenie systemu, co także przekłada się na bezpośredni wzrost wydajności.
Ta możliwość współpracy z szerokim asortymentem urządzeń jest jednym z powodów, dla których GDI jest opisywane jako interfejs graficzny niezależny od sprzętu. Jeśli urządzenie samo nie obsługuje operacji rysunkowych, GDI wkracza do akcji i zastępuje żądanie programowo. Z drugiej strony, jeśli urządzenie może samo obsłużyć żądanie, GDI się nie wtrąca i przekazuje je do urządzenia, powodując zwiększenie wydajności systemu.
Rodzaje urządzeń GDI
Rodzaje urządzeń obsługiwanych przez GDI można podzielić na dwie kategorie: urządzenia fizyczne oraz pseudourządzenia. Ten podział jest przydatny, gdyż pomaga wyjaśnić niezależną od urządzenia naturę GDI. Skierujmy więc uwagę na zrozumienie różnic pomiędzy tymi rodzajami urządzeń.
UrzÄ…dzenia fizyczne
Z punktu widzenia programu istnieją tylko dwa podstawowe urządzenia fizyczne: ekran monitora oraz drukarka. Gdy aplikacja rysuje na ekranie, zawsze rysuje wewnątrz okna. Menedżer okien zapewnia, że niepoprawnie zachowujące się okno nie przekroczy swoich granic i nie narysuje niczego wewnątrz innego okna.
GDI wyznacza te granice, korzystając z techniki zwanej obcinaniem (ang. clipping). Obcinanie polega na określaniu granic rysowania. Najbardziej oczywistymi i najczęściej występującymi granicami są granice okna, którymi zarządza Menedżer okien, możesz jednak tworzyć także własne regiony obcinania. Możesz na przykład wykorzystać to do wypełnienia obszarów rysowanych przez oddzielne części aplikacji, bez konieczności tworzenia w tym celu osobnych okien.
Kolejną techniką, używaną przez GDI w celu zapewnienia rozdziału pomiędzy różnymi częściami wydruku, jest buforowanie. Zadania wydruku są buforowane, czyli przechowywane, do momentu aż drukarka będzie mogła je obsłużyć. Bez buforowania drukarka mogłaby wymieszać strony należące do różnych zadań wydruku.
Wysyłając grafikę na drukarkę, musisz wziąć pod uwagę różne uwarunkowania. Należą do nich między innymi rozmiar i orientacja papieru oraz dostępne czcionki. Jednak zarówno w przypadku rysowania na ekranie, jak i na drukarce, korzystasz wciąż z tych samych funkcji GDI.
PseudourzÄ…dzenia
Pseudourządzenia zapewniają inny sposób przechowywania obrazków. GDI posiada dwa rodzaje pseudourządzeń: bitmapy i metapliki. Gdy odwołujemy się do pseudourządzenia, odwołujemy się do niego tak samo, jak odwoływalibyśmy się do urządzenia fizycznego. Jednak poza pamięcią RAM lub obszarem dysku używanymi do ich przechowywania, bitmapy i metapliki nie mają nic więcej wspólnego ze sprzętem. Można więc stwierdzić, że stanowią programową symulację urządzeń fizycznych.
Metapliki to listy wywołań rysunkowych. Bitmapy przechowują skumulowany rezultat wywołań rysunkowych, podczas gdy metapliki przed narysowaniem jakiegokolwiek piksela przechwytują wywołania graficzne. Potraktuj metapliki jako przepis na rysowanie wykorzystywany przez program. Metapliki mogą być "odtwarzane" w celu wyrysowania na innym urządzeniu (zwykle na ekranie) przechowywanego w metapliku obrazu.
Jak już wiemy, bitmapa jest tablicą danych reprezentującą obraz. W ten sam sposób, w jaki używasz funkcji GDI do rysowania na ekranie lub drukarce, możesz użyć ich także do rysowania na bitmapie. Nawet jeśli bitmapa symuluje rysowanie rastrowe, nie jesteś jednak ograniczony wyłącznie do wywołań rastrowych. Na bitmapie możesz wyrysować dowolny tekst, raster czy obiekt wektorowy.
W tej sekcji zostałeś wprowadzony do słownika terminów GDI. W tym momencie powinieneś już rozumieć operacje graficzne GDI oraz to, w jaki sposób upraszczaj ą programowanie Windows. Zostały opisane różne rodzaje funkcji rysunkowych oraz dwie kategorie urządzeń graficznych. Jednak zanim będziesz mógł cokolwiek narysować w Windows, musisz poznać jeszcze jeden element: kontekst urządzenia. Będzie on przedmiotem następnej sekcji.
Kontekst urzÄ…dzenia
Kontekst urządzenia - znany także jako DC (ang. device contexf) -jest strukturą danych tworzonych przez GDI w celu reprezentacji połączenia z urządzeniem graficznym. Na przykład, aby narysować coś na ekranie, programista Windows musi posiadać kontekst urządzenia dla tego ekranu. Także żeby narysować coś na drukarce, potrzebny jest inny kontekst urządzenia - stworzony specjalnie dla drukarki. Jeśli ten sam program chce narysować coś w metapliku lub na bitmapie, potrzebny jest trzeci i czwarty kontekst urządzenia. Rolą kontekstu urządzenia jest zapewnienie połączenia pomiędzy programem a urządzeniem fizycznym lub pseudourządzeniem.
Oprócz zapewnienia połączenia z urządzeniem kontekst urządzenia przechowuje kolekcję atrybutów ustawień rysowania. Na przykład, jednym z atrybutów kontekstu urządzenia jest kolor tekstu. Podczas wywoływania funkcji rysujących tekst wyświetlany jest tekst w kolorze opisanym atrybutem zawartym w kontekście urządzenia.
Wywołując specyficzne funkcje GDI, możesz w dowolnym momencie zmienić każdy z atrybutów rysowania. Na przykład, do zmiany koloru tekstu służy funkcja SetText-coior (). Każdej funkcji ustawiającej atrybut odpowiada funkcja zwracająca wartość tego atrybutu. Na przykład, aby odczytać aktualny kolor rysowania tekstu, powinieneś wywołać funkcję GetTextColor ().
Atrybuty rysunkowe kontekstu urzÄ…dzenia
W tabeli 9.1 zebrano 25 atrybutów przechowywanych przez GDI w kontekście urządzenia. Tabela informuje także, jakie atrybuty odnoszą się do poszczególnych rodzajów obiektów graficznych. W kolumnie Komentarz podano domyślne ustawienia oraz sposób zmiany poszczególnych atrybutów. Funkcje wymienione w tabeli 9.1 są funkcjami składowymi klasy coc, klasy MFC reprezentujące kontekst urządzenia GDI.
Tabela 9.1 Atrybuty kontekstu urzÄ…dzenia GDI
Atrybut
Tekst
Raster
Wektor
Komentarz
Kierunek łuków -wektor X - Domyślnie przeciwnie do ruchu wskazówek zegara. Ustawiany funkcją SetArcDirection () ze stałą AD_CLOCKWISE (zgodnie z ruchem wskazówek) lub AD_COUNTERCLOCKWISE (przeciwnie do ruchu wskazówek).
Kolor tła - X X X Domyślnie biały. Używany jako tło zwykłego tekstu, kolor konwersji monochromatycznych oraz jako tło piór stylów oraz siatkowanych pędzli.
Tryb tła - X X X Domyślnie jednolity. Ustawiany funkcją.SetBkColor () jako jednolity (OPAQUE) lub przezroczysty (TRANSPARENT).
Prostokąt obejmujący X X X - Domyślnie wyłączony. Gdy włączony, informuje GDI, by obliczało prostokąt obejmujący obszar rysowania. Ustawiany wywołaniem
SetBoundsRect(}.
Pędzel -(raster)X;(Wektor) - X - Domyślnie biały. Definiuje wewnętrzny kolor figur zamkniętych.
Początek pędzla - (wektor) - X - Domyślnie 0,0. Określa punkt w przestrzeni
urządzenia, oznaczający początek wzoru pędzla. Niezbędny w przypadku pędzli siatkowanych i roztrząsanych, przy których może wystąpić niezgodność wzoru.
Region obcinania - X X X - Domyślnie cała powierzchnia rysunkowa. Definiuje ograniczenia obszaru rysowania.
Tabela 9.1 cd. Atrybuty kontekstu urzÄ…dzenia GDI - ciÄ…g dalszy
Dostosowywanie kolorów - (raster)X Domyślnie bez dostosowywania. Definiuje zmiany w bitmapie rozciąganej funkcjami StretchBlt () i StretchDIBits (). Ustawiany wywołaniem
SetColorAdjustments().
Bieżące położenie - (text)X (wektor)X - Domyślnie 0,0. Para x,y stworzona początkowo w celu asystowania przy rysowaniu odcinków. Uaktywniana dla rysowania tekstu przez wywołanie SetTextAlign () ze znacznikiem TA_UPDATECP. Atrybut ustawiany wywołaniem SetCurrentPosition().
Tryb rysowania - wektorX - Domyślnie R2_COPYPEN. Wyznacza operację logiczną podczas rysowania grafiki wektorowej. Ustawiany wywołaniem SetROP2 ().
Czcionka - tekstX -Domyślnie czcionka systemowa. Wyznacza postać czcionek używanych do rysowania tekstu.
Tryb graficzny - XXX Domyślnie GM_COMPATIBLE. Określa, czy GDI ignoruje przekształcenia świata i inne rozszerzenia Win32. Ustawiany wywołaniem
SetGraphicsMode(}.
Tryb
odwzorowania współrzę dnych - XXX Domyślnie MM_TEXT. Steruje większością zagadnień związanych z odwzorowaniem współrzędnych. W trybie domyślnym jednostka logiczna odpowiada jednostce fizycznej (pikselowi). Ustawiany wywołaniem SetMapMode ().
Dokładność połączeń kantów - wektor X Domyślnie 10.0. Steruje "rozdzielczością"
w połączeniach odcinków geometrycznych przed zastosowaniem zakończeń kantów.
Paleta - XXX Domyślnie paleta systemowa. Obsługiwana jedynie przez niektóre urządzenia, pozwala aplikacji na ustawienie fizycznej palety kolorów.
Pióro - wektor X Domyślnie czarne. Używane przez grafikę
wektorową przy rysowaniu odcinków i krawędzi figur geometrycznych.
Tryb wypełniania wielokątów - wektor X Domyślnie ALTERNATE (zmienny). Wyznacza
sposób wypełniania wnętrza wielokątów o przecinających się krawędziach.
Tryb
Stretch-Blt() - raster X Domyślnie BLACKONWHITE (czarny na białym). Wyznacza sposób usuwania pikseli z bitmapy ściskanej funkcją StretchBlt (). Ustawiany wywołaniem SetStretchBltMode ().
Wyrówna- nie tekstu - (tekst) X - Domyślnie TA_LEFT l TA_TOP. Wyznacza sposób wyrównania tekstu w obszarze rysowania. Ustawiany wywołaniem SetTextAlign ().
Kolor tekstu -(tekst) - Domyślnie czarny. Ustawiany wywołaniem
SetTextColor()
Dodatkowy
odstęp
tekstu - (tekst) - X - Domyślnie 0. Wyznacza ilość pikseli dodawanych do komórki każdego ze znaków napisu. Ustawiany wywołaniem SetTextCharacterExtra () .
Justowanie tekstu - tekst X - Domyślnie 0,0. Określa, ile pikseli należy dodać do każdej spacji, aby linia tekstu miała zadaną szerokość. Ustawiany wywołaniem
SetTextJustification()
Granice widoku - X X X - Domyślnie 1,1. Używany w trybach odwzorowania MM_ISOTROPIC orazMM_ANISOTROPIC. Pomaga przeskalować rysunek przez zmodyfikowanie skali współrzędnych urządzenia. Ustawiany wywołaniem
SetViewportExt() .
Początek widoku - X X X - Domyślnie 0,0. Określa położenie początku układu współrzędnych urządzenia. Ustawiany wywołaniem
SetYiewportOrg().
Granice okna - X X X - Domyślnie 1,1. Używany w trybach odwzorowania
MM_ISOTROPIC orazMM_ANlSOTROP!C. Pomaga przeskalować rysunek przez zmodyfikowanie skali współrzędnych okna. Ustawiany wywołaniem
SetWindowExt() .
Początek okna - X X X - Domyślnie 0,0. Określa położenie początku układu współrzędnych okna. Ustawiany wywołaniem
SetWindowOrg().
Gdy za pomocą AppWizarda tworzysz jedno- lub wielodokumentowy program, w klasie widoku znajdziesz funkcję onDraw (). Jest ona automatycznie tworzona przez AppWizarda i jest wywoływana za każdym razem, gdy okno widoku otrzyma komunikat WM_PAINT (więcej informacji na temat tego komunikatu podamy w następnej sekcji). Do funkcji przekazywany jest wskaźnik do obiektu klasy CDC, używanego do rysowania w klasie widoku. Listing 9.1 przedstawia prostą funkcję OnDraw () rysującą prostokąt w oknie widoku.
Listing 9.1. Funkcja OnDra\v() rysujÄ…ca prostokÄ…t_____________ ____
void CMyYiew::OnDraw(CDC *pDC) {
CMyDoc *pDoc = GetDocument();
ASSERT_VALID(pDoc);
// W tym miejscu dodaliśmy kod rysujący prostokąt
CRect Rect;
Rect.left = Rect.top = 10;
Rect.right = Rect.bottom = 100;
// Tworzymy czerwony pędzel.
// Makro RGB pozwala na podanie składowych R,G oraz B.
// Ich zakres należy od O do 255.
CBrush Brush(RGB(255, 0, 0)};
// Rysujemy prostokÄ…t za pomocÄ… funkcji GDI FillRect() pDC->FillRect( &Rect, &Brush);
}
Domyślne ustawienia kontekstu urządzenia stanowią dobry punkt wyjścia do rysowania. Najlepszym sposobem nauki korzystania z GDI jest pisanie małych programów rysujących kilka prostych obiektów. Gdy zaczniesz eksperymentować z nowymi funkcjami rysunkowymi w celu uzyskania sensownych wyników, nie będziesz musiał dokonywać wielu zmian w GDI. Gdy lepiej zrozumiesz, jak działają różne funkcje rysunkowe, będziesz mógł zacząć analizować rolę poszczególnych atrybutów rysowania.
Komunikat WM_ PAINT
W przypadku rysowania w oknie najważniejszym komunikatem jest WM_PAINT. Mówiąc najprościej, ten komunikat prosi okno, by odrysowało swoją zawartość. Komunikat WM_PAINT może pojawić się z wielu powodów. Na przykład, inne okno mogło zostać otwarte ponad Twoim oknem, niszcząc jego zawartość, lub użytkownik mógł zdecydować się na rozwinięcie uprzednio zwiniętego okna, lub po prostu otrzymujesz ten komunikat, gdyż Twój program właśnie zaczyna działanie.
Bez względu na powód wysłania komunikatu WM_PAINT, oznacza on, że system nie mógł sam odtworzyć zawartości obszaru roboczego okna. Oryginalny zespół twórców Windows przeanalizował kilka różnych sposobów, w jakie system mógłby zapamiętać dane okna. Jednym z rozwiązań było utworzenie migawki z okna tuż przed przysłonięciem go przez inne okno, jednak ilość danych konieczna do zapamiętania sprawiła, że to rozwiązanie jest niepraktyczne.
W środowisku znakowym ekran pełen danych mógł być przechowany w małym buforze o rozmiarze 2K. W graficznym środowisku Windows, 256-kolorowy ekran o rozdzielczości 640 x 480 pikseli zajmuje około 300K. Przy kilku działających aplikacjach zapamiętanie okien mogłoby szybko pochłonąć nawet parę megabajtów. W przypadku pierwszych wersji Windows było to ceną zbyt wygórowaną, gdy cała pamięć operacyjna miała rozmiar jednego megabajta! Nawet w przypadku Windows 98, zaprojektowanych do pracy już przy 16 megabajtach pamięci, cały magabajt przeznaczony na zapamiętanie wyglądu okien to za dużo.
Nawet jeśli pamięć nie byłaby problemem, pozostaje inne zagadnienie. Jeśli system przechowywałby migawki okien i nastąpiłaby zmiana w którejś z części okna, bufor stałby się bezużyteczny. W takich sytuacjach aplikacja i tak musiałby odświeżyć zawartość swojego okna. W rezultacie cała pamięć i czas procesora poświęcony na zachowanie migawki zostałyby stracone.
Komunikat WM_PAINT stanowi rozwiązanie problemu utrzymania aktualnej zawartości okna. W Windows cały obowiązek utrzymania zawartości okna spoczywa wyłącznie na aplikacji. Komunikat WM_PAINT oferuje mechanizm, poprzez który system informuje okno, że powinno odświeżyć swoją zawartość. Jedyne więc co musisz zrobić, to zapewnić, by program cały czas posiadał aktualne informacje o swoim stanie i odpowiednio wykorzystał je przy rysowaniu zawartości okna.
Współrzędne rysowania
Po stworzeniu kontekstu urządzenia jego domyślnymi współrzędnymi są piksele, czyli fizyczne jednostki urządzenia. Jednak w razie potrzeby możesz zmienić jednostki układu na cale, centymetry czy punkty drukarskie. Możesz także określić skalę rysunku. Jednak domyślnymi jednostkami, którymi będziemy posługiwać się w tym rozdziale, są fizyczne jednostki urządzenia (płksele). W terminologii kontekstu urządzenia będziemy posługiwali się trybem odwzorowania znanym jako MM_TEXT.
Domyślny początek układu - tj. współrzędne (O, 0) -jest położony w lewym górnym rogu powierzchni rysunkowej. Na ekranie oznacza to lewy górny róg obszaru roboczego okna. Na kartce wydruku oznacza to lewy górny róg kartki. Tak jak można zmienić układ współrzędnych, można także zmienić jego początek. Jednak w przykładach w tym rozdziale pozostaniemy przy domyślnym początku.
Domyślne kierunki osi x i y w trybie MM_TEXT są nieco inne niż zwykle przyjęte osie układu kartezjańskiego. Domyślny układ współrzędnych w oknie jest nazywany układem współrzędnych obszaru roboczego. Ponieważ ten sam układ jest używany jako układ współrzędnych myszy, współrzędne obszaru roboczego dobrze nadają się do sprawdzania, który obiekcie w oknie użytkownik kliknął myszką.
Generowanie komunikatu WM_PAINT
We wprowadzeniu do komunikatu WM_PAINT stwierdziliśmy, że to najważniejszy z komunikatów rysunkowych. Stwierdziliśmy także, że nigdy nie możesz być pewien przyczyny jego otrzymania: być może okno zmieniło rozmiar lub mogło zostać odsłonięte przez inne okno. W każdym razie nie musisz się tym przejmować, lecz jedynie odświeżyć zawartość okna.
W pewnych przypadkach przyczyną tego komunikatu nie jest zdarzenie zewnętrzne, takie jak zmiana rozmiaru okna przez użytkownika. Zamiast tego może wystąpić wewnętrzny czynnik, który tylko Ty możesz rozpoznać. Na przykład, użytkownik mógł wprowadzić w oknie tekstowym jakiś nowy tekst lub w arkuszu kalkulacyjnym mógł wstawić lub usunąć kolumnę danych. Bez względu na przyczynę, gdy zmieniają się dane wyświetlane w oknie, powinieneś wygenerować komunikat odświeżenia zawartości okna.
Aby wygenerować ten komunikat, ogłaszasz, że część okna stała się nieważna. Służy do tego kilka funkcji składowych klasy cwnd. Najprostszą z nich jest funkcja invalidate (), gdyż wymaga przekazania tylko jednego parametru, wartości logicznej określającej, czy przed przystąpieniem do odrysowania zawartości okna ma zostać także odrysowane tło. Ta funkcja informuje system, że nieważny stał się cały obszar roboczy okna:
// Wymuszenie odrysowania całej zawartości okna // z uprzednim odmalowaniem tła. Invalidate(TRUE);
Kolejną funkcją generującą komunikat odmalowania jest invalidateRect (). Ta funkcja pozwala na określenie prostokąta przeznaczonego do odrysowania. W przypadku wolniejszego sprzętu pomaga to zminimalizować migotanie okna na ekranie. Jednak nawet w najszybszych komputerach powinieneś minimalizować czas odświeżania, ponieważ zbyt dużo migotania ekranu bardzo denerwuje użytkowników. Z punktu widzenia ergonomii takie migotanie powoduje zmęczenie - które nie tylko denerwuje użytkowników, ale zmusza ich także do częstszych przerw w pracy przy korzystaniu z oprogramowania. Oto jak możesz zażądać odświeżenia prostokąta ograniczonego współrzędnymi 10, 10 oraz 100, 100:
// Zdefiniowanie prostokąta odświeżania i zażądanie // komunikatu WM_PAINT. CRect Rect(10, 10, 100, 100); InvąlidąteRect( &Rect, TRUE);
W porównaniu z innymi komunikatami - na przykład komunikatami myszy i klawiatury - komunikaty rysowania mają bardzo niski priorytet. W końcu, gdy nadejdą kolejne dane, konieczne będą dodatkowe komunikaty rysowania. Gdy nakazujesz odświeżenie okna, przed otrzymaniem komunikatu WM_PAINT Twoje okno może otrzymać wiele innych komunikatów.
W pewnych przypadkach może zajść potrzeba podniesienia priorytetu komunikatu WM_PAINT. Aby wymusić natychmiastowy komunikat odmalowania, wywołaj funkcję UpdateWindow (). Możesz to uczynić na przykład wtedy, gdy już funkcją invalidate () zażądałeś odświeżenia, jednak chcesz natychmiast coś narysować w świeżo odrysowanym oknie. Niczym niezwykłym nie jest zadeklarowanie części okna jako nieważnej, a następnie natychmiastowe wymuszenie odświeżenia:
Invalidaterect(&Rect, UpdateWindow();
TRUE);
Podsumowując, gdy chcesz, by obraz w oknie uległ zmianie, unieważnij obszar, w którym zmiany mają być widoczne. Właśnie ten obszar zostanie odrysowany w następnym komunikacie WM_PAINT. Wywołanie UpdateWindow () nie deklaruje, że któryś z obszarów okna jest nieważny, a jedynie po prostu przyspiesza odrysowanie już unieważnionych obszarów. Jeśli nie ma unieważnionych obszarów, wywołanie UpdateWindow () w żaden sposób nie wpływa na zawartość okna, Założenia Windows odnoszące się do rysowania ograniczają odrysowywanie - poprzez obszary obcinania - jedynie do tych obszarów okna, które zostały unieważnione.
Jak wspominaliśmy wcześniej, wywołanie updatewindow () wymusza natychmiastowe odrysowanie, tak że możesz rysować w oknie o zaktualizowanej zawartości. Ma to jednak sens, gdy rysujesz w odpowiedzi na inne komunikaty niż WM_PAINT. Pomówmy więc o tym i zastanówmy się nad kilkoma związanymi z tym zagadnieniami.
Rysowanie poza komunikatem WM_PAINT
W pewnych przypadkach musisz narysować coś w odpowiedzi na komunikat inny niż WM_PAINT. Jedna ze szkół sugeruje, by rysować tylko wtedy, gdy otrzymasz komunikat rysowania. W ten sposób centralizujesz kod rysunkowy, dzięki czemu staje się on bardziej stabilny, co zawsze jest pożyteczne.
Jednak aby poprawić wydajność programu lub polepszyć interakcję z użytkownikiem, możesz być zmuszony do rysowania w odpowiedzi na inne komunikaty. Weźmy na przykład okno do edycji tekstu. Jeśli program generowałby komunikaty odrysowywania przy każdym wpisanym znaku, działałby bardzo wolno (szczególnie wyraźnie objawiałoby się to w wolniejszych komputerach). Narzut związany z ciągłym tworzeniem komunikatów odświeżania zajmowałby zbyt wiele czasu procesora.
Weźmy także program rysunkowy pozwalający użytkownikowi na wskazanie obiektu graficznego i przesuwanie go w obrębie okna. W trakcie przesuwania obiektu trzeba go odrysować w celu wskazania użytkownikowi nowego położenia. Także w tym przypadku tworzenie komunikatu odświeżania dla każdego nowego polecenia powodowałoby za duży narzut. W obu przypadkach powinieneś rysować także w odpowiedzi na komunikaty inne niż WM_PAINT.
Przed tym jednak słowo ostrzeżenia. Jeśli rysujesz tymczasowy obiekt - na przykład rozciągliwy gumowy prostokąt - nie ma problemu. Jeśli jednak rysujesz bardziej trwałe obiekty, Twój kod rysunkowy musi o nich wiedzieć. W końcu nie masz pojęcia, kiedy użytkownik wymusi odświeżenie okna. Wystarczy na przykład, że zwinie i rozwinie okno lub przysłoni je oknem innej aplikacji. Gdy Twoja aplikacja stanie się aktywna, będziesz musiał obsłużyć komunikat rysowania dla całego okna. Wszelki brak synchronizacji pomiędzy kodem rysunkowym i nierysunkowym stanie się natychmiast boleśnie widoczny dla użytkowników.
Odkładając na razie tego typu zagadnienia, rysowanie w odpowiedzi na inne komunikaty jest względnie łatwe. Zamiast używania klasy coc użyjesz obiektu klasy cciientoc. Obszar obcinania dla klasy cclientDC jest ustawiony na cały obszar roboczy okna. Na przykład, listing 9.2 pokazuje, w jaki sposób można wypisać napis "Hello Windows 98" wyśrodkowany w miejscu, w którym użytkownik kliknął lewym przyciskiem myszy.
Listing 9.2. Wypisanie "Hello Windows 98" z użyciem klasy CClientDC______________
void CMyYiew::OnLButtonDown(UINT nFlags, CPoint point) {
// Tworzenie klasy CClientDC.
CClientDC ClientDC(this);
// Tworzenie obiektu CString zawierajÄ…cego napis. CString strText = "Hello Windows 98";
// Pobranie rozmiaru napisu w tym, kontekście urządzenia. CSize size = ClientDC.GetTextExtent(strText);
// Obliczenie współrzędnych tak aby tekst został wyśrodkowany // względem miejsca kliknięcia myszką. int xCentered = point.x - (size.cx/2); int yCentered = point.y - (size.cy/2);
// Wyrysowanie tekstu w oknie
ClientDC.TextOut(xCentered, yCentered, strText);
}
Czego się więc dotąd nauczyliśmy? Wiesz już, jak otrzymać kontekst urządzenia (DC) oraz jak przy domyślnych atrybutach tego kontekstu wypisać pojedynczą linię tekstu. Choć domyślne atrybuty kontekstu urządzenia stanowią dobry punkt wyjścia, z pewnością chcesz spróbować je zmodyfikować w celu zmiany wyglądu tekstu. Właśnie tym zajmiemy się w następnej kolejności.
Manipulowanie tekstem
Aby zmienić wygląd tekstu, musisz zmienić atrybuty kontekstu urządzenia wpływające na postać tekstu. Jak podawaliśmy w tabeli 9.1, bezpośrednio na wygląd tekstu wpływa osiem atrybutów. Najefektywniejszym sposobem zrozumienia działania atrybutów kontekstu urządzenia jest sprawdzenie tego w rzeczywistym programie. Jeśli masz pod ręką komputer, możesz spróbować stworzyć za pomocą AppWizarda niewielki program SDI, w którym będziesz mógł przetestować działanie omawianych poniżej atrybutów.
Obsługa koloru przez GDI
Zanim zmienisz kolor tekstu, powinieneś zrozumieć, jak w GDI określa się kolor. Podstawowym typem przechowywania koloru w Windows jest COLORREF. Najprostszym sposobem definiowania koloru jest użycie makra RGB (}, pobierającego trzy parametry określające składową czerwoną (ang. red), zieloną (ang. greeri) oraz niebieską (ang. blue) koloru. Dla każdego komponentu musisz podać wartość z zakresu od O do 255. Na przykład, oto trzy wartości koloru, po jednej dla każdej z barw podstawowych:
COLORREF crRed = RGB(255, O, 0); COLORREF crGreen = RGB(O, 255, 0); COLORREF crBlue = RGB(0, O, 255);
Choć może to wydawać się oczywiste, jednak występuje kilka czynników sprawiających, że definiowanie koloru jest bardziej złożone niż to się na pierwszy rzut oka wydaje. W GDI takie definicje kolorów są nazywane kolorami logicznymi. W tym kontekście, logiczny nie oznacza boolowski. Zamiast tego, ten termin jest używany w celu dokonania
192
Część II • Podstawy programowania Windows
rozróżnienia pomiędzy kolorami logicznymi a kolorami fizycznymi, czyli takimi, jakie może wyświetlić fizyczne urządzenie graficzne. Urządzenie o wysokiej rozdzielczości, pracujące z 16 milionami kolorów, nie powinno mieć problemu z wyświetleniem różnych kombinacji wartości składowych czerwonej, zielonej i niebieskiej. Jednak w przypadku czarno-białego wyświetlacza historia jest już zupełnie inna. Aby otrzymać ilość dostępnych kolorów, wywołaj funkcję coc: : GetDeviceCaps (), tak jak w poniższym przykładzie:
int nColors = pDC->GetDeviceCaps(NUMCOLORS);
W przypadku kolorowych monitorów system tworzy domyślną paletę z dwudziestoma kolorami. Dla urządzeń obsługujących jedynie 16 kolorów używanych jest pierwszych 16 z tych dwudziestu kolorów (reszta jest zamieniana na szare lub białe). W przypadku urządzeń obsługujących więcej niż 16 kolorów, owe dwadzieścia podstawowych kolorów tworzy domyślny zestaw kolorów dla aplikacji. Listing 9.3 zawiera standardowe wartości RGB dla tych kolorów.
Listing 9.3. Wartości RGB dla standardowych kolorów
/ / Dla urządzeń obsługujących 16 kolorów
const COLORREF g crBlack =RGB( 0, 0, 0) ;// czarny
const COLORREF g_crYellow=RGB(255, 255, 0);// żółty
const COLORREF g_crDkYellow =RGB(128, 128, 0);// ciemnożółty
const COLORREF g_crRed=RGB (255, 0, 0);/ czerwony
const COLORREF g crDkRed=RGB(128, 0, 0);// ciemnoczerwony
const COLORREF g_crMagenta=RGB(255, 0, 255);// fiolet
const COLORREF g_crDkMagenta=RGB(128,0,128);//ciemno fioletowy
const COLORREF g_crBlue=RGB(0, 0, 255);// błękit
const COLORREF g_crDkBlue=RGB(0, 0, 128);// ciemnoblękitny
const COLORREF g_crCyan=RGB(0, 255, 255); // cyjan
const COLORREF g_crDkCyan=RGB(0, 128, 128);// ciemnoturkusowy
const COLORREF g_crGreen=RGB(0, 255, 0);// zielony
const COLORREF g_crDkGreen=RGB(0, 128, 0);// ciemnozielony
const COLORREF g_crGray=RGB1192, 192, 192); // szary
const COLORREF g_crDkGray=RGB (128, 128, 128);// ciemnoszary
const COLORREF g crWhite=RGB(255, 255, 255); // biały
// Dodatkowe cztery kolory dla=wyświetlaczy obsługujących więcej
// niż 16 kolorów
const COLORREF g_crLtYellow=RGB(255, 251, 240);// jasnożółty
const COLORREF g_crLtGreen=RGBU92, 220, 192);// jasnozielony
const COLORREF g_ crLtBlue=RGB(166, 202, 240); // jasnobłękitny
const COLORREF g_crMedGray=RGB(160, 160, 164);// szary
W przypadku kart graficznych obsługujących więcej niż szesnaście kolorów możesz zawsze zdefiniować własną paletę. Gdy wybierzesz ją w kontekście urządzenia, będziesz miał dostęp do dużo szerszego zakresu kolorów. Jednak korzystanie z alternatywnych palet jest tematem zaawansowanym wykraczającym poza zakres tej książki. Domyślne palety doskonale wystarczają w większości przypadków i mają zastosowanie jedynie w bardzo specjalistycznych i wymagających aplikacjach.
Kolor tekstu
Gdy wiesz już, jak użyć makra RGB do wyboru koloru, przyjrzyjmy się atrybutom kontekstu urządzenia związanym z kolorem tekstu. Należą do nich: kolor tekstu, kolor tła oraz tryb rysowania tła. Gdy otrzymujesz kontekst urządzenia, wspomniane atrybuty przyjmują domyślne wartości, zebrane w tabeli 9.2.
Tabela 9.2. Domyślne ustawienia kontekstu urządzenia
Atrybut koloru tekstu
Domyślne ustawienie
Kolor tekstu-RGB (O, O, O): czarny tekst
Kolor tła-RGB (255, 255, 255): białe tło
Tryb rysowania tła-OPAQUE (tło nieprzezroczyste)
Aby ustawić kolor używany do rysowania tekstu, wywołaj funkcję CDC : : SetTextCoior (}. Ta funkcja jest zadeklarowana następująco:
COLORREF SetTextColor( COLORREF crColor );
Funkcja przyjmuje jako parametr wartość koloru, zwracając przy tym poprzedni kolor rysowanego tekstu. Na przykład, aby otrzymać czerwony napis:
pDC->SetTextColor(RGB(255, 0, 0) );
pDC->TextOut(x, y, "To jest napis w kolorze czerwonym.");
Aby ustawić kolor tła tekstu, wywołaj funkcję CDC: :SetBkColor (). Ta funkcja jest zadeklarowana następująco:
COLORREF SetBkColor( COLORREF crColor );
Funkcja przyjmuje jako parametr wartość koloru tła pomiędzy znakami, zwracając przy tym poprzedni kolor tła rysowanego tekstu. Na przykład, aby otrzymać napis na czarnym tle:
pDC->SetBkColor(RGB(0, 0O, 0) );
pDC->TextOut(x, y, "To jest napis na czarnym tle.");
Ostatnim atrybutem kontekstu urządzenia odnoszącym się do koloru tekstu jest tryb rysowania tła. Jest to po prostu dwustanowy przełącznik. W domyślnym ustawieniu, OPAQUE, GDI przy rysowaniu tekstu korzysta z koloru tła. W drugim ustawieniu, TRANSPARENT, GDI przy rysowaniu tekstu nie korzysta z koloru tła, pozostawiając je przezroczyste. Aby ustawić tryb rysowania tła, wywołaj funkcję CDC: : SetBkMode (). Ta funkcja jest zadeklarowana następująco:
COLORREF SetBkMode( int nBkMode );
Jeśli chcesz umożliwić użytkownikom wybór koloru tekstu i tła, prawdopodobnie skorzystasz ze standardowego okna dialogowego wyboru koloru. Standardowe okna dialogowe stanowią część systemu Windows 98. Za pomocą kilku linii kodu możesz otrzymać w pełni funkcjonalne okno dialogowe:
CColrDialog ColorDialog;
if( ColorDialog.DoModal() == IDOK){
COLORREF Color = ColorDialog.GetColor();
CString strText;
strText.Format("Wybrano kolor RGB( %d, %d, %d )", GetRYalue(Color),
GetGValue(Color), GetBValue(Color) AfxMessageBox(strText);
}
Wyrównywanie tekstu
Wyrównanie tekstu określa powiązanie pomiędzy współrzędnymi tekstu (x, y) a obszarem wypisywanego tekstu. Domyślne ustawienie wyrównuje tekst na prawo i poniżej współrzędnych tekstu. Aby ustawić wyrównanie tekstu, wywołaj funkcję CDT: : setTextAlign (). Ta funkcja wymaga podania pojedynczego parametru stanowiącego kombinację znaczników zebranych w tabeli 9.3. Znaczniki w każdej z kolumn wzajemnie się wykluczają- tzn. musisz wziąć po jednym znaczniku z każdej z kolumn. Pierwszy wiersz tabeli 9.3 zawiera ustawienia domyślne, wypisane pogrubioną czcionką.
Tabela 9.3. Stałe -wyrównania tekstu
Wyrównanie w poziomie, Wyrównanie w pionie, Aktualizacja bieżącej pozycji
TA_RIGHT , TA_TOP,TA_NOUPDATECP TA_CENTER, TA_BASELINE,TAJJPDATECP TA_RIGHTT ;A_BOTTOM,
Domyślne ustawienia możesz zmienić wtedy, gdy masz zamiar łączyć w jednej linii tekst w różnych rozmiarach (lub nawet tekst w tym samym rozmiarze, ale wypisany różną czcionką). Domyślne ustawienie w pionie, TA_TOP, da dziwne wyniki jeśli samodzielnie nie dostosujesz wartości y. Oto jak dostosować wyrównanie tekstu do różnych wysokości czcionek:
pDC->SetTextAlign( TA_LEFT | TA_BASELINE) ;
Zmiana wyrównania tekstu jest wygodna także wtedy, gdy chcesz dopasować prawą krawędź tekstu do jakiegoś innego obiektu graficznego. Choć możesz obliczyć wartość x, jednak łatwiej jest wyrównać tekst następująco:
pDC->SetTextAlign( TA_RIGHT| TA_TOP);
Inna sytuacja związana ze zmianą wyrównania tekstu występuje wtedy, gdy przy wypisywaniu tekstu chcesz użyć bieżącej pozycji w kontekście urządzenia. Bieżąca pozycja to para współrzędnych (x, y) zwykle używana przy grafice wektorowej. Ustawiając znacznik TA_UPDATECP, możesz wykorzystać ją także przy wypisywaniu tekstu:
pDC->SetTextAlign( TA UPDATECP);
Przy tym ustawieniu jedynymi współrzędnymi określającymi położenie wypisywanego tekstu jest bieżąca pozycja. Po wyrysowaniu linii tekstu bieżąca pozycja jest aktualizowana, dzięki czemu można wyrysować następny tekst. Do ustawiania bieżącej pozycji służy wywołanie funkcji coc: :MoveTo (). Ponieważ w poniższym fragmencie wykorzystujemy znacznik TA_UPDATECP, tekst jest wypisywany począwszy od pozycji 12, 92, a nie od współrzędnych podanych w wywołaniu funkcji TextOut ():
// Żądanie aktualizacji bieżącej pozycji pDC->SetTextAlign( TA_UPDATECP );
// Ustawienie bieżącej pozycji
pDC->MoveTo( 12, 92 };
// Mimo że podajemy współrzędne, są one ignorowane pDC->TextOut (100, 200,
"Ten napis nie jest wyrysowany w pozycji 100, 200");
Justowanie tekstu
Dwa ostatnie atrybuty tekstowe z podstawowego zestawu to Justowanie tekstu oraz dodatkowe odstępy pomiędzy wyrazami. Każdy z nich pomaga w wyrównaniu linii tekstu do zadanych marginesów. Tekst zostaje wyrównany do marginesów w celu wyrysowania na ekranie tego, co pojawi się także na wydruku.
Aby dostosować ustawienia obu atrybutów, wywołaj funkcje coc: : SetText Justification () oraz CDC: :SetTextCharacterExtra () . Funkcja CDC :: SetText Justif ication () pozwala na określenie ilości pikseli dodawanej do każdego znaku spacji. To ustawienie reprezentuje dodatkowe powiększenie spacji stosowanych przy danej czcionce. Jeśli potrzebne jest wypełnienie większej przestrzeni, zamiast tego zastosuj funkcję SetText-characterExtra (). Powoduje ona dodanie dodatkowych pikseli wolnego miejsca do każdego znaku (a nie tylko do spacji), dając w rezultacie efekt rozstrzelenia napisu.
Powinniśmy wspomnieć, że oba atrybuty datują się z samych początków Windows i że od tego czasu opracowano kilka technik pozwalających uzyskać te same rezultaty. Na przykład, funkcja ExtTextOut () umożliwia pełną kontrolę nad szerokością poszczególnych znaków. Jeśli jesteś skłonny poświęcić czas na związaną z tym pracę, wynik może być wart zachodu. Dodanie do Windows czcionek TrueType także spowodowało zmniejszenie różnicy pomiędzy tym, co widać na ekranie a tym, co pojawia się na wydruku. Aplikacja korzystająca wyłącznie z czcionek TrueType może automatycznie osiągnąć dużą zgodność obrazu na ekranie z obrazem na wydruku.
Wszystkie te podstawowe atrybuty tekstu wymieniliśmy na początku, gdyż są zwyczajnie , najprostsze. W następnej sekcji skierujemy uwagę ku atrybutom tekstu mającym dużo większy wpływ na postać napisów, a mianowicie czcionkom.
Czcionki
Ta sekcja obejmuje podstawy tworzenia i wykorzystywania czcionek. Najprostszym podejściem do pracy z czcionkami jest użycie magazynowych czcionek GDI. Jednak aby odwołać się do szerszego zakresu czcionek zainstalowanych w typowym systemie Windows, konieczne będzie stworzenie obiektu c Font.
Czym jest czcionka?
Czcionka (ang.font) jest kolekcją obrazków używanych do reprezentowania znaków o tym samym rozmiarze i stylu. Czcionki określa się zwykle przez podanie rozmiaru oraz nazwy, takiej jak Arial czy Times New Roman, a czasem również przez podanie stylu. W odniesieniu do konkretnych czcionek używa się terminów w rodzaju 18-punktowy Times Roman czy 8-punktowy wytłuszczony Arial.
Jeśli nigdy dotąd nie pracowałeś z czcionkami, możesz być wstrząśnięty na wieść, że istniej ą dosłownie tysiące różnych czcionek. Wraz z Windows 98 dostarczany jest tylko podstawowy zestaw, który jednak może być łatwo uzupełniony. Niczym niezwykłym jest fakt, że niektóre aplikacje Windows instalują w systemie własne czcionki. Na przykład, popularny pakiet CorelDRA fFjest dostarczany wraz z kolekcją ponad 800 różnych czcionek.
Zanim jednak zagłębimy się w dostosowywanie czcionek ekranowych, konieczne jest zrozumienie, w jaki sposób różne obiekty graficzne mogą być powiązane z kontekstem urządzenia. Proces wiązania obiektu graficznego z kontekstem urządzenia nazywa się wybieraniem obiektu w kontekście urządzenia.
Wybieranie obiektów w kontekście urządzenia
Za każdym razem gdy w kontekście urządzenia wybierasz niemagazynowy obiekt GDI (tj. taki, którego Windows nie ma w podręcznym składziku), dobrym pomysłem jest zapamiętanie, jaki obiekt był poprzednio wybrany. Używając funkcji Selectobject (), zawsze otrzymujesz wskaźnik do poprzednio wybranego obiektu. Na przykład, gdy w celu wybrania nowo stworzonej czcionki w kontekście urządzenia użyjesz funkcji Selectobject o, otrzymasz wskaźnik do poprzednio wybranego obiektu CFont. Otrzymany wskaźnik może zostać zapamiętany, podobnie jak w poniższym przykładzie:
CFont *p01dFont;
pOldFont = pDC->SelectObject(SNewFont);
Gdy zakończysz korzystanie z niemagazynowej czcionki (lub innego niemagazynowego obiektu GDI), musisz wybrać w kontekście urządzenia poprzednio wybrany obiekt. Przy próbie usunięcia obiektu GDI (lub przy próbie usunięcia go przez destruktor) wciąż wybranego w kontekście GDI, próba usunięcia się nie powiedzie, a pamięć zajmowana przez obiekt GDI zostanie zablokowana do momentu zakończenia działania aplikacji. Oto końcowa linia kodu, którą powinieneś dopisać po zakończeniu korzystania z niemagazynowego obiektu GDI:
pDC->SelectObject(pOldFont);
Wybieranie czcionek magazynowych
Aby wybrać czcionkę, najpierw musisz ją mieć. W programie MFC oznacza to posiadanie poprawnie zainicjalizowanego obiektu CFont. Najprostszym sposobem otrzymania czcionki jest użycie predefiniowanej czcionki magazynowej dostarczanej przez Windows. Wybór jednej z czcionek magazynowych przedstawia poniższy fragment kodu:
CFont fontStock; fontStock.CreateStockObject(ANSI FIXED FONT)
Podobnie jak inne obiekty rysunkowe czcionka musi przed użyciem zostać połączona z kontekstem urządzenia. Aby połączyć czcionkę z kontekstem urządzenia, wywołaj funkcję CDC: : Selectobject (). Na przykład, poniższy fragment kodu łączy utworzoną przed chwilą czcionkę z kontekstem urządzenia (wybiera ją w kontekście urządzenia):
pDC->SelectObject(&fontStock);
Dopóki w kontekście urządzenia nie wybierzemy innej czcionki, cały tekst rysowany za pomocą funkcji tekstowych będzie korzystał właśnie z tej czcionki.
Czcionki różnią się zasadniczo od innych atrybutów rysunkowych. Większość atrybutów to po prostu zmienne składowe przechowywane w kontekście urządzenia. Jednak czcionki są osobnymi obiektami systemu i posiadają własne życie, niezależne od kontekstu urządzenia. Tworząc obiekt czcionki, musisz pamiętać, aby go usunąć, gdy stanie się już niepotrzebny, gdyż w przeciwnym razie w niepotrzebny sposób będzie się marnowało miejsce w pamięci.
W praktyce oznacza to po prostu niszczenie każdego tworzonego przez Ciebie obiektu CFont. Destruktor tej klasy zapewnia, że nie nastąpi żadne marnotrawstwo pamięci systemowej. Jak jednak wyłapać te obiekty CFont, których zapomniałeś usunąć? Na szczęście narzędzia Visual C++ mogą Ci w tym wydatnie pomóc. Po prostu we wbudowanym w Visual C++ debuggerze uruchom wersję programu przeznaczoną do debuggowania. Gdy program zakończy działania, w oknie danych wyjściowych pojawi się pełna lista obiektów, które nie zostały usunięte.
W przypadku czcionek magazynowych nie następuje ich usuwanie nawet w przypadku usunięcia reprezentujących je obiektów CFont. Powód jest prosty: czcionki magazynowe są tworzone przez sam system z przeznaczeniem do wykorzystania przez wszystkie aplikacje.
Wybieranie czcionek niemagazynowych
Aby wybrać czcionkę inną niż czcionka magazynowa, powinieneś przedtem wysłać do GDI żądanie utworzenia czcionki. Jednym ze sposobów utworzenia czcionki jest użycie struktury LOGFONT - reprezentującej opis czcionki logicznej. Aby zażądać od GDI utworzenia czcionki, wypełnij pola tej struktury i przekaż ją do funkcji inicjującej klasę CFont, funkcji CFont: :CreateFontindirect (}. Słowo "indirect" (pośredni) w nazwie odnosi się do faktu, że funkcja otrzymuje wskaźnik do definicji obiektu.
Inna funkcja inicjująca, CFont: :CreateFont, otrzymuje serię parametrów, które zebrane razem odpowiadają zawartości struktury LOGFONT.
Czcionka logiczna przypomina opisywany wcześniej kolor logiczny, reprezentuje żądany ideał, na podstawie którego GDI i sterownik urządzenia starają się wybrać jak najbardziej dopasowaną czcionkę fizyczną. W podobny sposób jak w wyniku zażądania koloru czerwonego otrzymujesz kolor czarny, tak po zażądaniu 24-punktowej czcionki Times New Roman możesz otrzymać 12-punktową czcionkę Courier.
Najprostszym rozwiązaniem jest korzystanie zawsze z domyślnej czcionki urządzenia. Czcionka, którą możesz stworzyć na podstawie czcionki magazynowej, zawsze korzysta z domyślnych czcionek urządzenia. Zwykle domyślną czcionką urządzenia jest czcionka 10- lub 12-punktowa. Możesz także ograniczyć swoje żądania do około tuzina czcionek wbudowanych w Windows 98. Jak widać, po prostu nie należy prosić o coś, czym system nie dysponuje. Listing 9.4 pokazuje, w jaki sposób można stworzyć i wykorzystać czcionkę w funkcji OnDraw () programu.
Listing 9.4. Użycie funkcji OnDraw () \v celu stworzenia i wykorzystania czcionki_________
void CFraraeWnd::OnDraw(CDC* pDC) {
CFrameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);
// Tworzenie obiektu Cfont, a następnie stworzenie
// czcionki funkcjÄ… CreateFont.
CFont NewFont;
NewFont.CreateFont( 45, 45,
O, O, FW_DONTCARE,
FALSE, FALSE, FALSE,
OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
"Times New Roman" );
// Wybranie utworzonej czcionki w kontekście urządzenia
// i zapamiętanie poprzednio wybranej czcionki, aby można
// ją było później odtworzyć.
CFont *p01dFont;
pOldFont = pDC->SelectObject( &NewFont);
// Wypisanie tekstu
pDC->TextOut(10, 10, "Hello, Czionki!");
// Wybranie starej czcionki w kontekście urządzenia. pDC->SelectObject(pOldFont);
Funkcja CreateFont () oczekuje mnóstwa parametrów, które omówimy poniżej.
Pierwszym parametrem jest wysokość. Określa ona żądaną wysokość (w jednostkach logicznych) czcionki. Wysokość czcionki może być podana jako wartość większa od zera; w takim przypadku zostanie ona zamieniona na jednostki urządzenia i dopasowana
do wysokości istniejących czcionek. Może również mieć wartość 0; w takim przypadku jest wybierana sensowna (czyli średnia) domyślna wysokość. Można przekazać także wartość mniejszą od zera; w tym przypadku absolutna wartość wysokości określa rozmiar czcionki w jednostkach urządzenia. Absolutna wartość parametru nHeight po konwersji nie może przekraczać 16 384 jednostek urządzenia. Przy wybieraniu czcionek system (a właściwie tzw. font mapper) szuka największej czcionki o rozmiarze nie przekraczającym zadanego rozmiaru lub czcionki najmniejszej, gdy wszystkie czcionki są większe od żądanej.
Drugim parametrem jest szerokość. Określa ona średnią szerokość (w jednostkach logicznych) znaków czcionki. Jeśli podasz wartość O, stosunek wymiarów czcionki zostanie dopasowany do stosunku wymiarów urządzenia (na podstawie absolutnej wartości różnicy).
Trzeci parametr to kąt nachylenia linii bazowej. Określa on kąt (w dziesiątych częściach stopnia) pomiędzy linią bazową czcionki a linią poziomą. Kąt jest mierzony w kierunku przeciwnym do ruchu wskazówek zegara, zaś osią obrotu jest początek linii bazowej, lecz lewy górny narożnik ramki pierwszego znaku tekstu.
Czwarty parametr to (teoretycznie) kąt obrotu pojedynczego znaku względem linii bazowej. Kąt jest podawany w dziesiątych częściach stopnia i mierzony w kierunku przeciwnym do ruchu wskazówek zegara w trybach odwzorowania z osią pionową skierowaną w dół i w kierunku zgodnym z ruchem wskazówek w trybach odwzorowania z osią pionową skierowaną w górę.
Piąty parametr to grubość oczka czcionki w umownej skali od O do 1000. Choć nweight może mieć dowolną wartość z tego przedziału, jednak powszechnie używa się następujących stałych:
FW_DONTCARE //O
FW_THIN // 100
FW_EXTRALIGHT // 200
FW_ULTRALIGHT // 200
FW_LIGHT // 300
FW_NORMAL // 400
FW_REGULAR // 400
FW_MEDIUM // 500
FW_SEMIBOLD // 600
FW_DEMIBOLD // 600
FW_BOLD // 700
FW_EXTRABOLD // 800
FW_ULTRABOLD // 800
FW_HEAVY // 900
FW_BLACK // 900
Te wartości to jedynie przybliżenia; rzeczywisty wygląd zależy od samej czcionki. Niektóre czcionki posiadają jedynie grubości FW_NORMAL, FW_REGULAR i FW_BOLD. Jeśli użyjesz stałej FW_DONTCARE, zostanie wybrana czcionka o domyślnej grubości.
Szósty parametr to wartość logiczna określająca, czy czcionka jest pochylona; siódmy parametr to wartość logiczna określająca, czy czcionka jest podkreślona, zaś ósmy parametr to wartość logiczna określająca, czy czcionka jest przekreślona.
Dziewiąty parametr określa zestaw znaków czcionki. Dostępne predefmiowane wartości to:
ANSI_CHARSET
BALTIC_CHARSET
CHINESEBIG5_CHARSET
DEFAULT_CHARSET
EASTEUROPE_CHARSET
GB2312_CHARSET
GREEK_CHARSET
HANGUL_CHARSET
MAC_CHARSET
OEM_CHARSET
RUSSIAN_CHARSET
SHIFTJIS_CHARSET
SYMBOL_CHARSET
TURKISH_CHARSET
Zestaw znaków OEM jest zestawem znaków zależnym od systemu. W systemie mogą istnieć także czcionki z innymi zestawami znaków. Aplikacja używająca czcionki z nieznanym zestawem znaków nie może podejmować próby tłumaczenia ani interpretowania tekstów mających zostać wypisanych z użyciem tej czcionki. Zamiast tego napisy powinny zostać przekazane bezpośrednio sterownikowi urządzenia wyjściowego. Font mapper nie używa wartości DEFAULT_CHARSET. Aplikacja może więc jej użyć do zaznaczenia, że nazwa i rozmiar czcionki w pełni opisują czcionkę logiczną. Jeśli nie istnieje czcionka o podanej nazwie, może zostać zastąpiona czcionką o dowolnym zestawie znaków. Aby uniknąć nieoczekiwanych rezultatów, aplikacje powinny z rozwagą używać wartości DEFAULT_CHARSET.
Dziesiąty parametr określa żądną precyzję dopasowania czcionki fizycznej. Precyzja dopasowania określa dokładność, z jaką wybrana czcionka jest zgodna z wysokością, szerokością, orientacją znaków, pochyleniem i proporcjonalnością czcionki logicznej. Dostępne wartości to:
OUT_CHARACTER_PRECIS
OUT_DEFAULT_PRECIS
OUT_DEVICE_PRECIS
OUT_RASTER_PRECIS
OUT_STRING_PRECIS
OUT_STROKE_PRECIS
OUT_TT_PRECIS
Aplikacje mogą używać wartości OUT_DEVICE_PRECIS, OUT_RASTER_PRECIS oraz OUT_TT_PRECIS w celu określenia, w jaki sposób font mapper wybiera czcionkę w momencie, gdy system posiada więcej niż jedną czcionkę o danej nazwie. Na przykład, jeśli system posiada czcionkę System w formie rastrowej oraz w formie TrueType, podanie wartości OUT_TT_PRECIS wymusza na font mapperze wybranie wersji TrueType. (Podanie wartości OUT_TT_PRECISION wymusza wybór czcionki TrueType bez względu na to, do jakiego rodzaju czcionki odnosi się nazwa, nawet jeśli w systemie nie ma czcionki TrueType o podanej nazwie).
Jedenasty parametr określa żądaną precyzję obcinania. Precyzja obcinania definiuje sposób rysowania znaków tylko częściowo mieszczących się w regionie obcinania. Dostępne wartości są następujące:
CLIP_DEFAULT_PRECIS
CLIP_CHARACTER_PRECIS
CLIP_STROKE_PRECIS
CLIP_MASK
CLIP_EMBEDDED
CLIP_LH_ANGLES
CLIP_TT_ALWAYS
Aby użyć osadzonej czcionki przeznaczonej tylko do odczytu, aplikacja musi podać wartość CLIP_EMBEDDED. Aby utrzymać stałą rotację czcionek urządzenia, TrueType oraz wektorowych, aplikacja może użyć operatora OR w celu połączenia wartości CLIP_LH_ANGLES z inną wartością precyzji obcinania. Jeśli zostanie ustawiony bit CLIP_LH_ANGLES, obrót wszystkich czcionek zależy od orientacji układu współrzędnych. Więcej informacji na temat orientacji układu współrzędnych znajdziesz w opisie czwartego parametru, odnoszącego się do obrotu poszczególnych znaków. Jeśli bit CLIP_LH_ANGLES nie jest ustawiony, czcionki urządzenia są obracane zawsze w kierunku przeciwnym do ruchu wskazówek zegara, a od orientacji układu współrzędnych zależą obroty czcionek innych rodzajów.
Dwunasty parametr określa dokładność rysowania czcionki, czyli jak bardzo GDI musi się starać dopasować atrybuty czcionki fizycznej do atrybutów czcionki logicznej. Dostępnymi wartościami sąDEFAULT_QUALiTY, DRAFT_QUALITY oraz PROOF_QUALITY.
Trzynasty parametr określa proporcjonalność i rodzinę, do której należy czcionka. Dwa najmniej znaczące bity określają proporcjonalność czcionki i mogą być kombinacją wartości DEFAULT_PITCH, VARIABLE_PITCH lub FIXED_PITCH z wartościami
FF_DECORATIVE, FF_MODERN, FF_ROMAN, FF_SCRIPT lub FF_SWISS. Aplikacja może
określić proporcjonalność i rodzinę czcionki, używając operatora OR do połączenia stałej określającej proporcjonalność ze stałą określającą rodzinę. Rodziny czcionek opisują ogólny wygląd czcionki i służą określeniu rodzaju czcionki, gdy system nie dysponuje czcionką o podanej nazwie.
Czternasty i ostatni parametr to wskaźnik do zakończonego zerem łańcucha znaków określającego nazwę czcionki. Długość łańcucha nie może przekraczać trzydziestu znaków. Do wyliczenia wszystkich czcionek zainstalowanych w systemie można wykorzystać funkcję EnumFontFamiiies (). Jeśli ten parametr ma wartość NULL, GDI wybierze pierwszą czcionkę pasującą do pozostałych parametrów.
Pióra i pędzle
GDI udostępnia wiele funkcji API, które mogą zostać użyte przez aplikacje do tworzenia własnych piór i pędzli. Do funkcji tych należą CreatePen (), CreateSolidBrush (), CreateHatchBrushO oraz CreatePatternBrush (). MFC reprezentuje te funkcje w klasach CPen i CBrush, dzięki czemu pióra i pędzle mogą być obsługiwane poprzez wskaźniki, a nie poprzez "gołe" uchwyty GDI.
Pióra
MFC reprezentuje pióra poprzez obiekty klasy CPen. Najprostszym sposobem utworzenia pióra jest skonstruowanie obiektu klasy CPen, przekazując mu jednocześnie parametry definiujące wygląd pióra:
CPen Pen( PS SOLID, 1 ,RGB( 255, O, 0) )
Drugim sposobem utworzenia pióra GDI jest skonstruowanie niezainicjowanego obiektu klasy CPen i wywołanie jego funkcji CPen: :CreatePen():
CPen Pen; Pen.CreatePen(PS SOLID, l, RGB( 255,0,0 ));
Trzecią metodą jest skonstruowanie niezainicjowanego obiektu klasy CPen, wypełnienie struktury LOGPEN opisującej pióro i wywołanie funkcji CPen: :CreatePenindirect ():
CPen Pen;
LOGPEN LogPen;
LogPen.lopnStyle = PS_SOLID;
LogPen.lopnWidth = l;
LogPen.lopnColor = RGB( 255, 0, 0);
Pen.CreatePenlndirect( & LogPen );
Funkcje CreatePenO oraz CreatePenlndirect () w momencie pomyślnego utworzenia pióra zwracają wartość TRUE. Jeśli do stworzenia pióra użyjesz konstruktora klasy CPen, w wypadku niepowodzenia zostanie zgłoszony wyjątek CResourceException. Zdarza się to jednak tylko wtedy, gdy w systemie wystąpi krytyczny brak pamięci.
Pióro posiada trzy charakterystyczne elementy: styl, szerokość oraz kolor. We wszystkich dotychczasowych przykładach utworzyliśmy pióro o stylu PS_SOLID (jednolite), o szerokości jednej logicznej jednostki i w jasnoczerwonym kolorze. Pierwszy z trzech parametrów przekazanych konstruktorowi, funkcji CreatePenO lub CreatePenlndirect () określa styl pióra, definiujący rodzaj linii rysowanej przez pióro. Styl PS_SOLID tworzy pióro rysujące jednolitą, nieprzerwaną linię. Inne style piór zostały zebrane w tabeli 9.4.
Puste pióro nie rysuje niczego. Do czego mogłoby Ci się przydać takie pióro? Uwierz lub nie, ale jest ono bardzo często przydatne. Przypuśćmy, że chcesz narysować jednolite koło bez obrzeża. Jeśli narysujesz je za pomocą funkcji MFC coc:rEllipse(), Windows automatycznie narysuje otaczający okrąg za pomocą pióra aktualnie wybranego w kontekście urządzenia. Nie możesz nakazać funkcji Ellipset), by nie rysowała krawędzi, możesz jednak wybrać w kontekście urządzenia puste pióro, dzięki czemu koło nie będzie miało widocznej krawędzi. W podobny sposób są wykorzystywane puste pędzle. Jeśli chcesz, by okrąg posiadał obwód, lecz nie miał wypełnionego wnętrza, przed przystąpieniem do rysowania wybierz w kontekście urządzenia pusty pędzel.
Drugim parametrem przekazywanym funkcjom tworzącym pióro jest szerokość pióra -czyli szerokość linii rysowanych przez pióro. Szerokość pióra jest podawana w jednostkach logicznych, więc rzeczywista szerokość pióra na ekranie zależy od trybu odwzorowania. Pióra o stylach PS_SOLID, PS_NULL oraz PS_FRAME możesz tworzyć o dowolnej szerokości, jednak w przypadku innych stylów szerokość musi wynosić jedną jednostkę logiczną. Podanie, przy dowolnym stylu, szerokości zero powoduje utworzenie pióra o szerokości jednego piksela, bez względu na tryb odwzorowania.
Tabela 9.4. Style piór
Styl
Opis
PS_SOLID-Jednolita linia. PS_DASH-Linia kreskowana. Ten styl ma zastosowanie tylko wtedy,
gdy szerokość pióra wynosi nie więcej niż jedną jednostkę urządzenia.
PS_DOT-Linia kropkowana. Ten styl ma zastosowanie tylko wtedy,
gdy szerokość pióra wynosi nie więcej niż jednąjednostkę urządzenia.
PS_DASHDOT-Linia składająca się na przemian z kropek i kresek. Ten styl ma zastosowanie tylko wtedy, gdy szerokość pióra wynosi nie więcej niż jedną jednostkę urządzenia.
PS_DASHDOTDOT-Linia składająca się na przemian z kreski i dwóch kropek. Ten styl ma zastosowanie tylko wtedy, gdy szerokość pióra wynosi nie więcej niż jednąjednostkę urządzenia.
PS_NULL-Puste pióro.
PS_INSIDEFRAME-Tworzy pióro rysujące linię wewnątrz ramki otaczającej zamknięte kształty GDI.
Trzecim i ostatnim parametrem jest kolor pióra. Jest to 24-bitowa wartość RGB koloru, tworzona najczęściej za pomocą makra RGB, zamieniającego poszczególne wartości składowe koloru na odpowiednią wartość COLORREF.
Windows posiada trzy predefmiowane jednolite pióra o grubości l jednostki, których możesz używać bez wcześniejszego inicjowania obiektu CPen. Nazywane piórami magazynowymi, pióra te należą do grupy obiektów GDI znanych jako obiekty magazynowe (ang. stock objects}, przywoływanych w wyniku wywołania funkcji CreateStockObject ().
Dostępne pióra magazynowe są zdefiniowane jako WHITE_PEN (pióro białe), BLACK_PEN (pióro czarne) oraz NULL_PEN (puste pióro). Poniższy fragment kodu tworzy białe pióro magazynowe:
CPen Pen;
Pen.CreateStockObject( WHITE_PEN);
Poniższy kod tworzy identyczne białe pióro nie będące obiektem magazynowym:
CPen Pen;
Pen.CreatePen( PS_SOLID, l, RGB( 255, 255, 255 ) );
Jeśli żaden z podstawowych stylów piór nie spełnia Twoich potrzeb, klasa CPen udostępnia także osobny konstruktor przeznaczony do tworzenia piór kosmetycznych i geometrycznych, obsługujących wiele różnych opcji stylów. Na przykład możesz stworzyć pióro geometryczne rysujące wzór zdefiniowany w postaci bitmapy, możesz także precyzyjnie kontrolować sposób rysowania zakończeń (płaskie, zaokrąglone, kwadratowe) i załamań linii (ścięte, spiczaste lub zaokrąglone). Poniższy kod tworzy pióro geometryczne o szerokości 16 jednostek, rysujące jednolite zielone linie z płaskimi końcami. Gdy dwie linie się zetkną, powstające zagięcie jest zaokrąglane, tworząc gładkie przejście:
LOGPEN LogPen;
LogPen.lopnStyle = PS_SOLID; LogPen.lopnColor = RGB(O, 255, O ) ; CPen Pen( PS_GEOMETRIC | PS_SOLID | PS_ENDCAP_FLAT | PS_JOIN_ ROUND,&LogPen);
Windows 98 nakłada wiele ograniczeń na korzystanie z piór kosmetycznych i geometrycznych, z których nie najmniej ważną jest to, że aby style zakończeń i załamań działały, figury muszą być narysowane do ścieżek, a dopiero potem przetworzone za pomocą funkcji CDC: :StrokePath(). Ścieżkę definiuje się poprzez ujęcie poleceń rysunkowych pomiędzy wywołania funkcji coc: : BeginPath () i coc:: EndPath (), tak jak w poniższym przykładzie:
pDC->BeginPath(); pDC->MoveTo( O, O); pDC->LineTo( 200, 0); pDC->LineTo( 200, 200); pDC->LineTo( O, 200) ; pDC->CloseFigure() ; pDC->EndPath(); pDC->StrokePath();
Pędzle
Klasa MFC CBrush reprezentuje pędzle GDI. Pędzle występują w trzech podstawowych odmianach: jednolite, siatkowane oraz deseniowe. Pędzle jednolite zamalowują obszar jednolitym kolorem. Jeśli sprzęt zainstalowany w systemie nie umożliwia bezpośredniego użycia podanego koloru pędzla, Windows symuluje żądany kolor za pomocą roztrząsania kolorów, które mogą zostać wyświetlone. Pędzle siatkowane wypełniają obszar jednym z sześciu predefmiowanych wzorów siatek, wybranych ze wzorów najczęściej występujących w różnych rysunkach technicznych i projektach. Wzór deseniowy powoduje wypełnienie obszaru bitmapą. Klasa CBrush udostępnia konstruktor dla każdej odmiany pędzla. Do tworzenia jednolitego pędzla możesz użyć konstruktora CBrush, przekazując mu jedynie wartość COLORREF określającą kolor pędzla:
CBrush Brush( RGB( 255, O, 0) );
Jednolity pędzel możesz także utworzyć w dwóch krokach, tworząc niezainicjowany obiekt klasy CBrush i wywołując jego funkcję CBrush: :CreateSolidBrush ( ) :
CBrush Brush; Brush.CreateSolidBrush( RGB(255, O, 0));
W obu przypadkach utworzyliśmy jednolity pędzel w jasnoczerwonym kolorze. Aby stworzyć pędzel, możesz także wypełnić strukturę LOGBRUSH i wywołać funkcję CBrush: :CreateBrushIndirect (). Podobnie jak w przypadku konstruktorów klasy CPen, wszystkie tworzące pędzle konstruktory klasy CBrush w momencie niepowodzenia (zwykle krytycznego braku pamięci) zgłaszają wyjątek braku zasobów.
Tworzenie pędzli siatkowych odbywa się przez przekazanie konstruktorowi klasy CBrush zarówno indeksu wzoru, jak i wartości koloru lub przez wywołanie funkcji CBrush: :CreateHatchBrushIndirect (). Oto przykład tworzenia siatkowego pędzla malującego prostokątną siatkę linii obróconych o 45 stopni względem osi pionowej:
CBrush Brush( HS_DIAGCROSS, RGB( 255, O, 0)); );
Następny przykład tworzy ten sam pędzel w dwóch krokach:
CBrush Brush;
Brush.CreateHatchBrush( HS_DIAGCROSS, RGB( 255, O, O ) );
HS_DIAGCROSS jest jednym z sześciu wzorów do wyboru. Wszystkie dostępne style zostały zebrane w tabeli 9.5.
Tabela 9.5. Wzorv siatkowania
Wzór siatkowania
Opis
HS_BDIAGONAL-UkoÅ›ne linie od lewej do prawej pod kÄ…tem 45°.
HS_CROSS-Siatka pionowych i poziomych linii.
HS_DIAGCROSS-Ukośna siatka przecinających się linii.
HS_FDIAGONAL-UkoÅ›ne linie od prawej do lewej pod kÄ…tem 45°.
HS_HORIZONATL-Linie poziome.
HS VERTICAL-Linie pionowe.
Podczas malowania pędzlem siatkowym Windows wypełnia przestrzeń między liniami siatki domyślnym kolorem tła ustawionym w kontekście urządzenia. Kolor tła możesz zmienić, wywołując funkcję coc: : SetBkColor () lub w ogóle wyłączyć rysowanie tła, wywołując funkcję coc: :SetBkMode () z parametrem TRANSPARENT (przezroczyste) zamiast parametru OPAQUE (nieprzezroczyste). Poniższy przykład rysuje kwadrat o boku 100 jednostek i wypełnia go ukośną siatką białych linii rysowanych na jasnoszarym tle:
CBrush Brush( HS_DIAGCROSS, RGB( 255, 255, 255 ) ); CBrush *pOldBrush;
pOldBrush = pDC->SelectObject(SBrush); pDC->SetBkColor( RGB( 192, 192, 192 ) ); pDC->Rectangle(O, O, 100, 100); pDC->SelectObject(pOldBrush);
Następny przykład rysuje siatkowany na czarno kwadrat na istniejącym tle. Kolor tła i tryb jego rysowania wpływa także na sposób wypełniania przez Windows odstępów między odcinkami linii rysowanych stylowymi piórami oraz na sposób rysowania tła wypisywanych znaków.
CBrush Brush( HS_DIAGCROSS, RGB( O, O, O ) ); CBrush *p01dBrush;
pOldBrush = pDC->SelectObject(&Brush); pDC->SetBkMode( TRANSPARENT ); pDC->Rectangle(O, O, 100, 100); pDC->SelectObject(pOldBrush);
Windows posiada siedem dostępnych pędzli magazynowych:
BLACK_BRUSH // czarny
DKGRAY_BRUSH // ciemnoszary
GRAY_BRUSH // szary
LTGRAY_BRUSH // jasnoszary
HOLLOW_BRUSH // pusty
NULL_BRUSH // pusty
WHITE BRUSH // biały
Wszystkie z nich są pędzlami jednolitymi i w każdym przypadku identyfikator odpowiada wartości koloru pędzla. HOLLOW_BRUSH i DARK__BRUSH to synonimy tego samego pędzla, który niczego nie maluje.
Tryby odwzorowania
Gdy aplikacja rysuje odcinek od punktu A do punktu B, współrzędne przekazywane funkcjom MoveTo () oraz LineTo () nie określają fizycznego położenia na ekranie. Zamiast tego określają współrzędne w logicznym układzie współrzędnych obowiązującym w danej chwili w kontekście urządzenia. GDI zamienia współrzędne logiczne na współrzędne fizyczne na ekranie lub innym urządzeniu, używając równań biorących pod uwagę zarówno bieżący tryb odwzorowania (ang. mapping modę), jak i inne atrybuty kontekstu urządzenia.
W układzie współrzędnych urządzenia punkt (O, 0) odnosi się do lewego górnego rogu powierzchni wyświetlania urządzenia, wartości x i y rosną na prawo i w dół, zaś jednostka logiczna odpowiada jednemu pikselowi. W logicznym układzie współrzędnych punkt (O, 0) domyślnie znajduje się w lewym górnym rogu powierzchni wyświetlania, lecz może zostać przeniesiony w inne miejsce, zaś orientacja osi x i y a także fizyczne odległości odpowiadające jednostkom osi x i y zależą od trybu odwzorowania. Do zmiany trybu odwzorowania służy funkcja coc: : SetMapMode (), zaś do zmiany początku logicznego układu współrzędnych służą funkcje coc: :SetviewportOrg (} oraz coc: : setwindowOrg (). Windows obsługuje osiem różnych trybów odwzorowania, zebranych w tabeli 9.6.
Domyślny tryb odwzorowania, MM_TEXT, jest najprostszy do opanowania, gdyż w tym trybie współrzędne logiczne odpowiadają bezpośrednio współrzędnym fizycznym: jednostka logiczna w osi je lub y odpowiada pojedynczemu pikselowi na ekranie, zaś wartości x i y rosną w prawo i w dół. W trybie MM_TEXT, podobnie jak i w innych trybach odwzorowania, początek logicznego układu współrzędnych pokrywa się z początkiem układu współrzędnych urządzenia, chyba że przesuniesz je wywołaniami funkcji coc: : SetwindowOrg () lub coc: : Setviewportorg (). W przypadku kontekstu urządzenia dla obszaru roboczego okna oznacza to, że piksel w lewym górnym rogu okna ma początkowo współrzędne logiczne (O, 0).
Jeśli wolisz, by początek układu współrzędnych znajdował się w środku okna, a nie w lewym górnym rogu, możesz przesunąć go za pomocą funkcji CDC : : SetwindowOrg () lub CDC: : setviewportOrg (). Odnosi się to do wszystkich trybów odwzorowania, nie tylko do trybu MM_TEXT. Jeśli wywołaniem cwnd::GetciientRect() zainicjujesz obiekt o nazwie Rect należący do klasy CRect, przypisując mu współrzędne obszaru roboczego okna, zaś kontekst urządzenia będzie reprezentował obszar roboczy okna, poniższy przykład przesunie początek układu współrzędnych na środek obszaru roboczego okna:
CRect Rect;
GetClientRect( &Rect); pDC->SetViewportOrg( Rect.Width() / Rect.Height() / 2 )
Tabela 9.6. Tryby odwzorowania w Windows
Tryb odwzorowania
Opis
MM_ISOTROPIC-Jednostki logiczne są dowolne, z tym że są takie same na obu osiach.
tj. jednostce na osi x odpowiada taka sama jednostka na osi y.
Do określenia żądanej orientacji i rozciągłości osi oraz do określenia
jednostek fizycznych użyj funkcji SetWindowExt ()
oraz SetViewportExt (). W razie potrzeby GDI zapewni,
że jednostki w obu osiach będą takie same.
MM_ANISOTROPIC-Jednostki logiczne są dowolne w obu osiach. Do określenia żądanej orientacji i rozciągłości osi oraz do określenia jednostek fizycznych użyj funkcji SetWindowExt () oraz SetViewportExt {). Ustawienie trybu odwzorowania na MM_ANISOTROPIC nie powoduje zmiany obowiązujących jednostek i orientacji osi.
MM_HIENGLISH-Każda jednostka logiczna odpowiada jednej tysiącznej cala (0.001). Oś x jest skierowana w prawo, zaś oś y w górę.
MM_LOENGLISHKażda jednostka logiczna odpowiada jednej setnej cala (0.01). Oś x jest skierowana w prawo, zaś oś y w górę
.
MM_HIMETRIC-Każda jednostka logiczna odpowiada jednej setnej milimetra (0.01). Oś x jest skierowana w prawo, zaś oś y w górę.
MM_LOMETRIC-Każda jednostka logiczna odpowiada jednej dziesiątej milimetra (0.1). Oś x jest skierowana w prawo, zaś oś y w górę.
MM_TEXT-Każda jednostka logiczna odpowiada jednemu pikselowi. Oś x jest skierowana w prawo, zaś oś y w dół.
MM_TWIP-Każda jednostka logiczna odpowiada jednej dwudziestej punktu. (Ponieważ punkt odpowiada 1/72 cala, więc twip to 1/1440 cala, zajrzyj jednak do sekcji,.Ilość punktów czcionki" przy omawianiu operacji rastrowych). Oś x jest skierowana w prawo, zaś oś y w górę.
Operacje rastrowe
Jedną z rzeczy, które możesz zrobić, jest zmiana sposobu, w jaki rysowany kolor łączy się z kolorami już występującymi na ekranie. W większości przypadków zechcesz jednak, by na ekranie pojawiło się dokładnie to, co rysujesz. Na przykład, jeśli rysujesz czerwone koło, w 99 procentach przypadków zechcesz, by na ekranie pojawiło się czerwone koło. Jednak w pozostałym procencie przypadków zwykła operacja rysowania może nie wystarczyć.
Operacje rastrowe mogą być wykorzystywane na przykład w prostej animacji. Powszechną techniką przesuwania figur na ekranie jest wykonanie operacji XOR przed przeniesieniem figury w nowe miejsce. Tzn. ustawiasz operację XOR, po czym rysujesz figurę na ekranie. Ponowne narysowanie figury w tym samym miejscu powoduje całkowite usunięcie figury z ekranu. Dzieje się tak, ponieważ gdy za pierwszym razem rysujesz figurę, wykonujesz operację XOR z już istniejącym tłem. W każdym miejscu gdzie rysunek źródłowy ma
ustawiony bit, odpowiadający mu bit docelowy zmienia wartość na przeciwną. Na przykład, jeśli bit źródłowy jest ustawiony (ma wartość 1), zaś bit docelowy nie jest ustawiony (ma wartość 0), w rezultacie otrzymujemy bit o wartości 1. Jeśli figura musi zostać przesunięta, wykonanie operacji XOR na tej samej figurze w tym samym miejscu powoduje przełączenie dokładnie tych samych bitów, czyli w efekcie usunięcie figury z ekranu. W tym momencie figurę można narysować już w innym miejscu ekranu (oczywiście korzystając z operacji XOR). Kody operacji rastrowych zostały zebrane w tabeli 9.7.
Tabela 9.7. Kody operacji rastrowych
Operacja rastrowa
Opis
R2_WHITE-Piksel jest zawsze biały.
R2_BLACK-Piksel jest zawsze czarny.
R2_NOP-Piksel pozostaje niezmieniony.
Piksel pozostaje niezmieniony.
R2_NOT-Piksel przybiera odwrócony kolor ekranu.
R2_COPYPEN-Piksel przybiera kolor pióra.
R2_NOTCOPYPEN-Piksel przybiera odwrócony kolor pióra.
R2_MERGEPENNOT-Piksel jest kombinacją koloru pióra oraz odwróconego koloru ekranu (końcowy piksel = (NOT piksel ekranu) OR pióro).
R2_MASKPENNOT-Piksel jest wspólną kombinacją koloru pióra oraz odwróconego koloru ekranu (końcowy piksel = (NOT piksel ekranu) AND pióro).
R2_MERGENOTPEN-Piksel jest kombinacją koloru ekranu oraz odwróconego koloru pióra (końcowy piksel = (NOT pióro) OR piksel ekranu).
R2_MASKNOTPEN-Piksel jest wspólną kombinacją koloru ekranu oraz odwróconego koloru pióra (końcowy piksel = (NOT pióro) AND piksel ekranu).
R2_MERGEPEN-Piksel jest kombinacją koloru ekranu oraz koloru pióra (końcowy piksel = pióro OR piksel ekranu).
R2_NOTMERGEPEN--Piksel jest odwróceniem koloru R2_MERGEPEN (końcowy piksel = NOT(pióro OR piksel ekranu)).
R2_MASKPEN-Piksel jest wspólną kombinacją koloru ekranu oraz koloru pióra (końcowy piksel = (pióro AND piksel ekranu)).
R2_NOTMASKPEN-Piksel jest odwróceniem koloru R2_MASKPEN (końcowy piksel = NOT(pióro AND piksel ekranu)).
R2_XORPEN-Piksel jest kombinacją bitów występujących w piórze lub na ekranie, ale nie w obu naraz (końcowy piksel = pióro XOR piksel ekranu).
R2 NOTXORPEN-Piksel jest odwróceniem koloru R2_XORPEN (końcowy piksel - NOT(pióro XOR piksel ekranu)).
Funkcje rysujÄ…ce tekst
Jak podano w tabeli 9.8, GDI posiada pięć funkcji związanych z rysowaniem tekstu. Tabela 9.8. Funkcje GDI związane z rysowaniem tekstu
Funkcja
Opis
DrawText ()-Podczas rysowania tekstu zapewnia pewne formatowanie. Z czternastu znaczników określających opcje rysowania kilka odnosi się do ułożenia tekstu (na przykład po prawej lub po lewej stronie, u dołu, na środku czy u góry). Kolejny znacznik określa, czy znaki tabulacji mają być rozwijane jako tabulatory, zaś znacznik DT_WORDBREAK wymusza zawijanie słów w celu wyrysowania kilku linii tekstu.
ExtTextOut ()-Rysuje pojedynczą linię tekstu z trzema dodatkowymi możliwościami. Po pierwsze, możesz określić prostokąt obcinania w celu ograniczenia tekstu w zadanym obszarze. Po drugie, możesz nakazać przed wypisaniem tekstu wypełnienie obszaru kolorem tła. Po trzecie, możesz sterować odstępami znaków, przekazując funkcji tablice zawierające odpowiednie dane.
GraySt ring ()-Rysuje szary, wyłączony tekst, używany do wypisywania wyłączonych pozycji i opcji w menu i oknach dialogowych.
TabbedTextOut ()-Podobnie jak funkcja TextOut () wypisuje linię tekstu. Jedyna różnica polega na tym, że możesz zdefiniować tablicę pozycji tabulatorów, rozwijanych w momencie napotkania znaków tabulacji.
TextOut ()-Rysuje pojedynczÄ… liniÄ™ tekstu.
Aby narysować pojedynczą linię tekstu, najprostszą funkcją do wywołania jest coc: : Textout (). Ta funkcja jest zdefiniowana na dwa różne sposoby:
BOOL TextOut( int x, int y, LPCTSTR IpszString, int nCount); oraz BOOL TextOut ( int x, int y, const CString& strText);
W przypadku tej funkcji, x i y to współrzędne rysowanego tekstu. Domyślnie tekst jest rysowany na prawo i w dół od podanych współrzędnych. Parametr IpszString jest wskaźnikiem do ciągu wypisywanych znaków. Czwarty parametr, nCount, określa liczbę wypisywanych znaków. strText jest parametrem typu cstring, zawierającym zarówno ciąg znaków, jak i informację o jego długości. Oto przykład funkcji wywołującej funkcję TextOut () w celu wypisania napisu "Hello Windows 98" w miejscu o współrzędnych (100, 100):
void CMyView::OnFunction()
{
CClientDC ClientDC(this);
LPCTSTR lpszText = "Hello Windows 98";
ClientDC.TextOut(100, 100, lpszText, strlen(lpszText;
}
Oto ta sama funkcja, przepisana z wykorzystaniem drugiej formy funkcji TextOut ():
void CMyYiew::OnFunction() {
CClientDC ClientDC(this);
CString strText = "Hello Windows 98";
ClientDC.TextOut (100, 100, strText }; }
Przekazując współrzędne funkcjom rysunkowym GDI takim jak TextOut (), pamiętaj, że Windows 98 rozpoznaje jedynie 16 najmniej znaczących bitów współrzędnej. Mimo że możesz przekazać wartość 32-bitową, GDI w Windows 98 operuje jedynie na 16 bitach (podobnie jak GDI w Windows 3.1). Jedynie w Windows NT wykorzystywane i obsługiwane są wszystkie 32-bity. Jeśli nie chcesz, aby Twoje programy działały poprawnie tylko w Windows NT, ogranicz zakres współrzędnych rysowania do mieszczącego się w 16 bitach.
Funkcja CDC: :TextOut () rysuje pojedynczą linię tekstu. W wielu przypadkach zechcesz jednak narysować kilka linii, co wymaga kilku wywołań funkcji TextOut () z różnymi współrzędnymi w każdym wywołaniu. W odróżnieniu od wypisywania tekstu w środowiskach znakowych, współrzędne rysowania GDI nie są tym samym co komórki znaków tekstu. Aby prawidłowo rozłożyć tekst rysowany w kilku wywołaniach, musisz wykonać parę obliczeń związanych z rozmiarami tekstu. Zajmiemy się tym w następnej sekcji.
Obliczanie współrzędnych tekstu
GDI posiada kilka funkcji przydatnych przy obliczaniu współrzędnych tekstu. Musisz korzystać właśnie z nich, gdyż rozłożenie tekstu zależy nie tylko od wybranej czcionki, ale także od rozdzielczości docelowego urządzenia. Zanim narysujesz jakikolwiek tekst, musisz poprosić GDI o właściwe współrzędne tekstu.
Tabela 9.9 zawiera kilka z najbardziej przydatnych funkcji GDI służących do obliczania rozmiarów tekstu. Aby pomóc Ci w opanowaniu tych funkcji, przyjrzyjmy się ich najczęstszym zastosowaniom. Dyskusję rozpoczniemy od sposobu obliczenia ilości punktów, standardowego zadania związanego z operowaniem tekstem. Potem przejdziemy do następnego łańcucha - lub następnej linii tekstu. Ostatni temat związany ze współrzędnymi tekstu będzie dotyczył wyśrodkowania tekstu względem zadanego punktu.
Mość punktów czcionki
Nawet okazjonalny użytkownik programów do obróbki tekstów wie, że rozmiary czcionek są mierzone w punktach. Jeden punkt to w przybliżeniu 1/72.54 cala, czyli rozmiar niewielki, ale jeszcze widoczny gołym okiem. W Windows jako rozmiar punktu przyjęto dokładnie 1/72 cala - co w większości przypadków jest sensownym przybliżeniem. Rozmiar przeciętnego tekstu waha się od 8 do 12 punktów. Nagłówki zwykle mają po 14, 24, 36 lub nawet więcej punktów. Mówiąc o rozmiarze tekstu zwykle mamy na myśli wysokość znaków, a nie ich szerokość, choć w Windows można zażądać czcionki spełniającej oba wymagania.
Tabela 9.9. Funkcje do mierzenia tekstu
Funkcja
Opis
GetCharWidth ()-Zwraca domyślne szerokości znaków (dla dla czcionki aktualnie wybranej w kontekście urządzenia) dla zakresu znaków.
GetDeviceCaps()-Odczytuje różne bity danych specyficznych dla urządzenia. W kontekście tekstu LOGPIXELSY określa ilość pikseli na pionowy cal logiczny. Jest to użyteczne przy obliczaniu ilości punktów tekstu.
GetTextExtent()-Zwraca szerokość i wysokość, jaką dany tekst miałby po wyrysowaniu w kontekście urządzenia.
GetTabbed-
TextExtent ( }-Podobnie jak GetTextExtent (), z tym że przy obliczaniu wymiarów tekstu brane są pod uwagę także zawarte w nim tabulatory.
GetTextMetrics ()-Zwraca strukturę TEKTMETRIC wypełnioną danymi dotyczącymi czcionki aktualnie wybranej w kontekście urządzenia.
Jak już wspominaliśmy, w tym rozdziale skupiamy się na punktach urządzenia dlatego, że najprościej jest się nimi posługiwać. Oto podstawowa formuła do konwersji pomiędzy jednostkami urządzenia a rozmiarem tekstu w punktach:
Rozmiar w punktach = (72 * jednostki urządzenia) / cal logiczny Idąc w drugą stronę, oto formuła konwersji z punktów na jednostki urządzenia:
Jednostki urzÄ…dzenia = (Rozmiar w punktach * cal logiczny) / 72
Prawdopodobnie do wykonania operacji użyjesz pomocniczej funkcji Windows MulDiv (), a nie operatorów kompilatora. Ta funkcja eliminuje zaokrąglenia i błędy przepełnień, które mogą wystąpić przy użyciu zwykłej arytmetyki całkowitej. Oto poprzednia formuła przepisana z użyciem tej funkcji:
JednostkiUrzadzenia = ::MulDiv( RozmiarWPunktach, CalLogiczny, 72);
Powyższe formuły wymagają kilku wyjaśnień. Po pierwsze, termin cal logiczny odnosi się do ilości pikseli w jednym calu powierzchni urządzenia. Jest nazywany calem logicznym z powodu jego definicji dla wyświetlaczy ekranowych. W przypadku takich wyświetlaczy cal logiczny jest zwykle większy niż cal fizyczny. Dzięki temu czcionki 8-punktowe - będące zwykle najmniejszymi stosowanymi czcionkami - są jeszcze czytelne. W przypadku drukarek cal logiczny jest taki sam jak cal fizyczny.
Aby odczytać cal logiczny dla konkretnego urządzenia, wywołaj funkcję coc: :Get-Deviceaps (), zwracającą informacje o danym urządzeniu. Funkcja wymaga pojedynczego parametru, będącego indeksem informacji, którą chcemy odczytać. Dwa indeksy odnoszą się do cala logicznego: LOGPIXELSX dla ilości punktów na cal w poziomie oraz LOGPIXELSY dla ilości punktów na cal w pionie. Ponieważ ilość punktów tekstu odnosi się do wysokości tekstu, w celu wyznaczenia cala logicznego użyjemy indeksu LOGPIXELSY:
CalLogiczny = pDC->GetDeviceCaps(LOGPIXELSY);
Kolejna uwaga dotyczy faktu, że w naszej formule zakładamy, że punkt odpowiada 1/72 cala, a nie 1/72.54 cala. To zaokrąglenie jest konieczne, gdy Windows - a szczególnie GDI - nie korzysta z arytmetyki zmiennoprzecinkowej. Powodem jest oczywiście wydajność. Procesory Intela z rodziny x86 poprzedzające procesor 80486 nie posiadały sprzętowego koprocesora matematycznego. Ponieważ wykonywane programowo obliczenia zmiennoprzecinkowe mogą być bardzo powolne, Windows korzysta z arytmetyki całkowitej, co daje szybkie rezultaty przy nieznacznym pogorszeniu precyzji. Choć może zmieni się to w kolejnych wersjach, jednak na razie Windows pozostaje wiernie przy arytmetyce na liczbach całkowitych.
Następny łańcuch w tej samej linii
Czasem występuje konieczność rozbicia pojedynczej linii tekstu na kilka wywołań funkcji rysujących tekst, czyli na kilka wywołań funkcji TextOut (). Może to być potrzebne z wielu powodów. Może wynikać ze zwykłej wygody i sposobu przechowywania danych lub też z konieczności wypisania w jednej linii fragmentów tekstu posiadających różne atrybuty. Na przykład, wypisanie czerwonego napisu obok niebieskiego wymaga dwóch wywołań funkcji TextOut (). Użycie w tej samej linii dwóch różnych czcionek także wymaga osobnych wywołań tej funkcji.
Aby obliczyć pozycję następnego łańcucha, wywołaj funkcję CDC: :GetTextExtent (). Ta funkcja zwraca rozmiary łańcucha znaków wypisanego przy użyciu aktualnie wybranej czcionki. Funkcja jest zdefiniowana następująco:
CSize GetTextExtent( LPCTSTR 1pszString, int nCount) const;
Gdzie 1pszString to wskaźnik do łańcucha znaków, zaś nCount to ilość wypisywanych znaków.
Zwracana wartość, es i ze, to struktura z dwiema składowymi: ex zawiera szerokość łańcucha znaków, zaś cy jego wysokość. Na przykład, oto sposób wyrysowania napisu "Hello Windows 98" w tej samej linii za pomocą dwóch wywołań funkcji TextOut (; :
int x, y;
x = 100;
y = 100;
LPCTSTR 1pszHello = "Hello ";
LPCTSTR 1pszWin98 = "Windows 98";
// Wyrysowanie pierwszego łańcucha.
ClientDC.TextOut( x, y, IpszHello, strlen(1pszHello));
// Obliczenie rozmiaru pierwszego łańcucha.
CSize sizeString = ClientDC.GetTextExtent(1pszHello,
strlen(1pszHello}};
// Dostosowanie współrzędnej x. x += sizeString.ex;
// Wyrysowanie drugiego łańcucha.
ClientDC.TextOut(x, y, lpszWin98, strlen(lpszWin98));
W celu zachowania odstępu między łańcuchami pierwszy z nich - Hello -jest zakończony spacją.
Następny łańcuch w następnej linii
Kolejna popularna operacja na tekście wiąże się z wyznaczeniem ilości miejsca między dwiema liniami tekstu. Jako skrótu możesz użyć wartości zwróconej przez omawianą przed chwilą funkcję, GetTextExtent (). W końcu ta funkcja zwraca zarówno szerokość, jak i wysokość rysowanego łańcucha znaków. Oto jak można jej użyć w celu obliczenia odstępu pomiędzy kolejnymi liniami tekstu:
// Wyrysowanie pierwszego łańcucha.
ClientDC.TextOut( x, y, 1pszHello, strlen(IpszHello));
// Obliczenie rozmiaru pierwszego łańcucha.
CSize sizeString = ClientDC.GetTextExtent(1pszHello,
strlen(IpszHello)};
// Dostosowanie współrzędnej y. y += sizeString.cy;
// Wyrysowanie drugiego łańcucha.
ClientDC.TextOut(x, y, lpszWin98, strlen(lpszWin98));
Choć to rozwiązanie sprawdza się w większości przypadków, nie jest jednak najbardziej dokładne. Bierze co prawda pod uwagę wysokości znaków, jednak projektant czcionki mógł zdecydować, że pomiędzy liniami tekstu lepiej jest zastosować większy odstęp. A jeśli projektant zadał sobie trud, aby dodatkowo zdefiniować odstęp pomiędzy liniami tekstu, warto więc z tego skorzystać.
Termin używany przez projektantów czcionek do określania dodatkowego odstępu pomiędzy liniami tekstu to odstąp zewnętrzny (ang. external leading). Wziął się on z czasów, gdy teksty były składane ręcznie z metalowych czcionek i oznaczał płaskie paski metalu wstawiane pomiędzy linie tekstu. Stanowi odstęp zewnętrzny, gdyż nie jest zawarty w wysokości czcionek. Inny termin, odstęp wewnętrzny (ang. internal leading) odnosi się do wewnętrznego wymiaru czcionki, zawartego w komórce znaku.
Wartości odstępu wewnętrznego i zewnętrznego są przechowywane w strukturze TEXTMETRIC. Aby otrzymać wypełnioną strukturę z informacjami odnoszącymi się do czcionki aktualnie wybranej w kontekście urządzenia, wywołaj funkcję coc: :GetTextMetrics (). Oto przykład jej wywołania:
TEKTMETRIC TmSys;
ClientDC.GetTextMetrics(&TmSys);
Zwróć uwagę, że nazwa struktury - TEXTMETRIC -jest wyrażona w liczbie pojedynczej, podczas gdy nazwa funkcji - GetTextMetrics () - odnosi się do liczby mnogiej. Ta denerwująca niespójność jest cechą Windows API.
Aby obliczyć odstęp pomiędzy liniami tekstu, musisz użyć dwóch składowych struktury TEKTMETRIC: wysokości celki znaku oraz wartości odstępu zewnętrznego. W przypadku wypełnionej uprzednio struktury TEXXMETRIC, przed narysowaniem następnej linii tekstu, do wartości y należy dodać następującą wartość:
int cyLineHeight = TmSys.tmHeight + TmSys.tmExternalLeading;
We wcześniejszym przykładzie należałoby j ą dodać do wartości y przed narysowaniem następnej linii tekstu:
// Dostosowanie współrzędnej y. y +=cyLineHeight;
// Wyrysowanie drugiego łańcucha.
ClientDC.TextOut(x, y, lpszWin98, strlen(lpszWin98));
Pokazaliśmy dwa sposoby odczytania wysokości znaków tekstu, lecz możesz zastanawiać się, jak to się ma do ilości punktów czcionki. Uważaj, gdyż prawdopodobnie nie są one takie same -prawdopodobnie, gdyż mogą być takie same. Jednak aby się o tym przekonać, konieczne jest sprawdzenie jeszcze jednego wymiaru czcionki, odstępu wewnętrznego.
Odstęp wewnętrzny a odstęp zewnętrzny
Jak już mówiliśmy, obie wartości odnoszą się do odstępu pomiędzy liniami tekstu. Odstęp zewnętrzny to odstęp nie zawierający się w komórce znaku. Z drugiej strony, odstęp wewnętrzny to odstęp wliczony w wysokość celki znaku. Dennis Adler, specjalista Microsoftu do spraw czcionek, wyjaśnił to kiedyś jednemu z autorów. Aby obliczyć ilość punktów czcionki, od wysokości czcionki należy odjąć odstęp wewnętrzny. Służący do tego kod jest następujący:
int JednostkiUrzadzenia = TmSys.tmHeight - TmSys.tmlnterna1Leading;
Wynikiem tych obliczeń jest wysokość czcionki w jednostkach urządzenia. Aby zamienić ją na punkty, musisz wziąć pod uwagę logiczny cal urządzenia. Używając rozmiaru z poprzedniej formuły, oto jak obliczyć rozmiar czcionki w pikselach:
int CalLogiczny = ClientDC.GetDeviceCaps(LOGPIKELSY) ; int RozmiarWPunktach = ::MulDiv(72, JednostkiUrzadzenia, CalLogiczny);
Wyśrodkowanie tekstu
Często przydatne jest wyśrodkowanie tekstu względem określonego punktu. Na przykład, możesz zechcieć wyśrodkować etykietkę względem kolumny danych lub ułożyć tekst równo względem jakiegoś innego obiektu graficznego. Bez względu na powód, przy rozwiązywaniu tego typu problemów pomocne jest potraktowanie tekstu jak obiektu graficznego. Na szczęście tak się składa, że każdy tekst można opisać pewnym prostokątem.
Domyślnie, łańcuch znaków rozciąga się na prawo i poniżej od punktu współrzędnych tekstu. W rezultacie wyśrodkowanie tekstu sprowadza się do obliczenia, o ile trzeba go przesunąć w lewo i w górę. Rozmiar przesunięcia jest równy połowie rozmiarów prostokąta opisującego tekst. Aby pokazać to w praktyce, oto fragment kodu wyśrodkowującego tekst względem pewnego zadanego punktu (x, y):
// Tekst
CString strText = "Wyśrodkowana linia tekstu";
// Obliczenie przesunięcia w górę i w lewo. CSize size = ClientDC.GetTextExtent(strText); int xCentered = x - (size.ex / 2); int yCentered = y - (size.cy / 2);
// Wyrysowanie linii wyśrodkowanego tekstu. ClientDC.TextOut(xCentered, yCentered, strText;
Regiony obcinania
Przy wielu okazjach konieczne jest ograniczenie operacji rysunkowych do określonego regionu, tak aby nic nie mogło zostać poza nim narysowane. Odpowiada za to funkcja SelectclpRgn(), która ogranicza operacje rysunkowe dla konkretnego kontekstu urządzenia.
Funkcja selectclipRgn () wybiera dany region jako bieżący region obcinania dla kontekstu urządzenia. Używana jest jedynie kopia wybranego regionu. Sam region może być wybrany w dowolnej liczbie innych kontekstów urządzenia lub może być po prostu usunięty. Funkcja zakłada, że współrzędne regionu są podawane w jednostkach urządzenia.
Klasa CRgn reprezentuje region GDI Windows. Region to eliptyczny lub wielokątny obszar wewnątrz okna. Aby skorzystać regionów, powinieneś użyć funkcji składowych klasy CRgn wraz z funkcjami obcinania zdefiniowanymi jako funkcje składowe klasy coc. Poniższy przykład ilustruje, w jaki sposób można stworzyć zwykły prostokątny region ograniczony współrzędnymi (10, 10) oraz (100, 100):
CRgn Rgn; Rgn.CreateRectRgn(10,
100, 100);
Po utworzeniu regionu możesz ograniczyć rysowanie w kontekście urządzenia tak, aby odbywało się tylko w granicach zadanego regionu:
pDC->SelectClipRgn(&Rgn) ;
Bardziej zaawansowana wersja funkcji SelectciipRgn () umożliwia wybór pomiędzy pięcioma różnymi trybami. Te tryby wpływają na sposób łączenia regionu obcinania z innymi regionami obcinania wybranymi już w kontekście urządzenia oraz sposób użycia danego regionu. Owe pięć trybów zostało przedstawionych w tabeli 9.10.
Tabela 9.10. Tryby obcinania
Tryb
Opis
RGN_AND-Nowy region obcinania stanowi część wspólną bieżącego regionu obcinania oraz regionu wskazywanego przez pRgn.
RGN_COPY-Nowy region obcinania stanowi kopię regionu wskazywanego przez pRgn. Ten tryb działa podobnie do pierwszej wersji funkcji SelectClipRgn (). Jeśli parametr pRgn ma wartość NULL, nowym regionem obcinania staje się region pusty.
RGN_DI FF-Nowy region obcinania obejmuje część wspólną bieżącego regionu oraz regionu nie należącego do regionu wskazywanego przez pRgn.
RGN_OR-Nowy region obcinania stanowi sumę bieżącego regionu oraz regionu wskazywanego przez pRgn.
RGN_XOR-Nowy region obcinania stanowi sumę regionu bieżącego oraz regionu
wskazywanego przez pRgn z wyłączeniem obszarów pokrywających się.
Podsumowanie
W tym rozdziale omówiliśmy podstawy wyświetlania tekstu w oknie. Duża część materiału odnosiła się także do innych rodzajów obiektów GDI, które z pewnością kiedyś zdarzy Ci się wykorzystać. W tym rozdziale zaprezentowaliśmy jedynie podstawy stanowiące punkt wyjścia dla pewnych powszechnie stosowanych operacji.
W następnym rozdziale zajmiemy się bardziej szczegółowo innymi obiektami GDI, takimi jak palety czy bitmapy, a także bardziej zaawansowanymi technikami wyświetlania obrazu, na przykład podwójnym buforowaniem.
Wyszukiwarka
Podobne podstrony:
09 grafika dzwiek videoRozdział 09 Grafika PC09 GIMP tworzenie grafiki na potrzeby WWW (cz2)2008 09 Trzy wymiary Blendera [Grafika]pref 09amd102 io pl092002 09 Creating Virtual Worlds with Pov Ray and the Right Front EndAnaliza?N Ocena dzialan na rzecz?zpieczenstwa energetycznego dostawy gazu listopad 092003 09 Genialne schematy09 islamGM Kalendarz 09 humwięcej podobnych podstron