Один из наиболее трудных аспектов программирования в Windows - это вывод на принтер. ObjectWindows путем использования ряда модулей и инкапсуляции поведения устройства печати и самой распечатки делает печать более простой.
    Данная глава описывает основы процесса печати, после чего описываются следующие задачи:
    С одной стороны печать в Windows достаточно проста. Вы
можете использовать для генерации распечатки те же функции GDI, что
используются для вывода образов на экран. Для вывода текста
используется функция TextOut, а для вычерчивания прямоугольника
Rectangle.
    С другой стороны, процесс осложняется, так как Windows
требует непосредственного "общения" с драйверами принтера через
вызовы Escape или получения адреса DeviceMode или ExtDeviceMode.
Это еще более осложняется требованием Windows, чтобы приложение
считывало имя драйвера устройства из файла WIN.INI. Кроме того,
устройства печати обладают большими возможностями проверки
допустимости и возможностями разрешения, чем видеоустройства.
    ObjectWindows не может преодолеть все препятствия на этом
пути, но делает процесс печати более простым и легким для
понимания.
    Модуль ObjectWindows OPrinter предусматривает для упрощения
печати два объекта - TPrinter и TPrintout. TPrinter инкапсулирует
доступ к устройствам печати. Он предоставляет возможность
конфигурирования принтера, выводя диалог, в котором пользователь может
выбрать нужный принтер, а также установить параметры печати,
такие как графическое разрешение или ориентация (горизонтальная и
вертикальная) печати.
    TPtintout инкапсулирует задачу печати документа. К принтеру
этот объект имеет такое же отношение, как TWindow - к экрану.
Рисование на экране выполняется методом Paint объекта TWindow, а
печать на принтере - методом PrintPage объекта TPrintout. Чтобы
напечатать что-то на принтере, приложение должно передать методу
Print объекта TPrinter экземпляр TPrintout.
    В большинстве случаев приложению требуется в каждый момент
времени доступ только к одному принтеру. Простейшим способом
реализации этого является задание в объекте основного окна поля с
именем Printer (типа PPrinter), которые другие объекты в
программе вызывают для целей печати. Чтобы сделать принтер доступным,
поле Printer должно указывать на экземпляр TPrinter.
    В большинстве приложений это просто. Основное окно
приложения инициализирует объект принтера, который использует заданный
по умолчанию принтер, указанный в WIN.INI:
    В некоторых случаях может возникать необходимость
использовать в приложении различные принтеры одновременно из различных
окон. В этом случае постройте объект принтера в конструкторах
каждого такого окна, затем смените устройство принтера на один
или более таких принтеров. Если программа использует различные
принтеры, но не одновременно, то возможно лучше использовать один
и тот же объект принтера и при необходимости выбирать различные
принтеры.
    Хотя вы можете сомневаться насчет переопределения
конструктора 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 описывается
следующим образом:
    Перед запросом распечатки документа объект принтера
предоставляет вашему документу возможность разбивки на страницы. Для
этого вызываются два метода объекта распечатки - 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:
    Когда объект принтера предоставляет возможность разбивки
документа на страницы, то для печати каждой страницы он вызывает
метод PrintPage объекта распечатки. Процесс распечатки только
части документа, которому принадлежит данная страница, аналогичен
определению того, какую часть прокручиваемого окна нужно
отображать на экране. Например, можно отметить подобие методов
отображения окна и отображения страницы:
    При написании методов PrintPage следует иметь в виду
следующее:
    * Независимость от устройств. Убедитесь, что в вашем коде не
делается предположений относительно масштаба, коэффициента
относительного удлинения или цветах. Для различных
устройств видеоотображения и печати эти характеристики могут
отличаться, так что в программе следует избегать такой
зависимости.
    * Возможности устройств. Хотя большинство видеоустройств
поддерживают все операции GDI, некоторые принтеры этого не
делают. Например, многие устройства печати (такие как
графопостроители) совсем не воспринимают графических
изображений. При выполнении сложных задач вывода в программе
следует вызывать функцию API Windows GetDeviceCaps,
которая возвращает важную информацию о данном устройстве
вывода.
    У объектов распечатки имеется также последняя обязанность
указать объекту принтера, имеются ли после данной страницы еще
печатаемые страницы. Метод HasNextPage воспринимает в качестве
параметра номер строки и возвращает значение Boolean,
указывающее, существуют ли еще страницы. По умолчанию HasNextPage всегда
возвращает значение False. Чтобы напечатать несколько страниц,
ваши объекты распечатки должны переопределять HasNextPage для
возврата True, если документ имеет больше страниц для печати, и
False, если переданным параметром является последняя страница.
    Например, PrnTest сравнивает номер последней напечатанной
строки с последней строкой в файле и определяет, нужно ли
печатать еще страницы:
    Убедитесь, что HasNextPage возвращает в некоторой точке
значение False. Если HasNextPage всегда возвращает True, то процесс
печати попадет в бесконечный цикл.
    Объекты распечатки содержат также несколько других методов,
