Drukowanie jest w Windows całkiem proste, a to dzięki kontekstom urządzeń (DC). Ich podstawy poznaliśmy już w rozdziałach:
Przygotowanie dokumentu do wydruku polega właściwie na tym samym, co wyświetlenie go na ekranie.
Jak pewnie wiesz, gdy klikniemy gdzieś jakiś przycisk z ikoną drukarki, zazwyczaj najpierw pojawia się dialog, pozwalający wybrać drukarkę (kto powiedział, że musimy mieć tylko jedną?) oraz rozmaite ustawienia (które strony drukować, ile kopii itp.). Dopiero gdy użytkownik zatwierdzi te ustawienia, rozpoczyna się właściwe drukowanie i tak też będzie u nas.
W WinAPI wszystko kręci się wokół struktur, więc czas na poznanie kolejnej. Nazywa się
PRINTDLGEX, zawiera informacje o dialogu drukowania (po wyświetleniu dialogu będzie też zawierać opcje ustawione przez użytkownika) i jest dość wyrośnięta. Ze względu na ten rozmiar nie od rzeczy będzie zaalokować ją dynamicznie:
int r = 0;
HRESULT hResult;
LPPRINTDLGEX pPDX = NULL;
LPPRINTPAGERANGE pPageRanges = NULL;
pPDX =( LPPRINTDLGEX ) GlobalAlloc( GPTR, sizeof( PRINTDLGEX ) );
if( !pPDX )
return E_OUTOFMEMORY;
Druga struktura, która będzie nam potrzebna, to
PRINTPAGERANGE. Większość użytkowników zadowoli się bowiem wydrukowaniem wszystkich możliwych stron albo ewentualnie jednej wybranej. Niestety, może też mieć zachciankę, żeby wydrukować pierwszą, trzecią i od siódmej do dziesiątej, a my tę zachciankę musimy spełnić. Z tego powodu musimy przygotować sobie tablicę, przechowującą zakresy stron do wydruku, czyli właśnie tablicę struktur
PRINTPAGERANGE:
pPageRanges =( LPPRINTPAGERANGE ) GlobalAlloc( GPTR, 10 * sizeof( PRINTPAGERANGE ) );
if( !pPageRanges )
return E_OUTOFMEMORY;
Ponieważ nie wiemy, ile zakresów złośliwy użytkownik może mieć ochotę wpisać, ustalamy maksimum na 10. Na razie i tak wydrukujemy tylko jedną stronę niezależnie od wyboru użytkownika, ale warto wyrabiać sobie pozytywne nawyki :-).
Pora na wypełnianie struktur... Uwaga, to będzie długi fragment:
pPDX->lStructSize = sizeof( PRINTDLGEX );
pPDX->hwndOwner = hwnd;
pPDX->hDevMode = NULL;
pPDX->hDevNames = NULL;
pPDX->hDC = NULL;
pPDX->Flags = PD_RETURNDC | PD_COLLATE;
pPDX->Flags2 = 0;
pPDX->ExclusionFlags = 0;
pPDX->nPageRanges = 0;
pPDX->nMaxPageRanges = 10;
pPDX->lpPageRanges = pPageRanges;
pPDX->nMinPage = 1;
pPDX->nMaxPage = 1000;
pPDX->nCopies = 1;
pPDX->hInstance = 0;
pPDX->lpPrintTemplateName = NULL;
pPDX->lpCallback = NULL;
pPDX->nPropertyPages = 0;
pPDX->lphPropertyPages = NULL;
pPDX->nStartPage = START_PAGE_GENERAL;
pPDX->dwResultAction = 0;
...Zostałeś ostrzeżony :-). Pole
lStructSize nie wymaga chyba szerszych wyjaśnień, podobnie jak
hwndOwner.
hDevMode wymagałoby pewnie znacznie dłuższego opisu. Jest to uchwyt do struktury
DEVMODE, zawierającej jeszcze więcej ustawień drukowania, dostępnych po wciśnięciu przycisku "Preferencje". Jeśli myślałeś, że struktura
PRINTDLGEX jest duża, to popatrz sobie na
DEVMODE :-). Na szczęście możemy się tutaj wykręcić
NULL-em, wówczas system sam zaalokuje pamięć na tę wielką strukturę, wypełni ją domyślnymi wartościami, a my co najwyżej sprawdzimy sobie później, co tam user sobie wybrał. Podobnie postępujemy z polem
hDevNames.
Dalej mamy pole
hDC. To właśnie ów kontekst, o którym sobie wcześniej wspomnieliśmy. Zostanie on stworzony automatycznie po kliknięciu "Drukuj" przez użytkownika, a my będziemy mieli dostęp do DC właśnie przez ten uchwyt. Tymczasem zaś nie pozostaje nam nic innego, jak tylko ustawić go na
NULL (albo nic nie robić, bo nasza struktura jest zaalokowana z flagą
GPTR, czyli i tak zawiera same zera).
Tak oto doszliśmy do "sekcji" flag. Tutaj tak naprawdę interesuje nas tylko jedna flaga,
PD_RETURNDC, która powoduje stworzenie kontekstu, o którym mówiliśmy w poprzednim akapicie. Inne flagi mają dużo mniejsze znaczenie. W naszym przykładzie dołączyliśmy jeszcze
PD_COLLATE, dzięki czemu w razie drukowania w kilku kopiach domyślnie będzie zaznaczony checkbox "Sortuj", więc jeśli użytkownik się rozpędzi, to dostanie z podajnika kartki we właściwej kolejności i nie będzie musiał ich układać :-). Pole
Flags2 zostawiamy puste, podobnie
ExclusionFlags.
Teraz pora na zakresy stron. Najpierw jest wartość
nPageRanges, która określa, ile początkowo zakresów jest wpisanych. Nie chcemy kusić użytkownika do wpisywania tam różnych skomplikowanych rzeczy, więc dajemy tu 0.
nMaxPageRanges określa maksymalną liczbę zakresów. Zaalokowaliśmy ich 10, więc tyle też musimy tutaj wpisać. Wreszcie
lpPageRanges – to oczywiście wskaźnik na naszą tablicę z zakresami. Na razie są tam same zera, ale jeśli użytkownikowi przyjdzie do głowy coś wpisać, to będzie mógł teraz stworzyć sobie najwyżej 10 zakresów. Ich liczbę dostaniemy w
nPageRanges, a wskaźnik do tablicy z numerami stron będzie w
lpPageRanges. Oczywiście jeśli interesują nas takie informacje :-).
Mam nadzieję, że jeszcze się trzymasz, bo jesteśmy dopiero w połowie struktury :-). Następne pola to
nMinPage i
nMaxPage. Ponieważ to my wiemy, ile stron ma nasz dokument, musimy tę tajemnicę zdradzić systemowi, żeby wiedział, czy użytkownik nie podał przypadkiem numeru nieistniejącej strony. W powyższym przykładzie wpisaliśmy, że maksymalny numer strony to 1000, ale oczywiście nie zamierzamy stworzyć aż tak wielkiego dokumentu :-).
Pole
nCopies, jak pewnie się domyślasz, odpowiada miejscu w dialogu, gdzie użytkownik podaje liczbę kopii dokumentu. Zazwyczaj wystarcza jedna, więc tyle też wpisujemy jako domyślną wartość.
Pozostałe pola struktury pozwalają dodać do tego i tak już dość skomplikowanego dialogu jeszcze więcej opcji, żeby użytkownik miał się w czym pogubić ;-). Nie będziemy go jednak aż tak dręczyć. Pozostaje nam jeszcze jedno pole,
dwResultAction, które będzie zawierać wybraną przez użytkownika akcję – o tym jeszcze pomówimy, a na razie pora na wywołanie dialogu:
hResult = PrintDlgEx( pPDX );
Funkcja ta powinna nam zwrócić SOK... a nie, przepraszam, chodziło oczywiście o
S_OK, coś do popicia musimy sobie zrobić sami :-). Jeśli zwróciła coś innego, to zapewne coś bardzo złego przydarzyło się użytkownikowi podczas wypełniania dialogu i nie możemy kontynuować z drukowaniem. W przeciwnym przypadku pole
dwResultAction naszej ogromniastej struktury zawiera informację o tym, co użytkownik kliknął. Do wyboru miał trzy przyciski: Drukuj, Anuluj i Zastosuj. W tym drugim przypadku sprawa jest prosta. "Zastosuj" oznacza, że użytkownik chce sobie zapisać swoje ustawienia drukowania, ale rozmyślił się co do samego drukowania. Jeśli natomiast wybrał pierwszą opcję, to dostaniemy wartość
PD_RESULT_PRINT i możemy wreszcie zaczynać drukowanie!
if( hResult == S_OK && pPDX->dwResultAction == PD_RESULT_PRINT )
{
Teraz będzie nam potrzebna kolejna struktura, tym razem opisująca drukowany dokument. Tutaj na szczęście nie ma już za wiele do wypełniania:
DOCINFO docInfo;
ZeroMemory( & docInfo, sizeof( docInfo ) );
docInfo.cbSize = sizeof( docInfo );
docInfo.lpszDocName = "Test";
Kolejnym krokiem jest wywołanie funkcji
StartDoc, a jeśli nie dostaniemy jakiegoś paskudnego błędu, to jedziemy po kolei ze stronami, wywołując dla każdej
StartPage i
EndPage:
r = StartDoc( pPDX->hDC, & docInfo );
if( r != SP_ERROR )
{
r = StartPage( pPDX->hDC );
if( r > 0 )
{
Jeśli
StartPage zwraca liczbę większą od zera, to możemy zaczynać wrzucanie strony na DC drukarki. W założeniach dokument ten powinien być już od dawna gotowy, ale ponieważ na razie w sumie tylko się bawimy, więc równie dobrze możemy go dopiero teraz stworzyć. Tutaj czeka nas powrót do rysowania grafiki. Narysujemy sobie cokolwiek, wykorzystując kontekst, który dostaliśmy w naszej strukturze:
HBRUSH hbrNull =( HBRUSH ) GetStockObject( NULL_BRUSH );
HBRUSH hbrOld =( HBRUSH ) SelectObject( pPDX->hDC, hbrNull );
Ellipse( pPDX->hDC, 500, 500, 1000, 1000 );
Ellipse( pPDX->hDC, 1000, 800, 1500, 1300 );
Ellipse( pPDX->hDC, 800, 1000, 1800, 2000 );
SelectObject( pPDX->hDC, hbrOld );
const char * s = "To jest test drukarki.";
TextOut( pPDX->hDC, 50, 80, s, lstrlen( s ) );
r = EndPage( pPDX->hDC );
if( r > 0 )
r = EndDoc( pPDX->hDC );
}
}
}
Pewnym problemem może być przeliczanie jednostek, bo coś, co wygląda na duże na ekranie, niekoniecznie musi być równie duże po wydrukowaniu na papierze. Jest to dość obszerny temat, więc nie będziemy się nim tutaj zajmować – podajemy gotowe współrzędne, wyliczone na oko. Po skończonej zabawie wołamy
EndPage i wreszcie
EndDoc. W ten sposób nowe zadanie zostanie wysłane do drukarki i jeśli nic mu się po drodze nie stanie, to prawdopodobnie zobaczymy rezultat na kartce papieru.
Pozostaje jeszcze sprzątanie tego, co sami zaalokowaliśmy i tego, co mogło zostać stworzone automatycznie:
if( pPDX->hDC )
DeleteDC( pPDX->hDC );
if( pPDX->hDevMode )
GlobalFree( pPDX->hDevMode );
if( pPDX->hDevNames )
GlobalFree( pPDX->hDevNames );
GlobalFree( pPDX );
GlobalFree( pPageRanges );