Приложения 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, описанной в файле 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;
Рассмотрим назначение отдельных полей этой структуры.
Размер структуры PRINTDLG в байтах. Это поле следует заполнить перед вызовом функции PrintDlg.
Идентификатор окна, создавшего диалоговую панель. Если в поле Flags не указано значение PD_SHOWHELP, в поле hwndOwner можно указать NULL.
Идентификатор глобального блока памяти, содержащего структуру типа DEVMODE, которая используется для инициализации параметров принтера.
Если содержимое этого поля указать как NULL, после возвращения из функции PrintDlg поле будет содержать идентификатор глобального блока памяти, заказанного функцией. В этом блоке памяти будет расположена структура DEVMODE, заполненная выбранными параметрами принтера. Структура DEVMODE будет описана позже.
Идентификатор глобального блока памяти, содержащего структуру типа DEVNAMES, содержащей три текстовые строки. Первая строка определяет имя драйвера принтера, вторая - имя принтера, и третья - имя порта вывода, к которому подключен принтер.
Если содержимое этого поля указать как NULL, после возвращения из функции PrintDlg поле будет содержать идентификатор глобального блока памяти, заказанного функцией для структуры DEVNAMES. В структуре будут находиться строки, соответствующие выбранному принтеру.
Контекст устройства или информационный контекст.
Это поле заполняется после возвращения из функции PrintDlg, если в поле Flags указано одно из значений: PD_RETURNDC или PD_RETURNIC. В первом случае возвращается контекст принтера, который можно использовать для печати, во втором - информационный контекст, который можно использовать для получения разнообразной информации о принтере.
Это поле должно содержать флаги инициализации:
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" блокируется и удаляется из диалоговой панели.
Начальное значение для инициализации органа управления "From" диалоговой панели "Print". Используется только в том случае, если в поле Flags указан флаг PD_PAGENUMS. Максимальное значение для поля nFromPage составляет 0xfffe.
После возвращения из функции PrintDlg это поле содержит номер страницы документа, с которой должна начинаться печать.
Начальное значение для инициализации органа управления "To" диалоговой панели "Print". Используется только в том случае, если в поле Flags указан флаг PD_PAGENUMS. Максимальное значение для поля nFromPage составляет 0xfffe.
После возвращения из функции PrintDlg это поле содержит номер страницы документа, до которой должна выполняться печать.
Минимальное количество страниц, которое можно задать при помощи органов управления "From" и "To".
Максимальное количество страниц, которое можно задать при помощи органов управления "From" и "To".
Значение для инициализации органа управления "Copies", если поле hDevMode содержит значение NULL.
Идентификатор модуля, который содержит шаблоны диалоговых панелей. Используется только в том случае, если указаны флаги PD_ENABLEPRINTTEMPLATE или PD_ENABLESETUPTEMPLATE.
Произвольные данные, которые приложение может передать функции фильтра.
Адрес функции фильтра для диалоговой панели "Print".
Адрес функции фильтра для диалоговой панели "Print Setup".
Адрес текстовой строки, закрытой нулем, содержащей имя ресурса для шаблона диалоговой панели "Print". Для использования этого поля необходимо указать флаг PD_ENABLEPRINTTEMPLATE.
Адрес текстовой строки, закрытой нулем, содержащей имя ресурса для шаблона диалоговой панели "Print Setup". Для использования этого поля необходимо указать флаг PD_ENABLESETUPTEMPLATE.
Идентификатор блока памяти, содержащего предварительно загруженный шаблон диалоговой панели "Print". Для использования этого поля необходимо указать флаг PD_ENABLEPRINTTEMPLATEHANDLE.
Идентификатор блока памяти, содержащего предварительно загруженный шаблон диалоговой панели "Print Setup". Для использования этого поля необходимо указать флаг PD_ENABLESETUPTEMPLATEHANDLE.
Перед вызовом функции 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, "\0"); // Сбрасываем флаг обновления bUpdate = FALSE; SetFocus(hEdit); return 0; } // Загрузка файла для редактирования else if(wParam == ID_OPEN) { LPSTR lpTextBuffer; DWORD dwFileSize, dwCurrentPos; // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; } // Открываем входной файл. hfSrcFile = OpenFile(); if(!hfSrcFile) return 0; // Определяем размер файла dwCurrentPos = _llseek(hfSrcFile, 0L, 1); dwFileSize = _llseek(hfSrcFile, 0L, 2); _llseek(hfSrcFile, dwCurrentPos, 0); // Размер файла не должен превосходить 32000 байт if(dwFileSize >= 32000) { _lclose(hfSrcFile); MessageBox(hwnd, "Размер файла больше 32000 байт", szWindowTitle, MB_OK); return 0; } // Заказываем память для загрузки файла lpTextBuffer = (LPSTR)malloc(32000); if(lpTextBuffer == NULL) return 0; // Загружаем текст из файла в буфер _lread(hfSrcFile, lpTextBuffer, dwFileSize); // Закрываем буфер двоичным нулем lpTextBuffer[(WORD)dwFileSize] = '\0'; // Закрываем файл _lclose(hfSrcFile); // Переносим содержимое буфера в // текстовый редактор SetWindowText(hEdit, lpTextBuffer); // Освобождаем буфер free((void *)lpTextBuffer); // сбрасываем флаг обновления bUpdate = FALSE; SetFocus(hEdit); return 0; } // ------------------------------------------ // Печать текста // ------------------------------------------ else if(wParam == ID_PRINT) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer; // Определяем размер текста wSize = GetWindowTextLength(hEdit); // Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); // Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf); PrintFile(hwnd, npTextBuffer, wSize); // Расфиксируем блок памяти LocalUnlock(hTxtBuf); SetFocus(hEdit); return 0; } else if(wParam == ID_EXIT) { // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; } // Посылаем в функцию главного окна // сообщение WM_CLOSE SendMessage(hwnd, WM_CLOSE, 0, 0L); return 0; } return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } // ------------------------------- // Функция OpenFile // Сохранение файла // ------------------------------- HFILE OpenFile(void) { // Структура для выбора файла OPENFILENAME ofn; // Буфер для записи пути к выбранному файлу char szFile[256]; // Буфер для записи имени выбранного файла char szFileTitle[256]; // Фильтр расширений имени файлов char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0"; // Идентификатор открываемого файла HFILE hf; // Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0'; // Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME)); // Инициализируем нужные нам поля // Размер структуры ofn.lStructSize = sizeof(OPENFILENAME); // Идентификатор окна ofn.hwndOwner = NULL; // Адрес строки фильтра ofn.lpstrFilter = szFilter; // Номер позиции выбора ofn.nFilterIndex = 1; // Адрес буфера для записи пути // выбранного файла ofn.lpstrFile = szFile; // Размер буфера для записи пути // выбранного файла ofn.nMaxFile = sizeof(szFile); // Адрес буфера для записи имени // выбранного файла ofn.lpstrFileTitle = szFileTitle; // Размер буфера для записи имени // выбранного файла ofn.nMaxFileTitle = sizeof(szFileTitle); // В качестве начального каталога для // поиска выбираем текущий каталог ofn.lpstrInitialDir = NULL; // Определяем режимы выбора файла ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) { // Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ); // Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; } // ------------------------------- // Функция OpenSaveFile // Выбор файла для редактирования // ------------------------------- HFILE OpenSaveFile(void) { OPENFILENAME ofn; char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt\0Any Files\0*.*\0"; HFILE hf; szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_HIDEREADONLY; // Выбираем выходной файл if (GetSaveFileName(&ofn)) { // При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0); return hf; } else return 0; }
Подробное описание этого файла вы найдете в 12 томе, здесь же для экономии места мы расскажем только о фрагменте, выполняющем печать.
Когда вы нажимаете кнопку "Print", соответствующий обработчик определяет размер текста, загруженного в редактор, вызывая функцию GetWindowTextLength:
wSize = GetWindowTextLength(hEdit);
Далее он получает адрес блока памяти, содержащий текст, фиксируя его:
hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); npTextBuffer = (NPSTR)LocalLock(hTxtBuf);
После этого вызывается функция печати PrintFile, определенная в файле print.cpp (листинг 6.2):
PrintFile(hwnd, npTextBuffer, wSize);
В качестве параметров этой функции передаются идентификатор окна приложения, адрес буфера, содержащего печатаемый текст, и размер этого буфера в байтах.
После выполнения печати буфер расфиксируется, после чего редактор текста получает фокус ввода:
LocalUnlock(hTxtBuf); SetFocus(hEdit); return 0;
Все функции, предназначенные для работы с принтером, мы вынесли в отдельный файл (листинг 6.2).
Листинг 6.2. Файл prnfile/print.cpp
// ---------------------------------------------------- // Функции для работы с принтером // ---------------------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <string.h> #include "prnfile.hpp" // Прототипы функций BOOL CALLBACK _export AbortDlgFunc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL CALLBACK _export AbortFunc(HDC hdc, int nCode); HDC GetPrinterDC(HWND); // Внешние глобальные переменные extern HWND hdlgAbort; extern BOOL fAbort; BOOL fAbort = FALSE; HWND hdlgAbort = 0; static PRINTDLG pd; // ---------------------------------------------------- // Функция PrintFile // Печать файла // ---------------------------------------------------- BOOL PrintFile(HWND hwnd, NPSTR npBuff, WORD wSize) { HDC hdc; int cyPage; int cyChar, yPos, nLength; int i; WORD wCurPos = 0; TEXTMETRIC tm; ABORTPROC lpAbortFunc; BOOL fDone; char abBuffer[256]; DOCINFO docinfo; DLGPROC lpAbortDlgFunc; HINSTANCE hInst; int rc; // Получаем контекст устройства для принтера hdc = GetPrinterDC(hwnd); // Определяем разрешение принтера по вертикали cyPage = GetDeviceCaps(hdc, VERTRES); // Определяем метрики текста GetTextMetrics(hdc, &tm); // Вычисляем высоту шрифта cyChar = tm.tmHeight + tm.tmExternalLeading; // Создаем переходник для функции AbortFunc hInst = GetWindowInstance(hwnd); lpAbortFunc = (ABORTPROC)MakeProcInstance((FARPROC)AbortFunc, hInst); // Устанавливаем функцию AbortProc rc = SetAbortProc(hdc, lpAbortFunc); if(rc <= 0) { DeleteDC(hdc); return FALSE; } // Создаем переходник для функции диалога lpAbortDlgFunc = (DLGPROC)MakeProcInstance((FARPROC)AbortDlgFunc, hInst); // Создаем диалог для отмены печати hdlgAbort = CreateDialogParam ( hInst, MAKEINTRESOURCE(IDD_ABORT), hwnd, lpAbortDlgFunc, NULL) ; if(!hdlgAbort) { FreeProcInstance((FARPROC)lpAbortFunc); DeleteDC(hdc); return FALSE; } // Отображаем созданную диалоговую панель ShowWindow(hdlgAbort, SW_SHOWNORMAL); UpdateWindow(hdlgAbort); // Переводим окно приложения в неактивное // состояние EnableWindow(hwnd, FALSE); // Заполняем структуру docinfo docinfo.cbSize = sizeof(docinfo); docinfo.lpszDocName = NULL; docinfo.lpszOutput = NULL; // Начинаем печать документа rc = StartDoc(hdc, &docinfo); if(rc <= 0) { DestroyWindow(hdlgAbort); FreeProcInstance((FARPROC)lpAbortFunc); FreeProcInstance((FARPROC)lpAbortDlgFunc); DeleteDC(hdc); return FALSE; } // Флаг завершения печати документа fDone = FALSE; // Цикл печати страниц документа while(!fDone && !fAbort) { // Начинаем печать страницы документа StartPage(hdc); // Начальная позиция по вертикали yPos = 0; // Цикл по строкам страницы while(yPos + cyChar < cyPage) { // Проверка завершения печати страницы if(wCurPos > wSize) { fDone = TRUE; break; } i=0; nLength = 0; // Цикл по строке // Копируем строку в буфер abBuffer while((npBuff[wCurPos] != 0x0d) && (wCurPos < wSize)) { abBuffer[i] = npBuff[wCurPos]; i++; wCurPos++; nLength++; } // Рисуем одну строку текста TextOut(hdc, 0, yPos, abBuffer, nLength); // Переходим к следующей строке wCurPos += 2; yPos += cyChar ; } // Инициируем печать страницы rc = EndPage(hdc); if(rc < 0) { fAbort = TRUE; break; } } // При аварийном завершении печати вызываем // функцию AbortDoc, при нормальном - EndDoc if(fAbort) AbortDoc(hdc); else EndDoc(hdc); // Активизируем главное окно приложения EnableWindow(hwnd, TRUE); // Удаляем диалоговую панель DestroyWindow(hdlgAbort); // Освобождаем ресурсы FreeProcInstance((FARPROC)lpAbortFunc); FreeProcInstance((FARPROC)lpAbortDlgFunc); DeleteDC(hdc); return TRUE ; } // ---------------------------------------------------- // Функция AbortDlgFunc // Функция диалога для диалоговой панели, // позволяющей прервать процесс печати // ---------------------------------------------------- #pragma argsused 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; } // ---------------------------------------------------- // Функция AbortFunc // Обеспечивает возможность работы других // приложений во время печати // ---------------------------------------------------- #pragma argsused 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); } // ---------------------------------------------------- // Функция GetPrinterDC // Выводит на экран диалоговую панель "Print", // с помощью которой можно выбрать принтер. // Возвращает идентификатор контекста для // выбранного принтера // ---------------------------------------------------- HDC GetPrinterDC(HWND hwnd) { BOOL fResult; // Инициализируем структуру PRINTDLG memset(&pd, 0, sizeof(PRINTDLG)); pd.lStructSize = sizeof(PRINTDLG); pd.hwndOwner = hwnd; pd.Flags = PD_RETURNDC; // Отображаем диалоговую панель fResult = PrintDlg(&pd); // При необходимости освобождаем память, полученную // функцией PrintDlg для структур DEVMODE и DEVNAMES if(pd.hDevMode != 0) GlobalFree (pd.hDevMode); if(pd.hDevNames != 0) GlobalFree (pd.hDevNames); // В случае успешного завершения возвращаем // контекст принтера if(fResult) return pd.hDC; else return 0; }
Функция PrintFile выполняет печать файла, загруженного в текстовый редактор.
Она получает контекст печати, вызывая функцию GetPrinterDC, определенную в этом же файле. Функция GetPrinterDC выводит на экран стандартную диалоговую панель "Print", позволяющую выбрать принтер и задать параметры для выбранного принтера.
Далее определяется разрешение принтера по вертикали, метрики текста для шрифта, выбранного в контекст принтера, вычисляется высота шрифта. Вся эта информация необходима для правильного расположения строк текста на листе бумаги.
После этого функция PrintFile создает переходник для функции отмены печати и подключает последнюю, вызывая функцию SetAbortProc.
Далее создается переходник для функции диалога отмены печати, затем создается и выводится на экран диалоговая панель отмены печати. Шаблон диалоговой панели определен в файле ресурсов и содержит единственную кнопку с надписью "Cancel".
После отображения этой панели главное окно приложения переводится в неактивное состояние для передачи фокуса ввода диалоговой панели отмены печати.
Перед началом печати заполняется структура DOCINFO и вызывается функция StartDoc.
В цикле печати страниц документа выполняется проверка глобального флага отмены печати, а также флага завершения печати fDone.
Перед началом печати каждой страницы вызывается функция StartPage.
Далее выполняется построчное копирование текста из буфера текстового редактора в буфер abBuffer с последующим выводом содержимого этого буфера на принтер функцией TextOut.
Печать страницы выполняется после вызова функции EndPage.
При нормальном завершении процесса печати вызывается функция EndDoc, а при аварийном - AbortDoc.
После нормального или аварийного завершения печати документа активизируется главное окно приложения, а диалоговая панель отмены печати удаляется. Вслед за этим освобождаются созданные переходники и контекст принтера.
Идентификатор диалоговой панели определен в файле prnfile.hpp (листинг 6.3).
Листинг 6.3. Файл prnfile/prnfile.hpp
#define IDD_ABORT 25
Шаблон диалоговой панели отмены печати определен в файле описания ресурсов приложения (листинг 6.4).
Листинг 6.4. Файл prnfile/prnfile.rc
#include <g:\tcwin\include\windows.h> #include "prnfile.hpp" IDD_ABORT DIALOG 50, 30, 89, 43 STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU CAPTION "Печать..." BEGIN CONTROL "Cancel", IDCANCEL, "BUTTON", WS_GROUP, 29, 23, 32, 14 CTEXT "Cancel - отмена печати", -1, -1, 8, 90, 8, WS_CHILD | WS_VISIBLE | WS_GROUP END
Файл определения модуля приведен в листинге 6.5.
Листинг 6.5. Файл prnfile/prnfile.def
; ============================= ; Файл определения модуля ; ============================= NAME PRNFILE DESCRIPTION 'Приложение PRNFILE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple