Untitled
6. Работа с принтером
6.1. Контекст отображения для принтера
6.2. Функция PrintDlg
6.3. Функции для печати
6.4. Приложение PRNFILE
Приложения Windows работают с принтером совсем не так, как программы
MS-DOS. Последние используют для печати BIOS (прерывание INT 17h
) или функцию 05h прерывания MS-DOS INT 21h . Некоторые программы
даже используют порты ввода/вывода параллельного или последовательного
интерфейса, к которому подключен принтер. Мы описали все эти средства
в первой части второго тома "Библиотеки системного программиста"
(стр. 156).
Заметим, что создание программы MS-DOS, способной работать с любым
принтером в символьном и графическом режимах, - далеко не простая
задача. В мире созданы десятки различных моделей принтеров, каждая
из которых имеет свою систему команд и другие особенности. Практически
приложение должно иметь собственный набор драйверов для обеспечения
возможности работы с любыми моделями принтеров. Текстовый процессор
Microsoft Word for DOS версий 4.0 - 6.0 может работать со многими
типами принтеров благодаря богатому набору принтерных драйверов,
поставляющихся вместе с ним. Тем не менее этого набора иногда
оказывается недостаточно.
Ситуация с принтерами в MS-DOS напоминает ситуацию с видеоконтроллерами
- так как MS-DOS не имеет в своем составе драйверы видеоконтроллеров,
каждая программа MS-DOS должна иметь собственные драйверы.
Разработчики приложений Windows находятся в лучшем положении,
так как в этой операционной системе есть сильная поддержка принтеров.
Набор драйверов, поставляемых вместе с Windows, обеспечивает возможность
работы практически с любой моделью принтера для всех приложений.
Разработчики приложений не должны учитывать аппаратные особенности
моделей принтеров, так как эта работа выполняется принтерными
драйверами.
Однако поддержка принтеров не ограничивается учетом аппаратных
особенностей и набора команд. Приложения могут использовать для
вывода на принтер почти все функции GDI, рассмотренные нами в
этом томе. Для этого им достаточно получить контекст отображения,
связанный с принтером. Передавая идентификатор контекста отображения
в качестве первого параметра функциям GDI, приложения могут рисовать
на бумаге текст, вызывая функцию TextOut, или любые геометрические
фигуры, вызывая такие функции, как Ellipse, Rectangle и т. п.
Специальное приложение Print Manager позволяет организовать очередь
печати. Разные приложения могут помещать в эту очередь свои данные
(задания на печать), которые будут выводиться на принтер в фоновом
режиме в порядке поступления в очередь (пользователь может изменять
расположение заданий на печать в очереди). Это приложение описано
в документации пользователя операционной системы Windows. Вы также
можете найти его описание во втором томе нашей серии книг "Персональный
компьютер. Шаг за шагом", посвященный Windows.
Вывод на принтер в операционной системе Windows всегда буферизован,
причем под буферизацией понимается не просто использование буфера
временного хранения данных, предназначенных для вывода на принтер.
Когда приложение вызывает функции рисования GDI, указывая идентификатор
контекста принтера, соответствующие команды GDI не выполняются
сразу, а накапливаются в специально созданном метафайле. После
того как приложение завершит рисование одной страницы документа,
созданный метафайл проигрывается в контексте принтера. Именно
в этот момент и происходит печать.
Далеко не все принтеры способны рисовать сразу на всем листе бумаги,
как на экране видеомонитора. На это способны только лазерные принтеры,
которые готовят в своей памяти образ целой страницы и затем печатают
эту страницу. Матричные и струйные принтеры могут печатать только
в построчном режиме, поэтому проигрывание метафайла на таких принтерах
выполняется несколько раз для каждой строки. Всякий раз при проигрывании
метафайла в контексте принтера задается область ограничения, соответствующая
одной строке.
Механизм построчной печати скрыт от приложений. Поэтому они могут
рисовать изображения и писать текст на листе бумаги аналогично
тому, как они делают это в окнах приложения. Единственное что
требуется, это сообщить GDI о начале печати на новом листе бумаги
и о завершении печати листа бумаги (или рисования листа бумаги,
если угодно), вызвав соответствующие функции GDI. В дальнейшем
мы рассмотрим этот процесс более подробно.
На первый взгляд, контекст отображения для принтера получить нетрудно
- достаточно вызвать функцию CreateDC , указав имя драйвера, имя
устройства и имя порта вывода, к которому подключен принтер:
HDC WINAPI CreateDC(
LPCSTR lpszDriver, // имя драйвера
LPCSTR lpszDevice, // имя устройства
LPCSTR lpszOutput, // имя файла или порта вывода
const void FAR* lpvInitData); // данные для инициализации
Созданный при помощи функции CreateDC контекст устройства следует
удалить после использования, вызвав функцию DeleteDC :
BOOL WINAPI DeleteDC(HDC hdc);
Как мы уже говорили, параметр lpszDriver является указателем на
строку символов, содержащую имя драйвера, обслуживающего физическое
устройство. Имя драйвера совпадает с именем файла *.drv, содержащего
драйвер. Этот драйвер находится в системном каталоге Windows.
Имя устройства lpszDevice - это название устройства.
Параметр lpszOutput указывает на структуру данных типа DEVMODE,
используемую при инициализации устройства вывода. Если при работе
с устройством нужно использовать параметры, установленные при
помощи приложения Control Panel, параметр lpszOutput следует указать
как NULL.
Однако как определить эти параметры?
Текущий принтер описан в файле win.ini в разделе [windows]:
[windows]
...
...
device=HP LaserJet III,hppcl5a,LPT1:
...
Вы можете получить контекст отображения для текущего принтера,
указав эти параметры функции CreateDC:
hdc = CreateDC("hppcl5a", "HP LaserJet III", "LPT1:", NULL);
Однако к компьютеру может быть подключено несколько принтеров.
Например, к порту LPT1: может быть подключен лазерный принтер,
а к порту LPT2: - матричный или струйный принтер.
Список подключенных принтеров, имена драйверов и портов ввода/вывода
можно найти в разделе [devices] файла win.ini:
[devices]
Epson FX-850=EPSON9,LPT1:
HP LaserJet III=hppcl5a,LPT1:
Ваше приложение может получить параметры принтера, используемого
по умолчанию, а также параметры всех установленных принтеров непосредственно
из файла win.ini. Это можно легко сделать при помощи функции GetProfileString:
int GetProfileString(
LPCSTR lpszSection; // адрес раздела
LPCSTR lpszEntry; // адрес элемента раздела
LPCSTR lpszDefault; // адрес строки по умолчанию
LPSTR lpszReturnBuffer; // адрес буфера для записи
int cbReturnBuffer; // размер буфера
Параметр lpszSection должен указывать на имя раздела, в нашем
случае на строку "windows". Через параметр lpszEntry
передается адрес текстовой строки, содержащий имя элемента раздела,
в нашем случае это адрес строки "device".
Если указанного элемента или раздела нет в файле win.ini, используется
строка по умолчанию, адрес которой передается через параметр lpszDefault.
Найденная строка (или строка по умолчанию) будет записана в буфер,
адрес которого передается через параметр lpszReturnBuffer. Размер
буфера должен быть указан в параметре cbReturnBuffer.
Для получения контекста отображения текущего принтера обычно используется
такая функция:
HDC PASCAL GetPrinterDC()
{
char msgbuf[128];
LPSTR pch;
LPSTR pchFile;
LPSTR pchPort;
// Определяем текущий принтер из файла win.ini
if(!GetProfileString("windows", "device", "",
msgbuf, sizeof(msgbuf)))
return NULL;
// Выполняем разбор строки для выделения имени драйвера,
// имени устройства и имени порта ввода/вывода
for(pch=msgbuf; *pch && *pch != ','; pch=AnsiNext(pch));
if(*pch)
*pch++ = 0;
// Пропускаем управляющие символы и символ табуляции
while(*pch && *pch <= ' ')
pch=AnsiNext(pch);
pchFile = pch;
while(*pch && *pch != ',' && *pch > ' ')
pch = AnsiNext(pch);
if(*pch)
*pch++ = 0;
while(*pch && (*pch <= ' ' || *pch == ','))
pch = AnsiNext(pch);
pchPort = pch;
while(*pch && *pch > ' ')
pch = AnsiNext(pch);
*pch = 0;
// Возвращаем контекст отображения для принтера
return CreateDC(pchFile, msgbuf, pchPort, NULL);
}
Приведенная выше функция способна работать с двухбайтовыми кодами
символов, так как для сканирования строки используется функция
AnsiNext.
Ситуация, однако, усложняется при необходимости сделать выбор
между одним из установленных принтеров. Хорошо спроектированное
приложение должно позволять пользователю выбирать любой из установленных
принтеров, а не только тот, который используется по умолчанию.
В этом случае вам надо создать диалоговую панель (или меню), с
помощью которой пользователь мог бы выбрать нужный принтер.
Для получения списка установленных принтеров вы можете воспользоваться
все той же функцией GetProfileString , указав в качестве первого
параметра адрес строки "devices", а в качестве второго
- значение NULL:
GetProfileString("devices", NULL, "",
msgbuf, sizeof(msgbuf));
В этом случае в буфер будут переписаны все строки из раздела [devices],
каждая строка будет закрыта двоичным нулем, последняя строка будет
закрыта двумя двоичными нулями.
Однако есть еще одна задача, которую должно уметь решать ваше
приложение.
Как правило, для каждого принтера возможна настройка таких параметров,
как размер и расположение бумаги, выбор устройства подачи бумаги,
интенсивность печати и разрешающая способность и так далее. Для
выполнения такой настройки ваше приложение должно вызвать функцию
DeviceMode или более новую ExtDeviceMode , расположенную в драйвере
нужного принтера.
Заметим, что эти функции экспортируются драйвером как обычной
DLL-библиотекой (драйвер принтера и есть DLL-библиотека). Для
вызова одной из этих функций вы должны загрузить драйвер явным
образом, вызвав функцию LoadLibrary , а затем получить адрес точки
входа при помощи функции GetProcAddress . DLL-библиотеки и перечисленные
выше функции мы описали в 13 томе "Библиотеки системного
программиста".
Все это выглядит достаточно сложно и громоздко, однако стандартное
приложение Windows должно обеспечивать возможность выбора принтера
для печати и возможность установки параметров для выбранного принтера.
К счастью, DLL-библиотека commdlg.dll содержит функцию PrintDlg,
способную решить все перечисленные выше задачи и вдобавок получить
для выбранного принтера контекст отображения, который можно использовать
для рисования на принтере (или печати, если вам так больше нравится).
С помощью функции PrintDlg приложение может вывести на экран одну
из двух диалоговых панелей, представленных на рис. 6.1 и 6.2,
с помощью которых пользователь может напечатать документ, выбрать
нужный принтер или изменить его параметры.
Рис. 6.1. Диалоговая панель "Print"
В верхней части диалоговой панели "Print" в поле "Printer"
указано название принтера, который будет использован для печати.
Вы можете выбрать другой принтер, если нажмете кнопку "Setup...".
С помощью группы органов управления "Print Range" вы
можете выбрать диапазон страниц, которые должны быть распечатаны.
Можно распечатать все или отдельные страницы, выделенный фрагмент
текста или страницы в указанном диапазоне номеров страниц (поля
"From" и "To").
Можно задать качество печати (поле "Print Quality"),
указать количество копий (поле "Copies"), выполнить
печать в файл (поле "Print to File").
С помощью переключателя "Collate Copies" можно выбрать
порядок печати отдельных листов многостраничного документа, который
должен быть напечатан в нескольких копиях. Если этот переключатель
находится во включенном состоянии, вначале следует напечатать
все страницы первой копии, затем - второй, и так далее. Если же
этот переключатель выключен, вначале печатается несколько копий
первой страницы, затем несколько копий второй страницы и так до
конца документа. Последний режим печати удобен для лазерных принтеров,
где время подготовки одной страницы для печати больше времени
печати готовой страницы.
Если нажать на кнопку "Setup...", на экране появится
диалоговая панель "Print Setup" (рис. 6.2).
Рис. 6.2. Диалоговая панель "Print Setup"
В группе органов управления "Printer" вы можете выбрать
для печати либо принтер, принятый по умолчанию ("Default
Printer"), либо выбрать другой принтер из списка "Specific
Printer".
С помощью переключателей группы "Orientation" вы можете
выбрать вертикальное ("Portrait") либо горизонтальное
("Landscape") расположение текста на листе бумаги.
Группа "Paper" содержит два списка, с помощью которых
вы можете выбрать размер бумаги (список "Size") или
устройство подачи бумаги ("Source").
Нажав в этой диалоговой панели кнопку "Options...",
вы сможете выполнить настройку параметров принтера при помощи
диалоговой панели "Options" (рис. 6.3).
Рис. 6.3. Диалоговая панель "Options" для лазерного
принтера HP LaserJet III
Внешний вид диалоговой панели "Options" зависит от драйвера
принтера, так как эта панель формируется функцией DeviceMode или
ExtDeviceMode, расположенными в соответствующем драйвере принтера.
Для сравнения на рис. 6.4 представлен внешний вид диалоговой панели
"Options" для матричного принтера Epson FX-850.
Рис. 6.4. Диалоговая панель "Options" для матричного
принтера Epson FX-850
Если вас не устраивает внешний вид диалоговых панелей "Print"
и "Print Options", вы можете использовать вместе с функцией
PrintDlg свои собственные шаблоны диалоговых панелей. Можно также
подключить функцию фильтра для обеспечения дополнительной обработки
сообщений.
Приведем прототип функции PrintDlg, описанный в файле commdlg.h:
BOOL PrintDlg(PRINTDLG FAR* lppd);
При успешном завершении функция возвращает значение TRUE. В случае
ошибки, отмены печати или отмены выбора принтера (если функция
PrintDlg используется только для выбора принтера) функция возвращает
значение FALSE.
Структура PRINTDLG
В качестве параметра функции PrintDlg необходимо передать адрес
предварительно подготовленной структуры типа PRINTDLG , описанной
в файле commdlg.h:
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;
typedef PRINTDLG FAR* LPPRINTDLG;
Рассмотрим назначение отдельных полей этой структуры.
lStructSize
Размер структуры PRINTDLG в байтах. Это поле следует заполнить
перед вызовом функции PrintDlg.
hwndOwner
Идентификатор окна, создавшего диалоговую панель. Если в поле
Flags не указано значение PD_SHOWHELP, в поле hwndOwner можно
указать NULL.
hDevMode
Идентификатор глобального блока памяти, содержащего структуру
типа DEVMODE, которая используется для инициализации параметров
принтера.
Если содержимое этого поля указать как NULL, после возвращения
из функции PrintDlg поле будет содержать идентификатор глобального
блока памяти, заказанного функцией. В этом блоке памяти будет
расположена структура DEVMODE, заполненная выбранными параметрами
принтера. Структура DEVMODE будет описана позже.
hDevNames
Идентификатор глобального блока памяти, содержащего структуру
типа DEVNAMES, содержащей три текстовые строки. Первая строка
определяет имя драйвера принтера, вторая - имя принтера, и третья
- имя порта вывода, к которому подключен принтер.
Если содержимое этого поля указать как NULL, после возвращения
из функции PrintDlg поле будет содержать идентификатор глобального
блока памяти, заказанного функцией для структуры DEVNAMES. В структуре
будут находиться строки, соответствующие выбранному принтеру.
hDC
Контекст устройства или информационный контекст.
Это поле заполняется после возвращения из функции PrintDlg, если
в поле Flags указано одно из значений: PD_RETURNDC или PD_RETURNIC.
В первом случае возвращается контекст принтера, который можно
использовать для печати, во втором - информационный контекст,
который можно использовать для получения разнообразной информации
о принтере.
Flags
Это поле должно содержать флаги инициализации:
PD_ALLPAGES
Переключатель "All" в диалоговой панели "Print"
должен находиться во включенном состоянии, при этом предполагается
что необходимо распечатать весь текст, а не отдельные страницы
или выделенный фрагмент текста.
PD_SELECTION
Переключатель "Selection" в диалоговой панели "Print"
должен находиться во включенном состоянии, при этом предполагается
что необходимо распечатать выделенный фрагмент текста, но не весь
текст или отдельные страницы.
PD_PAGENUMS
Переключатель "Page" в диалоговой панели "Print"
должен находиться во включенном состоянии, при этом предполагается
что необходимо распечатать отдельные страницы текста, но не выделенный
фрагмент текста или весь текст.
PD_NOSELECTION
Переключатель "Selection" должен находиться в заблокированном
состоянии.
PD_NOPAGENUMS
Переключатель "Pages" и связанные с ним органы управления
должны находиться в заблокированном состоянии.
PD_COLLATE
Переключатель "Collate" должен находиться во включенном
состоянии.
PD_PRINTTOFILE
Переключатель "Print to File" должен находиться во включенном
состоянии.
PD_PRINTSETUP
При вызове функции PrintDlg вместо диалоговой панели "Print"
отображается диалоговая панель "Print Setup".
PD_NOWARNING
Отмена вывода сообщения о том, что в системе не установлен принтер
по умолчанию.
PD_RETURNDC
Функция PrintDlg должна вернуть в поле hDC идентификатор контекста
устройства, который можно использовать для печати.
PD_RETURNIC
Функция PrintDlg должна вернуть в поле hDC идентификатор информационного
контекста, который можно использовать для получения информации
о принтере.
PD_RETURNDEFAULT
После возвращения из функции PrintDlg поля hDevMode и hDevNames
будут содержать идентификаторы блоков памяти структур DEVMODE
и DEVNAMES, заполненных параметрами принтера, выбранного по умолчанию.
Если указан флаг PD_RETURNDEFAULT, перед вызовом функции PrintDlg
поля hDevMode и hDevNames должны содержать значения NULL, в противном
случае функция вернет признак ошибки.
PD_SHOWHELP
В диалоговой панели необходимо отобразить кнопку "Help".
PD_ENABLEPRINTHOOK
Разрешается использовать функцию фильтра для диалоговой панели
"Print".
PD_ENABLESETUPHOOK
Разрешается использовать функцию фильтра для диалоговой панели
"Print Setup".
PD_ENABLEPRINTTEMPLATE
Разрешается использовать шаблон диалоговой панели "Print",
определяемой полями hInstance и lpPrintTemplateName.
PD_ENABLESETUPTEMPLATE
Разрешается использовать шаблон диалоговой панели "Print
Setup", определяемой полями hInstance и lpSetupTemplateName.
PD_ENABLEPRINTTEMPLATEHANDLE
Поле hPrintTemplate содержит идентификатор блока памяти с загруженным
шаблоном диалоговой панели "Print". Содержимое поля
hInstance игнорируется.
PD_ENABLESETUPTEMPLATEHANDLE
Поле hSetupTemplate содержит идентификатор блока памяти с загруженным
шаблоном диалоговой панели "Print Setup". Содержимое
поля hInstance игнорируется.
PD_USEDEVMODECOPIES
Орган управления "Copies" блокируется, если принтерный
драйвер не способен печатать несколько копий.
PD_DISABLEPRINTTOFILE
Блокируется переключатель "Print to File".
PD_HIDEPRINTTOFILE
Переключатель "Print to File" блокируется и удаляется
из диалоговой панели.
nFromPage
Начальное значение для инициализации органа управления "From"
диалоговой панели "Print". Используется только в том
случае, если в поле Flags указан флаг PD_PAGENUMS. Максимальное
значение для поля nFromPage составляет 0xfffe.
После возвращения из функции PrintDlg это поле содержит номер
страницы документа, с которой должна начинаться печать.
nToPage
Начальное значение для инициализации органа управления "To"
диалоговой панели "Print". Используется только в том
случае, если в поле Flags указан флаг PD_PAGENUMS. Максимальное
значение для поля nFromPage составляет 0xfffe.
После возвращения из функции PrintDlg это поле содержит номер
страницы документа, до которой должна выполняться печать.
nMinPage
Минимальное количество страниц, которое можно задать при помощи
органов управления "From" и "To".
nMaxPage
Максимальное количество страниц, которое можно задать при помощи
органов управления "From" и "To".
nCopies
Значение для инициализации органа управления "Copies",
если поле hDevMode содержит значение NULL.
hInstance
Идентификатор модуля, который содержит шаблоны диалоговых панелей.
Используется только в том случае, если указаны флаги PD_ENABLEPRINTTEMPLATE
или PD_ENABLESETUPTEMPLATE.
lCustData
Произвольные данные, которые приложение может передать функции
фильтра.
lpfnPrintHook
Адрес функции фильтра для диалоговой панели "Print".
lpfnSetupHook
Адрес функции фильтра для диалоговой панели "Print Setup".
lpPrintTemplateName
Адрес текстовой строки, закрытой нулем, содержащей имя ресурса
для шаблона диалоговой панели "Print". Для использования
этого поля необходимо указать флаг PD_ENABLEPRINTTEMPLATE.
lpSetupTemplateName
Адрес текстовой строки, закрытой нулем, содержащей имя ресурса
для шаблона диалоговой панели "Print Setup". Для использования
этого поля необходимо указать флаг PD_ENABLESETUPTEMPLATE.
hPrintTemplate
Идентификатор блока памяти, содержащего предварительно загруженный
шаблон диалоговой панели "Print". Для использования
этого поля необходимо указать флаг PD_ENABLEPRINTTEMPLATEHANDLE.
hSetupTemplate
Идентификатор блока памяти, содержащего предварительно загруженный
шаблон диалоговой панели "Print Setup". Для использования
этого поля необходимо указать флаг PD_ENABLESETUPTEMPLATEHANDLE.
Работа с функцией PrintDlg
Перед вызовом функции PrintDlg следует проинициализировать нужные
поля, записав в остальные поля нулевые значения:
memset(&pd, 0, sizeof(PRINTDLG));
pd.lStructSize = sizeof(PRINTDLG);
pd.hwndOwner = hwnd;
pd.Flags = PD_RETURNDC;
fResult = PrintDlg(&pd);
Если перед вызовом функции PrintDlg в полях hDevMode и hDevNames
было значение NULL, функция заказывает глобальные блоки памяти
для структур DEVMODE и DEVNAMES, заполняя их перед возвратом управления.
Структура DEVMODE определена в файле print.h, который находится
в каталоге include системы разработки Borland Turbo C++ for Windows:
typedef struct tagDEVMODE
{
char dmDeviceName[CCHDEVICENAME];
UINT dmSpecVersion;
UINT dmDriverVersion;
UINT dmSize;
UINT dmDriverExtra;
DWORD dmFields;
int dmOrientation;
int dmPaperSize;
int dmPaperLength;
int dmPaperWidth;
int dmScale;
int dmCopies;
int dmDefaultSource;
int dmPrintQuality;
int dmColor;
int dmDuplex;
int dmYResolution;
int dmTTOption;
} DEVMODE;
typedef DEVMODE* PDEVMODE, NEAR* NPDEVMODE, FAR* LPDEVMODE;
Эта структура содержит разнообразную информацию о конфигурации
и параметрах принтера. Она подробно описана в документации, которая
поставляется вместе с SDK. Из за ограниченного объема книги мы
приведем краткое описание полей этой структуры.
Поле | Описание |
dmDeviceName | Имя драйвера принтера
|
dmSpecVersion | Номер версии структуры DEVMODE. Для Windows версии 3.1 это поле содержит значение 0x30a
|
dmDriverVersion | Версия драйвера
|
dmSize | Размер структуры DEVMODE в байтах
|
dmDriverExtra | Размер в байтах дополнительной структуры данных, которая может находиться в памяти сразу за структурой DEVMODE
|
dmFields | Набор флагов, каждый из которых отвечает за свое поле структуры DEVMODE. Если флаг установлен, соответствующее поле инициализируется. возможны следующие значения: DM_ORIENTATION , DM_PAPERSIZE , DM_PAPERLENGTH , DM_PAPERWIDTH , DM_SCALE , DM_COPIES , DM_DEFAULTSOURCE , DM_PRINTQUALITY , DM_COLOR , DM_DUPLEX , DM_YRESOLUTION , DM_TTOPTION
|
dmOrientation | Ориентация бумаги. Возможные значения:DMORIENT_PORTRAIT , DMORIENT_LANDSCAPE
|
dmPaperSize | Код размера бумаги. Например, для бумаги формата A4 используется константа DMPAPIER_A4
|
dmPaperLength | Длина листа бумаги в десятых долях миллиметра
|
dmPaperWidth | Ширина листа бумаги в десятых долях миллиметра
|
dmScale | Коэффициент масштабирования для печати
|
dmCopies | Количество печатаемых копий
|
dmDefaultSource | Код устройства подачи бумаги, используемого по умолчанию.
|
dmPrintQuality | Код разрешения принтера: DMRES_HIGH , DMRES_LOW , DMRES_MEDIUM , DMRES_DRAFT или положительное число, равное количеству точек на дюйм
|
dmColor | Режим печати для цветного принтера: DMCOLOR_COLOR - цветная печать, DMCOLOR_MONOCHROME - монохромная печать
|
dmDuplex | Возможность печати с двух сторон бумажного листа
|
dmYResolution | Разрешение принтера по вертикали в точках на дюйм
|
dmTTOption | Способ печати шрифтов True Type:DMTT_BITMAP - печать в графическом режиме, обычно используется для матричных принтеров;DMTT_DOWNLOAD - загрузка шрифтов True Type в память принтера, используется для лазерных принтеров, совместимых с принтерами HP LaserJet;DMTT_SUBDEV - замена шрифтов на принтерные шрифты, используется для PostScript-принтеров
|
Структура DEVNAMES , как мы уже говорили, содержит имя драйвера,
имя принтера и имя порта вывода, к которому подключен принтер:
typedef struct tagDEVNAMES
{
UINT wDriverOffset;
UINT wDeviceOffset;
UINT wOutputOffset;
UINT wDefault;
} DEVNAMES;
typedef DEVNAMES FAR* LPDEVNAMES;
Первые три слова структуры содержат смещения текстовых строк с
именами, соответственно, драйвера, принтера и порта вывода. Строки
расположены в памяти непосредственно за структурой DEVNAMES. Поле
wDefault может содержать флаг DN_DEFAULTPRN , в этом случае все
три строки описывают принтер, выбранный по умолчанию.
Вы можете подготовить свои значения для двух описанных выше структур,
заказать глобальные блоки памяти и передать их идентификаторы
функции PrintDlg, записав в соответствующие поля структуры PRINTDLG.
После возврата из функции PrintDlg необходимо освободить эти блоки
памяти, взяв их идентификаторы из полей hDevMode и hDevNames структуры
PRINTDLG. Учтите, что функция PrintDlg может изменить значения
последних двух полей, поэтому надо освобождать блоки памяти с
идентификаторами, взятыми из структуры PRINTDLG после возврата
из функции PrintDlg:
if(pd.hDevMode != 0)
GlobalFree (pd.hDevMode);
if(pd.hDevNames != 0)
GlobalFree (pd.hDevNames);
Если перед вызовом функции PrintDlg вы указали флаги PD_RETURNDC
или PD_RETURNIC, после возврата поле hDC будет содержать, соответственно,
идентификатор контекста устройства или идентификатор информационного
контекста:
if(fResult)
return pd.hDC;
else
return NULL;
В операционной системе Windows версии 3.0 и более ранних версий
для печати использовалась одна функция Escape, которая имела множество
подфункций (63 подфункции). Начиная с версии 3.1 вместо этой функции
рекомендуется использовать несколько других.
Чаще всего используются семь функций.
StartDoc
Эта функция начинает печать нового документа (формирует задание
на печать). Она должна вызываться один раз перед началом печати
нового документа
StartPage
Эта функция подготавливает устройство вывода к печати новой страницы
документа. После вызова этой функции приложение может начинать
печать, используя контекст принтера.
EndPage
Функция EndPage завершает процесс печати страницы. Метафайл, созданный
в процессе печати одной страницы, проигрывается на принтере.
EndDoc
Функция завершает процесс печати документа. Она вызывается один
раз после завершения печати документа.
AbortDoc
Эта функция предназначена для аварийного завершения процесса печати
документа.
SetAbortProc
Функция SetAbortProc используется для обеспечения возможности
аварийного завершения процесса печати, а также для того чтобы
другие приложения могли работать во время печати.
ResetDC
С помощью этой функции можно изменить параметры печати для отдельных
листов документа.
Как пользоваться этими функциями?
После получения контекста устройства для принтера ваше приложения
должно установить адрес функции отмены печати, вызвав функцию
SetAbortProc, и вывести на экран диалоговую панель, позволяющую
отменить печать. Диалоговая панель отмены печати должна быть немодальной.
Функция диалоговой панели обычно используется для установки глобального
флага отмены печати, который периодически проверяется функцией,
выполняющей печать.
Приведем прототип функции SetAbortProc :
int SetAbortProc(HDC hdc, ABORTPROC abrtprc);
Первый параметр определяет контекст устройства, для которого устанавливается
функция отмены печати, второй - адрес функции отмены печати, полученный
при помощи функции MakeProcInstance .
Функция отмены печати может выглядеть, например, следующим образом:
BOOL CALLBACK _export
AbortFunc(HDC hdc, int nCode)
{
MSG msg;
while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
if(!hdlgAbort || !IsDialogMessage(hdlgAbort, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return(!fAbort);
}
В качестве первого параметра функции передается контекст устройства,
в качестве второго - код ошибки. Однако обычно оба эти параметра
игнорируются.
GDI в процессе печати периодически вызывает функцию отмены печати,
которая действует аналогично обычному циклу обработки сообщений,
выполняя выборку сообщений из очереди и их диспетчеризацию. Если
в очереди больше нет сообщений, функция PeekMessage возвращает
FALSE, при этом цикл обработки сообщений завершается.
После этого функция возвращает инвертированное значение флага
отмены печати. Если печать должна быть продолжена, функция отмены
должна вернуть значение FALSE, если отменена - TRUE.
В качестве функции диалога, предназначенного для отмены печати,
можно использовать, например, такую функцию:
BOOL CALLBACK _export
AbortDlgFunc(HWND hdlg, UINT msg, WPARAM wParam,
LPARAM lParam)
{
switch(msg)
{
case WM_INITDIALOG:
{
fAbort = FALSE;
hdlgAbort = hdlg;
return TRUE;
}
case WM_COMMAND:
{
if (wParam == IDOK || wParam == IDCANCEL)
{
fAbort = TRUE;
return TRUE;
}
return FALSE;
}
case WM_DESTROY:
{
hdlgAbort = 0;
return FALSE;
}
}
return FALSE;
}
Основной задачей данной функции является установка глобального
флага отмены печати fAbort:
fAbort = TRUE;
Итак, мы получили контекст принтера, установили для этого контекста
процедуру отмены печати, вывели на экран диалоговую панель с кнопкой
"Cancel", позволяющую отменить печать. Теперь можно
приступать к печати документа.
Прежде всего необходимо вызвать функцию StartDoc :
int StartDoc(
HDC hdc, // контекст принтера
DOCINFO FAR* lpdi); // адрес структуры DOCINFO
Перед вызовом этой функции нужно подготовить структуру DOCINFO
, адрес которой передается через параметр lpdi:
typedef struct {
int cbSize;
LPCSTR lpszDocName;
LPCSTR lpszOutput;
} DOCINFO;
Поле cbSize должно содержать размер структуры в байтах.
Поле lpszDocName должно содержать указатель на текстовую строку,
закрытую двоичным нулем, в которой записано название документа.
Через поле lpszOutput можно передать адрес строки, содержащей
имя файла (если печать должна быть переназначена в файл). Поле
может содержать NULL, в этом случае выполняется печать на принтере.
Далее можно начинать цикл печати страниц документа.
Печать страницы документа начинается с вызова функции StartPage
:
int StartPage(HDC hdc);
Эта функция подготавливает принтер для приема данных. При успешном
завершении функция возвращает положительное значение, отличное
от нуля, при ошибке - нуль или значение, меньшее нуля.
После вызова функции StartPage приложение может начинать печать
страницы, вызывая обычные функции GDI, такие как TextOut, BitBlt,
StretchBlt, Rectangle и т. п.
Завершив печать одной страницы, приложение должно вызвать функцию
EndPage :
int EndPage(HDC hdc);
Эта функция не только загружает в принтер новую страницу, и даже
не столько загружает новую страницу, сколько проигрывает созданный
на этапе рисования метафайл, т. е. выполняет процесс печати страницы.
При успешном завершении функция возвращает положительное значение,
отличное от нуля, при ошибке - нуль или значение, меньшее нуля
В цикле печати необходимо проверять значение, возвращаемое функцией
EndPage, а также глобальный флаг отмены печати, который устанавливается
функцией диалоговой панели отмены печати.
После успешной печати всех страниц документа нужно вызвать функцию
EndDoc :
int EndDoc(HDC hdc);
Если печать была отменена, или произошла ошибка, вместо этой функции
следует вызвать функцию AbortDoc :
int AbortDoc(HDC hdc);
Если в процессе печати нужно изменить параметры принтера для отдельных
страниц документа, вы можете вызвать до функции StartPage функцию
ResetDC :
HDC ResetDC(
HDC hdc, // контекст печати
const DEVMODE FAR* lpdm); // адрес структуры DEVMODE
Эта функция изменяет контекст печати на основе информации, представленной
в структуре DEVMODE.
Приложение обычно вызывает эту функцию также в ответ на сообщение
WM_DEVMODECHANGE, которое посылается приложению при изменении
параметров принтера.
Учтите, что функция StartPage запрещает изменение контекста печати,
поэтому вы не сможете изменить параметры печати в процессе печати
страницы.
Для иллюстрации всего сказанного выше мы немного изменили приложение
TEDIT, описанное в 12 томе "Библиотеки системного программиста",
добавив в него возможность печати. В главном окне этого простейшего
редактора текста появилась кнопка "Print", с помощью
которой вы можете распечатать текст на любом установленном в системе
принтере (рис. 6.5).
Рис. 6.5. Главное окно приложения PRNFILE
Исходный основного файла приложения приведен в листинге 6.1.
Листинг 6.1. Файл prnfile/prnfile.cpp
// ----------------------------------------
// Редактор текстовых файлов с возможностью печати
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <commdlg.h>
#include <mem.h>
#include <string.h>
#include <stdlib.h>
// Идентификатор редактора текста
#define ID_EDIT 1
// Идентификаторы кнопок
#define ID_NEW 2
#define ID_OPEN 3
#define ID_SAVE 4
#define ID_PRINT 5
#define ID_EXIT 6
// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
HFILE OpenFile(void);
HFILE OpenSaveFile(void);
int PrintFile(HWND, NPSTR, WORD);
// Имя класса окна
char const szClassName[] = "TEditAppClass";
// Заголовок окна
char const szWindowTitle[] = "Text Editor";
// Идентификатор копии приложения
HINSTANCE hInst;
// Флаг изменений в тексте
BOOL bUpdate;
// =====================================
// Функция WinMain
// =====================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow)
{
MSG msg; // структура для работы с сообщениями
HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение
if(!InitApp(hInstance))
return FALSE;
// Сохраняем идентификатор копии приложения
// в глобальной переменной
hInst = hInstance;
// После успешной инициализации приложения создаем
// главное окно приложения
hwnd = CreateWindow(
szClassName, // имя класса окна
szWindowTitle, // заголовок окна
WS_OVERLAPPEDWINDOW, // стиль окна
CW_USEDEFAULT, // задаем расположение и размеры
CW_USEDEFAULT, // окна, принятые по умолчанию
CW_USEDEFAULT, //
CW_USEDEFAULT, //
0, // идентификатор родительского окна
0, // идентификатор меню
hInstance, // идентификатор приложения
NULL); // указатель на дополнительные
// параметры
// Если создать окно не удалось, завершаем приложение
if(!hwnd)
return FALSE;
// Рисуем главное окно
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений
while(GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================
BOOL
InitApp(HINSTANCE hInstance)
{
ATOM aWndClass; // атом для кода возврата
WNDCLASS wc; // структура для регистрации
// класса окна
memset(&wc, 0, sizeof(wc));
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = (LPSTR)NULL;
wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса
aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// =====================================
// Функция WndProc
// =====================================
LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Идентификатор редактора текста
static HWND hEdit;
// Идентификаторы кнопок
static HWND hButtNew;
static HWND hButtOpen;
static HWND hButtSave;
static HWND hButtPrint;
static HWND hButtExit;
// Идентификаторы файлов
static HFILE hfSrcFile, hfDstFile;
switch (msg)
{
case WM_CREATE:
{
// Создаем редактор текста
hEdit = CreateWindow("edit", NULL,
WS_CHILD | WS_VISIBLE | WS_BORDER |
WS_HSCROLL | WS_VSCROLL |
ES_LEFT | ES_AUTOHSCROLL | ES_AUTOVSCROLL |
ES_MULTILINE,
0, 0, 0, 0,
hwnd, (HMENU) ID_EDIT, hInst, NULL);
// Устанавливаем максимальную длину
// редактируемого текста, равную 32000 байт
SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L);
// Сбрасываем флаг обновления текста
bUpdate = FALSE;
// Создаем кнопки
hButtNew = CreateWindow("button", "New",
WS_CHILD | WS_VISIBLE |
BS_PUSHBUTTON,
0, 0, 80, 20,
hwnd, (HMENU) ID_NEW, hInst, NULL);
hButtOpen = CreateWindow("button", "Open",
WS_CHILD | WS_VISIBLE |
BS_PUSHBUTTON,
80, 0, 80, 20,
hwnd, (HMENU) ID_OPEN, hInst, NULL);
hButtSave = CreateWindow("button", "Save",
WS_CHILD | WS_VISIBLE |
BS_PUSHBUTTON,
160, 0, 80, 20,
hwnd, (HMENU) ID_SAVE, hInst, NULL);
hButtPrint = CreateWindow("button", "Print",
WS_CHILD | WS_VISIBLE |
BS_PUSHBUTTON,
240, 0, 80, 20,
hwnd, (HMENU) ID_PRINT, hInst, NULL);
hButtExit = CreateWindow("button", "Exit",
WS_CHILD | WS_VISIBLE |
BS_PUSHBUTTON,
320, 0, 80, 20,
hwnd, (HMENU) ID_EXIT, hInst, NULL);
return 0;
}
case WM_SIZE:
{
// Устанавливаем размер органа управления
// (текстового редактора) в соответствии
// с размерами главного окна приложения
MoveWindow(hEdit, 0, 20, LOWORD(lParam),
HIWORD(lParam) - 20, TRUE);
return 0;
}
// Когда главное окно приложения получает
// фокус ввода, отдаем фокус редактору текста
case WM_SETFOCUS:
{
SetFocus(hEdit);
return 0;
}
case WM_COMMAND:
{
// Обработка извещений текстового редактора
if(wParam == ID_EDIT)
{
// Ошибка
if(HIWORD(lParam) == EN_ERRSPACE)
{
MessageBox(hwnd, "Мало памяти",
szWindowTitle, MB_OK);
}
// Произошло изменение в редактируемом
// тексте
else if(HIWORD(lParam) == EN_UPDATE)
{
// Устанавливаем флаг обновления текста
bUpdate = TRUE;
}
return 0;
}
// Нажата кнопка сохранения текста
else if(wParam == ID_SAVE)
{
WORD wSize;
HANDLE hTxtBuf;
NPSTR npTextBuffer;
// Открываем выходной файл
hfDstFile = OpenSaveFile();
if(!hfDstFile) return 0;
// Определяем размер текста
wSize = GetWindowTextLength(hEdit);
// Получаем идентификатор блока памяти,
// в котором находится редактируемый текст
hTxtBuf =
(HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L);
// Фиксируем блок памяти и получаем указатель
// на него
npTextBuffer = (NPSTR)LocalLock(hTxtBuf);
// Записываем содержимое блока памяти в файл
if(wSize !=
_lwrite(hfDstFile, npTextBuffer, wSize))
{
// При ошибке закрываем файл и выдаем сообщение
_lclose(hfDstFile);
MessageBox(hwnd, "Ошибка при записи файла",
szWindowTitle, MB_OK);
return 0;
}
// Закрываем файл
_lclose(hfDstFile);
// Расфиксируем блок памяти
LocalUnlock(hTxtBuf);
// Так как файл был только что сохранен,
// сбрасываем флаг обновления
bUpdate = FALSE;
SetFocus(hEdit);
return 0;
}
// Создание нового файла
else if(wParam == ID_NEW)
{
// Проверяем флаг обновления
if(bUpdate)
{
if(IDYES == MessageBox(hwnd,
"Файл был изменен. Желаете сохранить?",
szWindowTitle, MB_YESNO | MB_ICONQUESTION))
return 0;
}
// Сбрасываем содержимое текстового редактора
SetWindowText(hEdit, " |