Глава 5. Повторное отображение графики
В следующих трех шагах вы узнаете как
* Отображать по запросу графический образ.
* Сохранять образ файла и загружать его.
* Печатать образ.
Шаг 7: Вывод на экран графики
-----------------------------------------------------------------
+-----------------------+
| Step 1: Basic App |
| Step 2: Text |
| Step 3: Lines |
| Step 4: Menu |
| Step 5: About Box |
| Step 6: Pens |
|XStepX7:XPaintingXXXXXX|
| Step 8: Streams |
| Step 9: Printing |
| Step 10: Palette |
| Step 11: BWCC |
| Step 12: Custom ctrls |
+-----------------------+
Возможно, вы удивитесь, узнав, что графика и текст, которые
вы рисуете в окне с помощью функций 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 содержит всю информацию, необходимую для изображения
данной линии: перо и набор точек.
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;
LinePen просто указывает на объект TPen, а Point - это набор
объектов точек. TLine и TLinePoint содержат методы Load и Store,
преимущества использования которых для записи картинок на диск вы
увидите в шаге 8. В отличие от них объект TLine весьма прост:
конструктор и деструктор создают и уничтожают LinePen, AddPoint
включает объект точки в Points, а Draw рисует линии между точками
Points.
Объект TLinePoint еще проще:
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;
TLinePoint не определяет никакого нового поведения - это
просто объект данных, который должен использоваться в TLine. Но
позднее (в шаге 8) он понадобиться как объект для записи в поток.
Не забудьте построить в TStepWindow.Init Drawing и уничтожить его
в TStepWindow.Done:
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;
Основное окно программы Steps содержит набор в своем поле
Drawing набор линий. Когда пользователь рисует линии, вы должны
преобразовывать их в объекты и добавлять в Drawing. Затем, когда
потребуется отобразить окно, путем итерации Drawing нужно отобра-
зить каждую его точку.
Изменение методов работы с "мышью"
-----------------------------------------------------------------
Чтобы сохранять линии в виде объектов, вы должны изменить
EMLButtonDown и WMMouseMove, чтобы не только рисовать линии, но
также сохранять точки в наборе линий. Поскольку текущую линию
придется обновлять не только одному методу, добавьте в
TStepWindow еще одно поле типа PLine с именем CurrentLine:
type
TStepWindow = object(TWindow);
CurrentLine: PLine;
.
.
.
end;
Кроме добавления изображения линии WMLButttonDown создает
при каждом вызове новый объект линии и добавляет его в набор в
Drawing. WMMouseMove просто добавляет новую точку в конец объекта
текущей линии и изображает в окне линейные сегменты. Сохраняя все
точки всех линий, ваше окно будет записывать информацию, необхо-
димую для точного воспроизведения картинки.
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;
Примечание: Уничтожать устаревшие CurrentLine не тре-
буется, поскольку они записаны в наборе Drawing. Все объек-
ты линий уничтожаются при уничтожении Drawing.
WMLButtonUp модификации не требует. Вам не нужно уничтожать
все объекты линий при очистке отображаемого окна, поэтому добавь-
те в CMFileNew вызов метода FreeAll:
procedure TStepWindow.CMFileNew(var Msg: TMessage);
begin
Drawing^.FreeAll;
InvalidateRect(HWindow, nil, True);
end;
Вывод сохраненной графики
-----------------------------------------------------------------
Теперь, когда TStepWindow сохраняет свою текущую строку, вы
должны научить его по команде (этой командой является Paint) ри-
совать ее. Давайте напишем для TStepWindow метод Paint, который
повторяет действия WMLButtonDown, WMMouseMove и WMLButtonUp. Пу-
тем итерации по набору линий Paint воссоздает картинку аналогично
тому, как это делаете вы. Метод Paint имеет следующий вид (см.
файл STEP07.PAS):
procedure TStepWindow.Paint(PaintDC: HDC; var PaintInfo:
TPintStruct);
procedure DrawLine(P: PLine); far;
begin
P^.Draw(PaintDC);
end;
begin
Drawing^.ForEach(@DrawLine);
end;
Примечание: Итерация методов описывается в Главе 19
"Наборы".
Метод Draw объекта линии для изображения каждой линии между
точками также использует итератор ForEach:
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: Сохранение рисунка в файле
-----------------------------------------------------------------
+-----------------------+
| Step 1: Basic App |
| Step 2: Text |
| Step 3: Lines |
| Step 4: Menu |
| Step 5: About Box |
| Step 6: Pens |
| Step 7: Painting |
|XStep 8:XStreamsXXXXXXX|
| Step 9: Printing |
| Step 10: Palette |
| Step 11: BWCC |
| Step 12: Custom ctrls |
+-----------------------+
Теперь, когда вы сохранили представление данных в виде ри-
сунка как часть оконного объекта, можно легко записать эти данные
в файл (фактически, в буферизованный поток DOS) и считать его об-
ратно.
Данный шаг посвящен добавлению полей объектов для записи
состояния сохраняемой информации, модификации сохраняемой в файле
информации и методам открытия. Используя предусмотренные в
ObjectWindows потоковые объекты, вы убедитесь в удобстве их при-
менения для сохранения данных в файле.
Отслеживание состояния
-----------------------------------------------------------------
Требуется отслеживать две характеристики рисунка. Изменение
файла мы уже отслеживали (в шаге 1 было добавлено поле
HasChanged), но теперь нужно знать, загружен ли файл в данный мо-
мент. Как и HasChanged, IsNewFile - это атрибут TStepWindow типа
Boolean, поэтому его также следует сделать полем:
TStepWindow = object(TWindow)
ButtonDown, HasChanged, IsNewFile: Boolean;
.
.
.
end.
Поле HasChanged принимает значение True, если текущий рису-
нок модифицирован. Модификация означает, что рисунок был изменен
с момента последнего сохранения или не сохранялся вовсе. Вы уже
устанавливаете поле HasChanged в True, когда пользователь начина-
ет рисовать, и в False, когда окно очищается. Когда пользователь
открывает новый файл или сохраняет существующий, HasChanged сле-
дует установить в False.
IsNewFile указывает, что рисунок не взят из файла, поэтому
сохранение рисунка потребует от пользователя задать имя файла.
IsNeFile имеет значение True только при первоначальном запуске
приложения и после выбора пользователем команды меню File|New
(Файл|Новый). Это поле устанавливается в False, когда файл откры-
вается или сохраняется. Фактически, FileSave использует
IsNewFile, чтобы увидеть, можно ли сохранить файл немедленно, или
пользователю требуется выбрать файл из файлового диалога.
Приведем методы сохранения и загрузки файла. На данный мо-
мент они выполняют только сохранение и загрузку файлов. Сохране-
ние файла сконцентрировано в одном новом методе, который называ-
ется WriteFile, а открытие файла выполняет метод ReadFile.
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;
Примечание: Данный текст программы можно найти в файле
STEP08A.PAS.
Сохранение и загрузка файлов
-----------------------------------------------------------------
Теперь, когда вы создали основную схему для построения и
загрузки файлов, осталось только выполнить фактическую загрузку и
сохранение в файле наборов точек. Для этого можно использовать
потоковый механизм автоматического сохранения объекта. Сначала вы
научитесь сохранять и загружать сами объекты точек и линий (как
это сделать для наборов вы уже знаете). Затем методы WriteFile и
FileOpen будут модифицированы для использования потоков.
Примечание: Подробнее об использовании потоков с объ-
ектами рассказывается в Главе 20 "Потоки".
Ниже приведен исходный код, показывающий как сохранять и
загружать сами объекты TLine и TLinePoint:
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;
Для регистрации TCollection при запуске прикладной программы
вы должны вызывать StreamRegistration (который находится в
Steps). Вы можете поместить это вызов в метод TStepWindow.Init.
Модуль DrawLine регистрирует в своем коде инициализации
TLinePoint и TLine, поэтому линии и точки регистрируются простым
включением DrawLine в оператор uses.
Заключительным шагом изменения методов WriteFile и ReadFile
будет фактическая запись в потоки и чтение из них (см.
STEP08B.PAS):
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: Печать графического образа
-----------------------------------------------------------------
+-----------------------+
| Step 1: Basic App |
| Step 2: Text |
| Step 3: Lines |
| Step 4: Menu |
| Step 5: About Box |
| Step 6: Pens |
| Step 7: Painting |
| Step 8: Streams |
|XStepX9:XPrintingXXXXXX|
| Step 10: Palette |
| Step 11: BWCC |
| Step 12: Custom ctrls |
+-----------------------+
Печать из Windows может представлять собой сложную задачу,
но ObjectWindows предоставляет простой механизм добавления
средств печати в вашу прикладную программу.
Добавление этих средств предусматривает следующие три шага:
* Построение объекта принтера.
* Создание объекта распечатки.
* Печать объекта распечатки.
Построение объекта принтера
-----------------------------------------------------------------
Любая программа ObjectWindows может получить доступ к прин-
теру с помощью объекта типа TPrinter. В этом случае основное окно
вашего приложения должно построить объект принтера и сохранить
его в объектном поле с именем Printer:
constructor TStepWindow.Init(AParent: PWindowsObject;
ATitle: PChar);
begin
inherited Init(AParent, ATitle);
.
.
.
Printer := New(PPrinter, Init);
end;
Примечание: Тип TPrinter определен в модуле OPrinter,
поэтому не забудьте добавить OPrinter в свой оператор uses.
Это все, что обычно приходится делать для инициализации объ-
екта принтера. По умолчанию TPrinter использует назначенный по
умолчанию принтер, заданный в файле WIN.INI. TPrinter предусмат-
ривает также механизм для выбора альтернативных принтеров.
Создание объекта распечатки
-----------------------------------------------------------------
ObjectWindows управляет выводимыми на печать данными точно
также, как выводом на экран. То есть вместо записи непосредствен-
но на устройство вывода (или даже непосредственно в Windows) вы
направляете свой вывод на объект, который знает, как представлять
информацию на устройстве вывода. Для вывода на печать используйте
объекты, полученные из абстрактного типа TPrintout и метода с
именем PrintPage.
Существует два основных случая, с которыми вы будете иметь
дело при генерации из приложения Windows данных для печати: пе-
чать документов и печать содержимого окна. Печать содержимого ок-
на проще, поскольку окна уже "знают" о своем представлении. Как
оказывается на самом деле, это просто особый случай печати доку-
мента.
Примечание: О печати документов рассказывается в Главе 15.
Запись в контекст устройства
-----------------------------------------------------------------
До настоящего момента вы всегда записывали текст и графику в
контекст дисплея (что является способом представления в Windows
пользовательской области окон). Контекст дисплея - это просто
специализированная версия контекста устройства - механизма, через
который приложения Windows работают с различными устройствами,
такими как экраны, принтеры или коммуникационные устройства.
Запись в контекст одного устройства мало отличается от запи-
си в контекст другого, поэтому, например, достаточно просто сооб-
щить, например, оконному объекту, что для записи на принтер нужно
использовать механизм Paint.
Создание распечатки окна
-----------------------------------------------------------------
TWindowPrint - это специальный потомок TPrintout, используе-
мый для печати содержимого окна. Печать содержимого окна не
представляет сложности по двум причинам: вы имеете дело только с
одной страницей, и объект окна уже знает, как отображать свой об-
раз.
Обычно при печати документа вашей прикладной программе нужно
выполнять итерации процесса печати для каждой страницы документа.
Так как окно имеет только один образ, при печати окна это не яв-
ляется необходимым.
Таким образом, печать также проста, как ориентация метода
Paint объекта окна на вывод вместо окна в контекст устройства,
подходящий для принтера:
procedure TWindowPrint.PrintPage(DC: HDC; Page: Word;
Size: TPoint; var Rect: TRect; Flags: Word);
var PS: TPaintStruct;
begin
Window^.Paint(DC, PS);
end;
Поскольку переданный PrintPage параметр DC уже указывает на
подходящий для принтера контекст устройства, в простейшем случае
PrintPage должен только сообщить объекту окна на вывод его содер-
жимого в этот контекст устройства. Теперь ваш объект распечатки
также знает о своем представлении.
Вывод распечатки
-----------------------------------------------------------------
Наличие объекта распечатки, которому известно о своем предс-
тавлении, это все, что нужно для передачи распечатки на принтер.
Программа Steps делает это в ответ на команду cm_FilePrint, гене-
рируемую командой Print меню File:
procedure TStepWindow.CMFilePrint(var Msg: TMessage);
var P: PPrintout;
begin
if IsNewFile then StrCopy(FileName, 'Untitled');
P := New(PWindowPrint, Init(FileName, @Self));
Printer^.Print(@Self, P);
Dispose(P, Done);
end;
CMFilePrint очень просто строит объект распечатки, озаглав-
ленный заданным именем (имя файла точек или 'Untitled') и запол-
няет его своим содержимым (так как это единственное окно в при-
ложении).
При наличии объекта распечатки CMFilePrint сообщает объекту
принтера, что его нужно напечатать, добавив какие-либо сообщения
об ошибках или диалоговые окна (отсюда параметр @Self). Когда пе-
чать закончится, CMFilePrint уничтожает объект распечатки.
Выбор другого принтера
-----------------------------------------------------------------
Одним из средств TPrinter является возможность изменения ус-
тановки принтера. TPrinter определяет метод Setup, который выво-
дит диалоговое окно установки принтера, позволяющее пользователю
выбрать принтер из числа установленных в Windows и обеспечивает
доступ к диалоговому окну конфигурации устройства.
Чтобы вывести диалоговое окно установки принтера, ваша прик-
ладная программа вызывает метод Setup объекта принтера. Steps де-
лает это в ответ на команду cm_FileSetup (см. STEP09.PAS):
procedure TStepWindow.CMFileSetup(var Msg: TMessage);
begin
Printer^.Setup(@Self);
end;
Диалоговое окно установки принтера является экземпляром типа
TPrinterSetupDlg (см. Рис. 5.1).
+---------------------------------------------------------------+
|#=#XXXXXXXXXXXXXXXXXXXXSelect PrinterXXXXXXXXXXXXXXXXXXXXXXXXXX|
+---------------------------------------------------------------|
| |
| Принтер и порт |
| +--------------------------------------------+-+ |
| |PostScript Printer on LPT1:#################|v| |
| +--------------------------------------------+-+ |
| +-----------+ +-----------+ +-----------+ |
| |####OK#####| |##Setup####| |##Cancel###| |
| +-----------+ +-----------+ +-----------+ |
| |
+---------------------------------------------------------------+
Рис. 5.1 Диалоговое окно установки принтера.
В комбинированном блоке диалогового окна выводятся принтеры,
заданные в WIN.INI. Это дает пользователю возможность доступа ко
всем установленным принтерам.
Назад | Содержание | Вперед