Печать на принтере
Для того, чтобы осуществлять графический вывод на принтер, необходимо создать объект Graphics, связанный с графическим контекстом принтера. Кроме того, необходимо создать документ принтера, на страницах которого мы будем рисовать. Создание графического контекста принтераОдин из вариантов создания графического контекста принтера для работы с ним из GDIPlus заключается в применении Windows API функции CreateDC. Эта функция получает имя принтера, создаёт необходимую структуру и возвращает указатель на неё: DECLARE Long CreateDC IN Gdi32.dll String lpszDriver, String lpszDevice, ; String lpszOutput, String Devmode hDC = CreateDC(NULL, Printer_Name, NULL, NULL) Функция получает следующие параметры:
Если функция CreateDC возвращает ноль, то это означает, что графический контекст не создан. Для получения имени принтера можно воспользоваться встроенной функцией GETPRINTER(). Эта функция формирует диалоговое окно (рис.23.1), в котором вы можете выбрать как локальный принтер, так и любой другой из доступных в вашей локальной сети, и возвращает строку с именем выбранного принтера: Printer_Name = GETPRINTER()
Рис. 23.1. Диалог для выбора принтера Если вы хотите печатать на принтере, установленном по умолчанию,
то воспользуйтесь функцией Printer_Name = SET("Printer", nType) где параметр nType может принимать одно из следующих значений: 2 — принтер по умолчанию в Windows Так как Visual FoxPro «не видит» память, зарезервированную функциями Windows API, то вы должны самостоятельно удалить графический контекст принтера, вызвав функцию DeleteDC: DECLARE Long DeleteDC IN Gdi32.dll Long hDC = DeleteDC(hDC) Создание связанного с принтером объекта GraphicsДля создания требуемой модификации объекта Graphics для вывода на принтер используется функция GdipCreateFromHDC. Вот её объявление в Visual FoxPro: DECLARE Long GdipCreateFromHDC IN Gdiplus.dll Long hdc, Long @ nativeGraphics Функции передаётся два параметра. Параметр hdc — это
указатель на структуру, содержащую графический контекста принтера, а
в передаваемом по ссылке параметре nativeGraphics
запоминается дескриптор созданного объекта Graphics. cPrinterName = SET("Printer", 2) hDC = CreateDC(NULL, cPrinterName, NULL, NULL) IF hDC != 0 lnGraphics = 0 Status = GdipCreateFromHDC(hDC, @lnGraphics) ENDIF Определение размера листа принтераСправедливо предположить, что для рисования на принтере вы захотите использовать систему координат, единицей измерения в которой являются миллиметры. Поэтому не плохо было бы узнать, страницу какого размера ваш принтер может напечатать. Для определения размеров страницы используется функция GetDeviceCaps. Вот её объявление: DECLARE Long GetDeviceCaps IN Gdi32.dll Long hdc, Long nIndex Параметр hdc — это указатель на структуру, содержащую графический контекст принтера, а nIndex — число, принимающее одно из значений, перечисленных в таблице 23.1. Таблица 23.1. Значения параметра nIndex функции GetDeviceCaps
В следующем фрагменте кода показано, как определить размеры листа (в мм) для принтера, установленного по умолчанию: CLEAR DECLARE Long CreateDC IN Gdi32.dll String, String, String, String DECLARE Long GetDeviceCaps IN Gdi32.dll Long, Long hDC = CreateDC(NULL, SET("Printer", 2), NULL, NULL) ? "Ширина", GetDeviceCaps(hDC, 4) ? "Высота", GetDeviceCaps(hDC, 6) Напомню, что установить единицу измерения для печати на принтере можно функцией GdipSetPageUnit, описание которой приведено в начале предыдущей главы. Документ принтераПри печати изображений на принтере первоначально весь графический вывод направляется в документ принтера. Документ может содержать произвольное количество страниц. Каждую страницу документа условно можно рассматривать как аналог растра с размерами, равными размерам листа бумаги. После того, как все необходимые страницы документа сформированы, документ закрывается и направляется в очередь печати. Создание документа принтераСоздаёт документ принтера функция StartDoc. Эта функция получает два
параметра: указатель на графический контекст принтера и указатель на
структуру DOCINFO. В общем случае структура DOCINFO может быть
представлена символьной строкой длиной 20 байт, первый байт которой
имеет значение 20. DECLARE Long StartDoc IN Gdi32.dll Long hdc, String Docinfo cDocInfo = CHR(20) + REPLICATE(CHR(0), 19) && Формируем структуру Result = StartDoc(hDC, cDocInfo) && Создаём документ Если документ создан успешно, то функция возвращает значение, отличное от нуля, а в случае ошибки — ноль. Заполнение структуры DOCINFOЭта структура подробно рассматривалась в главе 19, в разделе «Распределение памяти для структур с указателями». Напомню, что она содержит пять четырёхбайтовых полей, из которых интерес представляет второе поле, которое содержит указатель на строку с именем печатаемого документа. Это имя выводится в окне просмотра очереди печати (рис. 23.2) в колонке «Документ».
Рис. 23.2. Окно просмотра очереди печати Следующий фрагмент кода показывает, как документу принтера присвоить имя. Предполагается, что это имя хранится в переменной cDocName. nLen = LEN(cDocName) * Распределяем глобальную память для размещения строки с именем документа hGlobal = GlobalAlloc(0x0040, nLen) * Копируем строку в эту память = SYS(2600, hGlobal, nLen, cDocName) * Формируем структуру DOCINFO cDOCINFO = BINTOC(20,"4RS") + BINTOC(hGlobal,"4RS") + ; REPLICATE(CHR(0),12) Не забудьте освободить распределённую функцией GlobalAlloc память после закрытия документа принтера! Управление страницами документаФункция StartPage добавляет в документ новую страницу и делает её
активной. Вы можете рисовать только на активной странице.
Функция получает только один параметр — указатель на графический
контекст принтера. DECLARE Long StartPage IN Gdi32.dll Long hDC DECLARE Long EndPage IN Gdi32.dll Long hDC * Предполагаем, что документ уже создан Result = StartPage(hDC) * Здесь – вызов функций GDIPlus для рисования на странице Result = EndPage(hDC) * Создаём следующую страницу документа принтера Result = StartPage(hDC) * и так далее Функции StartPage и EndPage при успешном выполнении возвращают отличное от нуля значение и ноль в случае ошибки. Закрытие документаДля того, чтобы послать документ принтера в очередь печати, необходимо его закрыть. Делается это при помощи функции EndDoc. Функция получает только один параметр — указатель на графический контекст принтера: DECLARE Long EndDoc IN Gdi32.dll Long hDC Result = EndDoc(hDC) Класс для печати на принтереСоздадим из функций, реализующих печать на принтере, класс с именем GdipPrinter. Добавьте этот класс в библиотеку VfpGdiPlus.vcx (мы по-прежнему продолжаем создавать классы для работы с GDIPlus!), в качестве класса-родителя выберите Custom. Добавьте в класс следующие защищённые свойства:
Начальные значения всех этих свойств, кроме свойства DocStatus, установите равными нулю. Метод InitПоскольку класс GdipPrinter задуман как расширение класса
GdipImages,
то предполагается, что объект — экземпляр этого класса будет
создаваться уже после того, как создан объект — экземпляр класса
GdipImages. Таким образом, инициализация GDIPlus в этом методе не
требуется.
LPARAMETERS tcPinterName LOCAL lcPrinter, lnGraphics, lhDC IF VARTYPE(tcPrinterName) = "C" && Если имя принтера передано методу lcPrinter = tcPinterName && то используем его ELSE && иначе - lcPrinter = SET("Printer", 2) && берём принтер по умолчанию ENDIF IF LEN(ALLTRIM(lcPrinter)) DECLARE Long CreateDC IN Gdi32.dll String, String, String, String DECLARE Long GetDeviceCaps IN Gdi32.dll Long, Long lhDC = CreateDC(NULL, lcPrinter, NULL, NULL) IF lhDC != 0 this.hDC = lhDC lnGraphics = 0 DECLARE Long GdipCreateFromHDC IN Gdiplus.dll Long, Long @ this.Status = GdipCreateFromHDC(lhDC, @lnGraphics) IF this.Status = 0 this.nativeGraphics = lnGraphics RETURN .t. && Объект создан успешно ENDIF && иначе - DECLARE Long DeleteDC IN Gdi32.dll Long = DeleteDC(lhDC) && удаляем графический контекст принтера ENDIF ENDIF RETURN .f. Переменная lcPrinter получает либо
переданное в метод имя принтера, либо имя принтера, используемого на
компьютере по умолчанию. Функция CreateDC создаёт графический
контекст принтера и запоминает указатель на него в свойстве hDC.
Далее создаётся связанный с принтером объект Graphics; дескриптор
этого объекта сохраняется в свойстве nativeGraphics. * Подключение к произвольному принтеру в вашей локальной сети SET CLASSLIB TO vfpgdiplus.vcx oPrint = CREATEOBJECT("GdipPrinter", GETPRINTER()) * Подключение к принтеру, используемому по умолчанию. SET CLASSLIB TO vfpgdiplus.vcx oPrint = CREATEOBJECT("GdipPrinter") Метод GetGraphicsЭтот метод возвращает дескриптор объекта Graphics,
связанный с принтером. Дескриптор может использоваться функциями
рисования, рассмотренными в предыдущей главе. RETURN this.nativeGraphics Установка единицы измерения. Метод SetPageUnitВы можете указывать различные единицы измерения для каждой страницы документа принтера. Метод SetPageUnit получает три параметра, первый из которых (логического типа) определяет, какая единица измерения будет использоваться при рисовании на странице документа принтера (значение «ложь» определяет пиксели, значение «истина» — миллиметры), а в остальные передаваемые по ссылке параметры будет записано значение ширины и высоты листа в выбранной единице измерения. Код метода приведён в листинге 23.2.
LPARAMETERS tlUnit, tnListWidth, tnListHeight LOCAL lnUnit IF VARTYPE(tlUnit) + VARTYPE(tnListWidth) + VARTYPE(tnListHeight) = "LNN" DECLARE Long GetDeviceCaps IN Gdi32.dll Long, Long IF tlUnit && Выбрано: миллиметры lnUnit = 6 tnListWidth = GetDeviceCaps(this.hDC, 4) tnListHeight = GetDeviceCaps(this.hDC, 6) ELSE && Выбрано: мировые координаты lnUnit = 3 tnListWidth = GetDeviceCaps(this.hDC, 8) tnListHeight = GetDeviceCaps(this.hDC, 10) ENDIF DECLARE Long GdipSetPageUnit IN Gdiplus.dll Long, Long this.Status = GdipSetPageUnit(this.nativeGraphics, lnUnit) ELSE this.Status = 2 ENDIF RETURN this.Status = 0 В следующем фрагменте кода показано, как использовать метод SetPageUnit для установки единицы измерения «миллиметры»: nPageWidth = 0 nPageHeight = 0 oPrint.SetPageUnit(.t., @nPageWidth, @nPageHeight) После выполнения этого кода переменные nPageWidth и nPageHeight будут содержать значения ширины и высоты страницы принтера в миллиметрах. Создание документа принтера. Метод OpenDocumentСоздаёт документ принтера метод OpenDocument.
Он получает только один параметр — имя печатаемого документа.
LPARAMETERS tcDocumentName LOCAL lcDOCINFO, lnLen IF VARTYPE(tcDocumentName) = "C" * Имя документа передано. Формируем структуру DOCINFO tcDocumentName = ALLTRIM(tcDocumentName) lnLen = LEN(tcDocumentName) IF lnLen > 0 this.hGlobal = GlobalAlloc(0x0040, lnLen) = SYS(2600, this.hGlobal, lnLen, tcDocumentName) ENDIF ENDIF cDOCINFO = BINTOC(20,"4RS") + BINTOC(this.hGlobal,"4RS") + REPLICATE(CHR(0),12) DECLARE Long StartDoc IN Gdi32.dll Long, String IF StartDoc(this.hDC, lcDOCINFO) > 0 && Создаём документ this.DocStatus = .t. DECLARE Long StartPage IN Gdi32.dll Long IF StartPage(this.hDC) > 0 && Открываем страницу RETURN .t. ENDIF ENDIF RETURN .f. Метод NewPageМетод закрывает текущую страницу и открывает новую. Его код показан в листинге 23.4.
this.Status = IIF(this.DocStatus, 0, -1) IF this.Status = 0 DECLARE Long StartPage IN Gdi32.dll Long DECLARE Long EndPage IN Gdi32.dll Long IF EndPage(this.hDC) > 0 IF StartPage(this.hDC) > 0 RETURN .t. ELSE this.Status = -2 && Невозможно открыть / закрыть страницу ENDIF ELSE this.Status = -2 ENDIF ENDIF RETURN .f. После вызова этого метода вы уже не сможете вернуться на предыдущие страницы документа для правки. Метод CloseDocumentМетод закрывает страницу документа принтера и сам документ и направляет его в очередь печати. Код метода приведён в листинге 22.5.
this.Status = IIF(this.DocStatus, 0, -1) IF this.Status = 0 DECLARE Long EndPage IN Gdi32.dll Long DECLARE Long EndDoc IN Gdi32.dll Long = EndPage(this.hDC) IF EndDoc(this.hdc) > 0 this.DocStatus = .f. RETURN .t. ELSE this.Status = -3 ENDIF ENDIF RETURN .f. Метод проверяет, был ли создан документ принтера, и, если да, закрывает страницу и сам документ, после чего устанавливает свойство DocStatus класса в «ложь». Метод GetStatusКак и в классе GdipImages, назначение этого метода — вернуть значение свойства Status, содержащего коды завершения функций GDIPlus. Он содержит только одну команду: RETURN this.Status Метод DestroyТак как этот метод вызывается при уничтожении объекта, мы должны включить в него команды для освобождения памяти, занимаемой графическим контекстом принтера, объектом Graphics и, возможно, строкой — наименованием документа. Кроме того, если документ принтера ещё не закрыт, то в методе он закрывается и отправляется в очередь печати.
IF this.DocStatus this.CloseDocument() ENDIF DECLARE Long DeleteDC IN Gdi32.dll Long DECLARE Long GdipDeleteGraphics IN Gdiplus.dll Long = GdipDeleteGraphics(this.nativeGraphics) = DeleteDC(this.hDC) IF this.hglobal != 0 DECLARE Long GlobalFree IN WIN32API Long = GlobalFree(this.hGlobal) ENDIF ТестированиеТестовые примеры, приведённые ниже, демонстрируют способы печати на принтере графических примитивов и изображений. Печатаем графические примитивы и текстыСоздайте в проекте новый программный файл с именем test15.prg и введите в него код из листинга 23.7.
LOCAL lcPath, loGP, llStatus, lcText, lnWidth, lnHeight, lnLeft, lnGraphics lcPath = JUSTPATH(SYS(16)) SET DEFAULT TO (lcPath) SET CLASSLIB TO vfpgdiplus loGP = CREATEOBJECT("gdipimages") loPrint = CREATEOBJECT("gdipPrinter") IF VARTYPE(loPrint) != "O" = MESSAGEBOX("Не удалось подключиться к принтеру" RETURN ENDIF * Получаем дескриптор объекта Graphics, связанного с принтером lnGraphics = loPrint.GetGraphics() * Открываем документ принтера llStatus = loPrint.OpenDocument() IF llStatus * Создаём перо llStatus = loGP.CreatePen(1) ENDIF IF llStatus * Рисуем пять прямоугольников, чтобы отметить области, в которые выводится текст llStatus = loGP.DrawRectangle(10, 40, 300, 60, lnGraphics) llStatus = loGP.DrawRectangle(10, 110, 300, 60, lnGraphics) llStatus = loGP.DrawRectangle(10, 180, 300, 60, lnGraphics) llStatus = loGP.DrawRectangle(320, 40, 110, 180, lnGraphics) llStatus = loGP.DrawRectangle(440, 40, 80, 180, lnGraphics) llStatus = loGP.DrawRectangle(530, 40, 100, 180, lnGraphics) ENDIF IF llStatus * Создаём одноцветную кисть коричневого цвета llStatus = loGP.CreateSolidBrush(0xFF882000) ENDIF IF llStatus * Создаём шрифт высотой 16 пикселей llStatus = loGP.CreateFont("Times New Roman", 16) ENDIF IF llStatus * Центирование строки посредством вычисления её размеров lcText = "Эта строка центрируется по горизонтали" lnWidth = 0 lnHeight = 0 llStatus = loGP.GetLengthString(lcText, @lnWidth, @lnHeight, lnGraphics) lnLeft = (650 - lnWidth) / 2 && Определение левой точки ENDIF IF llStatus * Рисование строки llStatus = loGP.DrawString(lnLeft, 5, 0, 0, lcText, lnGraphics) ENDIF IF llStatus * Создаём объект StringFormat llStatus = loGP.CreateStringFormat() ENDIF IF llStatus lcText = "Этот текст выводится в прямоугольную область без форматирования" llStatus = loGP.DrawString(10, 40, 300, 60, lcText, lnGraphics) ENDIF IF llStatus lcText = "Строки этого текста центрируется внутри заданной прямоугольной области" llStatus = loGP.SetStringFormatParameter(1) llStatus = loGP.DrawString(10, 110, 300, 60, lcText, lnGraphics) ENDIF IF llStatus lcText = "Этот текст выровнен по правому краю заданной прямоугольной области" llStatus = loGP.SetStringFormatParameter(2) llStatus = loGP.DrawString(10, 180, 300, 60, lcText, lnGraphics) ENDIF IF llStatus lcText = "Этот текст выведен вертикально без форматирования " + ; "и коррекции качества начертания символов" llStatus = loGP.SetStringFormatParameter(0, .t.) llStatus = loGP.DrawString(320, 40, 110, 180, lcText, lnGraphics) ENDIF IF llStatus * Улучшаем качество вертикально выводимого текста llStatus = loGP.SetTextRendering(4, lnGraphics) ENDIF IF llStatus lcText = "Этот текст выведен вертикально и центрирован" llStatus = loGP.SetStringFormatParameter(1, .t.) llStatus = loGP.DrawString(440, 40, 80, 180, lcText, lnGraphics) ENDIF IF llStatus lcText = "Этот текст выведен вертикально и прижат к нижней границе прямоугольной области" llStatus = loGP.SetStringFormatParameter(2, .t.) llStatus = loGP.DrawString(530, 40, 100, 180, lcText, lnGraphics) ENDIF = MESSAGEBOX(IIF(llStatus, "ok","Ошибка" + STR(loGP.GetStatus()))) В начале тестового примера, после создания объекта — экземпляра класса GdipImages, создаётся объект — экземпляр класса GdipPrinter для принтера, используемого по умолчанию. Дескриптор объекта Graphics, связанный с принтером, запоминается в переменной
lnGraphics и в дальнейшем используется всеми функциями рисования для вывода на принтер. Печатаем изображенияСоздайте в проекте ещё один программный файл с именем test16.prg и введите в него код из листинга 23.8.
LOCAL lcPath, loGP, loPrint, llStatus, lcFile, lnGraphics lcPath = JUSTPATH(SYS(16)) SET DEFAULT TO (lcPath) SET CLASSLIB TO vfpgdiplus lcFile = GETFILE('JPG|GIF|BMP') IF EMPTY(lcFile) RETURN ENDIF * Создаём объект - экземпляр класса GdipImages loGP = CREATEOBJECT("gdipimages") * Загружаем графический файл IF loGP.LoadFromFile(lcFile) loPrint = CREATEOBJECT("gdipPrinter") IF VARTYPE(loPrint) != "O" = MESSAGEBOX("Не удалось подключиться к принтеру") RETURN ENDIF ELSE RETURN ENDIF * Получаем дескриптор объекта Graphics, связанного с принтером lnGraphics = loPrint.GetGraphics() lnWidth = 0 lnHeight = 0 loPrint.OpenDocument("Проверка графического вывода") loGP.DrawImage(lnGraphics, 30, 30) Обратите внимание на то, что документу при его создании присваивается имя. Запустите пример на выполнение и выберите любой файл, содержащий изображение, которое нужно распечатать на принтере. Попробуйте вызвать метод DrawImage, указав ширину
и высоту прямоугольной области. Изображение будет центрироваться
внутри этой области, полностью вписываясь в неё. На этом мы закончим изучение особенностей печати на принтере и перейдём к гораздо более интересному разделу — рисованию в окне формы. |