В следующих трех шагах вы узнаете как
    Возможно, вы удивитесь, узнав, что графика и текст,
которые
вы рисуете в окне с помощью функций Windows типа TextOut или
LineTo, исчезают при изменении размера окна или повторного вывода
его на экран. После того, как графические данные переданы
Windows, через вызовы функций Windows вы никогда не получаете их
обратно для повторного отображения.
    Чтобы в окне повторно отображалась графика, вы должны
сохранить эту графику (или данные для регенерации графики) в структуре
некоторого типа, такой как объект. С помощью объектов вы можете
полиморфически хранить простую или сложную графику, а также
хранить объекты и поля ваших оконных объектов.
    Существует два способа получения в окне графического образа.
С рисованием вы уже знакомы по предыдущим примерам. При рисовании
вы создаете графический образ в реальном времени в ответ на ввод
данных пользователем. Но окно не может требовать от пользователя
воссоздания графики при обновлении экрана.
    Окна должны иметь возможность воссоздавать по запросу свои
графические образы. Windows сообщает своим оконным объектам,
когда они требуют изображения или обновления. При этом окно должно
каким-то образом генерировать образ экрана. В ответ на
необходимость изображения. ObjectWindows автоматически вызывает метод
Paint вашего окна. Наследуемый и TWindow метод Paint не выполняет
никаких функций. В Paint вы должны поместить код для передачи
содержимого окна. Фактически Paint вызывается при первом выводе
окна. Paint отвечает за обновление (при необходимости) изображения
текущим содержимым.
    Существует еще одно важное отличие между отображением
графики в методе Paint и другим ее отображением (например, в ответ на
действия "мышью"). Содержимое экрана, которое должно
использоваться для отображения, передается в параметре PaintDC, так что
вашей программе не требуется получать или освобождать его. Однако
вам потребуется вновь выбрать для PaintDC изобразительные
средства.
    Чтобы отобразить содержимое окна, вместо повторения
тех действий, которые привели к первоначальному изображению (DragDC),
вы используете PaintDC. Визуальный эффект будет тот же, что и при
первоначальном рисовании пользователем (аналогично проигрыванию
аудиозаписи концерта). Но чтобы "проигрывать" ее в методе Paint,
сначала вам нужно сохранить графику в виде объектов.
    Созданные программой Steps изображения на самом деле просто
представляют наборы различного числа линий. При каждой буксировке
"мыши" вы добавляете другую линию. А каждая линия - это на самом
деле определенный набор точек, соединенных линейными сегментами.
Чтобы сохранить и воспроизвести такие изображения, вам необходим
гибкий и расширяемый тип данных
    Для размещения неизвестного числа линий или точек прекрасно
подходит определенный в модуле Objects тип TCollection. Это набор
объектов, который может динамически расширяться, когда вы
добавляете другие элементы. Этим наборам не важно, что они включают,
поэтому вы можете использовать один и тот же механизм и для
рисунка (набора линий), и для линии (набора точек).
    Примечание: О наборах рассказывается в Главе 19
"Наборы".
    Концептуально вам нужно просто сделать так, чтобы окно знало
о своем содержимом, так что оно сможет обновить изображение. Окно
содержит рисунок, представляющий собой набор линий. Таким
образом, вам нужно:
    Чтобы сохранить рисунок в виде набора линий, добавьте в
TStepWindow поле с именем Drawing. В любой момент Drawing
содержит текущий рисунок в виде набора объектов линий. Когда требуется
отобразить окно, оно использует для изображения линии данные,
записанные в Drawing.
    Далее нужно ответить на вопрос, что такое линия. В шаге 4 вы
видели, что изображаемая линия представляет собой просто набор
точек, передаваемых из Windows в программу через сообщение
wm_MouseMove. Для представления линий и точек вам необходимы
объектные типы. Поскольку эффективный объект изображения линии
должен быть повторно используемой частью, создайте отдельный модуль,
определяющий объекты линий и точек.
    TLine содержит всю информацию, необходимую для изображения
