Глава 15. Объекты печати
Один из наиболее трудных аспектов программирования в Windows
- это вывод на принтер. ObjectWindows путем использования ряда
модулей и инкапсуляции поведения устройства печати и самой распе-
чатки делает печать более простой.
Данная глава описывает основы процесса печати, после чего
описываются следующие задачи:
* Построение объекта принтера.
* Создание распечатки.
- Печать документа.
- Печать содержимого окна.
* Передача распечатки на принтер.
* Выбор другого принтера.
* Настройка конфигурации принтера.
Почему печать представляет трудности?
-----------------------------------------------------------------
С одной стороны печать в Windows достаточно проста. Вы може-
те использовать для генерации распечатки те же функции GDI, что
используются для вывода образов на экран. Для вывода текста ис-
пользуется функция TextOut, а для вычерчивания прямоугольника -
Rectangle.
С другой стороны, процесс осложняется, так как Windows тре-
бует непосредственного "общения" с драйверами принтера через вы-
зовы Escape или получения адреса DeviceMode или ExtDeviceMode.
Это еще более осложняется требованием Windows, чтобы приложение
считывало имя драйвера устройства из файла WIN.INI. Кроме того,
устройства печати обладают большими возможностями проверки допус-
тимости и возможностями разрешения, чем видеоустройства.
ObjectWindows не может преодолеть все препятствия на этом
пути, но делает процесс печати более простым и легким для понима-
ния.
Печать в ObjectWindows
-----------------------------------------------------------------
Модуль ObjectWindows OPrinter предусматривает для упрощения
печати два объекта - TPrinter и TPrintout. TPrinter инкапсулирует
доступ к устройствам печати. Он предоставляет возможность конфи-
гурирования принтера, выводя диалог, в котором пользователь может
выбрать нужный принтер, а также установить параметры печати, та-
кие как графическое разрешение или ориентация (горизонтальная и
вертикальная) печати.
TPtintout инкапсулирует задачу печати документа. К принтеру
этот объект имеет такое же отношение, как TWindow - к экрану. Ри-
сование на экране выполняется методом Paint объекта TWindow, а
печать на принтере - методом PrintPage объекта TPrintout. Чтобы
напечатать что-то на принтере, приложение должно передать методу
Print объекта TPrinter экземпляр TPrintout.
Построение объекта принтера
-----------------------------------------------------------------
В большинстве случаев приложению требуется в каждый момент
времени доступ только к одному принтеру. Простейшим способом реа-
лизации этого является задание в объекте основного окна поля с
именем Printer (типа PPrinter), которые другие объекты в програм-
ме вызывают для целей печати. Чтобы сделать принтер доступным,
поле Printer должно указывать на экземпляр TPrinter.
В большинстве приложений это просто. Основное окно приложе-
ния инициализирует объект принтера, который использует заданный
по умолчанию принтер, указанный в WIN.INI:
constructor TSomeWindow.Init(AParent: PWindowsObject;
ATitle: PChar);
begin
Inherited Init(AParent, ATitle);
.
.
.
Printer := New(PPrinter, Init);
end;
В некоторых случаях может возникать необходимость использо-
вать в приложении различные принтеры одновременно из различных
окон. В этом случае постройте объект принтера в конструкторах
каждого такого окна, затем смените устройство принтера на один
или более таких принтеров. Если программа использует различные
принтеры, но не одновременно, то возможно лучше использовать один
и тот же объект принтера и при необходимости выбирать различные
принтеры.
Хотя вы можете сомневаться насчет переопределения конструк-
тора TPrinter для использования принтера, отличного от заданного
в системе по умолчанию, рекомендуемой процедурой является исполь-
зование конструктора по умолчанию, а затем смена связанного с
объектом устройства. См. раздел "Выбор другого принтера".
Создание распечатки
-----------------------------------------------------------------
Единственной "хитрой" частью процесса печати в ObjectWindows
являются создание распечатки. Этот процесс аналогичен написанию
метода Paint для объекта окна: вы используете графические функции
Windows для генерации в контексте устройства нужного графического
образа. Контекст устройства оконного объекта обрабатывает ваши
взаимодействия с устройством экрана; аналогичным образом контекст
устройства распечатки изолирует вас от устройства печати.
Примечание: Графические функции Windows поясняются в
Главе 17.
Чтобы создать объект распечатки, постройте новый тип, произ-
водный от TPtintout, который переопределяет PrintPage. В очень
простых случаях это все, что требуется сделать. Если документ
имеет размер более одной страницы, то вам нужно также переопреде-
лить HasNextPage для возврата True. Текущий номер страницы пере-
дается в качестве параметра PrintPage.
Объект распечатки имеет поля, содержащие размер страницы и
контекст устройства, который уже инициализирован для принтера.
Объект принтера устанавливает значения, вызывая метод объекта
распечатки SetPrintParams. Используйте контекст устройства объек-
та распечатки в любом вызове графических функций Windows.
Модуль OPrinter включает в себя два специализированных объ-
екта распечатки, которые показывают диапазон сложности распеча-
ток. Объект TWindowPrintout, печатающий содержимое окна, очень
прост. TEditPrintout, который печатает содержимое управляющего
элемента редактирования, очень сложен, так как имеет множество
возможностей.
Печать документа
-----------------------------------------------------------------
Windows рассматривает распечатку как последовательность
страниц, поэтому задачей вашего объекта распечатки является прев-
ращение документа в последовательность станичных образов для пе-
чати Windows. Аналогично тому как оконные объекты используются
для отображения образов на экране дисплея, объекты распечатки ис-
пользуются для печати образов на принтере.
ObjectWindows предусматривает абстрактный объект распечатки
TPrintout, из которого вы можете создать производные объекты рас-
печатки. Вам нужно переопределить в TPrintout только несколько
методов.
Ваши объекты распечатки должны делать следующее:
* Устанавливать параметры принтера.
* Подсчитывать страницы.
* Отображать каждую страницу в контексте устройства.
* Указывать, есть ли еще страницы.
Остальная часть этой главы ссылается на пример программы
PrnTest, записанной на ваших дистрибутивных дискетах под именем
PRNTEST.PAS. PrnTest считывает текстовый файл в набор строк, а
затем по команде печатает документ. Объект PrnTest описывается
следующим образом:
type
PTextPrint = ^TTextPrint;
TTextPrint = object(TPrintout);
TextHeight, LinesPerPage, FirstOnPage, LastOnPage:
Integer;
TheLines; PCollection;
constructor Init(ATitle: PChar;
TheText: PPCharCollection);
function GetDialogInfo(var Pages: Intger): Boolean;
virtual;
function HasNextPage(Page: Word): Boolean; virtual;
procedure SetPrintParams(ADC: HDC; ASize: TPoint);
virtual;
procedure PrintPage(Page: Word; var Rect: TRect;
Flags: Word); virtual;
end;
Задание параметров печати
-----------------------------------------------------------------
Перед запросом распечатки документа объект принтера предос-
тавляет вашему документу возможность разбивки на страницы. Для
этого вызываются два метода объекта распечатки - SetPrintParams и
GetDialogInfo.
Функция SetPrintParams предназначена для инициализации
структур данных, которые могут потребоваться для получения эффек-
тивной распечатки отдельных страниц. SetPrintParams - это первая
возможность вашей распечатки обратиться к контексту устройства и
задать для принтера размер страницы. Приводимый ниже фрагмент
программы показывает пример переопределенного метода
SetPrintParams.
Если вы переопределяете SetPrintParams, убедитесь в вызове
наследуемого метода, устанавливающего значения полей распечатки
объекта.
Подсчет страниц
-----------------------------------------------------------------
После вызова SetPrintParams объект печати вызывает булевскую
функцию GetDialogInfo. GetDialogInfo задает информацию, необходи-
мую для вывода на экран диалогового блока, позволяющего пользова-
телю выбрать перед печатью диапазон страниц. В подсчете страниц в
GetDialogInfo и индикации вывода диалогового блока имеются два
аспекта.
Функция GetDialogInfo воспринимает единственный параметр-пе-
ременную Pages, которую она должна устанавливать в число страниц
в документе или в 0, если она не может подсчитать страницы. Возв-
ращаемое значение равно True, если вы хотите вывести диалоговый
блок, и False для подавления его вывода.
По умолчанию GetDialogInfo устанавливает Pages в 0 и возвра-
щает True, что означает, что она не знает, сколько может полу-
читься страниц, и что перед печатью будет выводиться диалоговый
блок. GetDialogInfo обычно переопределяется для установки Pages в
число страниц в документе и возвращает True.
Например, PrnTest подсчитывает, сколько строк текста выбран-
ного шрифта может поместиться в области печати в SetPrintParams,
а затем использует это число для подсчета количества страниц, ко-
торые нужно напечатать в GetDialogInfo:
procedure TTextPrint.SetPrintParams(ADC: HDC;
ASize: TPoint);
var TextMetrics: TTextMetric;
begin
inherited SetPrintParams(ADC, ASize); { установить DC и
размер Size }
GetTextMetrics(DC, TextMetrics); { получить информацию о
размере текста }
TextHeigh := TextMetrics.tmHeight; { вычислить высоту
строки }
LinesPerPages := Size.Y div TextHeight; { и число строк
на странице }
end;
function TTextPtint.GetDialogInfo(var Pages: Integer):
Boolean);
begin
Pages:= TheLines^.Count div LinesPerPage + 1;
GetDialogInfo := True { вывод перед печатью диалогового
блоки }
end;
Печать каждой страницы
-----------------------------------------------------------------
Когда объект принтера предоставляет возможность разбивки до-
кумента на страницы, то для печати каждой страницы он вызывает
метод PrintPage объекта распечатки. Процесс распечатки только
части документа, которому принадлежит данная страница, аналогичен
определению того, какую часть прокручиваемого окна нужно отобра-
жать на экране. Например, можно отметить подобие методов отобра-
жения окна и отображения страницы:
procedure TTextWindow.Paint(PaintDC: HDC;
var PaintInfo: TPaintStruct);
var
Line: Integer;
TextMetrics: TTextMetric;
TheText: PChar;
function TextVisible(ALine: Integer): Boolean;
begin
with Scroller^ do
TextVisible := IsVisible(0, (ALine div YUnit) +
YPos, 1, Attr.W div YUnit);
end;
begin
GetTextMetrics(PaintDC, TextMetrics);
Scroller^.SetUnits(TextMetrics.tmAveCharWidth,
TextMetrics.tmHeight);
Line := 0;
while (Line < FileLines^.Count) and TextVisible(Line) do
begin
TheText := PChar(FileLines^.At(Line));
if TheText <> nil then
TextOut(PaintDC, 0, Line * Scroller^.YUnit, TheText,
StrLen(TheText));
Inc(Line);
end;
end;
procedure TTextPrint.PrintPage(Page: Word; var Rect: TRect;
Flags: Word);
var
Line: Integer;
TheText: PChar;
begin
FirstOnPage := (Page - 1) * LinesPerPage;
LastOnPage := (Page * LinesPerPage) - 1;
if LastOnPage >= TheLines^.Count then
LastOnPage := TheLines^.Count - 1;
for Line := FirstOnPage to LastOnPage do
begin
TheText := Theines^.At(Line);
if TheText <> nil then
TextOut(DC, 0, (Line - FirstOnPage) * TextHeight,
TheText, StrLen(TheText));
end;
end;
При написании методов PrintPage следует иметь в виду следую-
щее:
* Независимость от устройств. Убедитесь, что в вашем коде не
делается предположений относительно масштаба, коэффициента
относительного удлинения или цветах. Для различных уст-
ройств видеоотображения и печати эти характеристики могут
отличаться, так что в программе следует избегать такой за-
висимости.
* Возможности устройств. Хотя большинство видеоустройств
поддерживают все операции GDI, некоторые принтеры этого не
делают. Например, многие устройства печати (такие как гра-
фопостроители) совсем не воспринимают графических изобра-
жений. При выполнении сложных задач вывода в программе
следует вызывать функцию API Windows GetDeviceCaps, кото-
рая возвращает важную информацию о данном устройстве выво-
да.
Указание оставшихся страниц
-----------------------------------------------------------------
У объектов распечатки имеется также последняя обязанность -
указать объекту принтера, имеются ли после данной страницы еще
печатаемые страницы. Метод HasNextPage воспринимает в качестве
параметра номер строки и возвращает значение Boolean, указываю-
щее, существуют ли еще страницы. По умолчанию HasNextPage всегда
возвращает значение False. Чтобы напечатать несколько страниц,
ваши объекты распечатки должны переопределять HasNextPage для
возврата True, если документ имеет больше страниц для печати, и
False, если переданным параметром является последняя страница.
Например, PrnTest сравнивает номер последней напечатанной
строки с последней строкой в файле и определяет, нужно ли печа-
тать еще страницы:
function TTextPrint.HasNextPage(Page: Word): Boolean;
begin
HasNextPage := LastOnPage < TheLines^.Count - 1;
end;
Убедитесь, что HasNextPage возвращает в некоторой точке зна-
чение False. Если HasNextPage всегда возвращает True, то процесс
печати попадет в бесконечный цикл.
Другие соглашения по печати
-----------------------------------------------------------------
Объекты распечатки содержат также несколько других методов,
которые вы можете при необходимости переопределить. Методы
BeginPrintint и EndPrinting вызываются, соответственно, перед пе-
чатью и после печати любого документа. Если вам требуется специ-
альная установка, вы можете выполнить ее в BeginPrinting и отме-
нить в EndPrinting.
Печать страниц выполняется последовательно. То есть, для
каждой страницы в последовательности принтер вызывает метод
PrintPage. Однако, перед первым вызовом PrintPage объект принтера
вызывает BeginDocument, передавая номер первой и последней стра-
ницы, которые будут печататься. Если для вашего документа при пе-
чати страниц, отличных от первой, требуется специальная подготов-
ка, переопределите метод BeginDocument. После распечатки послед-
ней страницы вызывается соответствующий метод EndDocument.
Может потребоваться также переопределение метода
GetSelection. GetSelection указывает в своем возвращаемом булевс-
ком значении, имеет ли документ выделенную часть. Если это так,
диалоговое окно печати предоставляет вам возможность распечатать
только эту выделенную часть. Позицию выделения указывают два па-
раметра-переменных Start и Stop. Например, TEditPrintout интерп-
ретирует Start и Stop как позиции символов, но может представлять
также строки текста, страницы и т.д.
Печать содержимое окна
-----------------------------------------------------------------
Поскольку окно не разбивается на несколько страниц, и окон-
ные объекты уже знают, как отображаться в контексте устройства,
простейшим видом генерируемой распечатки является копия содержи-
мого окна.
Чтобы еще более облегчить эту общую операцию, ObjectWindows
обеспечивает дополнительный вид объекта распечатки -
TWindowPrint. Любой оконный объект ObjectWindows может без моди-
фикации печатать свое содержимое в объект TWindowPrintout. Объек-
ты распечатки масштабируют образ для заполнения нужного числа
страниц и поддерживают коэффициент относительного удлинения.
Создание объекта распечатки окна требует только одного шага.
Все, что требуется сделать - это построить объект печати окна,
передавая ему строку заголовка и указатель на окно, которое тре-
буется напечатать:
PImage := New(PWindowPrintout, Init('Заголовок',
PSomeWindow));
Часто возникает необходимость в том, чтобы окно само созда-
вало свою распечатку, возможно в ответ на команды меню:
procedure TSomeWindow.CMPrint(var Msg: TMessage);
var P: PPrintout;
begin
P := New(PWindowPrintout, Init('Дамп экрана', @Self));
{ передать образ на экран }
Dispose(P, One);
end;
TWindowPrintout не предусматривает разбивки на страницы. При
печати документа вам нужно печатать каждую страницу отдельно, но
так как окна не имеют страниц, вам нужно напечатать только один
образ. Окно уже знает, как создать этот образ - оно имеет метод
Paint. TWindowsPrintout печатается путем вызова метода Paint окна
объекта с контекстом устройства печати вместо контекста дисплея.
Вывод распечатки на принтер
-----------------------------------------------------------------
При наличии объекта принтера и объекта распечатки фактичес-
кая печать выполняется достаточно просто, независимо от того,
происходит это из документа или из окна. Все, что вам нужно сде-
лать - это вызов метода Paint объекта принтера, передача указате-
ля на порождающее окно и указателя на объект распечатки:
Printer^.Print(PParentWindow, PPrintoutObject);
В этом случае порождающее окно - это окно, к которому будут
присоединены все всплывающие диалоговые блоки (например, состоя-
ния принтера или сообщений об ошибках). Обычно это будет окно,
генерирует команду печати, такое как основное окно со строкой ме-
ню. В процессе печати для предотвращения передачи множественных
команд печати это окно запрещается.
Предположим, у вас есть приложение, основное окно которого
является экземпляром TWidGetWindow. В меню этого окна вы можете
выбрать команду меню для печати содержимого окна, генерирующую
команду cm_Print. Метод реакции на сообщения может иметь следую-
щий вид:
procedure TWidgetWindow.CMPrint(var Msg: TMessage);
var P: PPrintout;
begin
P := New(PWindowPrint, Init('Widgets', @Self));
Printer^.Print(@Self, P);
Dispose(P, Done);
end;
Выбор другого принтера
-----------------------------------------------------------------
Когда у вас в приложении есть объект принтера, вы можете
связать его с любым установленным в Windows устройстве печати. По
умолчанию TPrinter использует заданный по умолчанию принтер
Windows (как это определено в разделе устройств файла WIN.INI).
Существует два способа задания альтернативного принтера: не-
посредственно в программе и через диалоговое окно пользователя.
Выбор принтера пользователем
-----------------------------------------------------------------
Наиболее общим способом назначения другого принтера является
вывод диалогового окна, предоставляющего пользователю возможность
выбора из списка установленных устройств печати. TPtinter делает
это автоматически при вызове его метода Setup. Как показано на
Рис. 15.1, Setup использует для этого диалогового окна объект
TPrinterSetupDlg.
+---------------------------------------------------------------+
|#=#XXXXXXXXXXXXXXXXXXXSelectPrinterXXXXXXXXXXXXXXXXXXXXXXXXXXXX|
+---------------------------------------------------------------|
| |
| Printer and port: |
| +--------------------------------------------+-+ |
| |PostScript Printer on LPT1: |v| |
| +--------------------------------------------+-+ |
| +-----------+ +-----------+ +-----------+ |
| |####OK#####| |##Setup####| |##Cancel###| |
| +-----------+ +-----------+ +-----------+ |
| |
+---------------------------------------------------------------+
Рис. 15.1 Диалоговое окно задания принтера.
Настройка конфигурации принтера
Одна из командных кнопок в диалоге выбора принтера позволяет
пользователям изменить конфигурацию конкретного принтера. Кнопка
Setup выводит диалоговый блок конфигурации, определенной в драй-
вере принтера. Ваше приложение не может управлять внешним видом
или функциями диалогового блока конфигурации драйвера.
Назначение конкретного принтера
-----------------------------------------------------------------
В некоторых случаях вам может потребоваться назначить для
своего объекта принтера специфическое устройство печати. TPrinter
имеет метод SetDevice, который именно это и делает.
SetDevice воспринимает в качестве параметров три строки: имя
устройства, имя драйвера и имя порта.
Назад | Содержание | Вперед