Część II
.
ra a
i
i
Rozdział 13
Druk w ni
o a e
Idea niezależności od urządzeń może wydawać się w pełru przemyślana i dobra,
kiedy wykorzystujemy ją do wyświetlania tekstu i grafiki. Ale w jakim stopniu
sprawdza się w odniesieniu do drukowania?
Zasadniczo wieści są pomyślne. Korzystając z programów Microsoft Windows
można drukować tekst i grafikę na papierze przy użyciu takich samych funkcji
GDI, jakie stosowaliśmy do wyświetlania na ekranie. Do większości zagadnień
związanych z niezależnością od urządzeń, które do tej pory omówiliśmy - wiele
z nich wiązało się z rozmiarem i rozdzielczością obszaru wyświetlania oraz jego
możliwościami wyświetlania kolorów - można podejść w podobny sposób i po-
dobnie je rozwiązać. Jednak drukarka nie jest po prostu urządzeniem stosującym
do wyświetlania papier zamiast kineskopu. Jest kilka znaczących różnic. Na przy-
kład nigdy nie zetkniemy się z problemem monitora nie podłączonego do karty
graficznej lub zagadnieniem "skończenia się" ekranu w monitorze, natomiast
sytuacje, gdy drukarka nie jest podłączona lub gdy skończył się w niej papier,
;.. zdarzają się często.
Nie martwimy się też, czy karta graficzna będzie w stanie wykonać określone
operacje graficzne. Karta graficzna potrafi obsługiwać grafikę albo nie. I jeśli nie,
to w ogóle nie może współpracować z systemem Windows. Pewne drukarki na-
tomiast nię są w stanie drukować grafiki (mimo to mogą współpracować z Win-
dows), plotery obsługują grafikę wektorową, ale mają poważne problemy z bit-
mapami.
Oto inne zagadnienia, które należy rozważyć:
ł Drukarki pracują wolniej od monitorów. Czasami dostraja się programy tak,
by uzyskać optymalną szybkość ich działania, ale nie zwraca się wtedy uwa-
gi na czas niezbędny na odświeżenie ekranu. Natomiast nikt nie zechce cze-
kać, aż powolna drukarka zakończy drukowanie, zarum będzie mógł powró-
cić do pracy.
ł Programy wielokrotnie wykorzystują powierzchnię ekranu, gdy nadpisują
poprzednie dane wyświetlone na ekranie nowymi danymi. Nie można tak
zrobić w przypadku drukarki. Musi ona wyrzucić stronę, którą skończyła dru-
kować, i dopiero wtedy przejść do nowej.
ł Na monitorze różne aplikacje umieszczane są w oknach. Na drukarce dane
pochodzące z różnych aplikacji muszą być podzielone na odrębne dokumen-
ty lub zadania drukowania.
Aby do reszty GDI dodać obsługę drukarki, Windows udostępnia szereg funkcji
mających zastosowanie wyłącznie do drukarek. Funkcje charakterystyczne dla
540 Część II: Grafika
drukarek - StartDoc, EndDoc, StartPage i EndPage - są odpowiedzialne za organi-
zację danych przesyłanych do drukarki w postaci stron. Program wywołuje zwy-
kłą funkcję GDI do wyświetlenia tekstu i grafiki na stronie w taki sam sposób,
w jaki robi to, aby wyświetlić je na ekranie.
IW zdziały 15, 17 i 18 zawierają dodatkowe informacje na temat drukowania bit-
map, sformatowanego tekstu oraz metaplików.
Podstawy drukowania
Gdy w Windows używasz drukarki, rozpoczynasz złożoną interakcję, wymaga-
jącą modułu biblioteki GDI32, moduhz sterownika urządzenia drukującego (ma-
jącego rozszerzenie .DIZV) oraz buforowania wydruku systemu Windows, a tak-
że kilku innych modułów, biorących udział w zdarzeniu. Zanim zaczniemy pro-
gramowanie dla drukarki, prześledźmy, jak przebiega ten proces.
Drukowanie a buforowanie
Kiedy program drukujący chce rozpocząć korzystanie z drukarki, najpierw za
pomocą funkcji CreateDC lub PrintDlg uzyskuje uchwyt kontekstu urządzenia
drukującego. Powoduje to wczytanie do pamięci moduhx biblioteki sterownika
urządzenia drukującego (jeśli jeszcze go tam nie ma) oraz jego samoinicjację.
Następnie program wywołuje funkcję StartDoc, oznaczającą początek nowego
dokumentu. Funkcja StartDoc znajduje się w module GDI. Moduł ten wywohzje
funkcję Control w sterowniku urządzenia drukującego, polecając mu przygoto-
wanie się do drukowania.
Wywołanie StartDoc rozpoczyna proces drukowania dokumentu, który kończy
działanie, gdy program wywoła funkcję EndDoc. Te dwa wywołania działają jak
okładki książki w przypadku zwykłych funkcji GDI, wyświetlających tekst lub
grafikę na stronie dokumentu. Każda ze stron ograniczona jest przez wywołanie
StartPage, rozpoczynające stronę, oraz EndPage na końcu strony.
Jeżeli program ma zlecenie na przykład narysowania na stronie elipsy, najpierw
wywołuje StartDoc, aby rozpocząć zadanie drukowania, następnie StartPage, by
zasygnalizować nową stronę. Następnie wywohzje Ellipse, tak jak to czyni rysu-
jąc elipsę na ekranie. Na ogół moduł GDI przechowuje każde wywołanie GDI,
które program kieruje do kontekstu urządzenia drukującego, w metapliku dys-
kowym o nazwie rozpoczynającej się od znaków ~EMF ("rozszerzony metaplik")
oraz rozszerzeniu .TMP. Możliwe jest jednak, co pokrótce omówię, ominięcie tego
kroku przez sterownik drukarki.
Gdy program aplikacji skończy z wywołaniami GDI definiującymi pierwszą stro-
nę, wówczas wywołuje EndPage. Teraz rozpoczyna się rzeczywista praca. Sterow-
nik drukarki musi przetłumaczyć poszczególne polecenia rysujące zapisane w me-
tapliku na wyjście dla drukarki. Wyjście drukarki wymagane do zdefiniowania
strony grafiki może być bardzo duże, szczególnie jeśli drukarka nie ma języka
wysokiego poziomu do kompozycji strony. Na przykład drukarka laserowa o roz-
dzielczości 600 dpi, korzystająca z papieru formatu A4, może wymagać ponad 4
megabajtów pairuęci do zdefiniowania pojedynczej strony grafiki.
Rozdział 13: Drukowanie 541
Z tego powodu sterowniki drukarek często wykorzystują technikę zwaną "pa-
smowaniem", dzielącą stronę na prostokątne pasma. Moduł GDI otrzymuje od
sterownika drukarki wymiary każdego z pasm. Wówczas ustala obszar, jaki ma
być przechowywany, równy temu pasmu, a następnie wywohxje funkcję Output
sterownika urządzenia drukującego dla każdej funkcji rysującej zawartej w me-
tapliku. Proces ten nazywany jest wgrywaniem metapliku do sterownika urzą-
dzenia. Moduł GDI musi wgrać do sterownika urządzenia cały metaplik dla każ- I,
dego pasma, które sterownik urządzenia zdefiniuje na stronie. Po zakończeniu
procesu metaplik może zostać wykasowany.
Dla każdego pasma sterownik urządzenia thzmaczy funkcje rysujące na wyjście I'..
niezbędne do zrealizowania ich na drukarce. Format wyjścia zależy od charakte-
i''
rystyki drukarki. Dla drukarek mozaikowych będzie to zbiór sekwencji sterują-
cych, w tym sekwencji graficznych. (Aby uzyskać pomoc przy budowaniu wyj-
ścia, sterownik drukarki może wywoływać różne procedury "pomocnicze", tak-
i
że znajdujące się w module GDI). Dla drukarek laserowych o języku opisu stro-
ny wysokiego poziomu (takim jak PostScript), wyjście na drukarkę będzie w tym
języku.
Sterownik drukarki przekazuje wyjście na drukarkę dla każdego pasma do mo-
f.
dułu GDI, który następnie zapisuje to wyjście w innym pliku tymczasowym. Plik
ten rozpoczyna się od znaków ~SPL i ma rozszerzenie .TMP. Gdy strona zostanie
zakończona, moduł GDI wykonuje międzyprocesowe odwołanie do bufora dru-
kowania informując, że nowe zadanie drukowania jest gotowe. Wówczas program
aplikacji przechodzi do następnej strony. Kiedy aplikacja skończy wszystkie strony,
które mają być wydrukowane, wywohxje EndDoc, aby zasygnalizować zakończe-
nie zadania drukowania. Rysunek 13-1 przedstawia interakcje programu, modu-
łu GDI oraz sterownika drukarki.
542 Część II; Gafika
Rozc
Program
Wywolanie funkcji drukujących (StartDoc, StartPage)
Wywofania GDI (LineTo, Rectangle i tak dalej)
Modut GDl Dyskowy
'----i rozszerzony
Wywotania Control y metaplik
Instrukcje drukujące Procedury
(Output, BitBlt i tak dalej) "pomocnicze"
.. ...., .,......... Przekazanie żądania
Sterowrk drukarki drukowania do GDI32
Modul GDI32
Międzyprocesowe wywolanie
do podsystemu bufora drukowania
Tak L;; zy lest następna Buforowanie
strona (SPOOLER.EXE)
Nie "....... .",.
Wyślij dane na drukarkę (patrz rysunek 13-2)
Plik
opisu
zadania
Rysunek 13-1. Interakcja programu aplikacji, modułu GDI, sterownika drukarki
oraz bufora
Bufor odciąża programy aplikacji od części pracy związanej z drukowaniem.
Element bufora Opis
Bufor żądania droku Przesyła strumień danych do dostawcy wydruku
Lokalny dostawca wydruku Tworzy pliki bufora przeznaczone dla lokalnej drukarki
Sieciowy dostawca wydruku Tworzy pliki bufora przeznaczone dla drukarki sieciowej
Przetwarzanie wydruku Przeprowadza debuforowanie, które jest konwersją buforowa-
nych danych niezależnych od urządzenia do postaci charakte-
rystycznej dla danej drukarki
Monitorowanie portu Kontroluje port, do którego podłączona jest drukarka
Monitorowanie języka Kontroluje obsługę komunikacji dwukierunkowej z drukarką,
aby ustawić konfigurację urządzenia oraz monitorować stan
drukarki
Windows wczytuje bufor podczas uruchamiania, zatem jest on aktywny w chwi-
li, gdy program rozpoczyna drukowanie. Gdy program drukuje dokument, mo-
duł GDI tworzy pliki zawierające wyjście na drukarkę. Zadanie bufora druko-
Rozdział 13: Drukowanie 3
wania to przesłanie tych plików do drukarki. Jest on powiadamiany o nowym
zadaniu drukowania przez moduł GDI. Wówczas rozpoczyna odczytywanie pli-
ku i przekazywanie go bezpośrednio do drukarki. Aby przekazać pliki, bufor wy-
korzystuje różne funkcje komunikacyjne dla portu szeregowego lub równoległe-
go, do którego podłączona jest drukarka. Gdy bufor zakończy przesyłanie pliku
do drukarki, kasuje tymczasowy plik przechowujący dane wyjściowe. Proces ten
został przedstawiony na rysunku 13-2.
Rysunek 13-2. Praca bufora drukowania
Większa część tego procesu jest transparentna dla programu aplikacji. Z perspek-
tywy aplikacji "drukowanie" odbywa się tylko w czasie, którego wymaga moduł
GDI, aby zapisać wyjście na drukarkę w plikach dyskowych. Potem - lub nawet
przed, jeśli drukowanie obsługiwane jest na drugim planie - program jest zwalnia-
ny i może robie inne rzeczy. Odpowiedzialność za rzeczywiste drukowanie doku-
mentu spoczywa na buforze drukowania, a nie na programie. Użytkownik może
wstrzymać zadania drukowania, zmienić ich priorytety lub anulować je, jeżeli jest
,
to konieczne. Takie rozwiązanie pozwala programom na szybsze "drukowanie'
niż byłoby to rnożliwe, gdyby drukowały w czasie rzeczywistym i musiały czekać,
aż drukarka skończy jedną stronę, zanim przystąpi do następnej.
Wprawdzie opisałem ogólnie przebieg drukowania, ale istnieje kilka wariantów jego
realizacji. Jectna z możliwości jest taka, że podczas korzystania przez system Windows
z drkarld, bufor drukowania nie musi być obecny. Zazwyczaj użytkownik może
wyłączyć buforowanie drukowania w oknie dialogowym właściwości dnikarki.
Część II: Grafika
Ale dlaczego użytkownik miałby pomijać bufor Windows? Być może, dysponuje
on hardwarowym lub softwarowym buforem drukowania, który działa szybciej
niż bufor Windows. A może drukarka funkcjonuje w sieci mającej swój własny
bufor. Ogólnie obowiązuje zasada, że jeden bufor jest szybszy niż dwa. Usunię-
cie bufora Windows przyspieszyłoby w tych przypadkach drukowanie, ponie-
waż wyjście na drukarkę nie musiałoby być zapisywane na dysku. Może ono być
przesłane bezpośrednio na drukarkę i zostać przechwycone przez zewnętrzny
sprzętowy lub programowy bufor drukowania.
Jeśli bufor Windows nie jest aktywny, moduł GDI nie przechowuje w pliku wyj-
ścia na drukarkę ze sterownika urządzenia. Zamiast tego przesyła wyjście bez-
pośrednio do portu szeregowego lub równoległego. W odróżnieniu od drukowa-
nia bez pośrednictwa bufora, drukowanie wykonywane przez GDI ma możliwość
wstrzymania działania aplikacji (w szczególności wykonujących drukowanie) aż
do zakończenia drukowania.
Oto inny przypadek: zwykle moduł GDI przechowuje wszystkie funkcje niezbędne
do zdefiniowania strony w metapliku, a następnie wgrywa ten metaplik do ste-
rownika drukarki kolejno dla każdego z pasm zdefiniowanych przez sterownik.
Jeżeli sterowruk drukarki nie zażąda dzielenia na pasma, metaplik nie zostanie
utworzony; GDI po prostu przekazuje funkcje rysujące bezpośrednio do sterow-
nika. W kolejnym wariancie do aplikacji należy dzielenie wyjścia na drukarkę na
pasma. To czyni kod drukujący aplikacji bardziej złożonym, lecz zwalnia moduł
GDI z tworzenia metapliku. I tym razem GDI po prostu przekazuje funkcje dla
każdego z pasm do sterowruka drukarki.
Teraz zapewne zaczynasz powoli rozumieć, dlaczego drukowanie z programu
Windows wymaga więcej zachodu niż korzystanie z monitora. Mogą nastąpić
pewne komplikacje - w szczególności wtedy, gdy podczas tworzenia metapliku
lub plików wyjścia na drukarkę modułowi GDI skończy się miejsce na dysku.
Możesz w tej sytuacji albo zasypać użytkownika meldunkami o pojawiających
się problemach i próbować jakoś je rozwiązać, albo zachować powściągliwość.
Dla aplikacji pierwszą czynnością, jaką należy wykonać podczas drukowania, jest
uzyskanie kontekstu urządzenia drukującego.
Kontekst urządzenia drukującego
Zanim będziesz mógł rysować na monitorze, musisz uzyskać uchwyt kontekstu
urządzenia. Podobnie przed przystąpieniem do drukowania musisz uzyskać
uchwyt kontekstu urządzenia drukującego. Kiedy już masz ten uchwyt (i wywo-
łałeś StartDoc, aby zapowiedzieć swój zamiar utworzenia nowego dokumentu,
oraz StartPage w celu rozpoczęcia strony), możesz używać uchwytu kontekstu
urządzenia drukującego w taki sam sposób, w jaki korzystasz z uchwytu kontek-
stu urządzenia wyświetlającego - czyli jako pierwszego parametru rozmaitych
funkcji rysujących GDI.
Wiele aplikacji korzysta ze standardowego okna dialogowego drukowania, otwie-
ranego wywołaniem funkcji PrintDlg. (W dalszej części tego rozdziału pokażę,
jak stosować tę funkcję). PrintDlg daje użytkownikowi okazję do zmiany drukar-
ki lub określenia innej charakterystyki zadania przed przystąpieniem do wydru-
Rozdział 13: Drukowanie 545
ku. Następnie podaje aplikacji uchwyt kontekstu urządzenia drukującego. Funk-
cja ta może oszczędzić aplikacji nieco pracy. Chociaż niektóre aplikacje (na przy-
kład Notatnik) wolą uzyskiwać kontekst urządzenia drukującego bez wyświe-
tlania okna dialogowego. Zadanie takie wymaga zastosowania CreateDC.
W rozdziale 5 pokazaliśmy, że można uzyskać uchwyt kontekstu urządzenia dla
całego ekranu wywołując:
hdc = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
Uchwyt kontekstu urządzenia drukującego uzyskasz poshzgując się tą samą funk-
cją. Jednak dla kontekstu urządzenia drukującego ogólna składnia CreateDC jest
następująca:
hdc = CreateDC (NULL, szDeviceName. NULL, plnitializationData):
Argumentowi plnitializationData z reguły także nadawana jest wartość NULL. Ar-
gument szDeviceName określa łańcuch znakowy podający systemowi Windows
nazwę urządzenia drukującego. Zanim będziesz mógł wymienić nazwę tego urzą-
dzenia, musisz sprawdzić, jakie drukarki są dostępne.
W systemie może być podłączonych kilka drukarek. Mogą też znajdować się inne
programy udające drukarki, na przykład oprogramowanie do wysyłania faksów.
Niezależnie od liczby dołączonych drukarek, tylko jedna jest "bieżącą lub "do-
myślną"- ta, którą użytkownik wybrał ostatnio. Niektóre małe programy Win-
dows drukują tylko na tej drukarce.
Metody uzyskiwania kontekstu domyślnego urządzenia drukującego ulegały
zmianom na przestrzeni lat. Obecnie standardowa metoda używa funkcji Enum-
Printers. Funkcja ta wypełnia tablicę struktur zawierających informację na temat
każdej z dołączonych drukarek. Możesz nawet, w zależności od żądanego po-
ziomu detali, wybrać rodzaj struktury użytej w funkcji. Te struktury nazywają
się PRINTER INFO x, gdzie x jest liczbą.
Niestety, rodzaj użytej struktury zależy także od tego, czy twój program został
uruchomiony w systemie Windows 98, czy w Microsoft Windows NT. IZysunek
13-3 prezentuje funkcję GetPrinterDC, która będzie działała w każdym z tych sys-
temów operacyjnych.
GETPRNDC.C
/*
GETPRNDC.C - Funkcja GetPrinterDC
*/
#include
HDC GetPrinterDC (void)
(
DWORD dwNeeded, dwReturned :
HDC hdc :
PRINTER_INFO_4 * pinfo4 :
PRINTERINFO5 * pinfo5 :
if (GetVersion () & 0x80000000) // Windows 98
EnumPrinters (PRINTERENUM DEFAULT, NULL, 5, NULL,
546 Część IIł Grafika
(ciąg dalszy ze strony 545)
0, &dwNeeded, RdwReturned)
pinfo5 = malloc (dwNeeded) ;
EnumPrinters (PRINTERENUM_DEFAULT, NULL, 5, (PBYTE) pinfo5,
dwNeeded, &dwNeeded, &dwReturned) ;
hdc = CreateDC (NULL, pinfo5->pPrinterName, NULL, NULL) ;
free (pinfo5) ;
)
else // Windows NT
(
EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 4, NULL,
0, &dwNeeded, &dwReturned) ;
pinfo4 = malloc (dwNeeded) ;
EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 4, (PBYTE) pinfo4,
dwNeeded, &dwNeeded, &dwReturned) ;
hdc = CreateDC (NULL, pinfo4->pPrinterName, NULL, NULL) ;
free (pinfo4)
?
return hdc :
Rysunek 13-3. Plik GETPRNDC.C
Funkcja GetPrinterDC używa funkcji GetUersion do określenia, czy program uru-
chomiony został w środowisku Windows 98, czy w systemie Windows NT. Nie-
zależnie od wersji, funkcja dwukrotnie wywołuje EnumPrinters - za pierwszym
razem po to, żeby uzyskać rozmiar potrzebnej struktury, a drugi raz; aby ją fak-
tycznie wypelnić. W systemie Windows 98 funkcja używa struktury PRTI'ER IN-
FO 5; w Windows NT wykorzystuje strukturę PIZINTER INFO 4. Struktury te
zostały wymienione w dokumentacji EnumPrinters (/Platform SDK/Graphics and
Multimedia Services/GDI/Printing and Print Spooler/Printing and Print Spooler Refe-
rence/ Printing and Print Spooler Functions/EnumPrinters, bezpośredriio przed sek-
cją przykładów) jako łatwe i wyjątkowo szybkie.
Rozszerzony program DEVCAPS
Pierwotny program DEVCAPS1 z rozdziału 5 wyświetlał podstawowe informa-
cje o monitorze dostępne z funkcji GetDeviceCaps. Nowa wersja, zaprezentowana
na rysunku 13-4, pokazuje więcej informacji zarówno na temat monitora, jak
i wszystkich drukarek dołączonych do systemu.
DEVCAPS2.C
/*
DEVCAPS2.C - Wywietl informacje o urządzeniach (wersja 2)
(c) Charles Petzold, 1998
*/
Rozdział 13: Drukowanie 547
include
!,ł
include "resource.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) :
void DoBasicInfo (HDC, HDC, int, int) :
void DoOtherInfo (HDC, HDC, int, int) :
void DoBitCodedCaps (HDC, HDC, int, int, int) :
typedef struct
int iMask :
TCHAR * szDesc :
si
BITS :
define IDM DEVMODE 1000
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppNameC] = TEXT ("DevCaps2") :
HWND hwnd :
MSG msg :
WNDCLASS wndclass :
wndclass.style = CS_HREDRAW CS UREDRAW :
wndclass.lpfnWndProc = WndProc :
wndclass.cbClsExtra = 0 :
wndclass.cbWndExtra = 0 :
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL. IDI APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDCARROW) : I!
;...:.
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITEBRUSH) ;
wndclass.lpszMenuName = szAppName :
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
(
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MBICONERROR) :
return 0 ;
hwnd = CreateWindow (szAppName NULL,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW USEDEFAULT,
CW_USEDEFAULT, CW USEDEFAULT,
NULL, NULL, hInstance, NULL) :
ShowWindow (hwnd, iCmdShow) :
UpdateWindow (hwnd) :
while (GetMessage (&msg, NULL, 0, 0))
TranslateMessage (&msg) :
DispatchMessage (&msg) ;
return msg.wParam :
wfi
548 Część II, Grafika
(ciąg dalszy ze strony 547)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
.
static TCHAR szDeviceC32], szWindowTextC64] ;
static int cxChar, cyChar, nCurrentDevice = IDM_SCREEN,
nCurrentInfo = IDM_BASIC ;
static DWORD dwNeeded, dwReturned ;
static PRINTER_INFO_4 * pinfo4 ;
static PRINTERINFO5 * pinfo5 ;
DWORD i ;
HDC hdc, hdcInfo ;
HMENU hMenu ;
HANDLE hPrint ;
PAINTSTRUCT s ;
P
TEXTMETRIC tm ;
switch (message)
(
case WM_CREATE :
hdc = GetDC (hwnd) ;
SelectObject (hdc, GetStockObject (SYSTEMFIXED_FONT)) ;
GetTextMetrfics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseOC (hwnd, hdc) ;
// ten przypadek zostawiamy nieopracowany
case WM_SETTINGCHANGE:
hMenu = GetSubMenu (GetMenu (hwnd), 0) ;
while (GetMenuItemCount (hMenu) > 1)
DeleteMenu (hMenu, 1, MF BYPOSITION) ;
// Przygotuj listę wszystkich lokalnych i sieciowych
// drukarek
// Najpierw ustal, jak duża tablica jest potrzebna;
// to wywolanie nie uda się, jeśli żdany rozmiar
// będzie pozostawiony na dwNeeded
//
// Następnie zarezerwuj miejsce na tablicę
// z informacjami i wypelnij jd
//
// Wstaw nazwę drukarki do menu
if (GetVersion () & 0x80000000)
// Windows 98
i
EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 5, NULL,
0, &dwNeeded, &dwReturned) ;
pinfo5 = malloc (dwNeeded) ;
EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 5, (PBYTE) pinfo5,
dwNeeded, &dwNeeded, &dwReturned) ;
Rozdział 13: Dmkowanie 549
for (i = 0 ; i < dwReturned ; i++)
(
AppendMenu (hMenu, (i+1) % 16 ? 0 : MF MENUBARBREAK. i + 1,
pinfo5Ci].pPrinterName) ;
free (pinfo5) :
)
else // Windows NT
(
EnumPrinters (PRINTERENUM_LOCAL, NULL, 4, NULL,
0. &dwNeeded, &dwReturned) ;
pinfo4 = malloc (dwNeeded> ;
EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 4, (PBYTE) pinfo4,
dwNeeded, &dwNeeded, &dwReturned) ;
I
for (i = 0 ; i < dwReturned ; i++)
AppendMenu (hMenu, (i+1) % 16 ? 0 : MF MENUBARBREAK, i + 1,
pinfo4Ci].pPrinterName) :
)
free (pinfo4) ;
)
AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ;
AppendMenu (hMenu. 0, IDM DEVMODE, TEXT ("Properties")) :
wParam = IDM SCREEN ;
%/ ten przypadek zostawiamy nieopracowany
case WM COMMAND :
hMenu = GetMenu (hwnd) ;
if (LOWORD (wParam) == IDM_SCREEN // ekran i drukarki
LOWORD (wParam) < IDMDEVMODE)
CheckMenuItem (hMenu, nCurrentDevice, MF UNCHECKED) ;
nCurrentDevice = LOWORD (wParam) ;
CheckMenuItem (hMenu, nCurrent0evice, MFCHECKED) ; ''
4
else if (LOWORD (wParam) == IDM DEVMODE) // wybór opcji Properties
(
GetMenuString (hMenu, nCurrentDevice, sz0evice,
sizeof (sz0evice) / sizeof (TCHAR), MFBYCOMMAND);
if (OpenPrinter (szDevice. &hPrint. NULL))
PrinterProperties (hwnd, hPrint) :
ClosePrinter (hPrint) ;
1
else // elementy menu rodzaju informacji
f
CheckMenuItem (hMenu, nCurrentInfo. MF UNCHECKED) ;
nCurrentInfo = LOWORD (wParam) ;
CheckMenuItem (hMenu, nCurrentInfo, MF CHECKED) ;
550 Część II: Grafika
(ciąg dalszy ze strony 549)
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_INITMENUPOPUP :
if (lParam = 0)
EnableMenuItem (GetMenu (hwnd), IDM_DEVMODE,
nCurrentDevice --- IDMSCREEN ? MF GRAYED : MF ENABLED) ;
return 0 ;
case WMPAINT :
lstrcpy (szWindowText, TEXT ("Device Capabilities: ")) ;
if (nCurrentDevice = IDM SCREEN)
lstrcpy (szDevice, TEXT ("DISPLAY")) ;
hdcInfo = CreateIC (szDevice, NULL, NULL, NULL) ;
)
else
{
hMenu = GetMenu (hwnd) ;
GetMenuString (hMenu, nCurrentDevice, szDevice,
sizeof (szDevice), MF_BYCOMMAND) ;
hdcInfo = CreateIC (NULL, szDevice, NULL, NULL) ;
l
lstrcat (szWindowText, szDevice) ;
SetWindowText (hwnd, szWindowText) ;
hdc = BeginPaint (hwnd, &ps) ;
SelectObject (hdc, GetStockObject (SYSTEM FIXEDFONT)) ;
if (hdcInfo)
(
switch (nCurrentInfo)
t
case IDM_BASIC :
DoBasicInfo (hdc, hdcInfo, cxChar, cyChar) ;
break ;
case IDM_OTHER :
DoOtherInfo (hdc, hdcInfo, cxChar, cyChar) ;
break ;
case IDM_CURVE :
case IDM_LINE :
case IDM_POLY :
case IDM TEXT :
)
DoBitCodedCaps (hdc, hdcInfo, cxChar, cyChar,
nCurrentInfo - IDM CURVE) ; voi
break ;
)
DeleteDC (hdcInfo) ;
)
EndPaint (hwnd, &ps) ;
return 0 ;
Rozdział 13: Dmkowanie 551
case WM DESTROY :
PostOuitMessage (0) ;
return 0 ;
l
return DefWindowProc (hwnd, message, wParam, lParam) ;
void DoBasicInfo (HDC hdc, HDC hdcInfo, int cxChar, int cyChar)
(
static struct
(
int nIndex ;
TCHAR * szDesc ;
)
infoC) =
(
HORZSIZE, TEXT ("HORZSIZE Width in millimeters:"),
VERTSIZE, TEXT ("UERTSIZE Height in millimeters:"),
HORZRES, TEXT ("HORZRES Width in pixels:"),
VERTRES, TEXT ("VERTRES Height in raster lines:"),
BITSPIXEL, TEXT ("BITSPIXEL Color bits per pixel:"),
PLANES, TEXT ("PLANES Number of color planes:"),
NUMBRUSHES, TEXT ("NUMBRUSHES Number of device brushes:"),
NUMPENS, TEXT ("NUMPENS Number of device pens:"),
NUMMARKERS, TEXT ("NUMMARKERS Number of device markers:"),
NUMFONTS, TEXT ("NUMFONTS Number of device fonts:"),
NUMCOLORS. TEXT ("NUMCOLORS Number of device colors:"),
PDEVICESIZE, TEXT ("PDEVICESIZE Size of device structure:"),
ASPECTX, TEXT ("ASPECTX Relative width of pixel:"),
ASPECTY, TEXT ("ASPECTY Relative height of pixel:"),
ASPECTXY, TEXT ("ASPECTXY Relative diagonal of pixel:"),
LOGPIXELSX, TEXT ("LOGPIXELSX Horizontal dots per inch:"),
LOGPIXELSY, TEXT ("LOGPIXELSY Vertical dots per inch:"),
SIZEPALETTE, TEXT ("SIZEPALETTE Number of palette entries:"),
NUMRESERUED. TEXT ("NUMRESERVED Reserved palette entries:."),
COLORRES, TEXT ("COLORRES Actual color resolution:")
PHYSICALWIDTH, TEXT ("PHYSICALWIDTH Printer page pixel width:"),
PHYSICALHEIGHT, TEXT ("PHYSICALHEIGHT Printer page pixel height:"),
PHYSICALOFFSETX, TEXT ("PHYSICALOFFSETX Printer page x offset:"),
PHYSICALOFFSETY, TEXT ("PHYSICALOFFSETY Printer page y offset:")
:
int i :
TCHAR szBufferC80] ;
for (i = 0 ; i < sizeof (info) / sizeof (infoCO)) ; i++)
TextOut (hdc, cxChar, (i + 1) * cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("%-45s%8d"), infoCi).szDesc,
GetOeviceCaps (hdcInfo, infoCi).nIndex))) ;
)
void DoOtherInfo (HDC hdc, HDC hdcInfo, int cxChar, int cyChar)
(
static BITS clipC) =
(
CPRECTANGLE, TEXT ("CPRECTANGLE Can Clip To Rectangle:")
1:
static BITS rasterC] =
552 Część II: Grafika Rozc
(ciąg dalszy ze strony 551)
RC_BITBLT, TEXT ("RC_BITBLT Capable of simple BitBlt:"),
RC_BANDING, TEXT ("RC_BANDING Requires banding support:"),
RC_SCALING, TEXT ("RC_SCALING Requires scaling support:"),
RC_BITMAP64, TEXT ("RC_BITMAP64 Supports bitmaps >64K:"),
RC_GDI2O_OUTPUT, TEXT ("RC_GDI2O_OUTPUT Has 2.0 output calls:"),
RC_DI_BITMAP, TEXT ("RC_DI_BITMAP Supports DIB to memory:"),
RC_PALETTE, TEXT ("RC_PALETTE Supports a palette:"),
RC_DIBTODEV, TEXT ("RC_DIBTODEV Supports bitmap conversion:"),
RC_BIGFONT, TEXT ("RC_BIGFONT Supports fonts >64K:"),
RC_STRETCHBLT, TEXT ("RC_STRETCHBLT Supports StretchBlt:"),
RC_FLOODFILL, TEXT ("RC_FLOODFILL Supports FloodFill:"),
RCSTRETCHDIB, TEXT ("RCSTRETCHDIB Supports StretchDIBits:")
static TCHAR * szTech[J = ( TEXT ("DT_PLOTTER (Vector plotter)"),
TEXT ("DT_RASDISPLAY (Raster display)"),
TEXT ("DT_RASPRINTER (Raster printer)"),
TEXT ("DT_RASCAMERA (Raster camera)"),
TEXT ("DT_CHARSTREAM (Character stream)"),
TEXT ("DT_METAFILE (Metafile)"),
TEXT ("DT DISPFILE (Display file)") ) ;
int i ;
TCHAR szBuffer[80] ;
TextOut (hdc, cxChar, cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("%-24s%O4XH"), TEXT ("DRIVERVERSION:"),
GetDeviceCaps (hdcInfo, DRIVERVERSION))) ;
TextOut (hdc, cxChar, 2 * cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("%-24s%-40s"), TEXT ("TECHNOLOGY:"),
szTech[GetDeviceCaps (hdcInfo, TECHNOLOGY)J)) ;
TextOut (hdc, cxChar, 4 * cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("CLIPCAPS (Clipping capabilities)"))) ;
for (i = 0 ; i < sizeof (clip) / sizeof (clip[OJ) ; i++)
TextOut (hdc, 9 * cxChar, (i + 6) * cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("%-45s %3s"), clip[i].szDesc,
GetDeviceCaps (hdcInfo, CLIPCAPS) & clip[iJ.iMask ?
TEXT ("Yes") : TEXT ("No"))) ;
TextOut (hdc, cxChar, 8 * cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("RASTERCAPS (Raster capabilities)"))) ;
for (i = 0 ; i < sizeof (raster) / sizeof (raster[OJ) ; i++)
TextOut (hdc, 9 * cxChar, (i + 10) * cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("%-45s %3s"), raster[iJ.szDesc,
GetDeviceCaps (hdcInfo, RASTERCAPS) & raster[iJ.iMask ?
TEXT ("Yes") : TEXT ("No"))) ;
void DoBitCodedCaps (HDC hdc, HDC hdcInfo, int cxChar, int cyChar, int iType)
(
static BITS curves[J =
(
Rozdział 13: Dukowanie 553
CC_CIRCLES, TEXT ("CC_CIRCLES Can do circles:"),
CC_PIE, TEXT ("CC_PIE Can do pie wedges:").
CC_CHORD, TEXT ("CC_CHORD Can do chord arcs:"),
CC_ELLIPSES, TEXT ("CC_ELLIPSES Can do ellipses:"),
CC_WIDE, TEXT ("CC_WIDE Can do wide borders:"),
CC_STYLED, TEXT ("CC_STYLED Can do styled borders:"),
CC_WIDESTYLED, TEXT ("CC_WIDESTYLED Can do wide and styled borders:"),
CC_INTERIORS, TEXT ("CCINTERIORS Can do interiors:")
:
static BITS linesC] =
I
LC_POLYLINE, TEXT ("LC_POLYLINE Can do polyline:"),
LC_MARKER, TEXT ("LC_MARKER Can do markers:").
LC_POLYMARKER, TEXT ("LC_POLYMARKER Can do polymarkers"),
LC_WIDE, TEXT ("LC_WIDE Can do wide lines:"),
LC_STYLED, TEXT ("LC_STYLED Can do styled lines:"),
LC_WIDESTYLED, TEXT ("LC_WIDESTYLED Can do wide and styled lines:"),
LC_INTERIORS, TEXT ("LCINTERIORS Can do interiors:")
l:
static BITS polyC] =
(
PCPOLYGON,
TEXT ("PCPOLYGON Can do alternate fill polygon:"),
PC RECTANGLE, TEXT ("PCRECTANGLE Can do rectangle:"),
PC WINDPOLYGON,
TEXT ("PC WINDPOLYGON Can do winding number fill polygon:"),
PC_SCANLINE, TEXT ("PC_SCANLINE Can do scanlines:"),
PC_WIDE, TEXT ("PC_WIDE Can do wide borders:"),
PC_STYLED, TEXT ("PC STYLED Can do styled borders:"),
PC WIDESTYLED,
TEXT ("PC_WIDESTYLED Can do wide and styled borders:"),
PC INTERIORS, TEXT ("PCINTERIORS Can do interiors:")
1:
static BITS textC] =
(
TC OP_CHARACTR,
TEXT ("TC OP CHARACTER Can do character output precision:"),
TC OP_STROKE,
TEXT ("TC OP STROKE Can do stroke output precision:"),
TC CP_STROKE,
TEXT ("TC CP STROKE Can do stroke clip precision:"),
TC CR_90
TEXT ("TCCP 90 Can do 90 degree character rotation:"),
TC CR_ANY,
TEXT ("TCCR ANY Can do any character rotation:"),
TC SF_X_YINDEP,
TEXT ("TC SF X YINDEP Can do scaling independent of X and Y:"),
TC_SA_DOUBLE,
TEXT ("TCS DOUBLE Can do doubled character for scaling:"),
TC_SA_INTEGER,
TEXT ("TCSAINTEGER Can do integer multiples for scaling:"),
TC_SA_CONTIN,
TEXT ("TCS CONTIN Can do any multiples for exact scaling:"),
TCEA_DOUBLE,
TEXT ("TCE DOUBLE Can do double weight characters:"),
Część II: Grafika
(ciąg dalszy ze strony 553)
TC_IA_ABLE, TEXT ("TC_IA_ABLE Can do italicizing:"),
TC_UA_ABLE, TEXT ("TC_UA_ABLE Can do underlining:"),
TC_SO ABLE, TEXT ("TC_SO_ABLE Can do strikeouts:"),
TC_RA_ABLE, TEXT ("TC_RA ABLE Can do raster fonts:"),
TCVA ABLE, TEXT ("TC V ABLE Can do vector fonts:")
static struct
(
int iIndex ;
TCHAR * szTitle ;
BITS (*pbits)C]
int iSize ;
1
bitinfo[] =
(
CURVECAPS, TEXT ("CURVCAPS (Curve Capabilities)"),
(BITS (*)[]) curves, sizeof (curves) / sizeof (curves[0]),
LINECAPS, TEXT ("LINECAPS (Line Capabilities)"),
(BITS (*)[]) lines, sizeof (lines) / sizeof (linesCO]),
POLYGONALCAPS, TEXT ("POLYGONALCAPS (Polygonal Capabilities)"),
(BITS (*)C]) poly, sizeof (poly) / sizeof (poly[0]),
TEXTCAPS, TEXT ("TEXTCAPS (Text Capabilities)"),
(BITS (*)[]) text, sizeof (text) / sizeof (textCO])
static TCHAR szBuffer[80] ;
BITS (*pbits)C] = bitinfo[iType].pbits ;
int i, iDevCaps = GetDeviceCaps (hdcInfo, bitinfoCiType].iIndex) ;
TextOut (hdc, cxChar, cyChar, bitinfo[iType].szTitle,
lstrlen (bitinfo[iType].szTitle)) ;
for (i = 0 ; i < bitinfo[iType].iSize ; i++)
TextOut (hdc, cxChar, (i + 3) * cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("%-55s %3s"), (*pbits)[i].sz0esc,
iDevCaps & (*pbits)[i].iMask ? TEXT ("Yes") : TEXT ("No")));
DEUCAPS2.RC (fragmenty)
// Microsoft Developer Studio generated resource script.
Itinclude "resource.h"
Ilinclude "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Menu
DEVCAPS2 MENU DISCARDABLE
BEGIN
POPUP "&Device"
BEGIN
MENUITEM "&Screen", IDM SCREEN, CHECKED
END
POPUP "&Capabilities"
Rozdział 13: Drukowanie 555
BEGIN IDM_BASIC
MENUITEM "&Basic Information",
MENUITEM "&Other Information", IDM_OTHER
MENUITEM "&Curve Capabilities", IDM_CURVE
MENUITEM "&Line Capabilities", IDM_LINE
MENUITEM "&Polygonal Capabilities", IDM_POLY
t4ENUITEM "&Text Capabilities", IDM TEXT
END
END
RESOURCE.H (fragmenty)
Microsoft Developer Studio generated include file.
Used by DevCaps2.rc
4define IDM_SCREEN 40001
4fdefine IDM_BASIC 40002
define IDM_OTHER 40003
4ldefine IDM_CURVE 40004
4define IDM_LINE 40005
define IDM_POLY 40006
define IDM TEXT 40007
Rysunek 13-4. Program DEVCAPS2
Ponieważ DEVCAPS2 uzyskuje jedynie kontekst informacji o drukarce, możesz
wybrać drukarki z menu DEVCAPS2 nawet wtedy, gdy ich port wyjściowy usta-
wiony jest na "none". Jeśli chcesz porównać możliwości różnych drukarek, mo-
żesz najpierw użyć folderu Drukarki i dodać kilka ich sterowników.
Wywołanie PrinterProperties
Menu Device programu DEVCAPS2 zawiera opcję o nazwie Properties. Chcąc
z niej skorzystać, najpierw wybierz drukarkę z menu Device, a następnie wybierz
opcję Properties. Pojawi się okno dialogowe. Skąd się ono bierze? Wywoływane
jest przez sterownik drukarki i prosi cię przynajmniej o wybranie rozmiaru pa-
pieru. Większość drukarek daje także możliwość wyboru orientacji pionowej lub
poziomej (ang. portrait i landscape). W trybie pionowej (który często jest ustawie-
niem domyślnym) góra wydruku będzie na krótkiej krawędzi kartki; w orientacji
poziomej górną krawędzią jest krawędź długa. Jeśli zmienisz tryb, zmiana ta zo-
stanie odzwierciedlona w informacji uzyskiwanej od funkcji GetDeviceCaps przez
program DEVCARS2: rozmiar i rozdzielczość pozioma zamieniana jest miejsca-
mi z rozmiarem i rozdzielczością pionową. Okna dialogowe Właściwości dla ko-
lorowych ploterów mogą być znacznie bardziej rozbudowane, bo muszą uwzględ-
nić kolory pisaków zainstalowanych w ploterze oraz typ używanego papieru (lub
kalki).
Wszystkie sterowniki drukarek zawierają eksportową funkcję o nazwie ExtDevi-
ceMode, która wywołuje wspomniane okno dialogowe i zapisuje informacje wpro-
wadzane przez użytkownika. Niektóre sterowruki zapisują te informacje w swo-
jej własnej sekcji ftegistry, niektóre tego nie robią. Sterowniki zapisujące informa-
cje mają do niej dostęp podczas następnej sesji z Windows.
556 Część II: Grafika
Programy windowsowe pozwalające użytkownikowi na wybór drukarki na ogół
wywohzją po prostu PrintDlg. Poshxgiwania się funkcją PrintDlg nauczę cię w dal-
szej części tego rozdziahz. Ta użyteczna funkcja bierze na siebie cały trud komu-
nikacji z użytkownikiem i obshxguje wszelkie zmiany, jakich zażąda on podczas
przygotowania do wydruku. PrintDlg także wywołuje okno dialogowe Właści-
wości po kliknięciu przez użytkownika przycisku o tej nazwie.
Program może wyświetlić okno dialogowe z właściwościami drukarki również
przez bezpośrednie wywołanie funkcji sterownika drukarki ExtDeviceMode lub
ExtDeveModePropSheet. Jednakże nie polecam tej metody. Dużo lepszym sposo-
bem jest wyświetlenie okna dialogowego pośrednio, przez wywołanie PrinterPro-
perties, tak jak to robi DEVCAPS2.
PrinterProperties wymaga uchwytu obiektu drukarki, który możesz uzyskać wy-
wołując funkcję OpenPrinter. Gdy użytkowruk zamknie okno dialogowe właści-
wości, PrinterProperties kończy działanie. Możesz wówczas zamknąć uchwyt dru-
karki, wywołując ClosePrinter. Oto jak robi to DEVCAPS2:
Program najpierw odczytuje nazwę drukarki, która aktualnie jest wybrana w menu
Device i zapisuje ją w tablicy znakowej szDevice:
GetMenuString (hMenu, nCurrentDevice, szDevice,
sizeof (szDevice) / sizeof (TCHAR), MF BYCOMMAND);
Potem uzyskuje uchwyt tego urządzenia, wykorzystując OpenPrinter. Jeśli wywo-
łanie się powiedzie, w następnej kolejności program wywołuje PrinterProperties,
aby otworzyć okno dialogowe, a na koniec CIosePrinter w celu usunięcia uchwy-
tu urządzenia:
if (OpenPrinter (sz0evice, &hPrint, NULL))
(
PrinterProperties (hwnd, hPrint);
ClosePrinter (hPrint);
Sprawdzanie cechy BitBlt
Za pomocą funkcji GetDeviceCaps odczytasz rozmiar oraz rozdzielczość obszaru
strony, który może być zadrukowany. (W większości przypadków obszar ten nie
będzie się pokrywał z wielkością papieru). Jeżeli chciałbyś wykonać własne ska-
lowanie, możesz także uzyskać w pikselach względną szerokość i wysokość.
Większość informacji o poszczególnych cechach drukarki przeznaczona jest dla
GDI, a nie dla aplikacji. Często, gdy drukarka samodzielnie nie może czegoś wy-
konać, zostanie to zasymulowane przez GDI. Istnieje natomiast cecha, którą pewne
aplikacje powinny sprawdzić.
Jest nią charakterystyka drukarki uzyskiwana z bitu RC BITBLT wartości zwra-
canej z GetDeviceCaps z parametrem RASTERCAPS (od ang. raster capabilities -
możliwości rastrowe). Bit ten wskazuje, czy w przypadku tego urządzenia moż-
liwy jest transfer bloków bitowych. Większość drukarek mozaikowych, lasero-
wych oraz atramentowych jest zdolna do transferów bloków bitowych. Nie mają
tej umiejętności plotery. Urządzenia, które nie mogą obshzgiwać transferów blo-
ków bitowych, nie obshzgują następujących funkcji GDI: CreateCompatibIeDC, Cre-
ateCompatibleBitmap, PatBlt, BitBlt, StretchBlt, GrayString, Drawlcon, SetPixel, Get-
Rozdział 13: Drukowanie 557
Pixel, FloodFill, ExtFloodFill, FillRgn, FrameRgn, InvertRgn, PaintRgn, FiIlRect, Fra-
meRect oraz InvertRect. Jest to najistotniejsza różnica między wykonywaniem
wywołań GDI dla monitorów oraz używaniem ich dla drukarki.
Najprostszy program drukujący
Teraz gotowi jesteśmy do drukowania i zaczniemy najprościej, jak to tylko możli-
we. W rzeczywistości nasz pierwszy program drukujący spowoduje jedynie wy-
rzucenie strony przez drukarkę. Program FORMFEED, pokazany na rysunku 13-
5, przedstawia absolutne minimm wymagane podczas drukowania.
FORMFEED.C
/*
FORMFEED.C - Nakazuje drukarce przejść do następnej strony
(c) Charles Petzold, 1998
*/
#include
HDC GetPrinterDC (void) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int iCmdShow)
static DOCINFO di = ( sizeof (DOCINFO), TEXT ("FormFeed") ) ;
HDC hdcPrint = GetPrinterDC () ;
if (hdcPrint != NULL)
(
if (StartDoc (hdcPrint, &di) > 0)
if (StartPage (hdcPrint) > 0 && EndPage (hdcPrint) > 0)
EndDoc (hdcPrint) ;
DeleteDC (hdcPrint)
return 0 ;
Rysunek 13-5. Program FORMFEED
Ten program wymaga pliku GETPIZNDC.C pokazanego, na rysunku 13-3.
Poza uzyskaniem kontekstu urządzenia drukującego (a następnie usunięciem go)
program wywołuje zaledwie cztery funkcje drukujące, omówione we wcześniej-
szej części tego rozdziału. FORMFEED najpierw wywołuje StartDoc, aby rozpo-
cząć nowy dokument. Sprawdza wartość zwracaną z funkcji i kontynuuje dzia-
łanie jedynie wówczas, gdy wartość ta jest dodatnia:
if (StartOoc (hdcPrint, &di) > 0)
Drugim argumentem funkcji StartDoc jest wskaźnik do struktury DOCINFO. Zawie-
ra ona w pierwszym polu rozmiar struktury, w drugim zaś tekst "FormFeed". Gdy
dokument jest drukowany lub oczekuje na wydruk, napis ten pojawia się w kolum-
nie Nazwa dokumentu kolejki zadań drukarki. Zazwyczaj tekst identyfikujący jest
połączeniem nazwy aplikacji wykonującej wydruk oraz nazwy drukowanego pliku.
558 Część II: Grafika
Jeśli StartDoc powiedzie się (co wskazuje dodatnia wartość zwracana), FORMFEED
wywohxje StartPage, po czym następuje bezpośrednio wywołanie do EndPage. Taka
sekwencja skłania drukarkę do przejścia do następnej strony. Tutaj także testo-
wane są wartości zwracane:
if (StartPage (hdcPrint)> 0 && EndPage (hdcPrint)>0)
Na koniec, jeśli wszystko do tego miejsca przebiegło bezbłędnie, dokument zo-
staje zakończony:
EndOoc (hdcPrint):
Zauważ, że funkcja EndDoc wywoływana jest tylko wówczas, gdy nie wystąpiły
żadne błędy wydruku. Jeżeli któraś z poprzednich funkcji drukujących zwróci
kod błędu, GDI zadba o przervanie wydruku dokumentu. Jeśli drukarka w tym
momencie nie drukuje, taki kod błędu często powoduje jej zresetowanie. Zwykłe
przetestowanie wartości zwracanych przez funkcje drukujące jest najłatwiejszym
sposobem sprawdzenia błędów. Jeżeli chcesz zakomunikować użytkownikowi
wystąpienie jakiegoś konkretnego błędu, musisz wywołać GetLastError, żeby móc
określić ten błąd.
Jeśli kiedykolwiek pisałeś prosty program przesuwający papier w drukarce w sys-
temie MS-DOS, wiesz, że kod ASCII 12 [Ctrl+L] aktywuje przesuwanie papieru
dla większości drukarek. Dlaczego nie można po prostu otworzyć portu drukar-
ki, korzystając z funkcji open z biblioteki C, a następnie wysłać kodu ASCII 12
używając write? Cóż, nic nie stoi na przeszkodzie, abyś tak zrobił. Najpierw mu-
sisz określić, do jakiego portu równoległego lub szeregowego podłączona jest dru-
karka, a potem - czy jakiś inny program (na przykład bufor drukarki) aktualnie
nie używa drukarki. Nie chcesz chyba, aby polecenie wyrzucenia papieru zosta-
ło wysłane w środku dokumentu drukowanego przez jakiś inny program? Na-
stępnie musisz sprawdzić, czy kod ASCII 12 jest znakiem powodującym wyrzu-
cenie papieru dla dołączonej drukarki, bo jak wiesz, kod ten nie jest uniwersalny.
W rzeczywistości polecenie wyrzucenia papieru w PostScripcie to nie 12, ale sło-
wo showpage.
Mówiąc krótko, niech nie przyjdzie ci do głowy obchodzenie Windows; podczas
drukowania trzymaj się funkcji tego systemu.
Drukowanie grafiki i tekstu
Drukowanie z programu windowsowego z reguły wymaga więcej zabiegów, niż
to pokazano w programie FORMFEED. Chcąc rzeczywiście coś wydrukować, po-
trzebujesz kilku wywołań GDI. Napiszmy program, który wydrukuje jedną stronę
tekstu i grafiki. Rozpoczniemy od metody pokazanej w programie FORMFEED,
a później rozbudujemy go. Rozpatrzymy trzy wersje tego programu, o nazwach
PRINTl, PRINT2 i PRINT3. Aby uniknąć powielenia znacznej ilości kodu źró-
dłowego, każdy z tych programów będzie wykorzystywał pokazany wcześniej
plik GETPRNDC.C oraz funkcje zawarte w pliku PRINT.C, przedstawionym na
rysunku 13-6.
Rozdział 13: Drukowanie 559
PRINT.C
/*
PRINT.C - Procedury wspólne dla programów
Printl, Print2 i Print3
*/
include
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL PrintMyPage (HWND) ;
extern HINSTANCE hInst ;
extern TCHAR szAppName[] ; ;:
extern TCHAR szCaptionC] ;
i
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
I
wndclass.style = CS_HREDRAW CSUREDRAW ; i
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC ARROW) ; j
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITEBRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
i
if (!RegisterClass (&wndclass))
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
R
szAppName, MBICONERROR) ;
return 0 ;
hInst = hInstance ;
hwnd = CreateWindow (szAppName, szCaption,
WS_OIIERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, 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 ;
)
560 Część II: Grafika
(ciąg dalszy ze strony 559)
void Pa9eGDICaIIs (HDC hdcPrn, int cxPage, int cyPage)
(
static TCHAR szTextStr[] = TEXT ("Hello, Printer!") ;
Rectangle (hdcPrn, 0, 0, cxPage, cyPage) ;
MoveToEx (hdcPrn, 0, 0, NULL) ;
LineTo (hdcPrn, cxPage, cyPage) ;
MoveToEx (hdcPrn, cxPage, 0, NULL) ;
LineTo (hdcPrn, 0, cyPa9e) ;
SaveDC (hdcPrn) ;
SetMapMode (hdcPrn, MMISOTROPIC) ;
SetWindowExtEx (hdcPrn. 1000. 1000, NULL) ;
SetViewportExtEx (hdcPrn, cxPage / 2, -cyPa9e / 2, NULL) ;
SetViewportOrgEx (hdcPrn, cxPage / 2, cyPage / 2, NULL) ;
Ellipse (hdcPrn, -500, 500, 500, -500) ;
SetTextAlign (hdcPrn, T BASELINE T CENTER) ;
TextOut (hdcPrn, 0, 0, szTextStr, lstrlen (szTextStr)) ;
RestoreDC (hdcPrn, -1) ;
)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message. WPARAM wParam, LPARAM lParam)
t
static int cxClient, cyClient ;
HDC hdc ;
HMENU hMenu ;
PAINTSTRUCT ps ;
switch (message)
(
case WM CREATE:
hMenu = GetSystemMenu (hwnd, FALSE) ;
AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ;
AppendMenu (hMenu, 0, 1, TEXT ("&Print")) ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_SYSCOMMAND:
if (wParam = 1)
(
if (!PrintMyPage (hwnd))
MessageBox (hwnd, TEXT ("Could not print page!"),
szAppName, MB OK MBICONEXCLAMATION) ;
return 0 ;
)
break ;
Rozdział 13: Drukowanie 561
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
PageGDICaIIs (hdc. cxClient, cyClient) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostOuitMessage (0) ;
return 0 ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 13-6. Plik PRINT.C, wykorzystywany w programach PRINTI, PRINTI
i PIINT3
Plik PRINT.C zawiera funkcje WinMain i WndProc, a także funkcję PageGDICaIIs, kto-
ra przyjmuje jako argumenty uchwyt kontekstu urządzenia dmkującego i dwie zmien-
ne, równe szerokości i wysokości strony dla drukarki. PageGDICaIIs rysuje prostokąt
okalający całą stronę, dwie linie między przeciwległymi narożnikami strony, elipsę
w środku strony (jej średnica jest równa połowie mniejszej wartości wysokości lub
szerokości strony dla drukarki) oraz tekst "Hello, Printer!" w środku elipsy.
W czasie przetwarzania komunikatu WMCREATE procedura WndProc dodaje
do menu systemowego opcję Print. Wybranie tej opcji powoduje wywołanie funkcji
PrintMyPage, którą rozbudujemy w trakcie tworzenia kolejnych wersji programu.
PrintMyPage zwróci TRUE, jeśli strona zostanie wydrukowana bez problemów,
lub FALSE, jeżeli w czasie wydruku wystąpi błąd. Jeśli PrintMyPage zwraca FALSE,
wówczas WndProc wyświetla okno komunikatu, aby poinformować cię o błędzie.
Drukowanie konturu
PRINTI, pierwsza wersja programu drukującego, zaprezentowana jest na rysunku
13-7. Po skompilowaniu PIINTI możesz go wykonać i z menu systemowego
wybrac opcję Print. Zaraz potem GDI zapisze żądane wyjście na drukarkę w pli-
ku tymczasowym, a następnie bufor prześle je do drukarki.
PRINTl.C
/*
PRINTl.C - Drukowanie konturu
(c) Charles Petzold, 1998
*/
Ilinclude
HDC GetPrinterDC (void) ; // w GETPRNDC.C
void PageGDICaIIs (HDC, int, int) ; // w PRINT.C
HINSTANCE hInst ;
TCHAR szAppName[] = TEXT ("Printl") ;
TCHAR szCaption[] = TEXT ("Print Program 1") ;
562 Część II: Grafika
(ciąg dalszy ze strony 561)
BOOL PrintMyPage (HWND hwnd)
(
static DOCINFO di = ( sizeof (DOCINFO), TEXT ("Printl: Printing") ) ;
BOOL bSuccess = TRUE ;
HDC hdcPrn ;
int xPage, yPage ;
if (NULL = (hdcPrn = GetPrinterDC ()))
return FALSE ;
xPage = GetDeviceCaps (hdcPrn, HORZRES) ;
yPage = GetDeviceCaps (hdcPrn, VERTRES) :
if, (StartDoc (hdcPrn, &di) > 0)
(
if (StartPage (hdcPrn) > 0)
(
PageGDICaIIs (hdcPrn, xPage, yPage)
if (EndPage (hdcPrn) > 0)
EndDoc (hdcPrn) ;
else
bSuccess = FALSE ;
)
else
bSuccess = FALSE ;
DeleteDC (hdcPrn) ;
return bSuccess ;
Rysunek 13-7. Program PftINTI
Spójrzmy na kod PRINTI.C. Jeśli PrintMyPage nie może uzyskać uchwytu kon-
tekstu urządzenia dla drukarki, zwraca FALSE, a procedura WndProc wyświetla
okno komunikatu informujące o wystąpieniu błędu. Jeżeli funkcji powiedzie się
uzyskanie uchwytu kontekstu urządzenia, wówczas określa w pikselach pozio-
my i pionowy rozmiar strony, wywołując GetDeviceCaps:
xPage = GetDeviceCaps (hdcPrn, HORZRES) ;
yPage = GetDeviceCaps (hdcPrn, VERTRES) ;
Nie jest to całkowity rozmiar papieru, a tylko jego obszar przeznaczony do zadru-
kowania. Po tych wywołaniach kod funkcji PrintMyPage w PRINT1 ma taką samą
strukturę jak kod FORMFEED, tyle że PRIT'1 wywołuje między StartPage i End-
Page funkcję PageGDICaIIs. PIZTTI wywołuje funkcję drukowania EndDoc jedynie
wtedy, gdy wywołania StartDoc, StartPage i EndPage zakończą się powodzeniem.
Anulowanie wydruku i procedura Abort
W przypadku długich dokumentów program powinien dawać użytkownikowi
dogodny sposób wstrzymania zadania drukowania, gdy aplikacja nadal druku-
je. Być może użytkownik zamierzał wydrukować tylko jedną stronę dokumentu,
lecz nieopatrznie wybrał wydrukowanie wszystkich 537 stron. Pomyłkę tę po-
winno się dać naprawić, zanim wszystkie 537 stron zostanie wydrukowane.
Rozdział 13: Drukowanie 563
Anulowanie zadania wydruku z aplikacji wymaga procedury "Abort" (przerwij).
Procedura przerywająca to niewielka funkcja eksportowa w twoim programie.
Adres tej funkcji podajesz systemowi Windows jako argument funkcji SetAbort-
Proc; wówczas w czasie drukowania GDI wywołuje cyklicznie tę procedurę, py-
tając w istocie "Mam dalej drukować?"
Zastanówmy się najpierw, co jest potrzebne do włączenia procedury przerywa-
jącej w proces drukowania, i sprawdźmy niektóre z możliwych dróg przebiegu
działania. Procedurę przerywającą można nazwać AbortProc i nadać jej następu-
jącą postać:
BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
(
[kod procedury]
Przed drukowaniem musisz zarejestrować procedurę przerywającą przez wywo-
łanie SetAbortProc:
SetAbortProc (hdcPrn, AbortProc) ;
Wywołanie to wykonujesz przed wywołaniem StartDoc. Nie musisz "odwoływać"
procedury przerywającej po zakończeniu drukowania.
W czasie przetwarzania wywołania EndPage (to znaczy w czasie wgrywania me-
tapliku do sterownika urządzenia i tworzenia tymczasowych plików wyjściowych
drukarki) GDI często wywołuje procedurę przerywającą. Parametr hdcPrn to
uchwyt kontekstu urządzenia drukującego. Parametr iCode ma wartość 0, jeśli
wszystko przebiega prawidłowo, lub SP OUTOFDISK, jeżeli moduł GDI nie ma
już miejsca na dysku na tyrnczasowe pliki wyjściowe wydruku.
AbortProc musi zwrócić TRUE (wartość niezerową), jeśli drukowanie ma być kon-
tynuowane, albo FALSE (0), jeżeli zadanie drukowania ma być przerwane. Pro-
cedura przerywająca może być bardzo prosta, na przykład:
BOOL CALLBACK AbortProc (NDC hdcPrn, int iCode)
(
MSG msg ;
while (PeekMessage (&msg, NULL, 0, 0, PM REMOVE))
f
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
1
return TRUE;
Funkcja ta może wydać się nieco dziwna. Istotnie, wygląda podejrzanie - niczym
pętla komunikatów. Co w tym miejscu robi pętla komunikatów? Tak, bo to jest ona.
Zauważ przy tym, że wywołuje ona PeekMessage zamiast GetMessage. Funkcję Peek-
Message omówiłem przy okazji programu RANDRECT na końcu rozdziału 5. Jak
zapewne pamiętasz, PeekMessage (tak jak GetMessage) zwraca sterowanie do pro-
gramu z komunikatem z kolejki komunikatów programu, jednak zwraca sterowa-
nie także wówczas, gdy w kolejce tej nie ma żadnych oczekujących komunikatów.
Pętla komunikatów w funkcji AbortProc wielokrotnie wywołuje PeekMessage, je-
żeli zwraca ona TRUE. Wartość TRUE oznacza, że funkcja PeekMessage odebrała
komunikat, który może być przesłany do jednej z procedur okien programu przy
564 Część II: Grafika
użyciu TranslateMessage i DispatchMessage. Gdy nie ma już więcej komunikatów
w kolejce komunikatów programu, wartość zwracana przez PeekMessage wynosi
FALSE, zatem AbortProc zwraca sterowanie do Windows.
Jak Windows korzysta z AbortProc
Kiedy program drukuje, większość pracy kumuluje się w czasie wywołania End-
Page. Przed tym wywołaniem moduł GDI po prostu dodaje kolejne rekordy do
umieszczonego na dysku metapliku przy każdym wywołaniu przez program
funkcji rysującej GDI. Gdy GDI otrzyma wywołanie EndPage, wówczas wgrywa
metaplik do sterownika urządzenia, po kolei dla każdego z pasm definiowanych
przez sterownik urządzenia na stronie. GDI zapisuje w pliku wyjście na drukar-
kę utworzone przez sterownik drukarki. Jeśli bufor nie jest aktywny, sam moduł
musi wpisać to wyjście na drukarkę do drukarki.
W czasie wywołania EndPage moduł GDI wywohxje procedurę przerywającą, którą
napisałeś. Zwykle parametr iCode wynosi 0, lecz jeśli GDI spostrzeże, że skoń-
czyło się miejsce na dysku z powodu innych plików tymczasowych, które do-
tychczas nie zostały wydrukowane, parametr iCode będzie miał wartość
SP OUTOFDISK. (Zwykle nie sprawdza się tej wartości, ale jeżeli chcesz, możesz
to zrobić). Następnie procedura przerywająca przechodzi do pętli PeekMessage,
aby odebrać komunikaty z kolejki komunikatów programu.
Jeśli w kolejce komunikatów programu nie ma komunikatów, PeekMessage zwra-
ca FALSE. Procedura przerywająca opuszcza wówczas pętlę komunikatów i zwra-
ca wartość TRUE do moduhx GDI, informując tym samym, że drukowanie ma
być kontynuowane. Teraz moduł może dalej przetwarzać wywołania EndPage.
Moduł GDI zatrzymuje proces drukowania, jeżeli wystąpi błąd, a głównym ce-
lem procedury przerywającej jest umożliwienie użytkownikowi wstrzymania
drukowania. W tym celu potrzebne jest nam okno dialogowe wyświetlające przy-
cisk Cancel. Wykonajmy te dwie czynności po kolei. Najpierw dodamy procedu-
rę przerywającą i utworzymy program PRINT2, a następnie dodamy okno dialo-
gowe z przyciskiem Cancel w PRINT3, tak aby procedura przerywająca mogła
być wykorzystana.
Implementacja procedury AbortProc
Popatrzmy chwilę na mechanizm procedury przerywającej. Można ją zdefinio-
wać w następujący sposób:
BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
(
MSG msg ;
while (PeekMessage (&msg, NULL, 0, 0, PM REMOVE))
(
TranslateMessage (&msg) :
DispatchMessage (&msg) ;
)
return TRUE;
Rozdział 13: Drukowanie 565
Aby coś wydrukować, dajesz systemowi Windows wskaźnik do tej procedury:
SetAbortProc (hdcPrn, AbortProc) ;
Wywołanie to wykonujesz przed wywołaniem StartDoc. I to wszystko.
No, nie zupełnie. Pominęliśmy ważne zagadnienie pętli PeekMessage w funkcji
AbortProc - to istotny problem. AbortProc jest wywoływana jedynie wówczas, gdy '
twój program jest w trakcie drukowania. Jeśli otrzymasz komunikat w procedu-
rze AbortProc i prześlesz go do procedury swojego własnego okna, możesz uzy-
skać niepożądane efekty. Użytkownik może ponownie wybrać Print z menu (a pro-
gram jest właśnie w środku procedury druku), może wczytać nowy plik pod-
,
czas gdy program próbuje wydrukować poprzednik, może nawet zamknąć pro-
gram! Jeżeli tak się stanie, wszystkie okna twojego programu zostaną pozamyka-
ne. Ewentualnie wyjdziesz z procedury drukującej, lecz nie będziesz miał się gdzie
udać, chyba że do nieistniejącej już procedury okna.
To dopiero łamigłówka. A twój program nie jest na nią przygotowany. Dlatego,
gdy ustawiasz procedurę przerywającą, powinieneś najpierw w taki sposób zdez-
aktywować okna swojego programu, aby nie mogły być wprowadzane żadne dane,
ani za pomocą klawiatury, ani myszy. Robi się to tak:
EnableWindow (hwnd, FALSE) ;
To zapobiega wprowadzeniu danych z klawiatury lub z myszy do kolejki komu-
nikatów. Zatem użytkownik nie może nic z twoim programem zrobić, dopóki ten
drukuje. Gdy zakończy się drukowanie, ponownie uaktywniasz okno:
EnableWindow (hwnd, TRUE) ;
Po co więc zawracamy sobie głowę wywołaniami TranslateMessage i DispatchMes-
sage w AbortProc, skoro żadne komunikaty myszy lub klawiaturowe nie trafią do
kolejki komunikatów? Rzeczywiście, TranslateMessage nie jest koniecznie potrzebne
(choć prawie zawsze się je dołącza). Natomiast DispatchMessage musimy użyć na
wypadek, gdyby do kolejki komunikatów trafił WMPAINT. Jeśli WM-PAINT
nie zostanie właściwie przetworzony za pomocą BeginPaint i EndPaint w proce-
durze okna, komunikat ten pozostanie w kolejce i zakorkuje przebieg prac, po-
nieważ PeekMessage nigdy nie zwróci FALSE.
Gdy dezaktywujesz swoje okno na czas drukowania, twój program zamiera na
ekranie. Jednak użytkownik może przejść do innego programu i wykonać w nim
jakieś prace, bufor zaś będzie w tym czasie dalej przesyłał pliki wyjściowe na dru-
karkę.
Program PRINT2, pokazany na rysunku 13-8, dodaje do PRINTI procedurę prze-
rywającą oraz niezbędną podstawę - wywołanie funkcji AbortProc oraz dwa wy-
wołania EnableWindow. Pierwsze z nich ma na celu dezaktywację okna, zaś dru-
gie ponownie je uaktywnia.
PRI NT2 . C
/*
PRINT2.C - Drukowanie z procedurd przerywajdcą
(c) Charles Petzold, 1998
*/
#include
566 Część II: Grafika
(ciąg daiszy ze strony 565)
HDC GetPrinterDC (void) ; // w GETPRNDC.C
void PageGDICaIIs (HDC, int, int) ; // w PRINT.C
HINSTANCE hInst ;
TCHAR szAppName[] = TEXT ("Print2") ;
TCHAR szCaption[] = TEXT ("Print Program 2 (Abort Procedure)") ;
BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
MSG msg ;
while (PeekMessage (&msg, NULL, 0, 0, PMREMOVE))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return TRUE ;
BOOL PrintMyPage (HWND hwnd)
(
static DOCINFO di = ( sizeof (DOCINFO), TEXT ("Print2: Printing") 1 ;
BOOL bSuccess = TRUE ;
HDC hdcPrn ;
short xPage, yPage ;
if (NULL = (hdcPrn = GetPrinterDC ()))
return FALSE ;
xPage = GetDeviceCaps (hdcPrn, HORZRES) ;
yPage = GetOeviceCaps (hdcPrn, VERTRES) ;
EnableWindow (hwnd, FALSE) ;
SetAbortProc (hdcPrn, AbortProc) ;
if (StartDoc (hdcPrn, &di) > 0)
(
if (StartPage (hdcPrn) > 0)
P
PageGDICaIIs (hdcPrn, xPage, yPage) ; /i
if (EndPage (hdcPrn) > 0)
EndDoc (hdcPrn) ;
else
bSuccess = FALSE ;
4i
HD
else
vo
bSuccess = FALSE ;
EnableWindow (hwnd, TRUE) ; HII
DeleteDC (hdcPrn) ; TCf
return bSuccess ; TCF
BOC
Rysunek 13-8. Program PRINT2
Rozdział 13: Drukowanie 567
Tworzenie okna dialogowego Printing
Program PRINT2 nie ma jeszcze zadowalającej formy. Po pierwsze, nie informu-
je wprost, kiedy jest w trakcie drukowania, a kiedy je zakończył. Tylko brak reak-
cji na działanie myszą sugeruje, że program musi nadal przetwarzać procedurę
PrintMyPage. PRINT2 także nie umożliwia użytkowrukowi przerwania wydru-
ku w czasie buforowania.
Przecież większość programów windowsowych daje użytkownikom szansę anu-
lowania właśnie trwającej operacji wydruku. Na ekranie pojawia się małe okno
dialogowe, zawierające tekst oraz przycisk z etykietą Anuluj. Program wyświe-
tla to okno dialogowe przez cały czas, gdy GDI zapisuje wyjście na drukarkę w
pliku dyskowym lub (jeśli bufor jest nieaktywny) gdy drukarka drukuje. Jest to
niemodalne okno dialogowe, a ty musisz napisać jego procedurę.
W nazwach wspomnianego okna dialogowego i jego procedury często używane
jest słowo abort (przerwij). Aby wyraźnie odróżnić tę procedurę okna dialogowe-
go od procedury przerywającej, będę nazywał procedurę tego okna dialogowego
procedurą okna dialogowego drukowania. Procedura przerywania (o nazwie
AbortProc) oraz procedura okna dialogowego drukowania (którą nazwę PrintDlg-
Proc) to dwie oddzielne funkcje eksportowane. Jeżeli chcesz profesjonalnie dru-
kować w stylu Windows, musisz mieć je obydwie.
Oto jak będą współdziałały te funkcje: pętla PeekMessage w AbortProc musi być
zmodyfikowana tak, aby przesyłać komunikaty dla niemodalnego okna dialogo-
wego z procedury okna dialogowego. PrintDlgProc musi przetworzyć komuni-
katy 4VMCOMMAND, aby sprawdzić stan przycisku Cancel. Jeśli przycisk Can-
cel zostanie wybrany, funkcja nada zmiennej globalnej o nazwie bUserAbort war-
tość TRUE. Wartość zwracana przez AbortProc jest odwrotnością bUserAbort. Jak
pamiętasz, AbortProc zwraca TRUE, by kontynuować drukowanie, aFALSE, by je
przerwać. W PRINT2 zawsze zwracane było TRUE. Teraz, jeżeli w oknie dialo-
gowym drukowania użytkownik kliknie przycisk Cancel, zwrócona zostanie
wartość FALSE. To rozwiązanie logiczne zostało zaimplementowane w progra-
mie PRINT3, pokazanym na rysunku 13-9.
PRI NT3 . C
*
PRINT3.C - Drukowanie z wykorzystaniem
okna dialogowego drukowania
(c) Charles Petzold, 1998
*/
4linclude
HDC GetPrinterDC (void) ; // w GETPRNDC.C
void PageGDICaIIs (HDC, int, int) ; // w PRINT.C
HINSTANCE hInst ;
TCHAR szAppNameC] = TEXT ("Print3") ;
TCHAR szCaption[] = TEXT ("Print Program 3 (Dialog Box)") ;
BOOL bUserAbort ;
568 Część II: Grafika
(ciąg dalszy ze strony 567)
HWND hDlgPrint ;
BOOL CALLBACK PrintDlgProc (HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
(
switch (message)
(
case WM_INITDIALOG:
SetWindowText (hDlg, szAppName) ;
EnableMenuItem (GetSystemMenu (hDlg, FALSE), SC CLOSE, MF GRAYED) ;
return TRUE ;
case WM_COMMAND:
bUserAbort = TRUE ;
EnableWindow (GetParent (hDlg), TRUE) ;
DestroyWindow (hDlg) ;
hDlgPrint = NULL ;
return TRUE ;
)
return FALSE ;
)
BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
(
MSG msg ;
while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PMREMOVE))
(
if (!hDlgPrint !IsDialogMessage (hDlgPrint, &msg))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
1
1
return !bUserAbort ;
)
BOOL PrintMyPage (HWND hwnd)
(
static DOCINFO di = ( sizeof (DOCINFO), TEXT ("Print3: Printing") 1 ;
BOOL bSuccess = TRUE ;
HDC hdcPrn ;
int xPage, yPage ;
if (NULL = (hdcPrn = GetPrinterDC ()))
return FALSE ;
xPage = GetDeviceCaps (hdcPrn, HORZRES) ;
yPage = GetDeviceCaps (hdcPrn, VERTRES) ;
EnableWindow (hwnd, FALSE) ;
bUserAbort = FALSE ;
hDlgPrint = CreateDialog (hInst, TEXT ("PrintDlgBox"),
hwnd, PrintDlgProc) ;
Rozdział 13: Drukowanie 569
SetAbortProc (hdcPrn, AbortProc) ;
if (StartDoc (hdcPrn, &di) > 0)
t
if (StartPage (hdcPrn) > 0)
f
PageGDICaIIs (hdcPrn, xPage, yPage) ;
if (EndPage (hdcPrn) > 0)
EndDoc (hdcPrn) ;
else
bSuccess = FALSE ; '
else
bSuccess = FALSE ;
if (!bUserAbort)
(
EnableWindow (hwnd, TRUE) ;
DestroyWindow (hOlgPrint) ;
J
DeleteDC (hdcPrn) ;
return bSuccess && !bUserAbort ;
PRINT.RC (fragmenty)
// Microsoft Developer Studio generated resource script.
include "resource.h"
A'
include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Dialog
4
PRINTDLGBOX DIALOG DISCARDABLE 20, 20, 186, 63
STYLE DS_MODALFRAME WSPOPUP WS llISIBLE WS CAPTION WS SYSMENU
FONT 8, "MS Sans Serif"
BEGIN
PUSHBUTTON "Cancel",IDCANCEL,67,42,50,14
CTEXT "Cancel Printing",IDC STATIC,7,21,172,8
END
Rysunek 13-9. Program PRINT3
Na czas eksperymentó z PRINT3 możesz wyłączyć buforowanie druku. W prze-
ciwnym wypadku przycisk Cancel, który widoczny jest jedynie wtedy, gdy bu-
for odbiera dane od PRINT3, może zniknąć zbyt szybko, abyś mógł go kliknąć.
Nie dziw się, jeśli działania nie zostaną zatrzymane natychmiast po kliknięciu
przycisku Cancel, w szczególności w przypadku powolnej drukarki. Drukarka
ma wewnętrzny bufor, który musi zostać opróżniony, zanim zostanie zatrzyma-
na. Kliknięcie Cancel jedynie informuje GDI, żeby nie przesyłał więcej danych
do bufora drukarki.
570 Część II: Grafika
Do PRINT3 dodane zostały dwie zmienne globalne: zmienna boolowska o na-
zwie bUserAbort oraz uchwyt okna dialogowego o nazwie hDlgPrint. Funkcja Print-
MyPage nadaje początkowo zmiennej bUserAbort wartość FALSE i podobnie jak
w PRINT2 główne okno programu jest zdezaktywowane. Wskaźnik do AbortProc
jest używany w wywołaniu SetAbortProc, a wskaźnik do PrintDlgProc - wyko-
rzystywany w wywołaniu CreateDialog. Uchwyt okna zwracany z CreateDialog
zostaje zapisany na zmiennej hDIgPrint.
Oto jak wygląda pętla komunikatu w procedurze AbortProc:
while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM REMOVE))
(
if (!hDlgPrint !IsDialogMessage (hDlgPrint, &msg))
(
Translatehlessage (&msg)
DispatchMessage (&msg>
t
return !bUserAbort ;
Wywołuje ona funkcję PeekMessage jedynie wówczas, gdy zmienna bUserAbort ma
wartość FALSE - to znaczy, gdy użytkownik jeszcze nie zrezygnował z operacji
drukowania. Funkcja IsDialogiVlessage jest potrzebna do przesłania komunikatu
do niemodalnego okna dialogowego. W przypadku niemodalnych okien dialo-
gowych normalną procedurą jest sprawdzenie uchwytu okna dialogowego przed
wykonaniem wywołania. Procedura AbortProc zwraca odwrotność wartości zmien-
nej bUserAbort. Początkowo bUserAbort ma wartość FALSE, zatem AbortProc zwraca
TRUE, co znaczy, że drukowanie ma być kontynuowane. Lecz zmiennej bUserA-
bort można nadać wartość TRUE w procedurze okna dialogowego drukowania.
Funkcja PrintDlgProc jest stosunkowo prosta. W czasie przetwarzania WM INIT
DIALOG funkcja przypisuje nagłówkowi okna nazwę programu i dezaktywuje
opcję Zamknij menu systemowego. Jeżeli użytkownik kliknie przycisk Cancel,
PrintDlgProc otrzyma komunikat WM COMMAND:
case WM_COMMAND:
bUserAbort = TRUE ;
EnableWindow (GetParent (h0lg), TRUE) ;
DestroyWindow (hDlg) ;
hOlgPrint = NULL ;
return TRUE ;
Nadanie zmiennej bUserAbort wartości TRUE oznacza, że użytkownik postano-
wił anulować operację drukowania. Główne okno jest uaktywniane, okno dialo-
gowe zaś - niszczone. (Ważne jest, abyś te dwa działania przeprowadził w takiej
właśnie kolejności. W przeciwnym wypadku jakiś inny program działający w Win-
dows stanie się programem aktywnym, twój zaś mógłby zniknąć gdzieś w tle).
Standardowo zmiennej hDlgPrint przypisywane jest NULL, aby zapobiec wywo-
łaniu IsDialogMessage w pętli komunikatu.
To okno dialogowe otrzymuje komunikaty, wyłącznie wtedy, gdy AbortProc otrzy-
muje komunikaty z PeekMessage i przesyła je do procedury okna dialogowego za
pomocą IsDialogMessage. Natomiast procedura AbortProc wywoływana jest tylko
wtedy, gdy GDI przetwarza funkcję EndPage. Jeśli moduł ten stwierdzi, że war-
tość zwracana z AbortProc to FALSE, ponownie przekazuje sterowanie z wywo-
Rozdział 13: Drukowanie 571
łania EndPage do funkcji PrintMyPage. Nie zwraca kodu błędu. W tym rnomencie
PrintMyPage uznaje, że strona została zakończona i wywołuje funkcję EndDoc.
Jednak nic nie zostaje wydrukowane, ponieważ moduł GDI nie zakończył prze-
twarzania wywołania EndPage.
Trzeba jeszcze trochę posprzątać. jeżeli użytkownik nie anulował zadania dru-
kowania z okna dialogowego, okno to pozostaje wyświetlone. PrintMyPage po-
nownie uaktywnia swoje okno główne oraz niszczy okno dialogowe:
if (!bUserAbort)
EnableWindow (hwnd, TRUE) ;
DestroyWindow (hDlgPrint) ;
)
O tym, co się wydarzyło, informuje stan dwóch zmiennych: bUserAbort mówi, czy
użytkownik zrezygnował z zadania drukowania, bSuccess zaś sygnalizuje wystą-
pienie błędu. Możesz z tymi zmiennymi zrobić, co chcesz. PrintMyPage po pro-
stu przeprowadza logiczną operację AND i zwraca jej wynik do WndProc: !
return bSuccess && !bUserAbort ; ,
Drukowanie w programach POPPAD
Teraz jesteśmy już gotowi, aby wyposażyć w umiejętność drukowania serię pro-
gramów POPPAD i uznać je za zakończone. W tym celu potrzebne będą poszcze-
gólne pliki POPPAD z rozdziału 11 oraz plik POPPRNT.C, przedstawiony na ry-
sunku 13-10.
POPPRNT.C
/*
POPPRNT.C - Funkcje drukujdce edytora
*/
iinclude
iinclude
#include "resource.h"
BOOL bUserAbort ;
HWND hDlgPrint ;
BOOL CALLBACK PrintDlgProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
switch (msg)
case WM_INITDIALOG :
EnableMenuItem (GetSystemMenu (hDlg, FALSE), SC CLOSE, MF GRAYED) ;
return TRUE ;
case WM_COMMAND :
bUserAbort = TRUE ;
EnableWindow (GetParent (hDlg), TRUE) ;
DestroyWindow (hDlg) ;
hDlgPrint = NULL ;
return TRUE ;
Część II: Grafika Rozdział '
572
(ciąg dalszy ze strony 571) i f
return FALSE ;
)
BOOL CALLACK AbortProc (HDC hPrinterDC. int iCode)
(
MSG msg :
while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PMREMOVE))
if (!hDlgPrint !IsOialo9Message (hDlgPrint, &msg))
t
TranslateMessage (&msg) :
DispatchMessage (&msg) :
)
)
return !bUserAbort ;
BOOL PopPrntPrintFile (HINSTANCE hInst, HWND hwnd, HWND hwndEdit,
PTSTR szTitleName)
(
stdtic DOCINFO di = ( sizeof (DOCINFO) ) :
static PRINTDLG pd ;
BOOL bSuccess ;
yChar iCharsPerLine, iLinesPerPage, iTotalLines,
int
iTotalPages, iPage, iLine, iLineNum :
PTSTR pstrBuffer :
TCHAR szJobName C64 + MAXPATH7 :
TEXTMETRIC tm :
WORD iColCopy, iNoiColCopy :
// Wywolaj okno dialogowe Drukuj
pd.lStructSize = sizeof (PRINTDLG) ;
pd.hwndOwner = hwnd ;
pd.hDevMode = NULL :
pd.hDevNames = NULL :
pd.hDC = NULL ;
pd.Flags = PD_ALLPAGES PD_COLLATE
PD_RETURNDC PD NOSELECTION :
pd.nFromPage = 0
pd.nToPa9e = 0
pd.nMinPa9e
pd.nMaxPage = 0
pd.nCopies = I
pd.hInstance = NULL :
pd.lCustOata = OL ;
pd.lpfnPrintHook = NULL ;
pd.lpfn5etupHook = NULL :
pd.lpPrintTemplateName = NULL :
pd.lpSetupTemplateName = NULL :
pd.hPrintTemplate = NULL ;
pd.hSetupTemplate = NULL :
if (!PrintDlg (&pd))
return TRUE ;
573
Rozdział 13: Drukowanie
if (0 = (iTotalLines = SendMessa e (hwndEdit, EM GETLINECOUNT,
9 ,;:
0, 0)))
return TRUE ;
// Oblicz dla pliku potrzebne wymiary
GetTextMetrics (pd.hDC, &tm) :
yChar = tm.tmHeight + tm.tmExternalLeading :
iCharsPerLine = GetDeviceCaps (pd.hDC, HORZRES) / tm.tmAveCharWidth :
iLinesPerPage = GetDeviceCaps (pd.hDC, VERTRES) / yChar ; g
iTotalPages = (iTotalLines + iLinesPerPage - 1) / iLinesPerPa e ;
// Alokuj bufor dla każdej linii tekstu
+
pstrBuffer = malloc (sizeof (TCHAR) * (iCharsPerLine 1)) :
i:
// Wyświetl okno dialogowe drukowania
EnableWindow (hwnd, FALSE) ;
k;
bSuccess = TRUE :
bUserAbort = FALSE :
1 Print = Create0ialog (hInst, TEXT ("PrintDlgBox"),
hD g
hwnd, PrintDlgProc) .
SetDlgItemText (hDlgPrint, IDCFILENAME, szTitleName) :
SetAbortProc (pd.hDC, AbortProc) :
// Rozpocznij dokument
GetWindowText (hwnd, szJobName, sizeof (szJobName)) :
di.lpszDocName = szJobName :
if (StartDoc (pd.hDC, &di) > 0)
Py
(
// Do sortowania potrzebna jest ta pętla oraz iNoiColCo
for (iColCopy = 0 : ? pd.nCo ies : 1) :
< ((WORD) pd.Flags & PD COLLATE p
iColCopy
iColCopy++)
for (iPage = 0 ; iPage < iTotalPages ; iPage++)
for (iNoiColCopy = 0 ' , pd.nCo ies);
iNoiColCopy < (pd.Flags & PD COLLATE 1 y P
iNoiColCopy++)
// Rozpocznij:stronę
if (StartPage (pd.hDC) < 0)
bSuccess = FALSE :
.I
break ;
)
574 Część II: Grafika Ro
(ciąg dalszy ze strony 573)
// Drukuj linie na każdej stronie
for (iLine = 0 ; iLine < iLinesPerPage ; iLine++)
(
iLineNum = iLinesPerPage * iPage + iLine ;
if (iLineNum > iTotalLines)
break ;
I
*(int *) pstrBuffer = iCharsPerLine ;
TextOut (pd.hDC, 0, yChar * iLine. pstrBuffer,
(int) SendMessage (hwndEdit, EMGETLINE,
(WPARAM) iLineNum, (LPARAM) pstrBuffer));
)
if (EndPage (pd.hDC) < 0)
(
bSuccess = FALSE ;
break ;
if (bUserAbort)
break ;
)
if (!bSuccesJ bUserAbort)
break ;
)
if (!bSuccess bUserAbort)
break ;
)
)
else
bSuccess = FALSE ;
if (bSuccess)
EndDoc (pd.hDC) ;
if (!bUserAbort)
(
EnableWindow (hwnd, TRUE) ;
DestroyWindow (hDlgPrint) ;
}
free (pstrBuffer) ;
DeleteDC (pd.hDC) ;
return bSuccess && !bUserAbort ;
?
Rysunek 13-10. Plik POPPRNT.C umożliwiający drukowanie z programu POP-
PAD
Rozdział 13: Drukowanie 575
Z odnie z arkanami sztuki, program POPPAD jest tak prosty, jak to tylko możli-
we, gdyż wykorzystuje wysoki poziom "inteligencji" systemu Windows. Plik
POPPRNT.C demonstruje, jak używać funkcji PrintDlg. Funkcja ta znajduje się
w bibliotece podstawowych okien dialogowych i wykorzystuje strukturę typu
PRIN'TDLG.
Opcję Drukuj umieszcza się zazwyczaj w menu Plik programu (czyli Print w menu
File). Jeśli użytkownik wybierze opcję Drukuj, program może zainicjować pola
struktury PRINTDLG i wywołać PrintDlg.
Funkcja PrintDlg wyświetla okno dialogowe pozwalające użytkownikowi wybrać
zakres stron przeznaczonych do drukowania i dlatego szczególnie przydatne
w programach, które tak jak POPPAD mogą drukować dokumenty wielostroni-
cowe. W oknie tym znajduje się także pole edycji, pozwalające na wprowadzenie
liczby kopii oraz pole wyboru z etykietą Sortuj. Sortowanie kopii ma wpływ na
kolejność stron w przypadku drukowania kilku kopii. Jeśli na przykład, doku-
ment ma trzy strony i użytkownik zażąda wydrukowania trzech kopii, program
może je wydrukować na dwa sposoby. Przy sortowaniu kopii strony drukowane
są w kolejności 1, 2, 3, 1, 2, 3, 1, 2, 3. Kopie niesortowane mają kolejność 1, 1, 1, 2,
2, 2, 3, 3, 3. Wydrukowanie kopii we właściwej kolejności zależy od twojego pro-
gramu.
Okno dialogowe Drukuj umożliwia użytkownikowi także wybranie drukarki in-
nej niż domyślna. Znajduje się w nim przycisk z etykietą Właściwości, wywołu-
jący okno dialogowe trybu urządzenia. Pozwala ono użytkownikowi przynajmniej
na wybór orientacji strony - Pionowa lub Pozioma.
Po powrocie z funkcji PrintDlg pola w strukturze PRINTDLG wskazują zakres
stron przeznaczonych do wydruku oraz informują, czy w przypadku kilku kopii
mają one zostać posortowane. Struktura także dostarcza gotowy do użycia uchwyt
kontekstu urządzenia drukującego.
W POPPRNT.C funkcja PopPrntPrintFile (wywoływana w POPPAD, gdy użytkow-
ruk wybierze opcję Print z menu File) wywołuje PrintDlg, a następnie przystępu-
je do wydruku pliku. Funkcja PopPrntPrintFile wykonuje potem obliczenia, aby
określić liczbę znaków, które mogą zmieścić się w linii, oraz liczbę linii, które po-
winny znaleźć się na stronie. W procesie tym wykorzystywane są wywołania funk-
cji GetTextMetrics do określenia rozmiaru znaku oraz funkcji GetDeviceCaps do
określenia liczby znaków w linii oraz liczby linii na stronie.
Całkowitą liczbę linii w dokumencie (zmienna iTotalLines) program uzyskuje po-
przez przesłanie komunikatu EM GETLINECOLTNT do kontrolki edytora. Z pa-
mięci lokalnej alokowany jest bufor przechowujący zawartość każdej linii. Pierw-
sze słowo w buforze każdej linii ma wartość równą liczbie znaków w linii. Prze-
słanie kontrolce edytora komunikatu EM GETLINE powoduje skopiowanie linii
do bufora; następnie linia ta przesyłana jest do kontekstu urządzenia drukujące-
go za pomocą TextOut. (POPPRNT.C nie jest na tyle rozgarnięty, żeby zawijać li-
nie wykraczające poza szerokość strony drukarki. Z techniką zawijania zbyt dłu-
gich lini.i zapoznamy się w rozdziale 17).
Zwróć uwagę, że schemat drukowania dokumentu obejmuje dwie pętle for, za-
leżne od liczby kopii. Pierwsza z nich korzysta ze zmiennej o nazwie iColCopy
576 Część II Grafika
i działa, gdy użytkownik zażąda sortowania kopii; druga korzysta ze zmiennej
iNoiCoICopy i odpowiada kopiom niesortowanym.
Program wychodzi z pgtli for iterującej po numerach stron, jeśli StartPage lub End-
Page zwrócą błąd bądź jeśli bUserAbort ma wartość TRUE. Jeżeli wartością zwra-
caną z procedury przerywającej jest FALSE, EndPage nie zwraca błędu. Z tego
powodu bUserAbort testowana jest jawnie przed rozpoczęciem następnej strony.
Jeśli nie znaleziono żadnych błędów, wykonywane jest wywołanie EndDoc:
if (bSuccess)
EndDoc (pd.hDC) ;
Możesz zrobić eksperyment i wydrukować z POPPAD dokument wielostronico-
wy. Masz możliwość monitorowania postępu wydruku w oknie zadań wydruku
systemu Windows. Najpierw plik, który jest drukowany, pokazuje się w tym oknie,
gdy GDI skończy przetwarzanie pierwszego wywołania EndPage. W tym momen-
cie bufor rozpoczyna przesyłanie pliku do drukarki. Gdy w tym czasie anulujesz
z POPPAD zadanie drukowania, bufor także zrezygnuje z drukowania - na sku-
tek zwrócenia wartości FAISE z procedury przerywającej. Gdy plik pojawi się
w oknie zadań wydruku, możesz także anulować wydruk wybierając Anuluj dru-
kowanie z menu Dokument. W tym przypadku trwające w POPPAD wywołanie
EndPage zwraca błąd.
Programiści od niedawna pracujący w Windows często ulegają niezwykłej obse-
sji na tle funkcji AbortProc. Jest ona rzadko używana przy drukowaniu. Jak widać
w POPPAD, użytkownik może anulować zadanie drukowania prawie w każdej
chwili poprzez okno drukowania POPPAD bądź przez okno zadań drukowania.
Żadna z tych opcji nie wymaga od programu użycia funkcji AbortProc. Jedyne
miejsce, gdzie funkcja ta byłaby dopuszczalna w POPPAD, znajduje się między
wywołaniem StartDoc i pierwszym wywołaniem EndPage. Jednak kod ten wyko-
nywany jest tak szybko, że AbortProc nie jest potrzebna.
Rysunek 13-11 pokazuje prawidłową sekwencję wywołań funkcji drukujących
przy drukowaniu dokumentu wielostronicowego. Najlepsze miejsce poszukiwa-
nia wartości TRUE dla bUserAbort jest po każdym wywołaniu EndPage. Funkcja
EndDoc używana jest tylko wówczas, gdy poprzednie funkcje drukujące przebie-
gły bez błędów. Praktycznie wygenerowanie błędu przez jakiekolwiek wywoła-
nie funkcji drukującej kończy przedstawienie i można iść do domu.
Rozdział 13: Drukowanie
Rysunek 13-11. Sekwencja wywołań funkcji wydruku wielostronicowego
Wyszukiwarka
Podobne podstrony:
Programowniae windows petzold Petzold01
Programowniae windows petzold Petzold05
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 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