данной линии: перо и набор точек.
    LinePen просто указывает на объект TPen, а Point - это набор
объектов точек. TLine и TLinePoint содержат методы Load и Store,
преимущества использования которых для записи картинок на диск вы
увидите в шаге 8. В отличие от них объект TLine весьма прост:
конструктор и деструктор создают и уничтожают LinePen, AddPoint
включает объект точки в Points, а Draw рисует линии между точками
Points.
    Объект TLinePoint еще проще:
    TLinePoint не определяет никакого нового поведения - это
просто объект данных, который должен использоваться в TLine. Но
позднее (в шаге 8) он понадобиться как объект для записи в поток.
Не забудьте построить в TStepWindow.Init Drawing и уничтожить его
в TStepWindow.Done:
    Основное окно программы Steps содержит набор в своем поле
Drawing набор линий. Когда пользователь рисует линии, вы должны
преобразовывать их в объекты и добавлять в Drawing. Затем, когда
потребуется отобразить окно, путем итерации Drawing нужно
отобразить каждую его точку.
    Чтобы сохранять линии в виде объектов, вы должны изменить
EMLButtonDown и WMMouseMove, чтобы не только рисовать линии, но
также сохранять точки в наборе линий. Поскольку текущую линию
придется обновлять не только одному методу, добавьте в
TStepWindow еще одно поле типа PLine с именем CurrentLine:
    Кроме добавления изображения линии WMLButttonDown создает
при каждом вызове новый объект линии и добавляет его в набор в
Drawing. WMMouseMove просто добавляет новую точку в конец объекта
текущей линии и изображает в окне линейные сегменты. Сохраняя все
точки всех линий, ваше окно будет записывать информацию,
необходимую для точного воспроизведения картинки.
    Примечание: Уничтожать устаревшие CurrentLine не
требуется, поскольку они записаны в наборе Drawing. Все
объекты линий уничтожаются при уничтожении Drawing.
WMLButtonUp модификации не требует. Вам не нужно уничтожать
все объекты линий при очистке отображаемого окна, поэтому
добавьте в CMFileNew вызов метода FreeAll:
    Теперь, когда TStepWindow сохраняет свою текущую строку, вы
должны научить его по команде (этой командой является Paint)
рисовать ее. Давайте напишем для TStepWindow метод Paint, который
повторяет действия WMLButtonDown, WMMouseMove и WMLButtonUp.
Путем итерации по набору линий Paint воссоздает картинку аналогично
тому, как это делаете вы. Метод Paint имеет следующий вид (см.
файл STEP07.PAS):
    Примечание: Итерация методов описывается в Главе 19
"Наборы".
    Метод Draw объекта линии для изображения каждой линии между
точками также использует итератор ForEach:
    Теперь, когда вы сохранили представление данных в виде
рисунка как часть оконного объекта, можно легко записать эти данные
в файл (фактически, в буферизованный поток DOS) и считать его
обратно.
    Данный шаг посвящен добавлению полей объектов для записи
состояния сохраняемой информации, модификации сохраняемой в файле
информации и методам открытия. Используя предусмотренные в
ObjectWindows потоковые объекты, вы убедитесь в удобстве их
применения для сохранения данных в файле.
    Требуется отслеживать две характеристики рисунка. Изменение
файла мы уже отслеживали (в шаге 1 было добавлено поле
HasChanged), но теперь нужно знать, загружен ли файл в данный
момент. Как и HasChanged, IsNewFile - это атрибут TStepWindow типа
Boolean, поэтому его также следует сделать полем:
    Поле HasChanged принимает значение True, если текущий
рисунок модифицирован. Модификация означает, что рисунок был изменен
с момента последнего сохранения или не сохранялся вовсе. Вы уже
устанавливаете поле HasChanged в True, когда пользователь
начинает рисовать, и в False, когда окно очищается. Когда пользователь
открывает новый файл или сохраняет существующий, HasChanged
следует установить в False.
    IsNewFile указывает, что рисунок не взят из файла, поэтому