которые вы можете при необходимости переопределить. Методы
BeginPrintint и EndPrinting вызываются, соответственно, перед
печатью и после печати любого документа. Если вам требуется
специальная установка, вы можете выполнить ее в BeginPrinting и
отменить в EndPrinting.
    Печать страниц выполняется последовательно. То есть, для
каждой страницы в последовательности принтер вызывает метод
PrintPage. Однако, перед первым вызовом PrintPage объект принтера
вызывает BeginDocument, передавая номер первой и последней
страницы, которые будут печататься. Если для вашего документа при
печати страниц, отличных от первой, требуется специальная
подготовка, переопределите метод BeginDocument. После распечатки
последней страницы вызывается соответствующий метод EndDocument.
    Может потребоваться также переопределение метода
GetSelection. GetSelection указывает в своем возвращаемом
булевском значении, имеет ли документ выделенную часть. Если это так,
диалоговое окно печати предоставляет вам возможность распечатать
только эту выделенную часть. Позицию выделения указывают два
параметра-переменных Start и Stop. Например, TEditPrintout
интерпретирует Start и Stop как позиции символов, но может представлять
также строки текста, страницы и т.д.
    Поскольку окно не разбивается на несколько страниц, и
оконные объекты уже знают, как отображаться в контексте устройства,
простейшим видом генерируемой распечатки является копия
содержимого окна.
    Чтобы еще более облегчить эту общую операцию, ObjectWindows
обеспечивает дополнительный вид объекта распечатки
TWindowPrint. Любой оконный объект ObjectWindows может без
модификации печатать свое содержимое в объект TWindowPrintout.
Объекты распечатки масштабируют образ для заполнения нужного числа
страниц и поддерживают коэффициент относительного удлинения.
    Создание объекта распечатки окна требует только одного шага.
Все, что требуется сделать - это построить объект печати окна,
передавая ему строку заголовка и указатель на окно, которое
требуется напечатать:
    Часто возникает необходимость в том, чтобы окно само
создавало свою распечатку, возможно в ответ на команды меню:
    TWindowPrintout не предусматривает разбивки на страницы. При
печати документа вам нужно печатать каждую страницу отдельно, но
так как окна не имеют страниц, вам нужно напечатать только один
образ. Окно уже знает, как создать этот образ - оно имеет метод
Paint. TWindowsPrintout печатается путем вызова метода Paint окна
объекта с контекстом устройства печати вместо контекста дисплея.
    При наличии объекта принтера и объекта распечатки
фактическая печать выполняется достаточно просто, независимо от того,
происходит это из документа или из окна. Все, что вам нужно
сделать - это вызов метода Paint объекта принтера, передача
указателя на порождающее окно и указателя на объект распечатки:
    В этом случае порождающее окно - это окно, к которому будут
присоединены все всплывающие диалоговые блоки (например,
состояния принтера или сообщений об ошибках). Обычно это будет окно,
генерирует команду печати, такое как основное окно со строкой
меню. В процессе печати для предотвращения передачи множественных
команд печати это окно запрещается.
    Предположим, у вас есть приложение, основное окно которого
является экземпляром TWidGetWindow. В меню этого окна вы можете
выбрать команду меню для печати содержимого окна, генерирующую
команду cm_Print. Метод реакции на сообщения может иметь
следующий вид:
    Когда у вас в приложении есть объект принтера, вы можете
связать его с любым установленным в Windows устройстве печати. По
умолчанию TPrinter использует заданный по умолчанию принтер
Windows (как это определено в разделе устройств файла WIN.INI).
    Существует два способа задания альтернативного принтера:
непосредственно в программе и через диалоговое окно пользователя.
Почему печать представляет трудности?
Печать в ObjectWindows
Построение объекта принтера
constructor TSomeWindow.Init(AParent: PWindowsObject;
ATitle: PChar);
begin
Inherited Init(AParent, ATitle);
.
.
.
Printer := New(PPrinter, Init);
end;
Создание распечатки
Печать документа
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;
Задание параметров печати
Подсчет страниц
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;
Печать каждой страницы
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;
Указание оставшихся страниц
function TTextPrint.HasNextPage(Page: Word): Boolean;
begin
HasNextPage := LastOnPage < TheLines^.Count - 1;
end;
Другие соглашения по печати
Печать содержимое окна
PImage := New(PWindowPrintout, Init('Заголовок',
PSomeWindow));
procedure TSomeWindow.CMPrint(var Msg: TMessage);
var P: PPrintout;
begin
P := New(PWindowPrintout, Init('Дамп экрана', @Self));
{ передать образ на экран }
Dispose(P, One);
end;
Вывод распечатки на принтер
Printer^.Print(PParentWindow, PPrintoutObject);
procedure TWidgetWindow.CMPrint(var Msg: TMessage);
var P: PPrintout;
begin
P := New(PWindowPrint, Init('Widgets', @Self));
Printer^.Print(@Self, P);
Dispose(P, Done);
end;
Выбор другого принтера