Получение контекста устройства
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

 

Теперь, когда мы научились определять характеристики принтера, мы можем перейти непосредственно к работе с ним. Для начала нам надо получить хендл контекста устройства, связанного с принтером. Существует две возможности сделать это: самим разобраться с файлом WIN.INI и создать контекст устройства или воспользоваться стандартным диалогом для выбора и настройки принтера. Чаще применяется второй способ (как более "дружественный"), однако иногда проще все сделать самим (особенно, если Вы должны сами проверить наличие и характеристики принтеров - например при инсталляции программы).

Если мы хотим сами разобраться с принтером, то для создания контекста принтера мы должны воспользоваться процедурой:

HDC CreateDC( lpszDriver, lpszDevice, lpszOutput, lpvData );

При этом мы сначала должны определить имя драйвера lpszDriver, имя принтера lpszDevice и имя выводного устройства lpszOutput. Параметр lpvData мы будем устанавливать в NULL, для того, что бы произвести инициализацию по умолчанию (так как это было определено при инсталляции принтера или изменено через “Control Panel”).

Всю нужную для создания контекста устройства информацию мы получим из параметра “device=” секции [windows]:

HDC GetPrinterDC( void ) {

char buf[ 80 ];

char *Device, *Drive, *Output;

static char delimiters[]= ", ";

GetProfileString( "windows", "device", ",,,", buf, sizeof(buf)-1 );

Device= strtok( buf, "," );

Drive= strtok( NULL, delimiters );

Output= strtok( NULL, delimiters );

return Device && Drive && Output ?

CreateDC( Drive, Device, Output, NULL ) : NULL;}

Этот способ позволяет легко получить хендл контекста текущего выбранного принтера. А если нам желательно выбирать или настраивать принтер, то самым удобным будет способ с использованием стандартного диалога (каждый драйвер принтера содержит собственный диалог используемый для его настройки; этот диалог мы можем вызвать, обратившись к функции ExtDeviceMode драйвера - однако то–же самое делает стандартный диалог).

При использовании стандартного диалога мы должны вызвать функцию PrintDlg(...), определенную в COMMDLG.H:

#include "commdlg.h"

BOOL PrintDlg( PRINTDLG FAR* lppd );

typedef struct tagPD {

DWORD lStructSize;

HWND hwndOwner;

HGLOBAL hDevMode;

HGLOBAL hDevNames;

HDC hDC;

DWORD Flags;

UINT nFromPage;

UINT nToPage;

UINT nMinPage;

UINT nMaxPage;

UINT nCopies;

HINSTANCE hInstance;

LPARAM lCustData;

UINT (CALLBACK* lpfnPrintHook)(HWND, UINT, WPARAM, LPARAM);

UINT (CALLBACK* lpfnSetupHook)(HWND, UINT, WPARAM, LPARAM);

LPCSTR lpPrintTemplateName;

LPCSTR lpSetupTemplateName;

HGLOBAL hPrintTemplate;

HGLOBAL hSetupTemplate;

} PRINTDLG;

Вы должны заполнить нужные поля (обычно почти все - 0) этой структуры и вызвать функцию PrintDlg() для выбора текущего принтера и его настройки. Функция возвращает результат TRUE (не 0), если контекст принтера успешно создан, или FALSE (0), если была нажата кнопка “Cancel” или возникла ошибка.

Параметр lStructSize задает размер данной структуры, он должен быть равен sizeof(PRINTDLG), hwndOwner задает хендл окна–пользователя диалога (может быть 0).

Два хендла глобальных объектов hDevMode и hDevNames используются для начальной инициализации диалога. Обычно они задаются 0, а функция PrintDlg() сама создает эти блоки и указывает их хендлы. Если Вы не планируете больше обращаться к функции PrintDlg(), то вы должны их уничтожить (с помощью функции GlobalFree()). Однако обычно эти блоки уничтожаются только при завершении всего приложения - так что при повторном вызове PrintDlg() в структуре PRINTDLG уже содержаться эти хендлы и информация из этих блоков используется для повторной инициализации диалога.

Поле hDC при вызове диалога игнорируется, а после завершения может (если Вы это закажете) содержать хендл созданного контекста принтера.

Интересно рассмотреть только некоторые значения поля Flags:

PD_PRINTSETUP вместо диалога “выбор принтера” вызвать диалог “настройка принтера”.

PD_RETURNDC создать контекст принтера и вернуть его хендл в поле hDC

PD_RETURNIC создать информационный контекст принтера и вернуть его хендл в поле hDC

PD_RETURNDEFAULT не вызывать никакого диалога, просто инициализировать глобальные блоки hDevMode и hDevNames (перед таким вызовом функции PrintDlg() они должны быть равны 0)

После получения хендла контекста устройства принтера мы можем перейти к выводу на принтер. Но сначала нам надо разобраться в том, как Windows организует работу с принтером.

 

Основы печати

 

Когда Вы получили контекст устройства для принтера, на самом деле Вы не получили доступа к самому принтеру, а только лишь к промежуточному метафайлу. Пока Вы рисуете на принтере, все нужные команды запоминаются в этом метафайле и только после того, как Вы объявите о завершении работы со страницей, Windows осуществит печать подготовленной страницы из метафайла.

Реальный механизм сложнее, так как спулинг принтера может быть отключен, а, кроме того, Ваше приложение может использовать специальный метод пополосной печати, который заметно усложняет внутреннюю логику печати.

Такой сложный механизм позволяет осуществить спулинг печати и более-менее эффективное разделение принтера между разными приложениями.

Общие правила, с точки зрения приложения, сводятся к следующему:

начиная работу с принтером, Вы должны зарегистрировать собственное задание на печать. Это исключает одновременное печатание данных разными приложениями.

Далее Вы должны заполнить страницу нужными данными и подать команду на печать этой страницы и на переход к следующей.

Когда все страницы напечатаны, Вы должны сказать Windows о завершении печати Вашего задания.

В этой схеме есть несколько недостатков:

Во-первых, процесс печати даже одной страницы может быть весьма продолжительным (до 30-60 минут) - а при такой схеме Вы не можете даже прервать процесс печати;

Во-вторых, графический образ одной страницы на принтере высокого разрешения (особенно цветном) может занимать десятки мегабайт, что достаточно жестко ограничивает возможности печати.

Для того, что бы решить эти проблемы применяют различные приемы. Для того, что бы можно было выполнять какую-либо иную работу одновременно с печатью, и что бы печать можно было прервать в любой момент, добавляют специальную функцию, называемую AbortProc(), которая вызывается автоматически во время печати для обработки сообщений и для того, что бы работу принтера можно было прервать.

В этом случае, помимо регистрации задания, Вы должны установить собственную AbortProc до начала самой печати.

Для решения второй задачи вводят специальный метод пополосной печати (banding). В этом случае Вы запрашиваете у принтера данные о полосе, которую надо отобразить и заполняете эту полосу (или всю страницу - контекст устройства реально соответствует только этой полосе). Тогда максимальный размер временного файла, содержащего образ страницы, ограничен размерами полосы.

В этом случае Вам надо иметь в виду несколько нюансов:

при пополосной печати функция AbortProc() автоматически вызывается слишком редко, поэтому Вы должны сами предусмотреть вызовы этой функции с достаточной частотой.

ориентация полос не обязательно горизонтальна, так как принтер может печатать как в режиме “портрет” (Portrait), так и в режиме “ландшафт” (Landscape).

Полосу для печати у Вас запрашивает Windows. Вы не знаете заранее ни ориентацию этой полосы, ни ее размеры. Windows формирует запрос для печатания полосы до тех пор, пока не будет напечатана вся страница.

Теперь нам надо рассмотреть непосредственно текст программы, осуществляющий печать.

 

Печатание на принтере

 

Вообще для управления печатью достаточно только одной процедуры - Escape(), которая использовалась в прежних версиях Windows.

int Escape( hDC, nEscape, cbInput, lpsInData, lpvOutput );

Параметр nEscape указывает код выполняемой операции, параметры cbInput и lpsInData указывают исходные данные для операции, а lpvOutput - результат операции. Применение и форма исходных данных и результатов зависит от операции.

В версии Windows 3.1 к этой процедуре добавлен ряд вспомогательных, которые просто выполняют отдельные операции.

Сейчас мы рассмотрим некоторые основные операции, необходимые для печати. Первая операция регистрирует наше задание на печать:

Escape( hDC, STARTDOC, cbName, lpsName, NULL );

или

StartDoc( hDC, lpDocInfo );

struct DOCINFO {

int cbSize;

LPSTR lpszDocName;

LPSTR lpszOutput;};