сохранение рисунка потребует от пользователя задать имя файла.
IsNeFile имеет значение True только при первоначальном запуске
приложения и после выбора пользователем команды меню File|New
(Файл|Новый). Это поле устанавливается в False, когда файл
открывается или сохраняется. Фактически, FileSave использует
IsNewFile, чтобы увидеть, можно ли сохранить файл немедленно, или
пользователю требуется выбрать файл из файлового диалога.
    Приведем методы сохранения и загрузки файла. На данный
момент они выполняют только сохранение и загрузку файлов.
Сохранение файла сконцентрировано в одном новом методе, который
называется WriteFile, а открытие файла выполняет метод ReadFile.
    Примечание: Данный текст программы можно найти в файле
STEP08A.PAS.
    Теперь, когда вы создали основную схему для построения и
загрузки файлов, осталось только выполнить фактическую загрузку и
сохранение в файле наборов точек. Для этого можно использовать
потоковый механизм автоматического сохранения объекта. Сначала вы
научитесь сохранять и загружать сами объекты точек и линий (как
это сделать для наборов вы уже знаете). Затем методы WriteFile и
FileOpen будут модифицированы для использования потоков.
    Примечание: Подробнее об использовании потоков с
объектами рассказывается в Главе 20 "Потоки".
    Ниже приведен исходный код, показывающий как сохранять и
загружать сами объекты TLine и TLinePoint:
    Для регистрации TCollection при запуске прикладной программы
вы должны вызывать StreamRegistration (который находится в
Steps). Вы можете поместить это вызов в метод TStepWindow.Init.
Модуль DrawLine регистрирует в своем коде инициализации
TLinePoint и TLine, поэтому линии и точки регистрируются простым
включением DrawLine в оператор uses.
    Заключительным шагом изменения методов WriteFile и ReadFile
будет фактическая запись в потоки и чтение из них (см.
STEP08B.PAS):
    Печать из Windows может представлять собой сложную задачу,
но ObjectWindows предоставляет простой механизм добавления
средств печати в вашу прикладную программу.
    Добавление этих средств предусматривает следующие три шага:
    Любая программа ObjectWindows может получить доступ к
принтеру с помощью объекта типа TPrinter. В этом случае основное окно
вашего приложения должно построить объект принтера и сохранить
его в объектном поле с именем Printer:
    Примечание: Тип TPrinter определен в модуле OPrinter,
поэтому не забудьте добавить OPrinter в свой оператор uses.
    Это все, что обычно приходится делать для инициализации
объекта принтера. По умолчанию TPrinter использует назначенный по
умолчанию принтер, заданный в файле WIN.INI. TPrinter
предусматривает также механизм для выбора альтернативных принтеров.
    ObjectWindows управляет выводимыми на печать данными точно
также, как выводом на экран. То есть вместо записи
непосредственно на устройство вывода (или даже непосредственно в Windows) вы
направляете свой вывод на объект, который знает, как представлять
информацию на устройстве вывода. Для вывода на печать используйте
объекты, полученные из абстрактного типа TPrintout и метода с
именем PrintPage.
    Существует два основных случая, с которыми вы будете иметь
дело при генерации из приложения Windows данных для печати:
печать документов и печать содержимого окна. Печать содержимого
окна проще, поскольку окна уже "знают" о своем представлении. Как
оказывается на самом деле, это просто особый случай печати
документа.
    Примечание: О печати документов рассказывается в Главе 15.
    До настоящего момента вы всегда записывали текст и графику в
контекст дисплея (что является способом представления в Windows
пользовательской области окон). Контекст дисплея - это просто
специализированная версия контекста устройства - механизма, через
который приложения Windows работают с различными устройствами,
такими как экраны, принтеры или коммуникационные устройства.
    Запись в контекст одного устройства мало отличается от
