Delphi: заметки программиста. Часть 2
Печать в Delphi
Объект Printer автоматически создается в случае, если в программе указана ссылка на модуль Printers. Этот объект предоставляет программисту все необходимое для того, чтобы научить приложение выводить данные на один из подключенных к компьютеру принтеров.
Вывод на принтер в Windows ничем не отличается от вывода на экран: в распоряжение программиста предоставляется свойство Canvas объекта Printer, содержащее набор чертежных инструментов, и методы, свойственные классу TCanvas. Размер листа бумаги в пикселах определяют свойства Height и Width, а набор принтерных шрифтов – свойство Fonts.
Печать текста
Существует множество способов печати текста на принтере. Прежде всего следует назвать глобальную процедуру AssignPrn (она определена в модуле Printers), позволяющую использовать принтер как текстовый файл и печатать текстовые строки с помощью процедуры WriteLn. В листинге 1 (PrintText.dpr) приведен полный текст модуля, на форме которого расположены многострочный текстовый редактор Memo1 и четыре кнопки: для выбора текстового файла и ввода его содержимого в редактор, для выбора нужного шрифта отображения/печати документа, для инициации процесса печати и для завершения работы программы.
Описанный способ печати — самый примитивный: с его помощью невозможно вывести линии, разделяющие колонки или строки, трудно форматировать текст, вставлять заголовки, номера страниц и т.д.
Значительно более гибкие средства обеспечивает свойство Printer.Canvas. Покажем, как с его помощью можно напечатать текст, содержащийся в редакторе Memo1 (PrintText.dpr, листинг 2):
Как можно увидеть, прямое обращение к чертежным инструментам свойства Canvas требует от программиста значительно больших усилий, но зато предоставляет ему полный контроль над печатным изображением.
Во многих случаях для печати документа и внесения в него элементарных средств форматирования (печать общего заголовка, заголовка на каждой странице, номеров страниц и т.п.) проще использовать специальные компоненты, расположенные на странице QReport палитры компонентов Delphi. Эти компоненты разработаны для создания отчетов по базам данных, но могут с успехом использоваться и для печати обычных документов (PrintText.dpr).
Наконец, очень хороших результатов можно достичь, используя специализированные средства просмотра/печати документов, как, например, текстовый процессор MS Word.
Печать изображений
Печать изображений может показаться очень сложным делом, однако свойство Printer.Canvas содержит метод:
procedure StretchDraw(const Rect: TRect; Graphic: TGraphic );
который легко справляется с этой задачей. При обращении к нему в качестве первого параметра указывается прямоугольная область, отводимая на поверхности листа для распечатки изображения, а в качестве второго — объект класса TGraphic, в котором хранится изображение, например:
with Printer do begin BeginDoc; Canvas.StretchDraw(Canvas.ClipRect, Image1.Picture.Graphic); EndDoc; end;
Отображение файла в память
Для работы с файлом динамической подкачки страниц виртуальной памяти в Windows 32 используется механизм отображения файлов в адресное пространство приложения. Соответствующие функции API доступны любому приложению и могут применяться к любому файлу (кстати, таким способом загружаются в адресное пространство процесса исполняемые файлы и DLL). В результате отображения приложение может работать с файловыми данными как с размещенными в динамической памяти. В большинстве случаев такая возможность не только повышает скорость работы с данными, но и предоставляет программисту уникальные средства обработки сразу всех записей файла. Например, он может с помощью единственного оператора проверить, входит ли заданный образец поиска в какую-либо строку текстового файла.
Отображение файла осуществляется в три приема. Вначале файл создается обращением к функции:
function FileCreate (FileName: String): Integer;
или открывается с помощью:
function FileOpen (const FileName: String; Mode: LongWord): Integer;
В обеих функциях FileName — имя файла, возможно, с маршрутом доступа. Параметр Mode определяет режим доступа к файлу и может принимать одно из следующих значений: fmOpenRead — только чтение; fmOpenWrite — только запись; fmOpenReadWrite — чтение и запись. С помощью операции or эти константы можно комбинировать с одной из следующих нескольких функций, регулирующих совместный доступ к файлу: fmShareExclusive — совместный доступ запрещен; fmShareDenyWrite — другим приложениям запрещается запись; fmShareDenyRead — другим приложениям запрещается чтение; fmSchareDenyNone — совместный доступ неограничен. Обе функции возвращают дескриптор созданного (открытого) файла или 0, если операция оказалась неудачной.
На втором этапе создается объект отображения в память. Для этого используется функция:
function CreateFileMapping (hFile: THandle; lpFileMappingAttributes: PSecurityAttributes; flProtect, dwMaximumSizeHigh, dwMaximumSizeLow: DWord; lpName: PChar): THandle;
Здесь hFile — дескриптор файла; lpFileMappingAttributes — указатель на структуру, в которой определяется, может ли создаваемый объект порождать дочерние объекты (обычно не может — NIL); flProtect — определяет тип защиты, применяемый к окну отображения файла (см. об этом ниже); dwMaximumSizeHigh, dwMaximumSizeLow — соответственно старшие и младшие 32 разряда числа, содержащего размер файла (если вы будете отображать файлы длиной до 4 Гбайт, поместите в dwMaximumSizeHigh 0, если в dwMaximumSizeLow — длину файла; а если оба параметра равны 0, то размер окна отображения равен размеру файла); lpName — имя объекта отображения или NIL.
Параметр flProtect задает тип защиты, применяемый к окну просмотра файла, и может иметь одно из следующих значений: PAGE_READONLY — файл можно только читать (файл должен быть создан или открыт в режиме fmOpenRead); PAGE_READWRITE — файл можно читать и записывать в него новые данные (файл открывается в режиме fmOpenReadWrite); PAGE_WRITECOPY — файл открыт для записи и чтения, однако обновленные данные сохраняются в отдельной защищенной области памяти (отображенные файлы могут разделяться приложениями, в этом режиме каждое приложение сохраняет изменения в отдельной области памяти или участке файла подкачки); файл открывается в режиме fmOpenReadWrite или fmOpenWrite; (этот тип защиты нельзя использовать в Windows 95/98). С помощью операции or к параметру flProtect можно присоединить такие атрибуты: SEC_COMMIT — выделяет для отображения физическую память или участок файла подкачки; SEC_IMAGE — информация об атрибутах отображения берется из образа файла; SEC_NOCASHE — отображаемые данные не кэшируются и записываются непосредственно на диск; SEC_RESERVE — резервируются страницы раздела без выделения физической памяти.
Функция возвращает дескриптор объекта отображения или 0, если обращение было неудачным.
Наконец, на третьем этапе создается окно просмотра, то есть собственно отображение данных в адресное пространство программы.
function MapViewOfFile(hFileMappingObject: THandle;dwDesiresAccess: DWord; dwFileOffsetHigh, dwFileIffsetLow, dwNumberOfBytesToMap: DWord): Pointer;
Здесь hFileMappingObject — дескриптор объекта отображения; dwDesiresAccess — определяет способ доступа к данным и может иметь одно из следующих значений: FILE_MAP_WRITE — разрешает чтение и запись (при этом в функции CreateFileMapping должен использоваться атрибут PAGE_READWRITE); FILE_MAP_READ — разрешает только чтение (в функции CreateFileMapping должен использоваться атрибут PAGE_READONLY или PAGE_READWRITE); FILE_MAP_ALL_ACCESS — то же, что и FILE_MAP_WRITE; FILE_MAP_COPY — данные доступны для записи и чтения, однако обновленные данные сохраняются в отдельной защищенной области памяти (в функции CreateFileMapping должен использоваться атрибут PAGE_WRITECOPY); dwFileOffsetHigh, dwFileIffsetLow — определяют соответственно старшие и младшие разряды смещения от начала файла, начиная с которого осуществляется отображение; dwNumberOfBytesToMap — определяет длину окна отображения (0 — длина равна длине файла). Функция возвращает указатель на первый байт отображенных данных или NIL, если обращение к функции оказалось безуспешным.
После использования отображенных данных ресурсы окна отображения нужно освободить функцией:
function UnMapViewOfFile(lpBaseAddress: Pointer): BOOL;
единственный параметр обращения к которой должен содержать адрес первого отображенного байта, то есть адрес, возвращаемый функцией MapViewOfFile. Закрытие объекта отображения и самого файла осуществляется обращением к функции:
function CloseHandle(hObject: THandle).
В листинге 3 приводится текст модуля (FileInMemory.dpr), который создает окно, показанное на рис. 1.
Проект создает дисковый файл, состоящий из 100 тыс. случайных вещественных чисел (можно выбрать другую длину файла, если изменить значение редактора Длина массива). Файл с именем test.dat создается путем отображения файла в память (кнопка Память) и традиционным способом (кнопка Файл). В обоих случаях показывается время счета. Чем больше частота процессора и объем свободной оперативной памяти, тем больше будет разница во времени (листинг 3).
О таймере
Компонент Timer (таймер) служит для отсчета интервалов реального времени. Его свойство Interval определяет интервал временив миллисекундах , который должен пройти от включения таймера до наступления события OnTimer. Таймер включается при установке значения True в его свойство Enabled. Единожды включенный таймер все время будет возбуждать события OnTimer до тех пор, пока его свойство Enabled не примет значения False.
Следует учесть, что в силу специфики реализации стандартного аппаратного таймера IBM-совместимого компьютера минимальный реально достижимый интервал отсчета времени не может быть меньше 55 мс (этот интервал называется тиком), более того, любой интервал времени, отсчитываемый с помощью таймера, всегда кратен 55 мс. Чтобы убедиться в этом, проведите эксперимент, в котором подсчитывается среднее время между двумя срабатываниями таймера (Timer.dpr):
- Начните новый проект с пустой формой и положите на нее компонент TTimer.
- Установите в свойство Enabled таймера значение False.
- Напишите такой модуль главной формы (листинг 4):
Необходимость нескольких (MaxCount) срабатываний для точного усреднения результата связана с тем, что системные часы обновляются каждые 55 мс. После запуска программы и ввода 1 как требуемого периода срабатывания в редакторе mmOutput вы увидите строку
Задано 1 ms. Получено 55 ms.
в которой указывается, какое реальное время разделяет два соседних события OnTimer. Если вы установите период таймера в диапазоне от 56 до 110 мс, в строке будет указано 110 ms и т.д. (в силу дискретности обновления системных часов результаты могут несколько отличаться в ту или иную сторону).
В ряде практически важных областей применения (при разработке игр, в системах реального времени для управления внешними устройствам и т.п.) интервал 55 мс может оказаться слишком велик. Современный ПК имеет мультимедийный таймер, период срабатывания которого может быть от 1 мс и выше, однако этот таймер не имеет компонентного воплощения, поэтому для доступа к нему приходится использовать функции API.
Общая схема его использования такова. Сначала готовится процедура обратного вызова (call back) с заголовком:
procedure TimeProc(uID, uMsg: UINT; dwUser, dw1, dw2: DWORD); stdcall;
Здесь uID — идентификатор события таймера (см. об этом ниже); uMsg — не используется; dwUser — произвольное число, передаваемое процедуре в момент срабатывания таймера; dw1, dw2 — не используются.
Запуск таймера реализуется функцией:
function timeSetEvent(uDelay, uResolution: UINT; lpTimeProc: Pointer; dwUser: DWORD; fuEvent: UINT): UINT; stdcall; external 'winmm.dll';
Здесь uDelay — необходимый период срабатывания таймера (в мс); uResolution — разрешение таймера (значение 0 означает, что события срабатывания таймера будут возникать с максимально возможной частотой; в целях снижения нагрузки на систему вы можете увеличить это значение); lpTimeProc — адрес процедуры обратного вызова; dwUser — произвольное число, которое передается процедуре обратного вызова и которым программист может распоряжаться по своему усмотрению; fuEvent — параметр, управляющий периодичностью возникновения события таймера: TIME_ONESHOT (0) — событие возникает только один раз через uDelay миллисекунд; TIME_PERIODIC (1) — события возникают периодически каждые uDelay мс. При успешном обращении функция возвращает идентификатор события таймера и 0, если обращение было ошибочным.
Таймер останавливается, и связанные с ним системные ресурсы освобождаются функцией:
function timeKillEvent(uID: UINT): UINT; stdcall; external 'winmm.dll';
Здесь uID — идентификатор события таймера, полученный с помощью timeSetEvent.
В следующем примере (Timer.dpr) иллюстрируется использование мультимедийного таймера (листинг 5).
КомпьютерПресс 5'2001