Rozdział 4
W 'wi tlanie tekstu
s e
W poprzednim rozdziale omawialiśmy działanie prostego programu Windows
98, który wyświetla pojedynczą linię tekstu na środku swojego okna lub - do-
kładniej mówiąc-na środku swojego obszaru roboczego. Dowiedzieliśmy się, że
obszar roboczy jest tą częścią całego okna aplikacji, która nie obejmuje paska ty-
tułu, ramki okna oraz opcjonalnie paska menu, paska stanu i pasków przewija-
nia, (jeśli występują). Krótko mówiąc, obszar roboczy to ta część okna, w której
program może swobodnie rysować i dostarczać informacji wizualnej użytkowni-
kowi.
Z obszarem roboczym swojego programu możesz zrobić niemal wszystko -
wszystko, o ile nie założysz, że będzie on miał konkretny rozmiar lub że rozmiar
pozostanie stały w czasie działania programu. Jeśli nie jesteś przyzwyczajony do
pisania programów w środowisku graficznym, te warunki mogą stanowić dla cie-
bie wstrząs. Nie możesz myśleć kategoriami ustalonej liczby 80 znaków w linii.
Twój program musi dzielić ekran z innymi programami Windows. To użytkow-
nik Windows kontroluje rozmieszczenie okien programów na ekranie. Chociaż
jest możliwe, aby programista utworzył okno ustalonego rozmiaru (stosowne dla
kalkulatorów lub podobnych narzędzi), użytkownicy zwykle mogą zmieniać roz-
miar okien aplikacji. Twój program musi reagować na zadany rozmiar okna i ro-
bić z nim coś rozsądnego.
I tu pojawiają się dwie skrajności. Jednym razem twój program może mieć ob-
szar roboczy tak mały, że ledwie się w nim zmieści "Hello", a innym razem może
być uruchomiony na wielkim ekranie z dużą rozdzielczością i wtedy ma tak duży
obszar roboczy, że zmieszczą się dwie strony tekstu i zostanie jeszcze dużo wol-
nego miejsca. Inteligentne uwzględnienie obu sytuacji stanowi ważną część pro-
gramowania Windows.
W tym rozdziale nauczymy się, jak program może wyświetlić na powierzchni
swojego obszaru roboczego coś bardziej skomplikowanego, niż to, co pokazywa-
liśmy w poprzednim rozdziale. Kiedy program wyświetla tekst albo grafikę
w swoim obszarze roboczym, często mówimy, że maluje swój obszar roboczy. Ten
rozdział poświęcony jest nauce malowania.
Chociaż Windows zawiera wiele funkcji wyświetlających GDI, w tym rozdziale
zajmuję się tylko wyświetlaniem prostych linii tekstu. Ignoruję też dostępne
w Windows różne kroje i wielkości czcionek, używając tylko domyślnej czcionki
systemowej. Naprawdę nie jest to ograniczeniem, chociaż może na takie wyglą-
dać. Problemy, które napotkamy i rozwiążemy w tym rozdziale, dotyczą całości
programowania w Windows. Gdy wyświetlasz łącznie tekst i grafikę, wielkość
znaków domyślnej czcionki Windows często określa rozmiary grafiki.
68 Część I: Podstawy
Ten rozdział tylko pozornie jest poświęcony wyłącznie nauce malowania, w rze-
czywistości uczy podstaw programowania niezależnego od konkretnego urzą-
dzenia. Programy windowsowe nie zakładają wielkości obszaru roboczego czy
nawet wielkości znaków tekstu. Muszą wykorzystać własności Windows do uzy-
skania informacji o środowisku, w którym działają.
Malowanie i odświeżanie
W środowiskach znakowych programy mogą zasadniczo pisać w każdym miej-
scu ekranu. To, co program umieszcza na ekranie, pozostaje tam i nie znika nie-
spodziewanie. Program może wtedy pozbyć się danych potrzebnych do odtwo-
rzenia zawartości ekranu.
W Windows możesz rysować tekst i grafikę tylko w obszarze roboczym swojego
okna i nie masz pewności, że to, co wyświetliłeś, pozostanie tam aż do chwili, gdy
twój program wyświetli coś nowego. Na przykład użytkownik może przesunąć okno
innego programu na ekranie tak, że częściowo zakryje okno twojej aplikacji. Win-
dows nie próbuje zapamiętać zawartości obszaru twojego okna, które jest zakry-
wane przez inny program. Kiedy ten program zostanie usunięty, Windows popro-
si, żeby twój program odświeżył tę część swojego obszaru roboczego.
Windows jest systemem sterowanym komunikatami. Zawiadamia aplikacje o róż-
nych zdarzeniach, wstawiając komunikaty do kolejek komunikatów tych aplika-
cji albo wysyłając komunikat do odpowiedniej procedury okna. Wysyłając komu-
nikat WM PAINT, Windows zawiadamia procedurę okna, że część obszaru ro-
boczego okna potrzebuje odświeżenia.
Komunikat WM PAINT
Większość programów Windows podczas inicjacji w WinMain, tuż przed wejściem
w pętlę komunikatów, wywołuje funkcję UpdateWindow. Windows ma wtedy oka-
zję wysłać do procedury okna swój pierwszy komunikat WM PAINT. Ten komu-
nikat zawiadamia procedurę okna, że musi być namalowany obszar roboczy. Od
tej chwili procedura okna powinna być gotowa w każdej chwili do przetwarza-
nia dodatkowych komunikatów WM PAINT i w miarę potrzeb ponownego na-
malowania całego obszaru roboczego okna. Procedura okna otrzymuje komuni-
kat WM PAINT, gdy wystąpi jedno z następujących zdarzeń:
ł Użytkownik odsłoni uprzednio niewidoczny obszar okna.
ł Użytkownik zmieni wielkość okna (jeśli styl klasy okna ma ustawione bity
CS HREDRAW i CW VREDRAW).
ł Program użyje funkcji ScrollWindow albo ScroIIDC do przewinięcia części swo-
jego obszaru roboczego.
ł Program użyje funkcji InvalidatelZect albo InvalidateRgn, by jawnie wygenero-
wać komunikat WM PAINT.
W niektórych przypadkach, kiedy część obszaru roboczego jest chwilowo prze-
słonięta, Windows próbuje zapamiętać ten fragment ekranu i później go odtwo-
rzyć. Nie zawsze się to udaje. Windows może czasami wysyłać komunikat
WM PAINT, jeśli:
Rozdział 4: Wyświetlanie tekstu s9
ł Windows usunie okno dialogowe lub okno komunikatów, które zasłaniało część
okna,
ł menu zostało rozwinięte, a następnie zwinięte,
ł została wyświetlona wskazówka.
W kilka przypadkach Windows zawsze zapamiętuje zasłonięty fragment ekra-
nu, a następnie przywraca go. Ma to miejsce, gdy:
ł przez obszar roboczy jest przesuwany kursor myszy,
ł przez obszar roboczy jest przeciągana ikona.
Praca z komunikatem WM PAINT wymaga, żebyś zmienił swój sposób myśle-
nia o wyświetlaniu na ekranie. Program powinien być tak skonstruowany, by
gromadził wszystkie informacje konieczne do namalowania obszaru roboczego,
ale malował tylko "na żądanie" - gdy Windows wyśle procedurze okna komuni-
kat WM PAINT. Jeśli twój program wymaga aktualizacji obszaru roboczego, może
zmusić Windows do wygenerowania komunikatu WM PAINT. Może się to wy-
dawać okrężną drogą wyświetlenia czegoś na ekranie, ale zyska na tym przej-
rzystość programu.
Prostokąty zatwierdzone i unieważnione
Chociaż procedura okna powinna być gotowa do odświeżenia całego obszaru ro-
boczego za każdym razem, gdy otrzymuje komunikat WM PAINT, najczęściej
wystarczy odświeżyć mniejszy obszar, zwykle prostokątny, wewnątrz obszaru
roboczego. Dzieje się tak wtedy, gdy okno dialogowe zakrywa część obszaru ro-
boczego. Odświeżenia wymaga wówczas prostokątny obszar odsłonięty po za-
mknięciu okna dialogowego.
Obszar ten jest nazywany regionem unieważnionym lub regionem aktualizacji.
Obecność regionu unieważnionego w obszarze roboczym stanowi dla Windows
sygnał, aby umieścić w kolejce komunikatów aplikacji komunikat WM PAINT.
Twoja procedura okna otrzyma komunikat WM PAINT tylko wtedy, gdy część
twojego obszaru roboczego jest unieważniona.
Windows przechowuje dla własnych potrzeb "strukturę informacji o malowaniuł
dla każdego okna. Struktura ta zawiera, między innymi, współrzędne najmniejsze-
go prostokąta, który obejmuje unieważniony region. Nazywa się go prostokątem
unieważnionym. Jeśli inny region obszaru roboczego zostanie unieważniony, zanim
procedura okna przetworzy komunikat WMPAINT, to Windows obliczy nowy re-
gion unieważniony (i nowy prostokąt unieważniony), zawierający oba te obszary i za-
pamięta zaktualizowaną informację w strukturze informacji o malowaniu. Natomiast
nie umieści kolejnego komunikatu WMPAINT w kolejce komunikatów.
Procedura okna może unieważniać prostokąt w swoim własnym obszarze robo-
czym, wywołując InvalidateRect. Jeśli kolejka komunikatów zawiera już komuni-
kat WM PAINT, Windows oblicza nowy prostokąt unieważniony. W przeciwnym
razie umieszcza komunikat WMPAINT w kolejce komunikatów. Procedura okna
może otrzymać współrzędne unieważnionego prostokąta, kiedy otrzyma komu-
nikat WMPAINT (jak zobaczymy w dalszej części rozdziału). Może też je uzy-
skać w dowolnej chwili, wywohxjąc GetUpd.teRect.
w-
Część I: Podstawy
Po wywołaniu przez procedurę okna funkcji BeginPaint podczas przetwarzania
komunikatu WMPAINT, zatwierdzany jest cały obszar roboczy. Program może
też zatwierdzić każdy obszar prostokątny wewnątrz obszaru roboczego, wywo-
łując funkcję validateftect. Jeśli to wywołanie spowoduje zatwierdzenie całego
unieważnionego obszaru, wtedy wszystkie znajdujące się w kolejce komunikaty
WM-PAINT są z niej usuwane.
Wprowadzenie do GDI
Aby pomalować obszar roboczy swojego okna, użyj funkcji GDI Windows (ang.
Graphics Device Interface - interfejs urządzenia graficznego). Windows dostarcza
wielu funkcji GDI wyświetlających tekst w obszarze roboczym okna. W poprzed-
nim rozdziale poznaliśmy funkcję DrawText, ale najczęściej używana jest funkcja
TextOut:
TextOut (hdc, x, y, psText, iLength) ;
TextOut wyświetla łańcuch znaków w obszarze roboczym okna. Argument psText
jest wskaźnikiem do tego łańcucha znaków, a iLength jest jego długością. Argu-
menty x i y określają pozycję początku tekstu w obszarze roboczym. (Wkrótce
podam więcej szczegółów). Argument hdc jest uchwytem kontekstu urządzenia i
ważną częścią GDI. W rzeczywistości każda funkcja GDI wymaga tego uchwytu
jako swojego pierwszego argumentu.
Kontekst urządzenia
Przypomnij sobie, że uchwyt to po prostu liczba, która pozwala systemowi roz-
poznać obiekt. Otrzymujesz uchwyt od Windows i wykorzystujesz go w innych
funkcjach. Uchwyt kontekstu urządzenia jest wydanym przez Windows paszpor-
tem do funkcji GDI. Dysponując tym uchwytem, możesz swobodnie malować
w swoim obszarze roboczym i upiększać go, kierując się swoim gustem.
Kontekst urządzenia (nazywany także po prostu DC - ang. device context) w rze-
czywistości jest strukturą danych pamiętaną przez GDI. Kontekst urządzenia
jest związany z konkretnym urządzeniem wyjściowym, jak ekran monitora lub
drukarka. W przypadku ekranu kontekst urządzerńa jest zwykle powiązany z kon-
kretnym oknem na ekranie.
Niektóre wartości kontekstu urządzenia są atrybutami graficznymi. Atrybuty te
określają szczegółowo sposób działania funkcji wyświetlających GDI. Na przy-
kład dla funkcji TextOut atrybuty kontekstu urządzenia określają kolor tekstu, kolor
tła, sposób odwzorowania współrzędnych x i y funkcji na obszar roboczy okna
oraz rodzaj czcionki używanej przez Windows do wyświetlania tekstu.
Aby program mógł coś namalować, musi najpierw otrzymać uchwyt kontekstu
urządzenia. Gdy uzyskasz ten uchwyt, Windows wypełni wewnętrzną strukturę
kontekstu urządzenia domyślnymi wartościami atrybutów. Jak zobaczysz w dal-
szej części rozdziału, możesz zmienić te domyślne wartości przez wywołanie róż-
nych funkcji GDI. Inne funkcje GDI pozwolą ci otrzymać aktualne wartości tych
atrybutów. Są oczywiście jeszcze inne funkcje GDI, które pozwalają faktycznie
malować w obszarze roboczym okna.
T'
Rozdział 4: Wyświetlanie tekstu 71
Gdy program skończy malować swój obszar roboczy, powinien zwolnić uchwyt
kontekstu urządzenia. Zwolniony uchwyt staje się nieważny i nie może być wię-
cej używany. Program powinien otrzymać i zwolnić uchwyt podczas przetwa-
rzania pojedynczego komunikatu. Z wyjątkiem kontekstu urządzenia utworzo-
nego wywołaniem CreateDC (funkcji tej nie omawiam w tym rozdziale), nie po-
winieneś przetrzymywać uchwytu kontekstu urządzenia pomiędzy jednym a dru-
gim komunikatem.
Kiedy aplikacje Windows przygotowują się do malowania na ekranie, najczę-
ściej używają dwóch sposobów otrzymania uchwytu kontekstu urządzenia.
Otrzymywanie uchwytu kontekstu urządzenia. Sposób pierwszy
Możesz użyć tej metody, gdy przetwarzasz komunikat WNfPAINT. Główną rolę
odgrywają tu dwie funkcje: BeginPaint i EndPaint. Obie te funkcje wymagają
uchwytu okna, który jest przekazywany do procedury okna jako argument, i ad-
resu zmiennej struktury typu PAINTSTRUCT, która jest zdefiniowana w pliku
nagłówkowym WINUSER.H. Programujący w Windows zwykle nazywają tę
zmienną struktury ps (ang. paint structure) i definiują w procedurze okna nastę-
pująco:
PAINTSTRUCT ps ;
Podczas przetwarzania komunikatu WMPAINT procedura okna wywołuje naj-
pierw BeginPaint. Funkcja BeginPaint przede wszystkim sprawia, że tło obszaru
unieważnionego zostaje wyczyszczone i przygotowane do malowania. Funkcja
ta wypełnia także pola struktury ps. Wartością zwracaną przez BeginPaint jest
uchwyt kontekstu urządzenia. Jest on zwykle przechowywany w zmiennej o na-
zwie hdc. Możesz ją zdefiniować w swojej procedurze okna w następujący spo-
sób:
HDC hdc ;
Typ danych HDC jest zdefiniowany jako 32-bitowa liczba całkowita bez znaku.
Program może potem używać funkcji GDI, takich jak TextOut, które wymagają
uchwytu kontekstu urządzenia. Wywołanie EndPaint zwalnia uchwyt kontekstu
urządzenia.
Zazwyczaj przetwarzanie komunikatu WMPAINT wygląda tak:
case WM_PAINT:
hdc = BeginPaint (hwnd. &ps) ;
[wywolania funkcji GDI]
EndPaint (hwnd. &ps) ;
return 0 :
Podczas przetwarzania komunikatu WMPAINT procedura okna musi wywoły-
wać zawsze parę funkcji: BeginPaint i EndPaint. Jeśli procedura okna nie przetwarza
komunikatu WMPAINT, musi go przekazać do funkcji DefWindowProc, która jest
domyślną procedurą okna w Windows. Def tNindowProc przetwarza komunikat
WMPAINT w następujący sposób:
case WM_PAINT:
BeginPaint (hwnd, &ps)
EndPaint (hwnd, &ps) ;
return 0 :
w-
Część I: Podstawy
Sekwencja wywołań BeginPaint i EndPaint, bez wywoływania w międzyczasie
innych funkcji GDI, zatwierdza poprzednio unieważniony region.
Ale nie rób tego w ten sposób:
case WMPAINT:
return 0 ; // ŹLE !!1
Windows umieszcza komunikat WMPAINT w kolejce komunikatów, ponieważ
część obszaru roboczego jest unieważniona. Dopóki nie wywołasz BeginPaint i End-
Paint (lub validateRect), Windows nie zatwierdzi tego obszaru. Natomiast wyśle
ci następny komunikat WM PAINT i jeszcze jeden, aż do skutku.
Struktura informacji o malowaniu
Wspominałem wcześniej o strukturze informacji o malowaniu, którą Windows
zakłada dla każdego okna. To właśnie jest PAINTSTRUCT. Struktura ta jest zde-
finiowana następująco:
typedef struct tagPAINTSTRUCT
(
HDC hdc ;
BOOL fErase ;
RECT rcPaint ;
BOOL fRestore ;
BOOL fIncUpdate ;
BYTE rgbReservedC327 ;
) PAINTSTRUCT ;
Windows wypełnia pola tej struktury podczas wywoływania przez twój program
funkcji BeginPaint. Twój program może używać tylko trzech pierwszych pól. Reszta
jest do dyspozycji Windows. Pole hdc jest uchwytem kontekstu urządzenia. Przy
nadmiarowości właściwej dla Windows wartością zwracaną przez BeginPaint jest
także uchwyt kontekstu urządzenia. W większości przypadków fErase będzie
miało wartość FALSE (0), co oznacza, że Windows już wyczyścił tło unieważnio-
nego prostokąta. Zostało to zrobione wcześniej, w funkcji BeginPaint. (Gdybyś
chciał sam czyścić tło w swojej procedurze okna, możesz przetwarzać komuni-
kat WM ĘRASEBKGND). Windows czyści tło za pomocą pędzla określonego w
polu hbrBackground struktury WNDCLASS, służącej do rejestracji klasy okna w
WinMain. Wiele programów Windows używa zazwyczaj białego pędzla do tła
okna. Określenie pędzla odbywa się, gdy program ustawia pola struktury klasy
okna za pomocą instrukcji:
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE BRUSH) ;
Jednak jeśli twój program unieważnia prostokąt obszaru roboczego przez wywo-
łanie funkcji InvalidateRect, wówczas ostatni argument funkcji określa, czy chcesz
wyczyścić tło. Jeśli tym argumentem jest FALSE (to jest 0), Windows nie wyczy-
ści tła i po wywołaniu BeginPaint pole fErase struktury PAINTSTRUCT będzie
miało wartość TRUE (niezerową).
Pole rcPaint struktury PAINTSTRUCT jest strukturą typu RECT. Jak już wiesz
z rozdziału 3, struktura RECT definiuje prostokąt poprzez cztery pola o nazwach
left, top, right i bottom. Pole rcPaint struktury PAINTSTRUCT definiuje granice
unieważnionego prostokąta, jak widać na rysunku 4-1. Wartości te są wyrażone
w pikselach względem lewego górnego rogu obszaru roboczego. Unieważniony
prostokąt jest obszarem, który powinieneś odświeżyć.
Rozdział 4: Wyświetlanie tekstu 73
Góra
Dól
Rysunek 4-1. Granice unieważnionego prostokąta
Prostokąt rcPaint struktury PAINTSTRUCT jest nie tylko prostokątem unieważ-
nionym; jest także prostokątem obcinania (ang. clipping rectangle). Oznacza to, że
Windows ogranicza malowanie tylko do tego prostokąta obcinającego. Dokład-
niej: jeśli unieważniony region nie jest prostokątny, to Windows ogranicza malo-
wanie do wnętrza tego regionu.
Aby malować poza aktualizowanym prostokątem podczas przetwarzania komu-
nikatu WM PAINT, możesz wywołać następującą funkcję
InvalidateRect (hwnd, NULL, TRUE) ;
przed wywołaniem BeginPaint. To unieważni cały obszar roboczy i spowoduje,
że BeginPaint wyczyści tło. Wartość FALSE w ostatnim argumencie spowoduje
pozostawienie tła. Cokolwiek tam było, pozostanie.
Najwygodniejszym rozwiązaniem dla programu Windows jest po prostu odświe-
żanie całego obszaru roboczego za każdym razem, gdy nadejdzie komunikat
WMPAINT, niezależnie od zawartości struktury rcPaint. Na przykład jeśli część
obrazu wyświetlanego w obszarze roboczym zawiera koło, ale tylko część koła
znalazła się w obszarze unieważnionym, nie ma sensu rysowanie tylko unieważ-
nionej części koła. Narysuj całe koło. Gdy używasz uchwytu kontekstu urządze-
nia zwróconego przez BeginPaint, Windows i tak nie będzie malował poza ob-
szarem prostokąta rcPaint.
Podczas przetwarzania komunikatu WMPAINT w programie HELLOWIN z roz-
działu 2 nie martwiliśmy się o unieważnione prostokąty. Jeśli obszar, gdzie tekst
był wyświetlany, znajdował się w unieważnionym prostokącie, DrawText odświe-
żał go. Jeśli nie, to Windows w trakcie przetwarzania funkcji DrawText zauważał,
że nie ma nic do wyświetlenia na ekranie. Ale ustalenie tego zabiera trochę cza-
0 Lewy Prawy
74 Część I: Podstawy
su. Programista dbający o wydajność i szybkość (a to dotyczy, mam nadzieję, nas
wszystkich) podczas przetwarzania komunikatu WM-PAINT będzie chciał wy-
korzystać informację o unieważnionym prostokącie, aby uniknąć niepotrzebnych
wywołań GDI. Jest to szczególnie ważne, jeśli malowanie wymaga użycia plików
dyskowych, takich jak bitmapy.
Otrzymywanie uchwytu kontekstu urządzenia. Sposób drugi
Chociaż najlepiej jest tak zbudować program, aby podczas przetwarzania komu-
nikatu WM-PAINT móc odświeżać cały obszar roboczy, to czasem użyteczne jest
też malowanie części obszaru roboczego podczas przetwarzania innych komuni-
katów. Uchwyt kontekstu urządzenia może być ci potrzebny także do innych
celów, takich jak pobieranie informacji o kontekście urządzenia.
Aby otrzymać uchwyt kontekstu urządzenia obszaru roboczego okna, wywołaj
GetDC, dzięki czemu otrzymasz uchwyt i ReleaseDC, gdy już nie jest ci potrzebny:
hdc = GetDC (hwnd) ;
[wywołania funkcji GDI]
ReleaseDC (hwnd, hdc) ;
Podobnie jak BeginPaint i EndPaint, funkcje GetDC i ReleaseDC powinny być wy-
woływane parami. Jeśli wywołasz GetDC podczas przetwarzania komunikatu, po-
winieneś wywołać ReleaseDC, zanim opuścisz procedurę okna. Nie wywołuj GetDC
w jednym komunikacie, a ReleaseDC w innym.
W przeciwieństwie do uchwytu kontekstu urządzenia zwracanego przez Begin-
Paint, uchwyt kontekstu urządzenia zwracany przez GetDC ma prostokąt obci-
nania równy całemu obszarowi roboczemu. Możesz malować w każdym miejscu
obszaru roboczego, nie tylko w unieważnionym prostokącie (jeśli rzeczywiście
jest tam unieważniony prostokąt). W odróżnieniu od BeginPaint, GetDC nie za-
twierdza żadnych unieważnionych obszarów. Jeśli musisz zatwierdzić cały ob-
szar roboczy, możesz wywołać
ValidateRect (hwnd, NULL) ;
Z zasady należy używać wywołań GetDC i ReleaseDC w odpowiedzi na komuni-
katy klawiatury (jak w edytorze tekstu) lub na komunikaty myszy (jak w progra-
mie do rysowania). Pozwala to programowi na rysowanie w obszarze roboczym
w odpowiedzi na polecenia użytkownika z klawiatury lub myszy, bez celowego
unieważniania części obszaru roboczego w celu wygenerowania komunikatu
WM PAINT. Jednak jeśli nawet malujesz podczas komunikatów innych niż
WMPAINT, twój program musi nadal przechowywać wystarczające informacje
niezbędne do odświeżenia ekranu, na wypadek gdy otrzyma komunikat WMPA-
INT.
Funkcją podobną do GetDC jest GetWindowDC. Podczas gdy GetDC zwraca uchwyt
kontekstu urządzenia do rysowania w obszarze roboczym okna, GetWindowDC
zwraca uchwyt kontekstu urządzenia, który pozwala rysować w całym oknie. Na
przykład twój program może używać uchwytu kontekstu urządzenia zwrócone-
go przez GetWindowDC do malowania na pasku tytułu okna. Jednak wtedy twój
program będzie musiał przetwarzać również komunikaty WMNCPAINT (ma-
lowanie poza obszarem roboczym - ang. nonclient paint).
Rozdział 4: Wyświetlanie tekstu 75
Funkcja TextOut - szczegóły
TextOut jest najczęściej używaną funkcją GDI do wyświetlania tekstu. Jej skład-
nia to:
TextOut (hdc, x, y, psText, iLength) ;
Omówimy ją teraz bardziej szczegółowo.
Pierwszym argumentem jest uchwyt kontekstu urządzenia - to wartość hdc zwró-
cona przez GetDC albo wartość hdc zwrócona przez BeginPaint podczas przetwa-
rzania komunikatu WM PAINT.
Atrybuty kontekstu urządzenia sterują sposobem wyświetlania tekstu. Na przy-
kład jeden z atrybutów określa kolor znaków. Domyślnym kolorem (co przyjmu-
jemy z radością!) jest czamy. Domyślny kontekst urządzenia sugeruje białe tło
napisu. Gdy program wyświetla tekst na ekranie, Windows używa tego koloru
tła do wypełnienia prostokątnego obszaru otaczającego każdy znak, nazywane-
go ramką znaku (ang. character box).
Kolor tła napisów nie jest identyczny z tłem określanym przy definicji klasy okna.
Tło w klasie okna jest pędzlem (wzorcem, który nie musi mieć czystego koloru)
używanym przez Windows do czyszczenia obszaru roboczego. Nie jest on czę-
ścią struktury kontekstu urządzenia. Gdy definiujesz strukturę klasy okna, więk-
szość aplikacji Windows używa WHITE BRUSH - tak więc domyślny kolor tła
napisu w domyślnym kontekście urządzenia jest taki sam jak kolor pędzla uży-
wanego przez Windows do czyszczenia tła obszaru roboczego.
Argument psText jest wskaźnikiem do łańcucha znaków, a iLength jest liczbą zna-
ków w łańcuchu. Jeśli psText wskazuje na łańcuch znaków w unikodzie, to liczba
bajtów w łańcuchu jest równa podwojonej wartości iLength. Łańcuch znaków nie
powinien zawierać żadnych znaków sterujących ASCII, takich jak powrót karetki,
wysunięcie wiersza, tabulacja lub znak cofania. Windows wyświetla te znaki ste-
rujące jako ramki lub wypełnione prostokąty. TextOut nie rozpoznaje bajtu zero-
wego (lub liczby całkowitej zero w unikodzie) jako znacznika końca łańcucha zna-
ków. Funkcja używa argumentu iLength do określenia długości łańcucha znaków.
Argumenty x i y w TextOut określają położenie punktu początkowego napisu w ob-
szarze roboczym. Wartość x jest położeniem poziomym, a wartość y - pionowym.
Gómy lewy róg pierwszego znaku jest umieszczony w punkcie o współrzędnych
(x, y). W domyślnym kontekście urządzenia początkiem układu współrzędnych
(miejscem, w którym x i y są równe 0) jest lewy górny róg obszaru roboczego. Jeśli
używasz wartości zerowych dla x i y w TextOut, napis będzie się rozciągał od le-
wego górnego rogu obszaru roboczego.
Gdy przeczytasz dokumentację funkcji rysujących GDI, takich jak TextOut, stwier-
dzisz, że współrzędne przekazywane do funkcji są zwykle określane jako współ-
rzędne logiczne. Co to dokładnie oznacza, wyjaśnimy bardziej szczegółowo w roz-
dziale 5. Teraz wystarczy nam wiedza, że Windows ma wiele trybów odwzoro-
wania, które określają sposób, w jaki współrzędne logiczne podawane w funk-
cjach rysujących GDI są tłumaczone na fizyczne współrzędne ekranu. Tryb od-
wzorowania jest definiowany w kontekście urządzenia. Domyślny tryb odwzo-
rowania jest nazywany MMTEXT (stosując identyfikator zdefiniowany w pliku
Część I: Podstawy
nagłówkowym WINGDI.H). W trybie odwzorowania MM-TEXT jednostki logicz-
ne są takie same jak jednostki fizyczne (tj. piksele) i względem lewego górnego
rogu obszaru roboczego. Wartości x rosną w miarę przesuwania się w prawą stronę
obszaru roboczego, a wartości y rosną w miarę przesuwania się w dół obszaru
roboczego (zobacz rysunek 4-2). System współrzędnych MM-TEXT jest identyczny
z systemem współrzędnych, którego używa Windows do definiowania prosto-
kąta unieważnionego w strukturze PAINTSTRUCT. (Jednak nie jest tak dobrze
przy innych trybach odwzorowania).
Rysunek 4-2. Współrzędne x i y w trybie odwzorowania MM TEXT
Kontekst urządzenia definiuje też region obcinania. Jak mówiliśmy, domyślnym
regionem obcinania dla uchwytu kontekstu urządzenia otrzymanego od GetDC
jest cały obszar roboczy, a dla uchwytu kontekstu urządzenia otrzymanego od
BeginPaint - obszar unieważniony. Gdy wywołasz TextOut, Windows nie wyświetli
tej części łańcucha znaków, która leży poza regionem obcinania. Jeśli znak znaj-
duje się na granicy regionu obcinania, Windows wyświetli tylko część znaku znaj-
dującą się wewnątrz regionu. Wyświetlanie na zewnątrz obszaru roboczego okna
nie jest wcale proste, więc nie martw się, że zrobisz to przez nieuwagę.
Czcionka systemowa
Kontekst urządzenia definiuje też czcionkę, której Windows używa, gdy wywo-
łujesz TextOut w celu wyświetlenia tekstu. Domyślna czcionka nosi nazwę czcionki
systemowej lub (używając identyfikatora z pliku nagłówkowego WINGDI.H)
SYSTEM FONT. Czcionki systemowej Windows używa domyślnie do napisów
na paskach tytułów, w menu i oknach dialogowych.
W początkowych wersjach Windows czcionką systemową była czcionka niepro-
porcjonalna (ang. fixed-pitch font), co oznacza, że wszystkie znaki mają tę samą
Rozdział 4: Wyświetlanie tekstu 77
szerokość, zupełnie jak w maszynie do pisania. Jednak począwszy od Windows
3.0, czcionką systemową została czcionka proporcjonalna (ang. variable-pitch font),
co oznacza, że różne znaki mają różną szerokość. Na przykład "W" jest szersze
niż "i". Badania wykazały, że teksty wyświetlone czcionką proporcjonalną są
bardziej czytelne od tekstów wyświetlanych czcionką nieproporcjonalną. Ma to
prawdopodobnie związek z mniejszymi odległościami między znakami, co po-
zwala oczom i umysłowi łatwiej wyłowić całe słowa niż pojedyncze znaki. Jak
możesz sobie wyobrazić, przejście na czcionkę proporcjonalną wymagało całko-
witego przerobienia kodu Windows i zmusiło programistów do poznania nowych
metod pracy z tekstem.
Czcionka systemowa jest czcionką rastrową, co oznacza, że znaki są zdefiniowa-
ne w postaci matrycy pikseli. (W rozdziale 17 poznamy czcionki TrueType, które
są zdefiniowane w postaci skalowalnych konturów). Wielkość znaków czcionki
systemowej jest w pewnym stopniu oparta na rozmiarach ekranu. Została tak
zaprojektowana, aby na ekranie mieściło się co najmniej 25 linii po 80 znaków
tekstu każda.
Rozmiary znaku
Żeby wyświetlić wiele linii tekstu, używając funkcji TextOut, musisz znać roz-
miary znaków czcionki. Możesz rozmieścić kolejne wiersze tekstu na podstawie
wysokości znaku, a kolejne kolumny w obszarze roboczym na podstawie śred-
niej szerokości znaków.
Jaka jest wysokość i przeciętna szerokość znaków czcionki systemowej? Nie po-
wiem ci tego. Raczej nie mogę powiedzieć. A naprawdę, to mógłbym powiedzieć,
ale pewnie źle. Wszystko zależy od wielkości ekranu mierzonej w pikselach. Win-
dows wymaga minimalnej rozdzielczości ekranu 640 na 480 pikseli, lecz wielu
użytkowników woli rozdzielczość 800 na 600 lub 1024 na 768. W dodatku przy
tych dużych rozdzielczościach Windows pozwala użytkownikowi na wybór spo-
śród różnej wielkości czcionek systemowych.
Podobnie jak program może określić rozmiary (lub "metrykę") interfejsu użytkow-
nika przez wywołanie funkcji GetSystemMetrics, tak może określić też rozmiary
czcionki przez wywołanie GetTextMetrics. GetTextMetrics wymaga uchwytu kontek-
stu urządzenia, ponieważ zwraca informację o czcionce aktualnie wybieranej w kon-
tekście urządzenia. Windows kopiuje różne wartości metryki tekstu do struktury
typu TEXTMETRIC zdefiniowanej w WINGDI.H. Struktura TEXTMETRIC zawie-
ra 20 pól, ale jesteśmy zainteresowani tylko pierwszymi siedmioma:
typedef struct tagTEXTMETRIC
f
LONG tmHeight ;
LONG tmAscent ;
LONG tmDescent ;
LONG tmInternalLeading ;
LONG tmExternalLeading ;
LONG tmAveCharWidth ;
LONG tmMaxCharWidth ;
[inne pola struktury]
1
TEXTMETRIC, * PTEXTMETRIC ;
Część I: Podstawy
Wartości tych pól są wyrażone w jednostkach, które zależą od trybu odwzoro-
wania wybranego dla kontekstu urządzenia. W domyślnym kontekście urządze-
rua trybem odwzorowania jest MM TEXT, więc jednostką rozmiaru są piksele.
Zanim sięgniesz po funkcję GetTextMetrics, najpierw musisz zdefiniować zmien-
ną strukturalną, zwykle nazywaną tm:
TEXTMETRIC tm ;
Jeśli musisz określić metrykę tekstu, możesz dostać uchwyt kontekstu urządze-
nia i wywołać GetTextMetrics:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
ReleaseDC (hwnd, hdc) ;
Możesz wtedy sprawdzić wartości w strukturze matrycy tekstu i prawdopodob-
nie zachować kilka z nich w celu ponownego wykorzystania.
Rozmiary tekstu - szczegóły
Struktura TEXTMETRIC dostarcza różnych informacji o czcionce wybranej w kon-
tekście urządzenia. Jednak pionowy rozmiar czcionki jest zdefiniowany tylko w
pięciu polach struktury, z których cztery są pokazane na rysunku 4-3.
tmlnternalLeading
tmAscent
tmHeight
Linia bazowa
tmDescent
Rysunek 4-3. Cztery wartości definiujące pionowe rozmiary znaku czcionki
Najważniejszą wartością jest tmHeight, która jest sumą tmAscent i tmDescent.
Te dwie wartości określają maksymalny rozmiar znaku w pionie, odpowiednio
powyżej i poniżej linii bazowej. Termin leading (odstęp międzywierszowy) odno-
si się do przestrzeni, którą drukarka zostawia pomiędzy liniami tekstu. W struk-
turze TEXTMETRIC odstęp wewnętrzny jest włączony do tmAscent (i stąd do
tmHeight) i często obejmuje znaki akcentu. Pole TmlnternalLeading może być usta-
wione na 0, a wtedy litery akcentowane są nieco mniejsze, aby zmieścić nad nimi
znak akcentu.
Rozdział 4: Wyświetlanie tekstu 79
Struktura TEXTMETRIC zawiera też pole o nazwie tmExternalLeading, które nie
jest zawarte w wartości tmHeight. Jest to odstęp, który projektant czcionki propo-
nuje dać między kolejnymi wierszami wyświetlanego tekstu. Możesz zaakcepto-
wać lub odrzucić tę sugestię. W czcionce systemowej, którą spotkałem ostatnio,
tmExternalLeading był zerowy, więc nie pokazałem go na rysunku 4-3. (Mimo że
obiecywałem nie poruszać tematu rozmiarów czcionki systemowej, rysunek 4-3
przestawia domyślną czcionkę systemową Windows dla rozdzielczości ekranu
640 na 480).
Struktura TEXTMETRIC zawiera dwa pola opisujące szerokość znaku: pole tmA-
veCharWidth jest średnią ważoną małych liter, a tmMaxCharWidth jest szerokością
najszerszego znaku w czcionce. Dla czcionki nieproporcjonalnej te wartości są
identyczne. (Dla czcionki przedstawionej na rysunku 4-3 tymi wartościami są
odpowiednio 7 i 14).
Przykładowe programy w tym rozdziale wymagają innej szerokości znaku - śred-
niej szerokości wielkich liter. Możesz określić ją dość dokładnie jako 150% warto-
ści tmAveCharWidth.
Musisz zdawać sobie sprawę, że rozmiary czcionki systemowej zależą od wiel-
kości ekranu w pikselach podczas działania Windows, ale w niektórych przypad-
kach użytkownik może też wybrać wielkość czcionki systemowej. Windows do-
starcza interfejsu graficznego niezależnego od urządzeń, ale musisz mu pomóc.
Nie pisz swoich programów Windows, zakładając konkretną wielkość znaków.
Nie zapisuj na sztywno w programie żadnych wartości. Użyj funkcji GetTextMe-
trics, aby uzyskać tę informację.
Formatowanie tekstu
Ponieważ rozmiary czcionki systemowej nie zmieniają się podczas sesji Windows,
wystarczy, że tylko raz wywołasz GetTextMetrics w swoim programie. Dobrym
miejscem do tego celu jest przetwarzanie komunikatu WM CIZEATE w procedu-
rze okna. Komunikat WMCREATE jest pierwszym komunikatem, który otrzy-
muje procedura okna. Windows wywohzje twoją procedurę okna z komunika-
tem WMCREATE, kiedy wywołujesz CreateWindow w WinMain.
Załóżmy, że piszesz program windowsowy, który wyświetla kilka linii tekstu
biegnących w dół obszaru roboczego. Będziesz chciał poznać szerokość i wyso-
kość znaku. Wewnątrz procedury okna możesz zdefiniować dwie zmienne: do
przechowywania średniej szerokości znaku (cxChar) i całkowitej wysokości zna-
ku (cyChar):
static int cxChar, cyChar ;
Przedrostek c, dodawany do nazw zmiennych; oznacza "licznik", czyli tutaj licz-
bę pikseli. W połączeniu z x lub y przedrostek ten powoduje oznaczenie zmien-
nej jako szerokości albo wysokości. Zmienne są zdefiniowane jako static, ponie-
waż są potrzebne, gdy procedura okna przetwarza inne komunikaty, takie jak
WMPAINT. Możesz także zdefiniować te zmienne globalnie, poza funkcją.
Oto kod WM-CIZEATE, który pobiera szerokość i wysokość znaku czcionki sys-
temowej:
80 Część I: Podstawy
case WM_CREATE:
hdc = GetDC (hwnd) :
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
return 0 :
Zauważ, że włączyłem pole tmExternalLeading do obliczania cyChar. Nawet jeśli
to pole w czcionce systemowej jest równe 0, powinno być uwzględnione. Jeśli
kiedykolwiek będzie niezerowe, umożliwi tworzenie bardziej czytelnych odstę-
pów. Każda następna linia tekstu jest wyświetlana o cyChar pikseli niżej.
Często będziesz musiał także wyświetlać sformatowane liczby i proste napisy.
Jak już mówiłem w rozdziale 2, nie możesz użyć w tym celu tradycyjnego narzę-
dzia (czyli popularnej funkcji print, ale możesz sięgnąć po sprintf lub jej win-
dowsową wersję - wsprintf. Te funkcje działają dokładnie tak samo jak printf, z wy-
jątkiem tego, że zachowują sformatowany tekst w buforze. Możesz następnie użyć
TextOut, aby wyświetlić napis na ekranie. Wartością zwróconą przez sprintf lub
wsprintf jest dhxgość łańcucha znaków. Możesz przekazać tę wartość do TextDut
jako argument iLength. Następujący kod pokazuje typową kombinację wsprintf i
TextOut:
int iLength ;
TCHAR szBuffer [40] ;
[ inne linie programu ]
iLength = wsprintf (szBuffer, TEXT ("Suma %i i %i wynosi %i"),
iA. iB. iA + iB) :
TextOut (hdc, x, y, szBuffer, iLength) ;
W tak prostym przykładzie możemy obyć się bez definicji iLength i połączyć te
dwie instrukcje w jedną:
TextOut (hdc, x, y, szBuffer,
wsprintf (szBuffer, TEXT ("Suma %i i %i wynosi %i"),
iA, iB, iA + iB)) ;
Nie wygląda to zbyt ładnie, ale działa.
Składanie wszystkiego w całość
Teraz mamy już wszystko co potrzeba, by napisać prosty program wyświetlający
wiele linii tekstu na ekranie. Wiemy, jak uzyskać uchwyt kontekstu urządzenia
podczas komunikatu WM_PAINT, jak użyć funkcji TextOut i jak rozmieścić tekst
na podstawie wymiarów pojedynczego znaku. Pozostaje nam jedynie wyświetlić
cośinteresującego.
W poprzednim rozdziale widzieliśmy ciekawą informację udostępnianą przez
funkcję GetSystemMetrics. Funkcja zwraca dane o różnych elementach graficznych
w Windows, takich jak ikony, kursory, paski tytuhx i paski przewijania. Ich wy-
miary zmieniają się wraz ze zmianą karty graficznej i jej sterownika. GetSystem-
Metrics jest tą ważną funkcją, która umożliwia twojemu programowi wyświetla-
nie niezależne od urządzenia.
Rozdział 4: Wyświetlanie tekstu 81
Funkcja wymaga pojedynczego argumentu nazywanego indeksem. Indeks jest jed-
nym z 75 identyfikatorów całkowitych, zdefiniowanych w pliku nagłówkowym
Windows. (Liczba identyfikatorów wzrastała z każdą wersją Windows; dokumen-
tacja programisty Windows 1.0 wymieniała ich tylko 26). GetSystemMetrics zwraca
liczbę całkowitą, zwykle rozmiar elementu określonego w argumencie.
Napiszemy program, który wyświetla informacje udostępniane przez wywoła-
nie GetSystemMetrics w prostym formacie jednego elementu w linii. Ułatwimy sobie
pracę, jeśli utworzymy plik nagłówkowy, który zdefiniuje tablicę struktur zawie-
rającą zarówno identyfikatory pliku nagłówkowego Windows dla indeksów Get-
SystemMetrics, jak też i tekst, który chcemy wyświetlać dla każdej zwracanej war-
tości. Ten plik nagłówkowy o nazwie SYSMETS.H jest pokazany na rysunku 4-4.
SYSMETS.H
/*
SYSMETS.H - Struktura wyświetlająca informacje
o elementach graficznych Windows
*/
define NUMLINES ((int) (sizeof sysmetrics / sizeof sysmetrics C07))
struct
(
int iIndex :
TCHAR * szLabel
TCHAR * szDesc :
l
sysmetrics [] =
(
SM CXSCREEN, TEXT ("SM CXSCREEN"),
TEXT ("Screen width in pixels"),
SM_CYSCREEN, TEXT ("SM CYSCREEN"),
TEXT ("Screen height in pixels"),
SM_CXVSCROLL, TEXT ("SM CXVSCROLL"),
TEXT ("Vertical scroll width"),
SM_CYHSCROLL, TEXT ("SM_CYHSCROLL"),
TEXT ("Horizontal scroll height"),
SM_CYCAPTION, TEXT ("SM CYCAPTION").
TEXT ("Caption bar height"),
SM_CXBORDER, TEXT ("SM_CXBORDER"),
TEXT ("Window border width"),
SM_CYBORDER, TEXT ("SM_CYBORDER"),
TEXT ("Window border height"),
SM_CXFIXEDFRAME, TEXT ("SM_CXFIXEDFRAME"),
TEXT ("Dialog window frame width"),
SM_CYFIXEDFRAME, TEXT ("SM_CYFIXEDFRAME"),
TEXT ("Dialog window frame height"),
SM_CYVTHUMB, TEXT ("SM CYVTHUMB"),
TEXT ("Vertical scroll thumb height"),
SM_CXHTHUMB, TEXT ("SM_CXHTHUMB"),
TEXT ("Horizontal scroll thumb width"),
SM CXICON, TEXT ("SM CXICON"),
TEXT ("Icon width"),
SM CYICON, TEXT ("SM CYICON").
82 Część I: Podstawy
(ciąg dalszy ze strony 81)
TEXT ("Icon height"),
SM CXCURSOR, TEXT ("SM CXCURSOR"),
TEXT ("Cursor width"),
SM CYCURSOR, TEXT ("SM CYCURSOR"),
TEXT ("Cursor height"),
SM CYMENU, TEXT ("SM CYMENU"),
TEXT ("Menu b-ar height"),
SM CXFULLSCREEN, TEXT ("SM_CXFULLSCREEN"),
TEXT ("Full screen client area width"),
SM CYFULLSCREEN, TEXT ("SM_CYFULLSCREEN"),
T^XT ("Full screen client area height"),
SM CYKANJIWINDOW, TEXT ("SM CYKANJIWINDOW"),
TEXT ("Kanji window height"),
SM MOUSEPRESENT, TEXT ("SM MOUSEPRESENT"),
TEXT ("Mouse present flag"),
SM CYVSCROLL, TEXT ("SM CYVSCROLL"),
TEXT ("Vertical scroll arrow hei9ht"),
SM CXHSCROLL, TEXT ("SM CXHSCROLL"),
TEXT ("Horizontal scroll arrow width"),
SM DEBUG, TEXT ("SM_DEBUG"),
TEXT ("Debug version flag"),
SMSWAPBUTTON. TEXT ("SM SWAPBUTTON"),
TEXT ("Mouse buttons swapped fla9"),
SM CXMIN, TEXT ("SM_CXMIN"),
TEXT ("Minimum window width"),
SM CYMIN, TEXT ("SM_CYMIN"),
TEXT ("Minimum window height"),
SM CXSIZE, TEXT ("SM_CXSIZE"),
TEXT ("Min/Max/Close button width"),
SM CYSIZE, TEXT ("SM CYSIZE"),
TEXT ("Min/Max/Close button height"),
SM CXSIZEFRAME, TEXT ("SM_CXSIZEFRAME"),
TEXT ("Window sizing frame width"),
SM CYSIZEFRAME, TEXT ("SM_CYSIZEFRAME"),
TEXT ("Window sizing frame height"),
SMCXMINTRACK, TEXT ("SM_CXMINTRACK"),
TEXT ("Minimum window tracking width"),
SMCYMINTRACK, TEXT ("SM_CYMINTRACK"),
TEXT ("Minimum window tracking height"),
SM CXDOUBLECLK, TEXT ("SM_CXDOUBLECLK"),
TEXT ("Double click x tolerance"),
SM CYDOUBLECLK, TEXT ("SM_CYDOUBLECLK"),
TEXT ("Double click y tolerance"),
SM CXICONSPACING, TEXT ("SM_CXICONSPACING"),
TEXT ("Horizontal icon spacing"),
SMCYICONSPACING, TEXT ("SM CYICONSPACING"),
TEXT ("Vertical icon spacing"),
SM MENUDROPALIGNMENT, TEXT ("SM_MENUDROPALIGNMENT"),
TEXT ("Left or right menu drop"),
SM PENWINDOWS, TEXT ("SMPENWINDOWS"),
TEXT ("Pen extensions installed"),
SM DBCSENABLED, TEXT ("SM_DBCSENABLED"),
TEXT ("Double-Byte Char Set enabled"),
SMCMOUSEBUTTONS, TEXT ("SM_CMOUSEBUTTONS"),
TEXT ("Number of mouse buttons"),
SMSECURE, TEXT ("SMSECURE"),
Rozdział 4: Wyświetlanie tekstu 83
TEXT ("Security present flag"),
SM CXEDGE, TEXT ("SM_CXEDGE"),
TEXT ("3-D border width"),
SM CYEDGE, TEXT ("SM_CYEDGE"),
TEXT ("3-D border height"),
SM_CXMINSPACING, TEXT ("SM_CXMINSPACING"),
TEXT ("Minimized window spacing width"),
SM_CYMINSPACING, TEXT ("SM_CYMINSPACING"),
TEXT ("Minimized window spacing height"),
SM CXSMICON, TEXT ("SM_CXSMICON"),
TEXT ("Small icon width"),
SM CYSMICON, TEXT ("SM_CYSMICON"),
TEXT ("Small icon height"),
SM CYSMCAPTION, TEXT ("SM_CYSMCAPTION"),
TEXT ("Small caption height"),
SM CXSMSIZE, TEXT ("SM_CXSMSIZE"),
TEXT ("Small caption button width"),
SM CYSMSIZE, TEXT ("SM_CYSMSIZE"),
TEXT ("Small caption button height"),
SMCXMENUSIZE, TEXT ("SMCXMENUSIZE"),
TEXT ("Menu bar button width"),
SMCYMENUSIZE, TEXT ("SM CYMENUSIZE"),
TEXT ("Menu bar button height"),
SM RRANGE, TEXT ("SM ARRANGE"),
TEXT ("How minimized windows arranged"),
SM CXMINIMIZED, TEXT ("SM_CXMINIMIZED"),
TEXT ("Minimized window width"),
SM_CYMINIMIZED, TEXT ("SMCYMINIMIZED"),
TEXT ("Minimized window height"),
SM CXMAXTRACK, TEXT ("SM_CXMAXTRACK"),
TEXT ("Maximum draggable width"),
SM_CYMAXTRACK, TEXT ("SM_CYMAXTRACK"),
TEXT ("Maximum draggable height"),
SM CXMAXIMIZED, TEXT ("SM_CXMAXIMIZED"),
TEXT ("Width of maximized window"),
SM CYMAXIMIZED, TEXT ("SM CYMAXIMIZED"),
TEXT ("Height of maximized window"),
SM_NETWORK, TEXT ("SMNETWORK"),
TEXT ("Network present flag"),
SM CLEANBOOT, TEXT ("SM CLEANBOOT"),
TEXT ("How system was booted"),
SM CXDRAG, TEXT ("SM_CXDRAG"),
TEXT ("Avoid drag x tolerance"),
SM CYDRAG, TEXT ("SM_CYDRAG"),
TEXT ("Avoid drag y tolerance"),
SM_SHOWSOUNDS, TEXT ("SMSHOWSOUNDS"),
TEXT ("Present sounds visually"),
SM_CXMENUCHECK, TEXT ("SM CXMENUCHECK"),
TEXT ("Menu check-mark width"),
SM CYMENUCHECK, TEXT ("SM CYMENUCHECK"),
TEXT ("Menu check-mark height"),
SM_SLOWMACHINE, TEXT ("SM_SLOWMACHINE"),
TEXT ("Slow processor flag"),
SM_MIDEASTENABLED, TEXT ("SM_MIDEASTENABLED"),
TEXT ("Hebrew and Arabic enabled flag"),
SM_MOUSEWHEELPRESENT, TEXT ("SM MOUSEWHEELPRESENT"),
TEXT ("Mouse wheel present flag"),
SM XVIRTUALSCREEN, TEXT ("SMXVIRTUALSCREEN"),
84 Część I: Podstawy
(ciąg dalszy ze strony 83)
TEXT ("Virtual screen x origin"),
SM_YVIRTUALSCREEN, TEXT ("SM YVIRTUALSCREEN"),
TEXT ("Virtual screen y origin"),
SM_CXVIRTUALSCREEN, TEXT ("SM_CXVIRTUALSCREEN"),
TEXT ("Virtual screen width"),
SM_CYVIRTUALSCREEN, TEXT ("SM_CYVIRTUALSCREEN"),
TEXT ("Virtual screen height"),
SM_CMONITORS, TEXT ("SM_CMONITORS"),
TEXT ("Number of monitors"),
SM_SAMEDISPLAYFORMAT, TEXT ("SMSAMEDISPLAYFORMAT"),
TEXT ("Same color format flag")
Rysunek 4-4. Plik SYSMETS.H
Program, który wyświetla tę informację, nazywa się SYSMETSl. Plik kodu źró-
dłowego SYSMETSl.C pokazany jest na rysunku 4-5. Większość kodu powinna
wyglądać teraz znajomo. Kod WinMain jest rzeczywiście identyczny z kodem
w HELLOWIN, a sporo kodu z WndProc zostało już omówione.
SYSMETSI.C
/*
SYSMETSl.C - Program nr 1 wyświetlający wymiary
elementów graficznych
(c) Charles Petzold, 1998
*/
include
iinclude "sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppNameC7 = TEXT ("SysMetsl") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CS VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION> ;
wndclass.hCursor = LoadCursor (NULL, IDC RROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITEBRUSH) ;
wndclass.lpszMenuName = NULL ; j
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
(
Rozdział 4: Wyświetlanie tekstu 85
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MBICONERROR) ;
return 0 ;
)
hwnd = CreateWindow (szAppName, TEXT ("Get System Merics No. 1"),
WS_OVERLAPPEDWINDOW,
! CWUSEDEFAULT, CWUSEDEFAULT,
CWUSEDEFAULT, CW USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
)
i return msg.wParam ;
1
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static int cxChar, cxCaps, cyChar ;
HDC hdc ;
int i ;
PAINTSTRUCT ps ;
TCHAR szBuffer C10] ;
TEXTMETRIC tm ;
switch (message)
case WM_CREATE:
hdc = GetDC (hwnd) ;
I GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
for (i = 0 ; i < NUMLINES ; i++)
(
TextOut (hdc, 0, cyChar * i,
sysmetrics[i].szLabel,
lstrlen (sysmetricsCi].szLabel)) ;
( TextOut (hdc, 22 * cxCaps, cyChar * i,
sysmetrics[i].szDesc,
lstrlen (sysmetrics[i].szDesc)) ;
SetTextAlign (hdc, TARIGHT T TOP) ;
86 Część I: Podstawy
i
(ciąg dalszy ze strony 85)
TextOut (hdc, 22 * cxCaps + 40 * cxChar, cyChar * i, szBuffer,
wsprintf (szBuffer, TEXT ("5d"),
GetSystemMetrics (sysmetricsCi].iIndex))) ;
SetTextAlign (hdc, TA LEFT TA TOP) ;
)
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostOuitMessage (0) ;
return 0 ;
1
return DefWindowProc (hwnd, message, wParam, lParam) ;
)
Rysunek 4-5. Plik SYSMETSl.C
IZysunek 4-6 pokazuje SYSMETS1 działający na standardowej karcie VGA. Jak wyru-
ka z pierwszych dwóch linii obszaru roboczego programu, szerokość ekranu wynosi
640 pikseli, a wysokość - 480 pikseli. Te dwie wartości, jak też wiele innych pokazy-
wanych przez program, mogą być różne dla różnych rozdzielczości ekranu.
CXSCREEN Screen width in pixels 640
CYSCREEN Screen height in pixels 400
CXVSCROLL Vertical scroll width 16
CYHSCROLL Horizontal scroll height 16
CYCAPTION Caption bar height 19
CXBORDER Windowborderwidth 1
CYBORDER Window border height 1
CXFIXEDFRAME Dialog window frame width 3
CYFIXEDFRAME Dialog window frame helght 3
CYVTHUMB Vertical scrall thumb height 16
CXHTHUMB Harizontal scroll thumb width 16
CXICON Icon width 32
CYICON Icon height 32
CXCURSOR Cursorwidih 32
CYCURSOR Cursorheight 32
CYMENU Menu bar height 19
CXFULLSCREEN Full screen client area width 640
CYFULLSCREEN Full screen client area height 433
CYKANJIWINDOW Kanji window height 0
MOUSEPRESENT Mousepresentflag 1
CWSCROLL Vertical sttoll arrow height 16
CXHSCROLL Harizontal saoll arrowwidth 16
DEBUG Debug version flag 0
SWAPBUTTON Mouse bttons swapped flag 0
CXMIN Minimumwindowwidth 112
CYMIN Minimum window height 27
Rysunek 4-6. Ekran programu SYSMETS1
Procedura okna w SYSMETSl.C
W programie SYSMETSl.C procedura okna WndProc przetwarza trzy komunikaty:
WM CREATE, WM-PAINT i WM DESTROY. Komunikat WM DESTROY jest prze-
twarzany tak samo, jak w programie HELLOWIN z rozdziału 3.
Rozdział 4: Wyświetlanie tekstu 87
Komunikat WM CREATE jest pierwszym komunikatem, który otrzymuje proce-
dura okna. Windows generuje ten komunikat, gdy funkcja CreateWindow tworzy
okno. Podczas przetwarzania komunikatu WMCREATE program SYSMETSI
uzyskuje kontekst urządzenia dla okna przez wywołanie GetDC i otrzymuje roz-
miary tekstu dla domyślnej czcionki systemowej przez wywołanie GetTextMetrics.
SYSMETS1 zachowuje średnią szerokość znaku w cxChar, a całkowitą wysokość
znaku (włącznie z odstępem między wierszami) - w cyChar.
SYSMETS1 zachowuje też średnią szerokość wielkich liter w zmiennej statycznej
cxCaps. Dla czcionki nieproporcjonalnej cxCaps powinno być równe cxChar. Dla
czcionki proporcjonalnej cxCaps równa się 150% wartości cxChar. Mniej znaczący
bit pola tmPitchAndFamily w strukturze TEXTMETRIC ma wartość 1 dla czcionki
proporcjonalnej i 0 dla czcionki nieproporcjonalnej. SYSMETS1 używa tego bitu
do obliczania cxCaps na podstawie cxChar:
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
SYSMETS1 maluje całe okno podczas przetwarzania komunikatu WM PAINT.
Jak zwykle procedura okna najpierw uzyskuje uchwyt do kontekstu urządzenia
przez wywołanie BeginPaint. Pętla instrukcji for przegląda wszystkie linie struk-
tury sysmetrics zdefiniowanej w SYSMETS.H. W wyniku trzech wywołań funkcji
TextOut są wyświetlane trzy kolumny tekstu. W każdym przypadku trzeci argu-
ment TextOut (czyli pozycja początkowa y) jest ustawiany jako
cyChar * i
Ten argument określa w pikselach położenie górnej krawędzi napisu względem
górnej krawędzi obszaru roboczego.
Pierwsza instrukcja TextOut wyświetla wielkimi literami identyfikator w pierw-
szej z trzech kolumn. Drugi argument TextOut przyjmuje wartość 0, aby rozpo-
cząć wyświetlanie tekstu od lewej krawędzi obszaru roboczego. Tekst jest uzy-
skiwany z pola szLabel struktury sysmetrics. Do obliczenia dhzgości napisu wyko-
rzystujemy funkcję lstrlen. Otrzymaną wartość wstawiamy jako ostatni argument
TextOut.
Druga instrukcja TextOut wyświetla opisy wartości danych systemowych. Te opisy
pochodzą z pola szDesc struktury sysmetrics. W tym przypadku drugi argument
TextOut jest ustawiany na:
22 * cxCaps
Najdłuższy identyfikator wyświetlany w pierwszej kolumnie ma 20 znaków, więc
druga kolumna musi rozpoczynać się przynajmniej 20 x cxCaps na prawo od po-
czątku pierwszej kolumny tekstu. Ja stosuję 22, aby dodać pewien odstęp mię-
dzy kolumnami.
Trzecia instrukcja TextOut wyświetla wartość liczbową otrzymaną od funkcji Get-
SystemMetrics. Czcionka proporcjonalna utrudnia formatowanie kolumn liczb
wyrównanych do prawej strony. Na szczęście we wszystkich używanych obec-
nie czcionkach proporcjonalnych cyfry od 0 do 9 mają tę samą szerokość. W prze-
ciwnym razie wyświetlanie kolumn liczbowych byłoby koszmarem. Jednak sze-
rokość cyfr jest większa niż szerokość odstępu. Liczby mogą mieć jedną lub wię-
cej cyfr, więc różne liczby mogą rozpoczynać się w różnych miejscach na osi po-
ziomej.
T''
gg Część I: Podstawy
Czyż nie byłoby prościej, gdybyśmy mogli wyświetlać kolumnę liczb wyrówna-
nych do prawej strony, określając położenie końca liczb, zamiast ich początku?
Pozwala nam na to funkcja SetTextAlign. Po wywołaniu przez SYSMETSI
SetTextAlign (hdc, TARIGHT T TOP) ;
Windows powinien interpretować współrzędne przekazywane do kolejnych funk-
cji TextOut jako prawy górny róg tekstu, a nie lewy górny.
Funkcja TextOut do wyświetlania kolumny liczb ma swój drugi argument usta-
wiony na:
22 * cxCaps + 40 * cxChar
Wartość 40 x cxChar uwzględnia szerokość drugiej i trzeciej kolumny. Po funkcji
TextOut kolejne wywołanie SetTextAlign przywraca normalne pozycjonowanie dla
następnego przejścia pętli.
Brak miejsca
Z programem SYSMETSI jest jeden niewiellei kłopot: dopóki nie masz olbrzymiego
ekranu o wysokiej rozdzielczości, nie zobaczysz wielu linii z listy danych syste-
mowych. Jeśli zawęzisz okno, możesz nawet nie zobaczyć wartości.
SYSMETSI nie jest świadomy tego problemu. W przeciwnym razie mógłby wy-
świetlać okno komunikatu mówiące "Przepraszam!". Program nie wie, jak duży
jest jego obszar roboczy. Rozpoczyna wyświetlanie na górze okna i liczy na to, że
Windows obetnie wszystko, co wyjdzie poza dolną krawędź obszaru roboczego.
Oczywiście, to nie tak powinno działać. Naszym pierwszym zadaniem jest więc
określenie, jaka część wyświetlanego tekstu zmieści się w obszarze roboczym.
Wielkość obszaru roboczego
Jeśli poeksperymentujesz z istniejącymi aplikacjami Windows, stwierdzisz, że
rozmiary okna mogą się zmieniać w dużym zakresie. Jeśli okno jest maksymali-
zowane, obszar roboczy zajmuje prawie cały ekran. Rozmiary maksymalnego
obszaru roboczego są w rzeczywistości dostępne przez wywołanie GetSystemMe-
trics z argumentami SM CXFULLSCREEN i SM CYFULLSCREEN (zakładając,
że okno ma tylko pasek tytułu i żadnego menu). Minimalny rozmiar okna może
być tak niewielki, że obszar roboczy przestanie istnieć.
W poprzednim rozdziale używaliśmy funkcji GetClientftect do określenia rozmiaru
obszaru roboczego. Nie ma w tym nic złego, tyle tylko że wywoływanie jej za
każdym razem, gdy potrzebna ci jest ta informacja, jest nieefektywne. Znacznie
lepszą metodą określenia rozmiarów okna roboczego jest przetwarzanie komu-
nikatu WMSIZE wewnątrz własnej procedury okna. Windows wysyła komuni-
kat WMSIZE do procedury okna za każdym razem, gdy rozmiar okna się zmie-
nia. Zmienna lParam, przekazywana do procedury okna, w mniej znaczącym sło-
wie zawiera szerokość obszaru roboczego, zaś w bardziej znaczącym słowie -jego
wysokość. Aby zachować te rozmiary, musisz zdefiniować w swojej procedurze
okna dwie zmienne statyczne:
static int cxClient, cyClient :
Rozdział 4: Wyświetlanie tekstu 89
Podobnie jak cxChar i cyChar, te zmienne są zdefiniowane jako statyczne, ponie-
waż są ustawiane podczas przetwarzania jednego komunikatu i używane pod-
czas przetwarzania innego. Możesz wykorzystać sposób WM SIZE, jak tu:
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
Możesz ujrzeć taki kod praktycznie w każdym programie Windows. LOWORD
i HIWORD są makrami zdefiniowanymi w pliku nagłówkowym WINDEF.H Win-
dows. Jeśli jesteś ciekawy, to definicje tych makr wyglądają następująco:
define LOWORD(1) ((WORD)(1))
define HIWORD(1) ((WORD)(((DWORD)(1) Ż 16) & OxFFFF))
Obydwa makra zwracają wartości WORD - to znaczy 16 bitowe liczby całkowite
bez znaku (ang. unsigned short integer) - w zakresie od 0 do OxFFFF. Najczęściej
będziesz zapamiętywał te wartości w postaci 32-bitowych liczb całkowitych ze
znakiem. Nie powoduje to żadnych problemów z konwersją, a ułatwia użycie
takich wartości w późniejszych obliczeniach.
W wielu programach Windows po WM SIZE może następować komunikat
WM-PAINT. Dlaczego? Ponieważ definiując klasę okna określamy styl klasy jako
CS HREDRAW CS VREDRAW
Ten styl klasy mówi, żeby Windows wymuszał odświeżanie, jeśli zmieni się po-
ziomy albo pionowy rozmiar okna.
Możesz obliczyć liczbę pełnych linii tekstu możliwych do wyświetlenia wewnątrz
obszaru roboczego za pomocą wzoru:
cyClient / cyChar
Możesz otrzymać 0, jeśli wysokość obszaru roboczego jest zbyt mała, by wyświe-
tlić pełny znak. Podobnie, przybliżona liczba małych liter możliwych do wyświe-
tlenia w poziomie, wewnątrz obszaru roboczego, jest równa
cxClient / cxChar
Jeśli określisz cxChar i cyChar podczas komunikatu WM CREATE, nie martw się
o dzielenie przez 0 w tych obliczeniach. Twoja procedura okna otrzymuje komu-
nikat WMCREATE, kiedy WinMain wywołuje CreateWindow. Pierwszy komuni-
kat WM SIZE przychodzi trochę później, kiedy WinMain wywołuje ShowWindow,
a w tym momencie cxChar i cyChar mają już przypisane wartości dodatnie, różne
od zera.
Poznanie rozmiarów obszaru roboczego okna jest pierwszym krokiem do rozwią-
zania problemu przemieszczania tekstu w takim obszarze roboczym, który nie
jest dostatecznie duży, by zmieścić wszystko. Jeśli znasz inne aplikacje Windows,
które mają podobne wymagania, prawdopodobnie już wiesz, czego potrzebuje-
my: to tu jest miejsce na cudowny wynalazek - paski przewijania.
Paski przewijania
Paski przewijania to jedna z najlepszych własności graficznego interfejsu użyt-
kownika. Są one łatwe w użyciu i umożliwiają doskonałe wizualne sprzężenie
Część I: Podstawy
zwrotne. Możesz użyć paska przewijania przy wyświetlaniu wszystkiego - tek-
stu, grafiki, arkusza kalkulacyjnego, rekordów bazy danych, obrazków, stron
WWW - co wymaga więcej miejsca, niż jest dostępne w obszarze roboczym okna.
Paski przewijania są umieszczane pionowo (do przesuwania w górę i w dół) lub
poziomo (do przesuwania w lewo i w prawo). Możesz klikać myszą strzałki na
obu końcach paska przewijania lub obszar pomiędzy strzałkami. Suwak wędru-
je wzdłuż paska przewijania, wskazując przybliżone położenie wyświetlanej części
materiału w stosunku do całego dokumentu. Możesz także przeciągnąć suwak
do określonego położenia za pomocą myszy. Rysunek 4-7 pokazuje zalecany spo-
sób korzystania z pionowego paska przewijania przy wyświetlaniu tekstu.
Kliknij tu,
by przewinąć
jedną linię
do góry
(zawartość okna
przesuwa się w dól)
Przesuń suwak,
by znaleźć się
mniej więcej tam,
gdzie chcesz
Kliknij tu,
by przewinąć
jedną linię w dól
(zawartość okna
przesuwa się do góry)
Rysunek 4-7. Pionowy pasek przewijania
Programiści mają czasem problemy z terminologią przewijania, ponieważ ich
spojrzenie jest różne od spojrzenia użytkownika. Użytkownik, który przewija w
dół, chce zobaczyć dalszą (niższą) część dokumentu; jednak w rzeczywistości
program podnosi dokument w stosunku do okna widocznego na ekranie. Doku-
mentacja Windows i identyfikatory w pliku nagłówkowym są oparte na spojrze-
niu użytkownika: przewijanie w górę oznacza poruszanie się ku początkowi do-
kumentu; przewijanie w dół oznacza poruszanie się ku końcowi dokumentu.
Bardzo łatwo jest dołączyć poziomy albo pionowy pasek przewijania do okna
aplikacji. Musisz tylko wstawić identyfikator stylu okna (WS) paska pionowego
WS VSCROLL lub paska poziomego WS HSCROLL, lub oba, w trzecim argu-
mencie CreateWindow. Paski przewijania określone w funkcji CreateWindow są
umieszczane zawsze po prawej stronie lub na dole obszaru roboczego okna i zaj-
mują całą długość lub szerokość tego obszaru. Obszar roboczy nie obejmuje pa-
ska przewijania. Szerokość pionowego paska przewijania i wysokaść poziornego
paska przewijania są stałe dla konkretnego sterownika graficznego i rozdzielczo-
ści ekranu. Jeśli potrzebujesz tych wartości, możesz je uzyskać (jak mogłeś za-
uważyć) wywołując GetSystemMetrics.
Rozdział 4: Wyświetlanie tekstu 91
Windows wykonuje przetwarzanie wszystkich komunikatów myszy dotyczących
pasków przewijania. Jednak paski przewijania nie zawierają interfejsu klawiatu-
ry. jeśli chcesz za pomocą klawiszy ze strzałkami powielić niektóre funkcje pa-
sków przewijania, musisz sam napisać odpowiedni kod (zrobimy to w kolejnej
wersji programu SYSMETS, w dalszych rozdziałach).
Zakres i pozycja paska przewijania
Z każdym paskiem przewijania są związane "zakres" i "pozycja". Zakres paska
przewijania to para liczb całkowitych przedstawiających minimalną i maksymalną
wartość związaną z paskiem przewijania. Pozycja to położenie suwaka w tym
zakresie. Gdy suwak jest na górze (lub na lewym skraju) paska przewijania, po-
zycja suwaka odpowiada minimalnej wartości zakresu. Gdy suwak jest na dole
(lub na prawym skraju), pozycja suwaka odpowiada maksymalnej wartości za-
kresu.
Domyślny zakres paska przewijania wynosi od 0 (góra lub lewy skraj) do 100
(dół lub prawy skraj), ale łatwo jest zmienić taki zakres na coś bardziej dogodne-
go dla programu:
SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) :
Argument iBar przyjmuje wartość SB VERT albo SB HORZ. Parametry iMin i iMax
wyznaczają nową minimalną i maksymalną wartość zakresu. Możesz ustawić
bRedraw na wartość TRUE, jeśli chcesz, by Windows przerysował pasek przewi-
jania na podstawie nowego zakresu. (Jeśli po wywołaniu SetScrolLRange będziesz
chciał wywołać inne funkcje wpływające na wygląd paska przewijania, prawdo-
podobnie będziesz chciał ustawić bRedraw na FALSE, aby uniknąć niepotrzebne-
go odświeżania).
Położenie suwaka zawsze określa liczba całkowita. Na przykład pasek przewija-
nia z zakresem od 0 do 4 ma pięć położeń suwaka, jak pokazuje to rysunek 4-8.
Pozycja 0
Pozycja 1
Pozycja 2
Pozycja 3
Porycja 4
Rysunek 4-8. Paski przewijania z pięcioma położeniami suwaka
Pozycja 0 Pozycja 1 Pozycja 2 Pozycja 3 Pozyca a
92 Część I: Podstawy
Możesz użyć SetScrolIPos do ustawienia nowego położenia suwaka na pasku prze-
wijania:
SetScrollPos (hwnd, iBar, iPos, bRedraw) ;
Argument iPos jest nowym położeniem i musi mieścić się między iMin i iMax.
Windows dostarcza podobnych funkcji (GetScrollftange i GetScroIlPos), umożliwia-
jących uzyskanie aktualnego zakresu i pozycji paska przewijania.
Kiedy używasz w swoim programie pasków przewijania, dzielisz z Windows
odpowiedzialność za ich obsługę i aktualizację pozycji ich suwaków. Windows
odpowiada za następujące funkcje pasków przewijania:
ł obsługę komunikatów myszy przeznaczoną dla pasków przewijania,
ł sygnalizację reakcji paska przewijania na kliknięcie myszą, co objawia się
migotaniem z odwróceniem kolorów,
ł przemieszczanie suwaka w miarę jak użytkownik przeciąga go w obrębie paska
przewijania,
ł wysyłanie komunikatów paska przewijania do odpowiedniej procedury okna.
Twój program odpowiada za następujące funkcje:
ł inicjację zakresu i pozycji paska przewijania,
ł przetwarzanie komunikatów paska przewijania skierowanych do procedury
okna,
ł aktualizację pozycji suwaka paska przewijania,
ł zmianę zawartości obszaru roboczego w odpowiedzi na zmiany na pasku
przewijania.
Najlepiej będzie, jak przyjrzymy się teraz fragmentowi kodu.
Komunikaty paska przewijania
Windows wysyła procedurze okna komunikaty WM VSCROLL (pionowe prze-
wijanie) i WMHSCROLL (poziome przewijanie), kiedy klikamy myszą pasek
przewijania albo przeciągamy suwak. Każde działanie myszy na pasku przewi-
jania generuje co najmniej dwa komunikaty: pierwszy, gdy przycisk myszy zo-
stał wciśnięty, i drugi, gdy został zwolniony.
Jak wszystkim komunikatom, również WM VSCROLL i WMHSCROLL towa-
rzyszą parametry komunikatu wParam i IParam. W komunikatach generowanych
przez paski przewijania utworzone jako część twojego okna możesz pominąć
parametr IParam. Jest on używany tylko dla pasków przewijania tworzonych jako
okna potomne, zwykle w oknach dialogowych.
Parametr komunikatu wParam można podzielić na bardziej i mniej znaczące sło-
wo. Mniej znaczące słowo wParam jest liczbą, która wskazuje, co mysz robi z pa-
skiem przewijania. Ta liczba jest nazywana kodem powiadomienia (ang. notifica-
tion code). Kody powiadomienia mają wartości zdefiniowane przez identyfikato-
ry zaczynające się od przedrostka SB (ang. scroll bar - pasek przewijania). Oto
kody powiadomienia zdefiniowane w WINLlSER.H:
Rozdział 4: Wyświetlanie tekstu 93
define SB_LINEUP 0
define SB_LINELEFT 0
ldefine SB_LINEDOWN 1
Ildefine SB_LINERIGHT 1
Ildefine SB_PAGEUP 2
ldefine SB_PAGELEFT 2
Ildefine SB_PAGEDOWN 3
define SB_PAGERIGHT 3
define SB_THUMBPOSITION 4
define SB_THUMBTRACK 5
Idefine SB_TOP 6
lldefine SB_LEFT 6
Ildefine SB_BOTTOM 7
ldefine SB_RIGHT 7
Ildefine SBENDSCROLL 8
Możesz używać identyfikatorów zawierających słowa LEFT i RIGHT dla poziomych
pasków przewijania i identyfikatorów ze słowami UP, DOWN, TOP i BOTTOM dla
pionowych pasków przewijania. Kody powiadomienia powiązane z klikaniem
myszą różnych obszarów paska przewijania pokazane są na rysunku 4-9.
Wciśnięty: SB LINEUP lub SB LINELEFT
Zwolniony: SB ENDSCROLL
Wciśnięty: SB PAGEUP lub SB PAGELEFT
Zwolniony: SB ENDSCROLL
Wciśnięty: SB THUMBTRACK -
Zwolniony: SB THUMBPOSITION
Wciśnięty: SB PAGEDOWN lub SB PAGERIGHT
Zwolniony: SB ENDSCROLL
Wciśnięty: SB LINEDOWN lub SB LINERIGHT
Zwolniony: SB ENDSCROLL
""'~~ ł ...
Rysunek 4-9. Identyfikatory wartości wParam komunikatów paska przewijania
Jeśli przytrzymasz naciśnięty przycisk myszy na różnych częściach paska prze-
wijania, twój program może otrzymać wiele komunikatów. Kiedy przycisk my-
szy zostanie zwolniony, otrzymasz komunikat z kodem powiadomienia SB END-
SCROLL. Zasadniczo możesz ignorować komurukaty z tym kodem. Windows nie
zmienia pozycji suwaka na pasku przewijania. Robi to twoja aplikacja, wywołu-
jąc SetScrolIPos.
Jeśli umieścisz kursor myszy nad suwakiem paska przewijania i naciśniesz przy-
cisk myszy, możesz przesuwać suwak. Generuje to komunikaty paska przewija-
nia z kodami powiadomienia SB THUMBTRACK i SB THUMBPOSITION. Jeśli
94 Część I: Podstawy
mniej znaczącym słowem wParam jest SB THUMBTRACK, to bardziej znaczące
słowo wParam rejestruje bieżącą pozycję przeciąganego suwaka paska przewija-
nia. Ta pozycja znajduje się pomiędzy minimalną i maksymalną wartością zakre-
su paska przewijania. Kiedy mniej znaczącym słowem wParam jest SB THUMB-
POSITION, wtedy bardziej znaczące słowo wParam zawiera końcową pozycję
suwaka paska przewijania po zwolnieniu przycisku myszy. Przy innych działa-
niach paska przewijania bardziej znaczące słowo wParam należy pominąć.
Gdy przeciągasz myszą suwak paska przewijania i twój program otrzyma ko-
munikat SB THUMBTRACK, Windows w celu zapewnienia sprzężenia zwrot-
nego przemieści odpowiednio suwak. Jeśli jednak nie przetworzysz komunika-
tów SB THUMBTRACK albo SB THUMBPOSITION wywołując SetScrollPos, su-
wak powróci do swojej poprzedniej pozycji, gdy tylko zwolnisz przycisk myszy.
Program może przetwarzać którykolwiek z komunikatów: SB T'HUMBTRACK albo
SB THUMBPOSITION, ale zwykle nie przetwarza obydwóch. Jeśli przetwarzasz
komunikat S THUMBTRACK, powinieneś przesuwać zawartość swojego obsza-
ru roboczego wtedy, gdy użytkownik przeciąga suwak. Jeśli w zamian przetwa-
rzasz komunikat SB THUMBPOSITION, będziesz przesuwał zawartość obszaru
roboczego tylko wtedy, gdy użytkownik zatrzyma przeciąganie suwaka. Bardziej
pożądane (ale trudniejsze) jest przetwarzanie komunikatu SB THUMBTRACK; dla
pewnych typów danych twój program może mieć zbyt mało czasu na obsługę tych
komunikatów.
Jak mogłeś zauważyć, plik nagłówkowy WINUSER.H zawiera kody powiado-
mienia SB TOP, SB BOTTOM, SB LEFT i SB RIGHT, wskazujące, że pasek prze-
wijania został przemieszczony do swojej minimalnej lub maksymalnej pozycji.
Jednak nigdy nie otrzymasz tych kodów powiadomienia dla paska przewijania
utworzonego jako część twojego okna aplikacji.
Chociaż nie zdarza się to często, to jednak możliwe jest używanie wartości 32-bito-
wych dla zakresu paska przewijania. Bardziej znaczące słowo wParam, które jest
tylko 16-bitową wartością, nie może właściwie wskazywać pozycji dla działań
SB THUMBTRACK i SB THUMBPOSTITON. W tym przypadku musisz użyć funk-
cji GetScrolllnfo (opisanej w dalszej części rozdziahx), by uzyskać tę informację.
Przewijanie SYSMETS
Dość objaśnień. Nadszedł czas na praktyczne zastosowanie nabytych wiadomo-
ści. Zaczniemy od czegoś łatwego, a potrzebnego - od przewijania pionowego.
Przewijanie poziome może zaczekać. Kod programu SYSMETS2 jest pokazany
na rysunku 4-10. Prawdopodobnie jest on najprostszą realizacją paska przewija-
nia, jaką chciałbyś mieć w aplikacji.
SYSMETS2.C
/*
SYSMETS2.C - Program nr 2 wyświetlajdcy wymiary
elementów graficznych
(c) Charles Petzold, 1998
*/
Rozdział 4: Wyświetlanie tekstu 95
include
include "sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[] = TEXT ("SysMets2") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CS VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
' wndclass.hCursor = LoadCursor (NULL, IDCARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITEBRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MBICONERROR) ;
)
hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 2"),
WS_OVERLAPPEDWINDOW WSVSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CWUSEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
)
return msg.wParam ;
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static int cxChar, cxCaps, cyChar, cyClient, iVscrollPos ;
HDC hdc ;
int i, y ;
PAINTSTRUCT ps ;
TCHAR szBuffer[10] ;
TEXTMETRIC tm ;
switch (message)
gs Część I: Podstawy
(ciąg dalszy ze strony 95)
t '
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalLeadin9 ;
ReleaseDC (hwnd, hdc) ;
SetScrollRange (hwnd, SB VERT, 0, NUMLINES - 1, FALSE> ;
SetScrollPos (hwnd, SB VERT, iVscrollPos, TRUE) ;
return 0 ;
case WM_SIZE:
cyClient = HIWORD (lParam) ;
return 0 ;
case WM VSCROLL:
switch (LOWORD (wParam))
(
case SBLINEUP:
iVscrollPos -= 1 ;
break ;
case SB_LINEDOWN:
iVscrollPos += 1 ;
break ;
case SB_PAGEUP:
iVscrollPos -= cyClient / cyChar ;
break ;
case SBPAGEDOWN:
iVscrollPos += cyClient / cyChar ;
break ;
case SB THUMBPOSITION:
iVscrollPos = HIWORD (wParam) ;
break ;
default :
break ;
1
iV.scrollPos = max (0, min (iVscrollPos, NUMLINES - 1)) ;
if (iVscrollPos != GetScrollPos (hwnd. SB VERT))
(
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
InvalidateRect (hwnd, NULL, TRUE) ;
)
return 0 ;
case WMPAINT:
Rozdział 4: Wyświetlanie tekstu
hdc = BeginPaint (hwnd, &ps) ;
for (i = 0 ; i < NUMLINES ; i++)
i
y = cyChar * (i - iVscrollPos)
TextOut (hdc, 0, y,
sysmetricsCi].szLabel,
lstrlen (sysmetrics[i].szLabel)) :
TextOut (hdc, 22 * cxCaps, y,
sysmetricsCi].szDesc,
lstrlen (sysmetricsCi].szDesc)) ;
SetTextAlign (hdc, TARIGHT TA TOP) ;
TextOut (hdc, 22 * cxCaps + 40 * cxChar, y, szBuffer,
wsprintf (szBuffer, TEXT ("%5d"),
GetSystemMetrics (sysmetrics[i].iIndex))) ;
SetTextAlign (hdc. TALEFT T TOP) ;
)
EndPaint (hwnd, &ps) ;
return 0 ;
case WMDESTROY:
PostOuitMessage (0) ;
return 0 ;
1
return DefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 4ł10. Program SYSMETS2
Nowe wywołanie CreateWindow dodaje do okna pionowy pasek przewijania przez
włączenie WS VSCROLL w trzecim argumencie stylu okna :
WS OVERLAPPEDWINDOW WS USCROLL
Komunikat WMCREATE przetwarzany w procedurze okna WndProc ma dwie
dodatkowe linie określające zakres i początkową pozycję pionowego paska prze-
wijania:
SetScrollRange (hwnd, SB_VERT, 0, NUMLINES - 1, FALSE) ;
SetScrollPos (hwnd, SBVERT, iVscrollPos, TRUE) ;
Tablica struktury sysmetrics ma NUMLINES linii tekstu, więc zakres paska prze-
wijania jest równy przedżiałowi od 0 do NUMLINES - 1. Każda pozycja paska
przewijania odpowiada linii tekstu wyświetlanej na górze obszaru roboczego. Jeśli
suwak paska przewijania jest w pozycji 0, na górze obszaru roboczego jest umiesz-
czana pierwsza linia. Dla pozycji większych od zera wyświetlane są tam inne li-
nie. Kiedy pozycją jest NUMLINES - 1, na górze obszaru roboczego wyświetlana
jest ostatnia linia tekstu.
Aby pomóc w przetwarzaniu komunikatu 4VMVSCROLL, wewnątrz procedury
okna została zdefiniowana zmienna statyczna o nazwie iVscroIlPos. Zawiera ona
bieżącą pozycję suwaka paska przewijania. W przypadku komunikatów SB LI-
NEUP i SB LINEDOWN, wystarczy jedynie zmienić pozycję przewijania o 1.
W przypadku SB PAGEUP i SB PAGEDOWN musimy przemieścić tekst o za-
s
98 Część I: Podstawy
wartość jednego ekranu lub o cyClient podzielone przez cyChar linii. Dla komu-
nikatu SB THUMBPOSITION nową pozycją suwaka jest bardziej znaczące sło-
wo wParam. Komunikaty SB ENDSCROLL i SB THUMBTRACK są pomijane.
Gdy program oblicza nową wartość iVscrollPos na podstawie typu otrzymanego
komunikatu WMVSCROLL, stosując makra min i max upewnia się, że znajduje
się ona ciągle pomiędzy wartością minimalną i maksymalną zakresu paska prze-
wijania. Następnie program porównuje wartość iVscrollPos z poprzednim poło-
żeniem, uzyskanym przez wywołanie GetScroIlPos. Jeśli zmieniła się pozycja prze-
wijania, jest ona aktualizowana przez wywołanie SetScroIlPos, natomiast wywo-
łanie InvalidateRect unieważnia całe okno.
Funkcja InvalidateRect generuje komunikat WMPAINT. Kiedy oryginalny pro-
gram SYSMETS1 przetwarzał komunikat WMPAINT, współrzędna y każdej li-
n była obliczana następująco:
cyChar
W SYSMETS2 wzór został zmodyfikowany:
cyChar * (i - iVscrollPos)
Pętla ciągle wyświetla NUMLINES linii tekstu, ale dla wartości iVscrollPos róż-
nych od zera ta wartość jest ujemna. W rzeczywistości program wyświetla wcze-
śniejsze linie tekstu powyżej i poza obszarem roboczym. Oczywiście Windows
nie pozwala tym liniom ukazać się na ekranie, więc wszystko wygląda ładnie
i przyjemnie.
Mówiłem, że zaczniemy od prostego przykładu. Jednak ten kod jest mało sku-
teczny. Poprawimy go niebawem, ale najpierw omówimy odświeżanie obszaru
roboczego po przetworzeniu komunikatu WM VSCROLL.
Dostosowanie programu do malowania
Procedura okna w SYSMETS2 nie odświeża bezpośrednio obszaru roboczego po
przetworzeniu komunikatu paska przewijania. W zamian wywołuje funkcję In-
validateRect, unieważniając obszar roboczy. Na skutek tego Windows umieszcza
komunikat 4VMPAINT w kolejce komunikatów.
Najlepiej, jeśli program windowsowy wykonuje całe malowanie obszaru robo-
czego w odpowiedzi na komunikat WMPAINT. Ponieważ program po otrzy-
maniu komunikatu WMPAINT powinien być zawsze zdolny do odświeżania
całego obszaru roboczego okna, malowanie w odpowiedzi na inny komunikat
prawdopodobnie spowoduje włączenie kodu, będącego kopią funkcjonalną
WM PAINT.
Możesz buntować się, słysząc takie stwierdzenia. Zapewne wydaje ci się, że pro-
ponuję okrężną drogę. W początkach Windows programiści z trudem przyjmo-
wali ten pogląd, tak różny od programowania w trybie znakowym. Jak wspo-
mniałem wcześniej, istnieje wiele sytuacji, gdy twój program musi zareagować
na klawiaturę lub mysz, rysując coś natychmiast. Jest to niewątpliwie wygodne
i skuteczne, ale w wielu przypadkach po prostu niepotrzebne. Jeśli opanujesz sztu-
kę gromadzenia wszystkich informacji potrzebnych do malowania w odpowie-
dzi na komunikat WMPAINT, będziesz zadowolony z wyników.
Rozdział 4: Wyświetlanie tekstu 99
Jak pokazuje SYSMETS2, przy przetwarzaniu innych komunikatów niż WMPA-
INT program często określa, że musi ponownie namalować konkretny obszar
ekranu. Pomaga wtedy InvalidateRect. Możesz używać tej funkcji do unieważnia-
nia określonego prostokąta obszaru roboczego lub całego obszaru roboczego.
W niektórych aplikacjach proste zaznaczenie obszarów okna jako unieważnionych
w celu generacji komunikatów WM PAINT może nie dawać zadowalających rezul-
tatów. Gdy wywohxjesz InvalidateRect, Windows umieszcza komunikat WMPAINT
w kolejce komunikatów i procedura okna ostatecznie go przetworzy. Jednak Win-
dows traktuje WM PAINT jako komunikat o niskim priorytecie, więc jeśli w syste-
mie dzieje się dużo innych rzeczy, może chwilę potrwać, zanim twoja procedura okna
otrzyma komunikat WM PAIN'T. Każdy z nas widział na ekranie puste białe "dziu-
ry" pojawiające się po zamknięciu okna dialogowego, wtedy gdy program czekał na
odświeżenie swojego okna.
Jeśli chcesz natychmiast aktualizować unieważniony obszar, możesz bezpośred-
nio po InvalidateRect wywołać funkcję UpdateWindow:
UpdateWindow (hwnd) ;
Jeśli jakaś część obszaru roboczego została unieważniona, UpdateWindow powo-
duje natychmiastowe wywołanie procedury okna z komunikatem WM PAINT.
(UpdateWindow nie wywoła procedury okna, jeśli cały obszar roboczy jest zatwier-
dzony). W tym przypadku komunikat WM PAINT ominie kolejkę komunikatów.
Procedura okna zostanie wywołana bezpośrednio przez Windows. Kiedy proce-
dura okna zakończy odświeżanie, funkcja UpdateWindow zwróci sterowanie do
kodu, który ją wywołał.
Zauważ, że UpdateWindow jest funkcją używaną także przez WinMain do genero-
wania pierwszego komunikatu WM PAINT. Kiedy okno jest tworzone pierwszy
raz, cały obszar roboczy jest unieważniony. UpdateWindow wywołuje procedurę
okna, aby namalowała obszar roboczy.
Poprawianie przewijania
SYSMETS2 działa dobrze, ale jest mało efektywny i nie zasługuje na naśladowa-
nie w innych programach. Wkrótce przedstawię nową wersję, która koryguje jego
braki. Być może najbardziej interesujące jest to, że nowa wersja nie używa żadnej
z czterech omawianych dotychczas funkcji paska przewijania. W zamian używa
nowych funkcji, właściwych tylko dla Win32 API.
F'unkcje informacyjne pasków przewijania
Dokumentacja paska przewijania (w /Platform SDK/Liser Interface Serices/Controls/
Scroll Bars) utrzymuje, że funkcje SetScroIlRange, SetScrollPos, GetScroIlRange i Get-
ScrollPos są przestarzałe. Nie jest to do końca prawdą. Chociaż te funkcje pojawi-
ły się już w Windows 1.0, zostały przystosowane do obsługi 32-bitowych argu-
mentów Win32 API. Ciągle doskonale działają i prawdopodobnie pozostaną
w użyciu. Ponadto są one na tyle proste, że nie przestraszą początkującego pro-
gramisty. Dlatego ciągle odwołuję się do nich w tej książce.
w
100 Część I: Podstawy
W Win32 API wprowadzono dwie funkcje pasków przewijania, o nazwach Set-
ScrolIlnfo i GetScrolllnfo. Zastępują one całkowicie poprzednie funkcje i dodają dwie
nowe, ważne cechy.
Pierwsza cecha dotyczy rozmiaru suwaka paska przewijania. Jak mogłeś zauwa-
żyć, rozmiar suwaka w programie SYSMETS2 był stały. Jednak w niektórych apli-
kacjach Windows rozmiar suwaka jest proporcjonalny do ilości dokumentu wy-
świetlanego w oknie. Ta wyświetlana ilość jest znana jako "wielkość strony".
Można ją określić arytmetycznie:
Wielkość suwaka Wielkość strony Ilość wyświetlanego dokumentu
Długość przewijania Zakres Całkowita wielkość dokumentu
Możesz użyć SetScrolllnfo do ustawienia wielkości strony (i stąd wielkości suwa-
ka), jak zobaczymy to niebawem w programie SYSMETS3.
Funkcja GetScrolllnfo dodaje drugą ważną cechę lub raczej koryguje brak w aktu-
alnym API. Załóżmy, że chcesz używać zakresu, który ma 65536 lub więcej jed-
nostek. Dawniej, w czasach 16-bitowych Windows, nie było to możliwe. Oczywi-
ście w Win32 funkcje są zdefiniowane jako przyjmujące 32-bitowe argumenty i
tak naprawdę działają. (Pamiętaj, że jeśli używasz tak wielkiego zakresu, liczba
rzeczywistych fizycznych pozycji suwaka jest jeszcze ograniczona przez rozmiar
paska przewijania podawany w pikselach). Jednak gdy otrzymasz komunikat
WM VSCROLL albo WM HSCROLL z kodem powiadomienia SB THUMB-
TRACK albo SB THUMBPOSITION, dostaniesz tylko 16 bitów wskazujących
aktualną pozycję suwaka. Funkcja GetScrołllnfo pozwala otrzymywać rzeczywi-
ście wartość 32-bitową.
Składnia funkcji SetScrolllnfo i GetScrolllnfo jest następująca:
SetScrollInfo (hwnd, iBar, &si, bRedraw) ;
GetScrollInfo (hwnd, iBar, &si) ;
Argumentem iBar jest SB VERT albo SB HORZ, jak w innych funkcjach paska prze-
wijania. Może nim być także SB CTL dla kontroli paska przewijania. Ostatriim argu-
mentem SetScrolllnfo może być TRUE lub FALSE, zależnie od tego, czy chcesz, żeby
Windows przerysowywał pasek przewijania uwzględniając nową informację.
Trzecim argumentem obu funkcji jest struktura SCROLLINFO, zdefiniowana tak:
typedef struct tagSCROLLINFO
(
UINT cbSize ; // ustaw na sizeof (SCROLLINFO)
UINT fMask ; // wartość ustawiana lub otrzymywana
int nMin ; // minimalna wartość zakresu
int nMax ; // maksymalna wartość zakresu
UINT nPage ; // wielkość strony
int nPos ; // aktualne położenie
int nTrackPos ; // aktualne położenie śledzenia
)
SCROLLINFO, * PSCROLLINFO ;
W swoim programie możesz zdefiniować strukturę typu SCROLLINFO w taki
sposób:
Rozdział 4: Wyświetlanie tekstu 101
SCROLLINFO si ;
Przed wywołaniem SetScrolllnfo lub GetScrolllnfo musisz wpisać do pola cbSize
wielkość struktury:
si.cbSize = sizeof (si) ;
albo
si.cbSize = sizeof (SCROLLINFO) ;
Gdy poznasz lepiej Windows, znajdziesz wiele innych struktur, których pierw-
sze pole wskazuje w ten sposób rozmiar struktury. To pole pozwoli przyszłym
wersjom Windows rozszerzyć strukturę i dodać nowe własności, a mimo to po-
zostać zgodnym z wcześniej kompilowanymi programami.
Ustaw pole fMask na jeden lub więcej znaczników zaczynających się przedrost-
kiem SIF. Możesz łączyć te znaczniki funkcją OR ( I ) z C (alternatywy bitowej).
Kiedy z funkcją SetScrollInfo będziesz używał znacznika SIF RANGE, musisz
ustawić pola nMin i nMax na odpowiedni zakres paska przewijania. Kiedy bę-
dziesz używał tego znacznika z funkcją GetScrolllnfo, przy powrocie z wywoła-
nia funkcji pola nMin i nMax będą zawierały aktualny zakres.
Znacznik SIF POS działa podobnie. Kiedy jest używany z funkcją SetScrolllnfo,
musisz ustawić pole nPos struktury na odpowiednie położenie. Używając znacz-
nika SIF POS z GetScrolllnfo, otrzymasz aktualne położenie.
Znacznik SIF PAGE pozwala ustawić i otrzymać wielkość strony. Ustaw nPage na
właściwą wielkość strony przy wywołaniu funkcji SetScrolllnfo. GetScrolllnfo ze znacz-
nikiem SIF PAGE pozwala uzyskać aktualną wielkość strony. Nie używaj tego znacz-
nika, jeśli nie chcesz proporcjonalnego suwaka paska przewijania.
Używaj znacznika SIF TRACKPOS tylko z GetScrolllnfo podczas przetwarzania ko-
munikatów WM VSCROLL albo WM HSCROLL z kodem powiadomienia
SB_THUMBTRACK albo SB THUMBPOSTTION. Po powrocie z tej funkcji pole
nTrackPos struktury SCROLLINFO wskazuje aktualne 32-bitowe położenie suwaka.
Używaj znacznika SIF DISABLENOSCROLL tylko razem z funkcją SetScrolllnfo.
Jeśli ten znacznik jest określony, a nowe argumenty paska przewijania powinny
uczynić go niewidocznym, to pasek przewijania zostan,ie wyłączony. (Wyjaśnię
to niebawem).
Znacznik SIF ALL jest połączeniem znaczników SIF RANGE, SIF POS, SIF PA-
GE i SIF_TRACKPOS. Ułatwia ustawianie argumentów paska przewijania pod-
czas komunikatu WMSIZE. (Znacznik SIF TRACKPOS jest ignorowany, jeśli zo-
stanie określony w funkcji SetScrolllnfo). Jest także bardzo wygodny przy prze-
twarzaniu komunikatu paska przewijania.
Jak przewijać minimalnie?
W SYSMETS2 zakres przewijania jest ustawiony na wartość minimalną 0 i mak-
symalną NUMLINES - 1. Kiedy pasek przewijania jest w położeniu 0, pierwsza
linia informacji znajduje się na górze obszaru roboczego; kiedy pasek przewija-
nia jest w położeniu NUMLINES - 1, ostatnia linia znajduje się na górze obszaru
roboczego, a inne linie są niewidoczne.
102 Część I: Podstawy
Możesz powiedzieć, że SYSMETS2 przewija zbyt daleko. Wystarczyłoby, żeby prze-
wijał tylko do momentu, gdy ostatnia linia informacji ukaże się na dole obszaru ro-
boczego, a nie na górze. Możemy zrobić kilka zmian w SYSMETS2, aby to osiągnąć.
Zamiast ustawiać zakres paska przewijania podczas przetwarzania komunikatu
WM CREATE, możemy zaczekać, aż otrzymamy komunikat WM SIZE:
iVscrollMax = max (0, NUMLINES - cyClient / cyChar) ;
SetScrollRange (hwnd, SB VERT, 0, iVscrollMax, TRUE) ;
Załóżmy, że NUMLINES równa się 75, a dla konkretnego okna wartość cyClient
dzielona przez cyChar równa się 50. Inaczej mówiąc, mamy 75 linii informacji,
ale tylko 50 możemy wyświetlić w obszarze roboczym. Stosując pokazane powy-
żej dwie linie kodu, zakres jest ustawiany na minimum równe 0 i maksimum równe
25. Kiedy położenie paska przewijania będzie równe 0, program wyświetli linie
od 0 do 49. Kiedy położenie paska przewijania będzie równe 1, program wyświetli
linie od 1 do 50; a kiedy położenie paska przewijania będzie równe 25 (maksy-
malne), program wyświetli linie od 25 do 74. Oczywiście musimy wprowadzić
też zmiany w innych częściach programu, ale jest to całkowicie wykonalne.
Jedną z przyjemnych cech nowych funkcji paska przewijania jest to, że jeśli uży-
wasz rozmiaru strony paska przewijania, wiele z powyższych działań jest wyko-
nywane za ciebie. Używając struktury SCROLLINFO i funkcji SetScrolllnfo, mo-
żesz wykorzystać następujący kod:
si.cbSize = sizeof (SCROLLINFO) ;
si.cbMask = SIF RANGE SIFPAGE ;
si.nMin = 0 ;
si.nMax = NUMLINES - 1 ;
si.nPage = cyClient / cyChar ;
SetScrollInfo (hwnd, SBVERT, &si, TRUE) ;
Kiedy to zrobisz, Windows ograniczy maksymalne położenie paska przewijania
nie do si.nMax, ale do si.nMax - si.nPage + 1. Powtórzmy wcześniejsze założenia:
NUMLINES równa się 75 (więc si.nMax równa się 74), a si.nPage równa się 50.
Oznacza to, że maksymalne położenie paska przewijania jest ograniczone do 74
- 50 + 1, czyli 25. To dokładnie to, czego chcemy.
Co się stanie, jeśli rozmiar strony będzie tak duży jak zakres paska przewijania,
czyli jeśli w naszym przykładzie nPage wynosi 75 albo więcej? Windows zwykle
ukrywa pasek przewijania, ponieważ nie jest już potrzebny. Jeśli nie chcesz, by
pasek przewijania był ukrywany, to podczas wywoływania SetScrolllnfo użyj
SIF DISABLENOSCROLL .Wtedy Windows tylko wyłączy pasek przewijania, za-
miast go ukryć.
Nowy SYSMETS
SYSMETS3 - końcowa wersja naszego programu SYSMETS z tego rozdziału -
widnieje na rysunku 4-11. Ta wersja używa funkcji SetScrolllnfo i GetScrolllnfo,
dodaje poziomy pasek przewijania w lewo i w prawo i bardziej efektywnie od-
świeża obszar roboczy.
Rozdział 4: Wyświetlanie tekstu 103
SYSMETS3.C
,
/*
SYSMETS3.C - Program nr 3 wyświetlający wymiary
elementów graficznych
(c) Charles Petzold, 1998
*/
4include
include "sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
i
static TCHAR szAppNameC] = TEXT ("SysMets3") ;
HWND hwnd ;
j MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CS VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDCARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITEBRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MBICONERROR) ;
return 0 ;
1
hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 3"),
WS OVERLAPPEDWINDOW WS_VSCROLL WS-HSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CWUSEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
I
return msg.wParam ;
l
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
104 Część I: Podstawy
(ciąg dalszy ze strony 103)
static int cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth ;
HDC hdc ;
int i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd ;
PAINTSTRUCT ps ;
SCROLLINFO si ;
TCHAR szBuffer[10] ;
TEXTMETRIC tm ;
switch (message)
(
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
// Zapamiętaj szerokość trzech kolumn
iMaxWidth = 40 * cxChar + 22 * cxCaps ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
// Ustaw zakres pionowego paska przewijania i rozmiar strony
si.cbSize = sizeof (si) ;
si.fMask = SIFRANGE SIFPAGE ;
si.nMin = 0 ;
si.nMax = NUMLINES - 1 ;
si.nPage = cyClient / cyChar ;
SetScrollInfo (hwnd, SB VERT, &si, TRUE) ;
// Ustaw zakres poziomego paska przewijania i rozmiar strony
si.cbSize = sizeof (si) ;
si.fMask = SIFRANGE SIFPAGE ;
si.nMin = 0 ;
si.nMax = 2 + iMaxWidth / cxChar ;
si.nPage = cxClient / cxChar ;
SetScrollInfo (hwnd, SB HORZ, &si, TRUE) ;
return 0 :
case WM VSCROLL:
// Pobierz informację o pionowym pasku przewijania
si.cbSize = sizeof (si) ;
si.fMask = SIF_ALL ;
GetScrollInfo (hwnd, SB VERT, &si) ;
Rozdział 4: Wyświetlanie tekstu 105
// Zapamiętaj polożenie dla późniejszych porównań
iVertPos = si.nPos ;
switch (LOWORD (wParam))
case SB_TOP:
si.nPos = si.nMin ;
break ;
case SB_BOTTOM:
si.nPos = si.nMax ;
break ;
case SB_LINEUP:
si.nPos -= 1 ;
break ;
case SB_LINEDOWN:
si.nPos += 1 ;
break ;
case SBPAGEUP:
si.nPos -= si.nPage ;
break ;
case SB_PAGEDOWN:
si nPos += si.nPage ;
break ;
case SB_THUMBTRACK:
si.nPos = si.nTrackPos ;
break ;
i
default:
break ;
i
// Ustaw polożenie i pobierz je. Na skutek korekty
// dokonywanej przez Windows może być różne od ustawianego.
si.fMask = SIF_POS ;
( SetScrollInfo (hwnd, SBVERT, &si, TRUE) ;
GetScrollInfo (hwnd, SB VERT, &si) ;
// Jeśli polożenie zmienione, przewiń i odśwież okno
if (si.nPos != iVertPos)
(
ScrollWindow (hwnd, 0, cyChar * (iVertPos - si.nPos),
NULL, NULL) ;
! UpdateWindow (hwnd) ;
1
i return 0 ;
case WM HSCROLL:
// Pobierz informację o poziomym pasku przewijania
106 Część I: Podstawy
(ciąg dalszy ze strony 105)
si.cbSize = sizeof (si) ; '
si.fMask = SIF ALL ;
// Zapamiętaj położenie dla późniejszych porównań
GetScrollInfo (hwnd, SB HORZ, &si) ;
iHorzPos = si.nPos ;
switch (LOWORD (wParam))
(
case SB_LINELEFT:
si.nPos -= 1 ;
break ;
case SB_LINERIGHT:
si.nPos += 1 ;
break ;
case SB_PAGELEFT:
si nPos -= si.nPage ;
break ;
case SB_PAGERIGHT:
si.nPos += si.nPage ;
break ;
case SB_THUMBPOSITION:
si.nPos = si.nTrackPos ;
break ;
default :
break ;
)
// Ustaw polożenie i pobierz je. Na skutek korekty
// dokonywanej przez Windows może być różne od ustawianego.
si.fMask = SIF_POS ;
SetScrollInfo (hwnd, SB HORZ, &si, TRUE) ;
GetScrollInfo (hwnd, SB HORZ, &si) ;
// Jeśli polożenie zmienione, przewiń i odśwież okno
if (si.nPos != iHorzPos)
(
ScrollWindow (hwnd, cxChar * (iHorzPos - si.nPos), 0,
NULL, NULL) ;
?
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
// Pobierz położenie pionowego paska przewijania
si.cbSize = sizeof (si) ;
Rozdział 4: Wyświetlanie tekstu 107
si.fMask = SIF_POS ;
GetScrollInfo (hwnd, SB VERT, &si) ;
iVertPos = si.nPos ;
// Pobierz polożenie poziomego paska przewijania
GetScrollInfo (hwnd, SB HORZ, &si) ;
iHorzPos = si.nPos ;
// Znajdź ograniczenia malowania
iPaintBeg = max (0, iVertPos + ps.rcPaint.top / cyChar) ;
iPaintEnd = min (NUMLINES - 1,
iVertPos + ps.rcPaint.bottom / cyChar) ;
for (i = iPaintBeg ; i <= iPaintEnd ; i++)
(
x = cxChar * (1 - iHorzPos) ;
y = cyChar * (i - iVertPos) ;
TextOut (hdc, x, y,
sysmetricsCi].szLabel,
lstrlen (sysmetricsCi].szLabel)) ;
TextOut (hdc, x + 22 * cxCaps, y,
sysmetricsCi].szDesc,
lstrlen (sysmetricsCi].szDesc)) ;
SetTextAlign (hdc, TARIGHT T TOP) ;
TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer,
wsprintf (szBuffer, TEXT ("%5d"),
GetSystemMetrics (sysmetrics[i].iIndex))) ;
SetTextAlign (hdc, TALEFT T TOP) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM DESTROY :
PostOuitMessage (0) ;
return 0 ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ;
1
Rysunek 4-11. Program SYSMETS3
Ta wersja programu przekazuje Windows zadanie zapamiętywania informacji
paska przewijania i większość zadań sprawdzania granic. Na początku przetwa-
rzania komunikatów WM VSCROLL i WM HSCROLL program otrzymuje całą
informację paska przewijania, określa położenie na podstawie kodu powiadomie-
nia, a następnie ustawia je wywołując SetScrolllnfo. Następnie wywołuje funkcję
GetScrolllnfo. Jeśli położenie z wywołania SetScrolllnfo było poza zakresem, zo-
stało skorygowane przez Windows, a poprawna wartość jest zwracana przez funk-
cję GetScrolllnfo.
108 Część I: Podstawy
SYSMETS3 używa funkcji ScrollWindow do przewijania informacji w obszarze
roboczym okna, a nie do odświeżania okna. Chociaż funkcja ta jest raczej złożo-
na (w najnowszej wersji Windows została zastąpiona przez jeszcze bardziej zło-
żoną funkcję ScroIlWindowEx), SYSMETS3 posługuje się nią w dość prosty spo-
sób. Drugi argument funkcji podaje wielkość przewijania obszaru roboczego w po-
ziomie (w pikselach), a trzeci argument - wielkość przewijania obszaru robocze-
go w pionie.
Ostatnie dwa argumenty ScrollWindow są ustawione na NULL. Oznacza to, że cały
obszar roboczy ma być przewijany. Windows automatycznie unieważnia prosto-
kątny region obszaru roboczego, który został "odkryty" w wyniku przewijania.
Powoduje to wygenerowanie komunikatu 4VMPAINT. Funkcja InvalidateRect nie
jest już potrzebna. Zauważ, że ScrolIWindow nie jest funkcją GDI, ponieważ nie
wymaga uchwytu kontekstu urządzenia. Jest to jedna z niewielu funkcji Windows
nie należących do GDI, które zmieniają wygląd obszaru roboczego okna. Wyjąt-
kowo, ale jednocześnie dogodnie, jest udokumentowana razem z funkcjami pa-
ska przewijania.
WM HSCROLL przetwarza przerwania kodu powiadomienia SB THUMBPO-
SITION i ignoruje SB THUMBTRACK. Jeśli więc użytkownik przeciąga suwak
poziomego paska przewijania, program nie powinien przewijać zawartości okna
w poziomie, dopóki użytkownik nie zwolni przycisku myszy.
Strategia WM VSCROLL jest inna: tu program reaguje na komunikaty
SB THUMBTRACK i ignoruje SB THUMBPOSITTON. Program będzie więc prze-
wijać zawartość ekranu w pionie w odpowiedzi na przeciąganie przez użytkow-
nika suwaka po pionowym pasku przewijania. Jest to bardzo wygodne, ale uwa-
żaj: użytkownicy, którzy wykryją, że twój program przewija zawartość ekranu
wraz z ruchem suwaka po pasku przewijania, będą gorączkowo szarpać suwak
tam i z powrotem, próbując rzucić program na kolana. Na szczęście, dzisiejsze
szybkie pecety najprawdopodobniej przeżyją tę próbę wytrzymałości. Ale prze-
testuj swój kod na wolniejszym komputerze i zastanów się, czy nie wprowadzić
argumentu SB SLOWMACHINE w GetSystemMetrics, na wypadek, gdyby pro-
gram miał pracować na powolnych maszynach.
Jedyny sposób przyspieszenia przetwarzania WM PAINT jest przedstawiony
w SYSMETS3: kod WM PAINT decyduje, które linie znajdują się wewnątrz unie-
ważnionego prostokąta i tylko te linie przepisuje. Kod jest oczywiście bardziej
złożony, ale za to znacznie szybszy.
Nie lubię myszy
W dawnych czasach większość użytkowników Windows nie doceniała korzyści
płynących z używania myszy i naprawdę sam Windows (i wiele programów
windowsowych) nie wymagał myszy. Chociaż komputery osobiste bez myszy
podzieliły los monitorów monochromatycznych i drukarek mozaikowych, ciągle
zalecane jest pisanie programów, które pozwalają powielać działania myszy na
klawiaturze. Jest to szczególnie ważne dla tak podstawowych funkcji programu,
jak paski przewijania, ponieważ nasze klawiatury dysponują bogatym zestawem
klawiszy nawigacyjnych, które zapewniają alternatywę dla myszy.
; Rozdział 4: Wyświetlanie tekstu 109
W dalszych rozdziałach nauczysz się używać klawiatury i dodawać interfejs kla-
wiatury do programu. Pewnie zauważyłeś, że SYSMETS3 przetwarza komuni-
kat WM VSCIZOLL, jeśli kod powiadomienia jest równy SB TOP i SB BOTTOM.
Wspominałem wcześniej, że procedura okna nie otrzymuje tych komunikatów
od pasków przewijania, więc na razie ten kod jest zbyteczny. Kiedy powrócimy
do tego programu w następnych rozdziałach, zrozumiesz powód włączenia tych
działań.
Wyszukiwarka
Podobne podstrony:
Programowniae windows petzold Petzold01
Programowniae windows petzold Petzold08
Programowniae windows petzold Petzold09
Programowniae windows petzold Petzold13
Programowniae windows petzold Petzold24
Programowniae windows petzold Petzold02
Programowniae windows petzold Petzold21
Programowniae windows petzold Petzold22
Programowniae windows petzold Petzold14
Programowniae windows petzold Petzold04
Programowniae windows petzold Petzold20
Programowniae windows petzold Petzold03
Asembler Podstawy programowania w Windows
2 Podstawy programowania Windows (2)
Visual Studio 05 Programowanie z Windows API w jezyku C vs25pw
informatyka usb praktyczne programowanie z windows api w c andrzej daniluk ebook
więcej podobnych podstron