записи в контекст другого, поэтому, например, достаточно просто
сообщить, например, оконному объекту, что для записи на принтер нужно
использовать механизм Paint.
    TWindowPrint - это специальный потомок TPrintout,
используемый для печати содержимого окна. Печать содержимого окна не
представляет сложности по двум причинам: вы имеете дело только с
одной страницей, и объект окна уже знает, как отображать свой
образ.
    Обычно при печати документа вашей прикладной программе нужно
выполнять итерации процесса печати для каждой страницы документа.
Так как окно имеет только один образ, при печати окна это не
является необходимым.
    Таким образом, печать также проста, как ориентация метода
Paint объекта окна на вывод вместо окна в контекст устройства,
подходящий для принтера:
    Поскольку переданный PrintPage параметр DC уже указывает на
подходящий для принтера контекст устройства, в простейшем случае
PrintPage должен только сообщить объекту окна на вывод его
содержимого в этот контекст устройства. Теперь ваш объект распечатки
также знает о своем представлении.Шаг 7: Вывод на экран графики
Изображение и рисование
Сохранение графики в объектах
Добавление поля объекта
Определение объекта линии
type
PLine = ^TLine;
TLine = object(TObject)
Points: PCollection;
LinePen: PPen;
constructor Init(APen: PPen);
constructor Load(var S: TStream);
destructor Done; virtual;
procedure AddPoint(AX, AY: Word);
procedure Draw(ADC: HDC);
procedure Store(var S: TStream);
end;
type
PLinePoint = ^TLinePoint;
TLinePoint = object(TObject)
X, Y: Integer;
constructor Init(AX, AY: Integer);
constructor Load(var S: TStream);
procedure Store(var S: TStream);
end;
constructor TLinePoint.Init(AX, AY: Integer);
begin
X := AX;
Y := AY;
end;
constructor TStepWindow.Init(AParent: PWindowsObject;
ATitle: PChar);
begin
inherites Init(AParent, ATitle);
ButtonDown := False;
HasChanged := False;
CommonPen := New(PPen, Init(ps_Solid, 1, 0));
Drawing := New(PCollection, Init(50, 50));
end;
destructor TStepWindow.Done;
begin
Dispose(CommonPen, Done);
Dispose(Drawing, Done);
inherited Done;
end;
Изменение методов работы с "мышью"
type
TStepWindow = object(TWindow);
CurrentLine: PLine;
.
.
.
end;
procedure TStepWindow.WMLButtonDown(var Msg: TMessage);
begin
if not ButtonDown then
begin
ButtonDown := True;
SetCapture(HWindow);
DragDC := GetDC(HWindow);
CommonPen^.Select(DragDC);
MoveTo(DragDC, Msg.lParamLo, Msg.lParamHi);
CurrentLine := New(PLine, Init(CommonPen));
Drawing^.Insert(CurrentLine);
end;
end.
procedure TStepWindow.WMMouseMove(var Msg: TMessage);
begin
if ButtonDown then
begin
LineTo(DragDC, Msg.lParamLo, Msg.lParamHi);
CurrentLine^.AddPoint(Msg.LParamLo, Msg.LParamHi);
end;
end;
procedure TStepWindow.CMFileNew(var Msg: TMessage);
begin
Drawing^.FreeAll;
InvalidateRect(HWindow, nil, True);
end;
Вывод сохраненной графики
procedure TStepWindow.Paint(PaintDC: HDC; var PaintInfo:
TPintStruct);
procedure DrawLine(P: PLine); far;
begin
P^.Draw(PaintDC);
end;
begin
Drawing^.ForEach(@DrawLine);
end;
procedure TLine.Draw(ADC: HDC);
var First: Boolean;
procedure DrawLine(P: PLinePoint); far;
begin
if First then MoveTo(ADC, P^.X, P^.Y)
else LineTo(ADC, P^.X, P^.Y);
First := False;
end;
begin
First := True;
LinePen^.Select(ADC);
Points^.ForEach(@DrawLine);
LinePen^.Delete;
end;
Шаг 8: Сохранение рисунка в файле
Отслеживание состояния
TStepWindow = object(TWindow)
ButtonDown, HasChanged, IsNewFile: Boolean;
.
.
.
end.
procedure TStepWindow.CMFileNew(var Msg: TMessage);
begin
if CanClose then
begin
Drawing^.FreeAll;
InvalidateRect(HWindow, nil, True);
HasChanged := False;
IsNewFile := True;
end;
end;
procedure TStepWindow.CMFileOpen(var Msg: TMessage);
begin
if CanClose then
if Application^.ExecDialog(New(PFileDialog, Init(@Self,
PChar(sd_FileOpen), StrCopy(FileName, '*.PTS')))) =
id_Ok then ReadFile;
end;
procedure TStepWindow.CMFileSave(var Msg: TMessage);
begin
if IsNewFile then CMFileSaveAs(Msg) else WriteFile;
end;
procedure TStepWindow.CMFileSaceAs(var Msg: TMessage);
begin
if IsNewFile then StrCopy(FileName, '');
if Application^.ExecDialog(New(PFileDialog,
Init(@Self, PChar(sd_FileSave), FileName))) =
id_Ok then WriteFile;
end;
procedure TStepWindow.ReadFile;
begin
MessageBox(HWindow, @FileName, 'Загрузить файл:',
mb_Ok);
HasChanged := False;
IsNewFile := False;
end;
procedure TStepWindow.WriteFile;
begin
MessageBox(HWindow, @FileName, 'Сохранить файл:',
mb_Ok);
HasChanged := False;
IsNewFile := False;
end;
Сохранение и загрузка файлов
const
RLinePoint: TStreamRec = (
ObjType: 200;
VmtLink: Ofs(TypeOf(TLinePoint)^);
Load: @TLinePoint.Load;
Store: @TLinePoint.Store);
RLine: TStreamRec = (
ObjType: 201;
VmtLink: Ofs(TypeOf(TLine)^);
Load: @TLine.Load;
Store: @TLine.Store);
constructor TLinePoint.Load(var S: TStream);
begin
S.Read(X, SizeOf(X));
S.Read(Y, SizeOf(Y));
end;
procedure TLinePoint.Store(var S: TStream);
begin
S.Write(X, SizeOf(X));
S.Write(Y, SizeOf(Y));
end;
constructor TLine.Load(var S: TStream);
begin
Points := PCollection(S.Get);
LinePen := PPen(S.Get);
end;
procedure TLine.Store(var S: TStream);
begin
S.Put(Points);
S.Put(LinePen);
end;
procedure StreamRegistration;
begin
RegisterType(RCollection);
end;
procedure TStepWindow.ReadFile;
var
TempColl: PCollection;
TheFile: TDosStream;
begin
TheFile.Init(FileName, stOpen);
TempColl: := PCollection(TheFile.Get);
TheFile.Done;
if TempColl <> nil then
begin
Dispose(Drawing, Done);
Drawing := TempColl;
InvalidateRect(HWindow, nil, True);
end;
HasChanged := False;
IsNewFile := False;
end;
procedure TStepWindow.WriteFile;
var
TheFile: TDosStream;
begin
TheFile.Init(FileName, stCreate);
TheFile.Put(Drawng);
TheFile.Done;
IsNewFile := False;
HasChanged := False;
end;
Шаг 9: Печать графического образа
Построение объекта принтера
constructor TStepWindow.Init(AParent: PWindowsObject;
ATitle: PChar);
begin
inherited Init(AParent, ATitle);
.
.
.
Printer := New(PPrinter, Init);
end;
Создание объекта распечатки
Запись в контекст устройства
Создание распечатки окна
procedure TWindowPrint.PrintPage(DC: HDC; Page: Word;
Size: TPoint; var Rect: TRect; Flags: Word);
var PS: TPaintStruct;
begin
Window^.Paint(DC, PS);
end;