Параметры cbName и lpsName определяют название нашего задания. Возвращаемое значение, большее 0, указывает на успешную постановку задания в очередь, а значение 0 и меньше указывает на возникшую ошибку.

Далее, если мы хотим печатать страницу целиком, то после ее заполнения мы должны подать команду на печать страницы:

// заполнить страницу

Escape( hDC, NEWFRAME, 0, NULL, NULL );

или

StartPage( hDC );

// заполнить страницу

EndPage( hDC );

В случае успешного завершения задания мы должны сообщить об этом с помощью операции ENDDOC:

Escape( hDC, ENDDOC, 0, NULL, NULL );

или

EndDoc( hDC );

Однако нам может понадобится прервать печать досрочно. Для этого мы должны разработать и установить нашу собственную процедуру AbortProc. Предположим, что такая функция нами написана и ее имя AbortProc(). Тогда мы можем установить ее следующим способом:

FARPROC lpfnAbortProc= MakeProcInstance( (FARPROC)AbortProc, hInstance );

Escape( hDC, SETABORTPROC, 0, (LPSTR)lpfnAbortProc, NULL );

// печатаем...

FreeProcInstance( lpfnAbortProc );

или

FARPROC lpfnAbortProc= MakeProcInstance( (FARPROC)AbortProc, hInstance );

SetAbortProc( hDC, (ABORTPROC)lpfnAbortProc );

// печатаем...

FreeProcInstance( lpfnAbortProc );

При этом, если наше задание будет досрочно снято с печати, то мы должны сообщить об этом (что бы задание было корректно удалено из очереди):

Escape( hDC, ABORTDOC, 0, NULL, NULL );

или

AbortDoc( hDC );

Небольшой пример (поясняющий правила разработки процедуры AbortProc):

static BOOL bAbort;

BOOL PASCAL FAR _export AbortProc( HDC hdcPRN, short nCode ) {

MSG msg;

while ( !bAbort && PeekMessage( &msg,NULL,0,0,PM_REMOVE ) ) {

TranslateMessage( &msg );

DispatchMessage( &msg );}

return !bAbort;}

static BOOL DoPrint( void ) {

static char szName[]= "Test Printing";

FARPROC lpfnAbort;

BOOL answer= FALSE;

HDC hdcPRN;

hdcPRN= GetPrinterDC(); // получение hdcPRN см. выше

lpfnAbort= MakeProcInstance( (FARPROC)AbortProc, hInstance );

Escape( hdcPRN, SETABORTPROC, 0, (LPSTR)lpfnAbort, NULL );

if ( Escape( hdcPRN, STARTDOC, sizeof(szName)-1, szName, NULL ) > 0 ) {

while ( !bAbort && /* есть что печатать */ ) {

// рисуем на странице

if ( Escape( hdcPRN, NEWFRAME, 0,NULL,NULL ) <= 0 )bAbort= TRUE;}

if ( bAbort ) {

Escape( hdcPRN, ABORTDOC, 0,NULL,NULL );

} else {

Escape( hdcPRN, ENDDOC, 0,NULL,NULL );

answer= TRUE;}}

FreeProcInstance( (FARPROC)lpfnAbort );

DestroyDC( hdcPRN );

return answer;}

В этом примере надо обратить внимание на переменную bAbort. Она может быть установлена в значение TRUE для завершения печати. В данном примере досрочное завершение не предусмотрено, хотя его легко можно сделать, устанавливая эту переменную, например, в ответ на выбор пункта меню, на нажатие кнопки диалога или даже по таймеру.

Если мы будем использовать пополосную печать, то нам надо вместо операции NEWFRAME выполнять серию операций NEXTBAND до тех пор, пока полоса для печати не окажется пустой (что соответствует полностью напечатанной странице); при этом внутренний цикл для печати страниц придется несколько изменить:

...RECT rc;

...while ( !bAbort && /* есть что печатать */ ) {

while ( Escape(hdcPRN, NEXTBAND, 0, NULL, (LPSTR)&rc) > 0 ) {

if ( IsRectEmpty( &rc ) ) goto ok_page;

// заполняем полосу или всю страницу, иногда вызывая AbortProc:

lpfnAbortProc( hdcPRN, 0 );

// Внимание! используется указатель на функцию, а не она сама!}

bAbort= TRUE;

ok_page:;}...

Дата: 2019-07-25, просмотров: 176.