Pascal 7 & Objects

         

Добавление поля объекта


-----------------------------------------------------------------

Чтобы сохранить рисунок в виде набора линий, добавьте в TStepWindow поле с именем 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;



Изображение и рисование


-----------------------------------------------------------------

Существует два способа получения в окне графического образа. С рисованием вы уже знакомы по предыдущим примерам. При рисовании вы создаете графический образ в реальном времени в ответ на ввод данных пользователем. Но окно не может требовать от пользователя воссоздания графики при обновлении экрана.

Окна должны иметь возможность воссоздавать по запросу свои графические образы. Windows сообщает своим оконным объектам, ког- да они требуют изображения или обновления. При этом окно должно каким-то образом генерировать образ экрана. В ответ на необходи- мость изображения. ObjectWindows автоматически вызывает метод Paint вашего окна. Наследуемый и TWindow метод Paint не выполняет никаких функций. В Paint вы должны поместить код для передачи со- держимого окна. Фактически Paint вызывается при первом выводе ок- на. Paint отвечает за обновление (при необходимости) изображения текущим содержимым.

Существует еще одно важное отличие между отображением графи- ки в методе Paint и другим ее отображением (например, в ответ на действия "мышью"). Содержимое экрана, которое должно использо- ваться для отображения, передается в параметре PaintDC, так что вашей программе не требуется получать или освобождать его. Однако вам потребуется вновь выбрать для PaintDC изобразительные средс- тва.

Чтобы отобразить содержимое окна, вместо повторения тех действий, которые привели к первоначальному изображению (DragDC), вы используете PaintDC. Визуальный эффект будет тот же, что и при первоначальном рисовании пользователем (аналогично проигрыванию аудиозаписи концерта). Но чтобы "проигрывать" ее в методе Paint, сначала вам нужно сохранить графику в виде объектов.



Определение объекта линии


-----------------------------------------------------------------

Далее нужно ответить на вопрос, что такое линия. В шаге 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 нужно отобра- зить каждую его точку.



Отслеживание состояния


-----------------------------------------------------------------

Требуется отслеживать две характеристики рисунка. Изменение файла мы уже отслеживали (в шаге 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.


Построение объекта принтера


-----------------------------------------------------------------

Любая программа 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 предусмат- ривает также механизм для выбора альтернативных принтеров.



Вывод на экран графики


-----------------------------------------------------------------

+-----------------------+ | 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 вы никогда не получаете их обратно для повторного отображения.

Чтобы в окне повторно отображалась графика, вы должны сохра- нить эту графику (или данные для регенерации графики) в структуре некоторого типа, такой как объект. С помощью объектов вы можете полиморфически хранить простую или сложную графику, а также хра- нить объекты и поля ваших оконных объектов.



Сохранение рисунка в файле


-----------------------------------------------------------------

+-----------------------+ | 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 потоковые объекты, вы убедитесь в удобстве их при- менения для сохранения данных в файле.



Печать графического образа


-----------------------------------------------------------------

+-----------------------+ | 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 предоставляет простой механизм добавления средств печати в вашу прикладную программу.

Добавление этих средств предусматривает следующие три шага:

* Построение объекта принтера.

* Создание объекта распечатки.

* Печать объекта распечатки.



Сохранение графики в объектах


-----------------------------------------------------------------

Созданные программой Steps изображения на самом деле просто представляют наборы различного числа линий. При каждой буксировке "мыши" вы добавляете другую линию. А каждая линия - это на самом деле определенный набор точек, соединенных линейными сегментами. Чтобы сохранить и воспроизвести такие изображения, вам необходим гибкий и расширяемый тип данных

Для размещения неизвестного числа линий или точек прекрасно подходит определенный в модуле Objects тип TCollection. Это набор объектов, который может динамически расширяться, когда вы добав- ляете другие элементы. Этим наборам не важно, что они включают, поэтому вы можете использовать один и тот же механизм и для ри- сунка (набора линий), и для линии (набора точек).

Примечание: О наборах рассказывается в Главе 19 "Набо- ры".

Концептуально вам нужно просто сделать так, чтобы окно знало о своем содержимом, так что оно сможет обновить изображение. Окно содержит рисунок, представляющий собой набор линий. Таким обра- зом, вам нужно:

* Передать окну объект или поле, содержащее набор линий.

* Определить объект линии, который может отображаться.

* В ответ на сообщения "мыши" добавлять к сохраненным линиям точки.



Сохранение и загрузка файлов


-----------------------------------------------------------------

Теперь, когда вы создали основную схему для построения и загрузки файлов, осталось только выполнить фактическую загрузку и сохранение в файле наборов точек. Для этого можно использовать потоковый механизм автоматического сохранения объекта. Сначала вы научитесь сохранять и загружать сами объекты точек и линий (как это сделать для наборов вы уже знаете). Затем методы 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;

------------------------------------------------------------------------



Создание объекта распечатки


-----------------------------------------------------------------

ObjectWindows управляет выводимыми на печать данными точно также, как выводом на экран. То есть вместо записи непосредствен- но на устройство вывода (или даже непосредственно в Windows) вы направляете свой вывод на объект, который знает, как представлять информацию на устройстве вывода. Для вывода на печать используйте объекты, полученные из абстрактного типа TPrintout и метода с именем PrintPage.

Существует два основных случая, с которыми вы будете иметь дело при генерации из приложения Windows данных для печати: пе- чать документов и печать содержимого окна. Печать содержимого ок- на проще, поскольку окна уже "знают" о своем представлении. Как оказывается на самом деле, это просто особый случай печати доку- мента.

Примечание: О печати документов рассказывается в Главе 15.



Создание распечатки окна


-----------------------------------------------------------------

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 должен только сообщить объекту окна на вывод его содер- жимого в этот контекст устройства. Теперь ваш объект распечатки также знает о своем представлении.



Выбор другого принтера


-----------------------------------------------------------------

Одним из средств 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. Это дает пользователю возможность доступа ко всем установленным принтерам.



Вывод распечатки


-----------------------------------------------------------------

Наличие объекта распечатки, которому известно о своем предс- тавлении, это все, что нужно для передачи распечатки на принтер. Программа 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 уничтожает объект распечатки.



Вывод сохраненной графики


-----------------------------------------------------------------

Теперь, когда 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; ------------------------------------------------------------------------



Запись в контекст устройства


-----------------------------------------------------------------

До настоящего момента вы всегда записывали текст и графику в контекст дисплея (что является способом представления в Windows пользовательской области окон). Контекст дисплея - это просто специализированная версия контекста устройства - механизма, через который приложения Windows работают с различными устройствами, такими как экраны, принтеры или коммуникационные устройства.

Запись в контекст одного устройства мало отличается от запи- си в контекст другого, поэтому, например, достаточно просто сооб- щить, например, оконному объекту, что для записи на принтер нужно использовать механизм Paint.



Что дальше?


-----------------------------------------------------------------

Есть много дополнений и изменений, которые вы можете внести в программу Steps, чтобы сделать ее более полезной. Версию программы Steps, которая включает в себя эти изменения, вы можете найти в файле GRAFFITI.PAS.

Программа Graffiti содержит следующие изменения:

* Многодокументальный интерфейс (MDI).

* Сглаживание линий.

* Отмена.

* Улучшает поведение окна палитры.

* Прокрутка.



Динамическое изменение размеров палитры


-----------------------------------------------------------------

Так как каждое перо, которое вы сохраняете в палитре, имеет один и тот же размер (40 элементов изображения высотой и 128 ши- риной), вам нужно убедиться, что окно палитры может увеличиваться и сжиматься на этот размер каждый раз, когда вы удаляете перо. Объект TPenPalette определяет два метода, которые позволяют это делать: Grow и Shrink.

procedure TPenPalette.Grow var WindowRect: TRect; begin GetWindowRect(HWindow, WindowRect); with WindowRect do MoveWindow(HWindow, left, top, right - left, bottom - top + 40, True); end;

procedure TPenPalette.Shrink; var WindowRect: TRect; begin GetWindowRect(HWindow, WindowRect); with WindowRect do MoveWindow(HWindow, left, top, right - left, bottom - top - 40, True); end;

Оба метода находят координаты границ окна, модифицируют их и сообщают окну, что нужно использовать новые координаты границ. Функция API GetWindowRect возвращает структуру TRect, содержащую верхнюю, нижнюю, левую и правую координату. Grow добавляет в ниж- нюю область окна 40 элементов изображения, а Shink вычитает тот же объем.

В следующем разделе вы узнаете, как вызывать методы Grow и Shrink в ответ на нажатие командных кнопок Add Pen и Del Pen.



Добавление и удаление перьев


-----------------------------------------------------------------

В последнем разделе вы отвечали на сообщения от Add Pen (До- бавить перо) и Del Pen (Удалить перо) изменением размера окна па- литры. Теперь настало время изменить эту реакцию и фактически до- бавлять или удалять перья из палитры, что в свою очередь указыва- ет окну палитры на необходимость изменения размера. Вместо вызова собственных методов Grow и Shrink методы объекта палитры AddPen и DeletePen должны вызывать соответственно, методы IDAdd и IDDel в TPenPalette.

procedure TPenPalette.IDAdd(var Msg: TMessage); begin Pens^.AddPen(CommonPen); end;

procedure TPenPalette.IDDel(var Msg: TMessage); begin Pens^.DeletePen; end;

Метод AddPen воспринимает передаваемое перо, копирует его в набор и отмечает перо, как текущее выбранное. Затем он разрешает кнопку Del Pen в окне палитры пера, запрещает кнопку Add Pen, ес- ли набор полон, и сообщает порождающему окну о необходимости уве- личения размера, чтобы поместить новое перо.

procedure TPenPic.AddPen(APen: PPen); begin CurrentPen := PenSet^.Count; with APen^ do PenSet^.Insert(New(PPen, Init(Style, With, Color))); with PPenPalette(Parent)^ do begin DelBtn^.Enable; if PenSet^.Count >= MaxPens tnen AddBtn^.Disable; Grow; end; end;

Примечание: Чтобы использовать преимущества средств, специфических для TPenPAlette, TPenPic может выполнять при- ведение типа поля Parent. Большинство оконных объектов не связаны так жестко с конкретным порождающим типом, и поэто- му не должны делать никаких предположений относительно типа порождающих их окон.

Метод DeletePen по существу изменяет действия AddPen на об- ратные. При наличии выбранного в палитре пера оно удаляется из набора, а набор уплотняется таким образом, чтобы перья размеща- лись непрерывно. Затем он указывает, что в данный момент выбран- ных перьев нет (поскольку выбранное перо только что удалено) и запрещает командную кнопку Del Pen, так как Del Pen работает с выбранным пером. Далее он разрешает кнопку Add Pen, поскольку удаление пера автоматически освобождает место для по крайней мере еще одного пера. Наконец, он сообщает порождающему окну на необ- ходимость уменьшения его размера (так выводить теперь нужно мень- ше перьев).

procedure TPenPic.DeletePen; begin if CurrentPen > -1 then begin PenSet^.AtFree(CurrentPen); PenSet^.Pack; CurrentPen := -1; with PPenPelette(Parent)^ do begin AddBtn^.Enable; DelBtn^.Disable; Shrink; end; end; end;

Заметим, что AddPen и DeletePen используют преимущества того факта, что окна палитры пера для упрощения связи с методами имеет указатели на свои командные кнопки. Если бы TPenPalette не имел полей AddBtn и DelBtn, то объекту палитры пришлось бы искать их по идентификаторам и посылать им сообщения, либо нужно было бы послать сообщение порождающему окну, которое в свою очередь долж- но каким-то образом связываться с командными кнопками.



Добавление к окну дочернего окна


-----------------------------------------------------------------

Режимными дочерними окнами, которые вы до сих пор использо- вали, управлять легко. Вы можете их создать, использовать и отме- нить. Но здесь нам нужно работать с диалоговым окном, которое не является режимным. Например, оно должно закрываться, если закры- вается приложение, и становиться скрытым, если основное окно ми- нимизируется.

В основном поведение диалоговых окон (такое как закрытие или когда окно временно становится скрытым) автоматическое. Единс- твенный случай, когда вам нужно делать что-то особенное - это когда вы хотите сделать дочернее окно независимым от его порожда- ющего окна. Например, окно палитры, которое вы собираетесь выво- дить, сможет скрываться и выводиться, пока основное окно остается видимым. Окна, которые могут перемещаться или выводиться незави- симо от своих порождающих окон, называются независимыми дочерними окнами. На следующем шаге вы будете создавать зависимые окна, присоединенные к независимому окну палитры.

Так как основное окно должно посылать команды окну палитры, потребуется указатель на это окно, поэтому добавьте в TStepWindow его палитры пера. TStepWindow содержит теперь следующие поля:

TStepWindow = object(TWindow) DragDC: DHC; ButtonDown: Boolean; FileName: array[0fsPathName] of Char; HasChanged, IsNewFile: Boolean; Drawing: PCollection; Printer: PPrinter; PenPalette: PPenPalette; { окно палитры } . . . end;

Осталось только построить объект дочернего окна и присвоить его PenPalette. Этому посвящен следующий раздел.



Добавление к палитре командных кнопок


-----------------------------------------------------------------

Хотя они ведут себя идентично, между управляющими кнопками диалоговых блоков (таких как файловое диалоговое окно) и управля- ющими элементами окон (таких как окно палитры) имеется существен- ное различие. Управляющие элементы диалогового блока вы можете задать в ресурсе диалогового блока. Они не являются объектами, и диалоговый блок, которому они принадлежат, полностью отвечает за управление этими элементами. В Главе 11 показано, как создать из диалоговых ресурсов свои собственные диалоговые блоки и работать с их управляющими элементами.

Управляющие элементы окон задаются определением объекта. По- рождающее окно управляет их поведением через методы, определенные объектами управляющих элементов ObjectWindows. Например, чтобы получить следующий элемент, который пользователь выбрал в блоке списка, вызовите метод GetSelString объекта блока. Аналогично оконному объекту или объекту диалогового блока, объект управляю- щего элемента имеет соответствующий визуальный элемент.

Объект управляющего элемента и его управляющий элемент свя- заны через поле идентификатора объекта управляющего элемента. Каждый управляющий элемент имеет уникальный идентификатор, кото- рый используется его порождающим окном для идентификации управля- ющего элемента при маршрутизации управляющих событий (таких как щелчок на элементе "мышью"). Для ясности для каждого идентифика- тора управляющего элемента следует определить следующие констан- ты:

const id_Add = 101; id_Del = 102; MaxPens = 9;

MaxPens задает максимальное число перьев, которые будет со- держать палитра. Значение 9 хорошо подходит для стандартного эк- рана VGA.



Добавление "кнопок" палитры


-----------------------------------------------------------------

Теперь, когда у вас есть окно палитры, вам необходим просто способ вывода на экран и выбора перьев в палитре. Для этого вы можете использовать относительно простой потомок TWindow и набор объектов пера.

В данном разделе вы сделаете следующее:

* определите объект палитры пера;

* выберете перья по щелчку кнопкой "мыши".



Вывод всплывающего окна


Итак, вы создали два типа окон - основное окно (объект TStepWindow) и режимные дочерние окна, которые создаются и унич- тожаются каждый раз, когда они необходимы (например, блоки сооб- щений). Однако, в полноценной программе Windows дочерние окна часто требуется сохранять активными в течении неопределенного пе- риода времени (в качестве примера можно привести окно оперативной полосы SpeedBar в работающей под Windows интегрированной среде IDE).

До сих пор все дочерние окна в Steps имели фиксированный размер и создавались из шаблонов ресурсов. В шагах 10 - 12 вы бу- дете делать следующее:

* Создавать окна с динамическим размером, сохраняющиеся на все время работы программы.

* Добавлять в окно специализированные управляющие элементы.

* Создавать собственное интерактивное окно.

Наконец, мы дадим предложения по дальнейшему расширению программы Steps.



Имена методов реакции на сообщения управляющих элементов


-----------------------------------------------------------------

Как и в случае методов реакции на сообщения, имена которым присваиваются по сообщениям, методы, основанные на дочерних иден- тификаторах, также должны именоваться по идентификаторам сообще- ний. Так как две командные кнопки, на которые вы хотите реагиро- вать, имеют идентификаторы id_Add и id_Del, TPenPalette нужны ме- тоды с именами IDAdd и IDDel.

TPenPalette = object(TWindow) AddBtn, DelBtn: PBitButton; constructor Init(AParent: PWindowsObject; ATitle: PChar); procedure Grow; procedure SetupWindow; virtual; procedure Shrink; procedure IDAdd(var Msg: TMessage); virtual id_First + id_Add; procedure IDDel(var Msg: TMessage); virtual id_First + id_Del; end;

Теперь для выполнения соответствующих действий в ответ на командные кнопки осталось только определить методы IDAdd и IDDel. Пока что IDAdd должен просто вызывать увеличение окна, а IDDel - его сжатие

procedure TPenPalette.IDAdd(var Msg: TMessage); begin Grow; end;

procedure TPenPalette.IDDel(var Msg: TMessage); begin Shrink; end;

Примечание: Это дополняет содержимое файла STEP12A.PAS.



Многодокументальный интерфейс


-----------------------------------------------------------------

TStepWindow может очень легко работать в качестве дочернего окна MDI. Фактически с небольшими изменениями программа Graffiti использует в качестве своих дочерних окон те же окна, что Steps использует для основного окна. Так как владельцем окна палитры пера является объект TStepWindow, каждый отдельный рисунок имеет свой собственный отличный набор перьев. О многодокументальном ин- терфейсе рассказывается в Главе 14 "Объекты MDI").



Назначение порождающего окна


-----------------------------------------------------------------

Порождающие окна автоматически управляют своими дочерними окнами, поэтому при создании дочернего окна ему передается указа- тель на объект порождающего окна. Поскольку порождающее окно обычно строит свои дочерние окна, указателем порождающего окна обычно является @Self.

Важным исключением является основное окно приложения. Так как оно не имеет порождающего окна, конструктору основного окна передается nil.

Каждый оконный объект имеет список своих дочерних окон, обеспечивающий создание, вывод, сокрытие и закрытие окон в нужные моменты. Построение и уничтожение дочерних окон автоматически об- новляет список дочерних окон: построение дочернего окна добавляет его в список своего порождающего окна; уничтожение окна удаляет его из списка.



Нумерация ресурсов графических изображений


-----------------------------------------------------------------

Единственная сложная часть в определении графических изобра- жений для командных кнопок - это присваивание идентификаторов ре- сурсов. Управляющие элементы BWCC знают о том, какое графическое изображение использовать, основываясь на идентификаторе конкрет- ного управляющего элемента. Для командных кнопок в системах с VGA для ресурсов используется 1000 + идентификатор для "верхнего" об- раза, 3000 + идентификатор для "нижнего" образа и 5000 + иденти- фикатор для образа в фокусе.

Примечание: В системах с EGA используются, соответс- твенно, ресурсы 2000 + идентификатор, 4000 + идентификатор и 6000 + идентификатор.

Так как командная кнопка Add Pen имеет идентификатор 101 (id_Add), разрешение использования BWCC принимает вид ресурсов 1101, 3101 и 5101. В программе STEP11B.PAS, для доступа к специа- лизированными графическим изображениям, для командных кнопок Add Pen и Del Pen, используется директива:

{$R PENTAL.RES}

------------------------------------------------------------------------



Объекты управляющих элементов как поля


-----------------------------------------------------------------

Как и в случае других дочерних окон, часто удобно хранить указатель объекта управляющего элемента в виде поля. Это необхо- димо только для дочерних окон, с которыми вы позднее сможете ра- ботать непосредственно, вызывая методы их объектов. TPenPalette записывает каждый из этих объектов управляющих элементов в от- дельном поле. Приведем часть описания объекта TPenPalette:

TPenPalette = object(TWindow) AddBtn, DelBtn: PButton; . . . end;

После создания экземпляра этих дочерних объектов управляющих элементов вы можете манипулировать ими с помощью вызовов методов. Например, в соответствующие моменты можно разрешать или запрещать командные кнопки, вызывая их методы Enable и Disable. Используя метод ChildList порождающего окна, можно получить доступ к объек- там управляющих элементов дочерних окон, не записанных в полях, но гораздо удобнее делать это с помощью полей.



Определение объекта палитры


-----------------------------------------------------------------

Так как окно палитры пера может изменять свой размер, палит- ра в окне может фактически оставаться фиксированной. Чтобы пока- зать только часть палитры, в которой отображаются перья, вы може- те использовать возможности отсечения Windows.

Самой палитре необходимы только несколько полей данных: на- бор перьев, указание того, какое перо в данный момент выбрано, и описатели представляющих перья графических образов. Описатели графических изображений представляют собой частные поля не пото- му, что они должны быть секретными, а для предотвращения их неп- реднамеренного изменения другим кодом.

Приведем описание объекта палитры:

TPenPic = object(TWindow) PenSet: PCollection; CurrentPen: Integer; constructor Init(AParent: PWindowsObject); destructor Done: virtual; procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual; procedure AddPen(APen: PPen); procedure DeletePen; procedure SetupWindow; virtual; procedure WMLButtonDown(var Msg: TMessage); virtual wm_First + wm_LButtonDown; private UpPic, DownPic: HBitMap; end;

Объекту TPenPic не требуется очень много методов. Он имеет простой конструктор для создания набора перьев и деструктор для их уничтожения. Метод SetupWindow просто перемещает палитру внут- ри ее порождающего окна. AddPen и DeletePen включают перо в набор и удаляют перо из набора, а WMLButtonDown интерпретирует щелчки "мышью" для выбора перьев из палитры. Наконец, Paint рисует "кнопки", представляющие перья в наборе.

Отметим также, что TPenPic является потомком TWindow, а не TControl. Хотя поведение вашего нового объекта во многом напоми- нает поведение управляющего элемента окна, он должен быть произ- водным от TWindow, так как TControl работает только со стандарт- ными управляющими элементами, такими как "нажимаемые" командные кнопки и полосы прокрутки. При создании собственных управляющих элементов нужно начинать с TWindow.



Отмена


-----------------------------------------------------------------

Поскольку рисунок состоит из наборов линий, для стирания последней нарисованной линии легко использовать методы наборов. Graffiti связывает элемент меню Edit|Undo (Редактирование|Отмена) с набором линий метода AtDelete, удаляя последнюю линию в наборе, которая является также последней нарисованной линией. Повторяя удаление последней линии в наборе, вы можно эффективно отменять все изображение.

Хотя программа Graffiti этого не делает, вы можете добавить также функцию Redo (Возобновление), сохраняя удаленные линии в другом наборе, и перемещая их по одной в набор отображаемых ли- ний, либо даже в другой рисунок.



Отображение содержимого палитры


-----------------------------------------------------------------

За все, что мы насоздавали в объекте палитры, приходится расплачиваться, когда приходит момент отображать ее на экране. Поскольку перья хранятся в наборе, для отображения каждого пера вы легко можете выполнять итерацию. В самом деле, метод Paint состоит только из инициализации локальной переменной-счетчика, а затем выполняет итерацию по набору с помощью ForEach:

procedure TPenPic.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); var PenCount: Integer;

procedure ShowPen(P: PPen); far; var MemDC: HDC; TBitmap: HBitmap; begin MemDC := CreateCompatibleDC(PaintDC); Inc(PenCount); if PenCount = CurrentPen then TheBitmap = DownPic; else TheBitmap := UpPic; SelectObject(MemDC, TheBitmap); BitBlt(PaintDC, 0, PenCount * 40, 128, 40, MemDC, 0, 0, SrcCopy); P^.Select(PaintDC); MoveTo(PaintDC, 15, PenCount * 40 + 20); LineTo(PaintDC, 115, PenCount * 40 + 20); P^.Delete; DeleteDC(MemDC); end;

begin PenCount := -1; PenSet^.ForEach(@ShowPen); end;

Наиболее интересная часть содержится не в Paint, а во вло- женной процедуре ShowPen, которая вызывается для каждого пера в палитре. На самом деле ShowPen состоит из двух различных частей. Первая рисует графическое изображение фона, а вторая (которая уже должна быть вам достаточно знакома) использует объект пера для изображения по этому фону образца линии.

Изображение графических образов предусматривает три шага: создание контекста устройства памяти, выбор в контексте устройс- тва графического образа и копирование образа в контекст экрана.

Как вы видели в шаге 8, для различных видов устройств су- ществует различные контексты устройства. Для работы с графически- ми образами (битовыми массивами) Windows позволяет создавать кон- текст устройства памяти. Фактически, вы можете только выбирать битовые массивы в контексте устройства памяти, хотя они могут ко- пироваться в другие контексты устройства.

Метод CreateMemoryDC создает пустой контекст устройства па- мяти, совместимый с текущим PaintDC. Затем, в зависимости от то- го, является ли данное конкретное перо выбранным, ShowPen выбира- ет в контексте устройства графические образы UpPic или DownPic. Заметим, что в контексте устройства памяти графический образ интерпретируется аналогично любому другому изобразительному средству. Наконец, функция BitBlt копирует заданную часть кон- текста устройства памяти в PaintDC. Заметим, что контекст уст- ройства памяти требуется уничтожать.

После того как фон будет на месте, ShowPen использует ShowTo и LineTo аналогично тому, как это делается при рисовании непос- редственно в окне.



Построение окна палитры


-----------------------------------------------------------------

Объекты дочернего окна строятся обычно в конструкторах своих порождающих окон. Аналогично тому, как это происходит при инициа- лизации любого другого поля, вы присваиваете значения любому ука- зателю дочернего окна. В данном случае:

constructor TStepWindows.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); . . . PenPalette := New(PPenPalette, Init(@Self, 'Pan Palette'); end;



Поведение палитры


-----------------------------------------------------------------

Вы можете также отметить, что при щелчке "мышью" между па- литрой и основным окном, окно, где вы нажимаете кнопку, становит- ся активным (получает активную рамку), а другие окна становятся неактивными. Если вы часто перемещаетесь между двумя окнами (что может иметь место при работе с палитрой), это может показаться весьма раздражающим. Чтобы предотвратить это явление, вам нужно перехватывать передачу в окна сообщений sm_NCActivate, и когда параметр WParam сообщений равен 0 (попытка деактивизации рамки), вы можете изменить его на 1 (активизация рамки):

procedure TPenPalette.WVNCActivate(var Msg: TMessage); begin if Msg.WParam = 0 then Msg.WParam := 1; DefWndProc(Msg); end;

Вызов DefWndProc обеспечивает, что сообщение обрабатывается как обычно, но теперь рамка палитры деактивизироваться не будет. Аналогичный перехват вы можете добавить в TStepWindow.



Прокрутка


-----------------------------------------------------------------

Наконец, так как каждое дочернее окно MDI обычно невелико по размеру, Graffiti добавляет возможность автоматической прокрутки изображения при рисовании. В программе Steps, когда перо выходит за границы окна, линия продолжает рисоваться, хотя видеть ее вы больше не можете. Graffiti прокручивает изображение, так что вы будете видеть другие части рисунка.



Работа с управляющими элементами


-----------------------------------------------------------------

Любой тип окна, который имеет объекты управляющих элементов (или другое дочернее окно) должен определять для построения своих объектов управляющих элементов конструктор Init. Кроме того, для задания управляющих элементов перед выводом вы можете переопреде- лить SetupWindow. Порождающее окно (TPenPalette) автоматически создает и выводит все свои дочерние окна.

Приведем в качестве примера метод Init палитры пера. Первое, что он делает - это установка собственного расположения и атрибу- тов размера. Так как метод Init окна отвечает за задание его ат- рибутов создания, и поскольку вместе с ним создаются управляющие элементы окна, вы должны также в методе Init окна построить уп- равляющие элементы. В каждом вызове конструктора первый параметр - это @Self (порождающее окно). За ним следует идентификатор уп- равляющего элемента.

constructor TPenPalette.Init(AParent: PWindowsObject; ATitle: PChar); begin inherited Init(AParent, ATitle); with Attr do begin Style := Style or ws_Tiled or ws_SysMenu or ws_Visible; W := 133; H := GetSystemMetrics(sm_CYCaction) + 42; AddBtn := New(PButton, Init(@Self, id_Add, 'Добавить перо', 0, 0, 65, 40, True); DelBtn := New(PButton, Init(@Self, id_Del, 'Удалить перо', 0, 0, 65, 40, False); end;

После создания окна, чтобы задать управляющие элементы окна, вызывается виртуальный метод TPenPalette.SetupWindow. Поскольку здесь вы имеете дело только с командными кнопками, инициализация не требуется, но TPenPalette.SetupWindow первоначально запрещает одну из командных кнопок. Если бы вы использовали другой управля- ющий элемент (например, блок списка), то для инициализации объек- та управляющего элемента потребовалось бы вызывать SetupWindow.

Примечание: Когда вы переопределяете метод SetupWindow окна, не забудьте сначала вызывать наследуемый метод SetupWindow, так как он создает все дочерние управляющие элементы.

Вызов методов Init и SetupWindow вполне достаточен для пра- вильного вывода в окне палитры всех управляющих элементов. Коман- дные кнопки можно будет активизировать ("нажимать"), но без ка- ких-либо действий. В шаге 12 мы определим реакцию на события уп- равляющего элемента.



Размещение в порождающем окне


-----------------------------------------------------------------

Как вы могли заметить, TPenPic не задает свою позицию в конструкторе так, как это делают большинство управляющих элемен- тов. Причина здесь в том, что вы не можете полагаться на коорди- наты окна, пока оно не будет реально существовать. TPenPalette обходит эту проблему, создавая свои объекты кнопок путем вызова для определения высоты заголовка окна функции API GetSystemMetrics. Хотя это будет работать, существует и другой подход.

Вместо позиционирования палитры в конкретное место порождаю- щего окна вы можете поместить его в определенную позицию в порож- дающей области клиента. Таким образом, если вы добавляете меню или изменяете рамку, либо выполняете вне окна какое-либо другое изменение, ваш объект палитры все равно будет правильно позицио- нирован.

Изменение позиции окна выполняется в SetupWindow, так как окну палитры требуется использовать описатель своего порождающего окна, который до вызова собственного метода SetupWindow недосту- пен. При достижении SetupWindow дочернее окно может рассчитывать на допустимость описателя порождающего окна.

procedure TPenPic.SetupWindow; var ClientRect: TRect; begin inherited SetupWindow; GetClientRect(Parent^.HWindow, ClientRect); with ClientRect do MoveWindow(HWindow, 1, bottom - top + 1, 128, 40 * MaxPens, False; end;

Для возврата координат области клиента окна палитры метод TPicPen использует функцию API Windows GwetClientRect. Затем он перепозиционируется с помощью MoveWindow непосредственно под объ- екты кнопок, задавая высоту, достаточную для размещения всех перьев в наборе. Заметим, что последний параметр MoveWindow - это значение типа Boolean, указывающее, следует ли выполнять повтор- ное отображение окна после перемещения. Так как палитра на экран пока не выводилась, то заново отображать ее не имеет смысла, поэ- тому TPenPic.SetupWindow передает значение False.



Разрешение специализированных управляющих элементов


-----------------------------------------------------------------

Как вы уже знаете, объекты управляющих элементов создают стандартные управляющие элементы Windows. Например, только что созданный вами объект TButton дает в результате в палитре окна стандартную серую кнопку. Однако IDE и диалоговые блоки, создан- ные вами из ресурсов, используют кнопки другого типа с располо- женными на них графическими изображениями. ObjectWindows предос- тавляет вам простой способ использования в программах командных кнопок такого вида.

Использовать специализированные управляющие элементы Borland для Windows (BWCC) также просто, как использование модуля. Для этого нужно просто добавить BWCC в оператор uses основной прог- раммы. Это немедленно дает два эффекта. Первый состоит в том, что все стандартные диалоговые блоки (такое как файловое диалоговое окно, которое вы уже добавили в программу Steps) используют для таких общих элементов как кнопки OK или Cancel, а также кнопки с зависимой и независимой фиксацией, вместо стандартных управляющих элементов специализированные.

Примечание: Об использовании и проектировании специа- лизированных управляющих элементов Borland рассказывается в Главе 12.

Фактически, после добавления в оператор uses программы Steps BWCC вы можете перекомпилировать программу и получить доступ к диалоговым блокам. Без каких-либо других усилий вы существенно улучшите внешний вид программы и ее интерфейса.

Но как насчет кнопок в палитре пера? Они были созданы из уп- равляющих объектов с BWCC, используемых в программе, но выглядят как обычные командные кнопки. Ответ, конечно, состоит в том, что вы еще не определили для кнопок, графические изображения, так что по умолчанию они просто используют метки, переданные в конструк- торе Init. В следующем разделе вы увидите, как добавлять к специ- ализированным управляющим элементам графические изображения.



Реакция на события управляющих элементов


-----------------------------------------------------------------

Основное различие между окном палитры и режимными диалоговы- ми окнами, которые вы использовали ранее, состоит в том, что ре- жимное диалоговое окно манипулирует управляющими элементами, а затем считываете результаты, если пользователь щелкает "мышью" на командной кнопке OK. В данном безрежимном окне палитры вы имеете дело с активной, динамической частью программы и можете активно отвечать на каждый используемый управляющий элемент.

Пока командные кнопки выводятся в окне палитры, но щелчок кнопкой "мыши" не дает никакого эффекта. Щелчок и выбор "мышью" являются событиями управляющего элемента. Они аналогичны событиям меню, на которые вы отвечали в шаге 4.

Вы отвечали на события меню, определяя методы реакции на ко- манды. Что-то аналогичное нужно делать с сообщениями управляющих элементов. События управляющих элементов создают сообщения (на основе дочернего идентификатора), аналогичные командным сообщени- ям, но вместо идентификатора меню содержащие идентификатор управ- ляющего элемента. Для идентификации заголовка метода на основе дочернего идентификатора используйте сумму идентификаторов уп- равляющего элемента и констант id_First.

Примечание: Подробнее о командных сообщениях и уведом- ляющих сообщениях управляющих элементов рассказывается в Главе 16 "Сообщения окон".



Сглаживание линий


-----------------------------------------------------------------

Для сложных рисунков со многими длинными линиями повторное отображение рисунка может потребовать много времени. Graffiti ис- пользует одно из преимуществ графический функций GDI - функцию PolyLine, которая рисует сегменты каждой линии сразу, а не по од- ному. Эффектом будет более быстрое и плавное отображение окна.



Добавление всплывающего окна


-----------------------------------------------------------------

+-----------------------+ | 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 | | Step 9: Printing | |XStepX10:XPaletteXXXXXX| | Step 11: BWCC | | Step 12: Custom ctrls | +-----------------------+

Создание и уничтожение окон и диалоговых блоков прекрасно подходит, когда они используются не часто. Но в некоторых случаях желательно иметь дочернее окно, доступное большую часть времени. Примером такого окна является инструментальная палитра.

В этом шаге вы будете делать следующее:

* Добавите к основному окну поле.

* Построите плавающую палитру пера.

* Выведете и скроете палитру пера.

Палитра пера, которая выводится при выборе пользователем ко- манды Palette|Show (Палитра|Вывод) показана на Рис. 6.1.

+---------------------------------------+ |#=#XXXXXXXXXXXXXPenPaletteXXXXXXXXXXXXX| +------------------+--------------------| | @@ Add | Delete | | @@@@@@ Pen | @@@@@@ Pen | | @@ | | +------------------+--------------------| | | | ----------------------------- | | | +---------------------------------------| | | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | +---------------------------------------| | | | XXXXXXXXXXXXXXXXXXXXXXXXXXXXX | | | +---------------------------------------+

Рис. 6.1 Палитра пера программы Steps с тремя перьями.

Поскольку это первое "новое" окно, которое вы создаете и ко- торое будет создаваться автоматически, неплохо рассмотреть, как создаются и выводятся на экран объекты и элементы окна.



добавление специализированных управляющих элементов


-----------------------------------------------------------------

+-----------------------+ | 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 | | Step 9: Printing | | Step 10: Palette | |XStepX11:XBWCCXXXXXXXXX| | Step 12: Custom ctrls | +-----------------------+

В шаге 10 вы добавили к основному окну независимое дочернее окно. Теперь вы добавите зависимые дочерние окна, которые называ- ются управляющими элементами. Порождающим окном этих управляющих элементов является окно палитры пера. Нужно помнить о том, что окно палитры пера является независимым дочерним окном, для кото- рого порождающим окном является основное окно. Таким образом, па- литра пера является одновременно дочерним окном основного окна и порождающим окном для управляющих элементов.

В шаге 6 вы уже имели дело с управляющими элементами и объ- ектами управляющих элементов, но тогда вы просто связывали объек- ты с управляющими элементами, определенными в ресурсе. Построение управляющего элемента на этапе выполнения несколько сложнее, так как наряду с типом как вам требуется задать позицию и размер уп- равляющих элементов.

Палитра пера, показанная на Рис. 6.1, использует две специа- лизированные кнопки кисти и последовательность графических изоб- ражений, каждое из которых представляет перо, которое можно ис- пользовать для рисования. Эти перьевые "кнопки" фактически не яв- ляются управляющими элементами, а скорее представляют собой обра- зы в единственным дочернем окне, которое может их идентифициро- вать при щелчке "мышью".

В данном шаге вы добавите графические кнопки с помощью:

* добавления простых управляющих кнопок;

* реализации в программе специализированных управляющих эле- ментов;

* определения графических изображений для кнопок.

Для всех управляющих элементов, в целом, поведение задается типом ObjectWindows TControl и его типом-потомком, позволяющим работать с каждым типом управляющего элемента. Например, TListBox определяет объекты блока списка, а TEdit определяет каждый управ- ляющий объект редактирования. Вы должны также понимать, что TControl - это потомок TWindow.



Создание специализированного управляющего элемента окна


-----------------------------------------------------------------

+-----------------------+ | 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 | | Step 9: Printing | | Step 10: Palette | | Step 11: BWCC | |XStepX12:XCustomXctrlsX| +-----------------------+

Наиболее интересная часть создания палитры пера - это созда- ние вашего собственного специализированного окна палитры. Здесь вы можете, наконец, использовать возможности, предусмотренные стандартными инструментальными средствами окон, и что-то создать.

На этом шаге вы сделаете следующее:

* реализуете динамическое изменение размера окна палитры;

* зададите реакцию на уведомляющие сообщения от управляющих элементов;

* создадите объект палитры с несколькими областями.



Сокрытие вместо закрытия


-----------------------------------------------------------------

Если вы дважды щелкните "мышью" в блоке системного меню па- литры пера, оно исчезнет. Выбор команды Palette|Show не может больше выводить палитру, так как объект и его экранные элементы уничтожены. Выводить нечего. Вы можете переопределить это, доба- вив метод CanClose, который скрывает окно, а затем запрещает его закрытие (см. STEP11A.PAS):

function TPenPalette.CanClose: Boolean; begin Show(sw_Hide); CanClose := False; end;

Теперь двойной щелчок "мышью" в блоке системного меню скры- вает окно, но не закрывает его, так что позднее вы можете вывести его снова.

Обычно наличие дочернего окна, которое всегда возвращает из CanClose False, может предотвратить закрытие всего приложения. Но TStepWindow перед закрытием не проверяет своих дочерних окон, так как в шаге 1 вы переопределили его метод CanClose.



Создание для командных кнопок графических изображений


-----------------------------------------------------------------

Хотя вы можете создавать графические изображения (битовые массивы) для командных кнопок на этапе выполнения, эту задачу следует решать с помощью ресурсов. Используя пакет разработчика ресурсов, вам нужно создать для каждой командной кнопки три раз- личных битовых массива: один для позиционирования сверху, один для позиционирования снизу и один для позиционирования в фокусе ввода.

С этими графическими изображениями вы можете делать что угодно. Здесь ограничений нет. Вы можете изменять цвет образа в зависимости от состояния кнопки или перемещать образы (что обычно используется) при нажатии, а также добавлять вокруг текста кнопки линию из точек, когда она находится в фокусе (активна). На Рис. 6.2 показано три графических изображения для командной кнопки Add Pen палитры пера.

+------------------+ +------------------+ +------------------+ | |# | . | | . |# | @@ Add |# | @@ : Add : | | @@ : Add : |# | @@@@@@ Pen |# | @@@@@@ : Pen : | | @@@@@@ : Pen : |# | @@ |# | @@ . | | @@ . |# | |# | | | |# +------------------+# +------------------+ +------------------+# ################### ###################

Рис. 6.2 Графические изображения для специализированной кнопки.



Создание и уничтожение палитры


-----------------------------------------------------------------

Построение и уничтожение объекта палитры выполняется доста- точно просто. Конструктор Init вызывает TWindow.Init, затем изме- няет стиль окна (чтобы оно стало видимым дочерним окном). PenSet инициализируется как набор фиксированного размера, достаточно большой, чтобы содержать максимальное число заданных константой MaxPens перьев, и не возрастающий. Для текущего выбранного пера CurrentPen устанавливается в -1. Это означает, что выбранного пе- ра нет.

Наконец, Init загружает в UpPic и DownPic два графических образа. Они используются в качестве фона для каждого пера палит- ры. DownPic рисуется за выбранным пером, а UpPic - в качестве фо- на других перьев.

################### #+------------------+ +------------------+ #| | | |# #| | | |# #| | | |# +------------------+ +------------------+# ###################

Рис. 6.3 Фоновые образы палитры пера.

Деструктор Done перед вызовом наследуемого деструктора Done отменяет графические образы. Вызов DeleteObject для уничтожения графических образов имеет важное значение. Подобно контексту дисплея, графические образы являются ресурсами Windows, поддержи- ваемыми в ограниченной памяти Windows. Если размещаете их в памя- ти Windows и не удаляете, то ваша программа (и другие работающие параллельно с ней программы) потеряют доступ к этой памяти.

Init и Done объекта палитры выглядят следующим образом:

constructor TPenPic.Init(AParent: PWindowsObject); begin inherited Init(AParent, nil); AttrStyle := ws_Child or ws_Visible; PenSet := New(PCollection, Init(MaxPens, 0)); CurrentPen := -1; UpPic := LoadBitMap(HInstance, 'PAL_UP'); DownPic := LoadBitmap(HInstance, 'PAL_DOWN'); end;

destructor TPenPic.Done; begin DeleteObject(UpPic); DeleteObject(DownPic); Dispose(PenSet, Down); inherites Done; end;



Создание элементов экрана


-----------------------------------------------------------------

При построении объекта дочернего окна, ObjectWindows берет на себя функции по работе с соответствующими объекту элементами экрана. Это обратно тому, что вы делали в шаге 6 с помощью InitResource. Тогда вы имели созданный из ресурса элемент экрана и связывали с ним объект, благодаря чему могли манипулировать элементом экрана. Теперь вы создали собственный объект, и вам нужно сообщить Windows о необходимости создания соответствующего экранного элемента.

Когда вы в шаге 3 делали это для диалогового окна, то вызы- вали ExecDialog. Метод TApplication создает элемент экрана и вы- полняет режимное диалоговое окно. Соответствующим методом для не- режимных (или безрежимных) диалоговых окон является TApplication.MakeWindow. Основным отличием является то, что MakeWindow не выводит автоматически создаваемый элемент экрана и не переходит в режимное состояние.

Примечание: MakeWindow и создание элементов экрана подробно описываются в Главе 9 "Интерфейсные объекты".

Тогда процесс построения и вывода окна состоит из следующих трех этапов:

* Построение оконного объекта с помощью Init.

* Создание элемента экрана с помощью MakeWindow.

* Вывод окна с помощью Show.

К счастью, второй и третий шаги для основного окна приложе- ния выполняются автоматически. Кроме того, вызов для порождающего окна MakeWindow автоматически вызывает для любого окна в его списке дочерних окон MakeWindow, так что дочерние окна основного окна (такие как палитра пера) автоматически получают элементы эк- рана.

В следующем разделе мы выведем дочернее окно.



Выбор перьев с помощью "мыши"


-----------------------------------------------------------------

Наконец. TPenPic обеспечивает реакцию на щелчок в нарисован- ной области кнопкой "мыши". Заметим, что хотя размер объекта па- литры никогда не изменяется, он получает сообщение от щелчков "мышью" в области, фактически показываемой на экране. Палитра от- секается рамкой окна палитры. Это означает, что вы можете щелкать кнопкой "мыши" только непосредственно в палитре.

Поскольку каждый элемент в палитре имеет один и тот же раз- мер, WMLButton для определения того, в каком графическом образе была нажата кнопка "мыши", может просто разделить y-координату щелчка "мыши" (которая поступает в LParamHi) на 40 (размер каждо- го графического изображения). Затем он делает перо, на котором была нажата кнопка "мыши" текущим и задает в качестве пера для рисования копию выбранного пера палитры. Поскольку теперь есть выбранное перо, он разрешает кнопку Del Pen в окне палитры, затем запрещает палитру для обеспечения ее повторного отображения для того, чтобы показать новый выбор.

Код для WMLButtonDown имеет следующий вид (это дополняет текст STEP12B.PAS):

procedure TPenPic.WMLButtonDwon(var Msg: TMessage); begin CurrentPen := Msg.LParamHi div 40; if CurrentPen <> nil then Dispose(CurrentPen, Done); with PPen(PenSet^.At(CurrentPen))^ do CurrentPen := New(PPen, Init(Style, With, Color)); PPenPalette(Parent)^.DelBlt^.Enable; InvalidateRect(HWindow, nil, False); end;



Вывод и сокрытие палитры


-----------------------------------------------------------------

Дочерние окна, отличные от тех, которые были созданы и выве- дены по умолчанию их порождающими окнами (этот процесс управляет- ся с помощью EnableAutoCreate и DisableAutoCreate) в каждом ин- терфейсном объекте. Но вы можете скрыть или вывести дочернее окно по команде. Обе функции метода Show наследуются из TWindowsObject.

В зависимости от передаваемых параметров метод Show выводит либо скрывает окно. Параметр - это одна из констант sw_, опреде- ленная в Windows. В ответ на команды меню Palette|Show (Палит- ра|Вывод) или Palette|Hide (Палитра|Сокрытие), которые генериру- ют, соответственно, команды cm_PalShow и cm_PalHide, TStepWindow вызывает метод Show палитры пера (это дополняет STEP10.PAS):

procedure TStepWindow.CMPalShow(var Msg: TMessage); begin PenPalette^.Show(sw_ShowNA); end;

procedure TStepWindow.CMPalHide(var Msg: TMessage); begin PenPalette^.Show(sw_Hide); end;

Если у вас есть поле порождающего окна, указывающее на до- чернее окно, с которым необходимо работать, вы легко можете за- дать дочернее окно.

------------------------------------------------------------------------



Доступ к функциям API


-----------------------------------------------------------------

Чтобы из приложения ObjectWindows обратиться непосредствен- но к функциям API, вы должны использовать модуль WinProcs. WinProcs определяет для каждой функции Windows заголовок процеду- ры или функции Паскаля, что позволяет вам вызывать функции Windows как любую подпрограмму на Паскале. Перечень заголовков этих функций вы можете найти в файле WINPROCS.PAS или в оператив- ном справочнике Help.



Файлы ObjectWindows


-----------------------------------------------------------------

Перечисленные выше типы ObjectWindows реализует в скомпили- рованных модулях. В данном разделе кратко описывается содержимое поставляемых модулей. В каждом приложении ObjectWindows требуется только модуль OWindows.

В данной версии ObjectWindows различные части иерархии объ- ектов разбивают различные части иерархии объектов по отдельным моделям. Чтобы перекомпилировать код ObjectWindows более ранних версий, в общем случае нужно изменить все ссылки на модуль WObjects на OWindows и добавить к программам и модулям, использу- ющим наборы и потоки или диалоговые блоки, соответственно Objects и ODialogs.

В данную версию ObjectWindows добавлены также новые модули для печати, проверки данных, специализированных управляющих эле- ментов Borland и поддержки Windows 3.1.

В Таблице 7.1 перечислены модули, составляющие интерфейс ObjectWindows и AOPI Windows 3.0. Модули, поддерживающие расшире- ния Windows 3.1, представлены в Таблице 7.2.

Модули для ObjectWindows и API Windows Таблицы 7.1 +----------------+----------------------------------------------+ | Модуль | Содержимое | +----------------+----------------------------------------------| | Objects | Базовый объект TObject, наборы, потоки. | | OWindows | Приложения, окна, полосы прокрутки, окна MDI.| | ODialogs | Диалоговые блоки, диалоговые окна, управляю-| | | щие элементы. | | OPrinter | Печать, специальные распечатки. | | Validate | Проверка допустимости данных. | | BWCC | Специализированные управляющие элементы фирмы| | | Borland. | | OStdDlgs | Диалоговые блоки имен файлов, однострочный| | | ввод. | | OStdWnds | Окна текстового редактора, окна редактора| | | файлов. | | WinTypes | Все типы, используемые подпрограммами API| | | Windows 3.0, включая записи, стили, сообщений| | | и флаги. | | WinProcs | Описания процедур и функций для API Windows| | | 3.0. | +----------------+----------------------------------------------+



Файлы ресурсов


-----------------------------------------------------------------

Модули OStdDlgs, OStdWnds и OPrinter имеют связанные с ними файлы ресурсов. Ресурс модуля находится в файле с именем, эквива- лентным имени модуля, и расширением .RES. Ресурсы автоматически включаются при использовании соответствующих модулей, так что программа, которая использует модуль OstdDlgs, будет автоматичес- ки иметь доступ к ресурсам в OSTDDLGS.RES.



Кроме стандартных модулей API Windows


-----------------------------------------------------------------

Кроме стандартных модулей API Windows 3.0, вы можете писать программы, использующие преимущества средств, добавленных в вер- сию 3.1 Windows. Каждая из 11 DLL Windows 3.1 имеет соответствую- щий модуль:

Модули для доступа к средствам Windows 3.1 Таблица 7.2 +----------------+----------------------------------------------+ | Модуль | Средство | +----------------+----------------------------------------------| | ComDlg | Общие диалоговые блоки. | | DDTML | Сообщения динамического обмена данными. | | Dlgs | Константы диалогового блока. | | LZExpand | Расширения файла LZ. | | MMSystem | Расширения мультимедиа. | | OLE | Компоновка и встраивание объектов (OLE). | | ShellAPI | Оболочка API Windows. | | Stress | Строгая проверка типов. | | ToolHelp | Отладка и другие инструментальные средства. | | Ver | Версии. | | Win31 | Расширения Windows 3.1. | +----------------+----------------------------------------------+


Функции API Windows


-----------------------------------------------------------------

Функциональные возможности Windows заключены в ее около 600 функций. Каждая функция имеет имя. Взаимодействовать с операцион- ной средой Windows, модифицировать ее отображение или действие в ответ на ввод пользователя можно с помощью вызова функций API. Однако с помощью ObjectWindows вы можете создавать окна, выводить диалоговые блоки и манипулировать управляющими элементами, не вы- зывая функций Windows. Как все это работает?



Функции системного вызова


-----------------------------------------------------------------

Windows содержит ряд функций, которые позволяют вам прохо- дить в цикле (или перечислять) определенные типы элементов в сис- теме Windows. Например, перечислить шрифты системы и напечатать образец текста, используя каждый из них. Чтобы использовать такую функцию, вы должны передать указатель на нее (указатель системной функции), и Windows будет использовать его в процессе перечисле- ния. Данная функция при итерации по списку характеристик окна, окон, дочерних окон, шрифтов и т.д. вызывается непосредственно Windows.

Функции Windows, требующие функций системного вызова (или повторного вызова), включают в себя: EnimChildWindows, EnumClipboardFormats, EnumFonts, EnumMetaFile, EnumObjects, EnumPops, EnumTaskWindows и EnumWindows.

Функция системного вызова должна быть обычной функцией, а не методов объекта. Указатель на эту функцию передается в качестве первого параметра (типа TFarProc) данных методов. Например, если вы определили в Паскале функцию системного вызова ActOnWindow следующим образом:

function ActOnWindow(TheHandle: HWnd; The Value: Longint): Integer; far; export;

то можете передать ее в качестве функции системного вызова при вызове функции Windows EnumWindows:

ReturnValue := EnumWindows(TFarProc(ActOnWindow), ALongint);

Функция системного вызова должна иметь тот же тип возвращае- мого значения, что и вызывающая ее функция Windows. Функция ActOnWindows будет выполнять некоторое действие с окном, заданным переданным указателем. Параметр TheValue - это любое значение, выбранное для передачи в вызывающей программе.

Директива компилятора {$K+} позволяет автоматически управ- лять функциями системного вызова. Если вы не выбираете {$K+}, то для возврата адреса, по которому будет выполнять вызов Windows, должны передавать свои функции системного вызова через функцию API MakeProcInstance.



Иерархия ObjectWindows


-----------------------------------------------------------------

ObjectWindows представляет собой исчерпывающий набор объек- тов, облегчающий разработку программ для Microsoft Windows на Паскале. В данной главе вы найдете обзор иерархии объектов ObjectWindows. В остальных главах данной части дается детальное описание различных частей иерархии.

Кроме описания иерархии объектов, в данной главе описываются основные принципы программирования для операционной среды Windows, включая вызов API Windows.



Иерархия объектов


-----------------------------------------------------------------

ObjectWindows - это иерархия объектных типов, которые вы мо- жете использовать для работы с большинством обычных задач в при- ложении ObjectWindows. Схема объектов пользовательского интерфей- са библиотеки показана на Рис. 7.1. На Рис. 7.2 представлены объ- екты иерархии, используемые для управления данными и проверки их допустимости.

+----------------+ | TObject | +-------+--------+ +----+--------------+----------------------+------------+ +-------+--------+ +--------+-------+ +-------+--------+ | | TPrintout | | TPrinter | | TWindowsObject | | +-------+--------+ +----------------+ +-------+--------+ | +-------------------+ | | +-------+--------+ +--------+-------+ +-----------+ | | TEditPrintout | | TWindowPrintout| | +------------| +----------------+ +----------------+ | | | | +-------+--------+ | +--------------------+----------+ | TApplication | | | | +----------------+ | +------+---------+ +--------+-------+ +------------+ | TDialog | | TWindow | | +------+---------+ +--------+-------+ +-------+--------+ | | | TScroller | | | +----------------+ | +----------------------------+ +-------------------+-----------+ | +------+---------+ +-------+--------+ | | | TFileDialog | | TInputDialog | | | +----------------+ +----------------+ | | +----------------------+--------+ | +------+------------+ | | |TPrinterAbortDialog| +------------+ | +-------------------+ | | | | +--------+-------+ | +---------------------| | TPrintDialog | | +-------+--------+ | +----------------+ | |TPrinterSetupDlg| +------------+ | +----------------+ | | +--------+-------+ | | TDlgWindow | | +----------------+ |

| +------------------+------------------+----------| +-------+--------+ +-------+--------+ +-------+--------+ | | TControl | | TMDIClient | | TMDIWindow | | +-------+--------+ +----------------+ +----------------+ | +------------------+-----------+ +------+ +-------+--------+ +-------+--------+ | +-------+--------+ | TButton | | TScrollBar | | | TEditWindow | +-------+--------+ +----------------+ | +-------+--------+ +-------+--------+ | +-------+--------+ | TCheckBox | +-----------| | TFileWindow | +-------+--------+ | | +----------------+ +-------+--------+ +-------+--------+ +----------+ | TRadioButton | | TStatic | | | +----------------+ +-------+--------+ | | | | +-------+--------+ +-------+--------+ | | TListBox | | TEdit | | +-------+--------+ +----------------+ | | | +-------+--------+ +-----------+ | TComboBox | | +----------------+ +-------+--------+ | TGroupBox | +----------------+


Модуль OPRINTER: Модуль OWINDOWS: Модуль OSTDDLGS:

TPrinout TWindow TInputDialog TPtinter TAppication TInputDialog TEditPrintout TScroller TWindowPrintout TPrinterAbortDlg TPrintDialog TPrinterSetup

Модуль ODUIALOGS: Модуль OSTDWNDS:

TDialog TEditWindow TDlgWindow TFileWindow TButton TScrollBar TCheckBox TRadioButton TControl TStatic TEdit TListBox TComboBox TGroupBox Рис. 7.1. Иерархия объектных типов ObjectWindows.

+----------------+ | TObject | +-------+--------+ +----+--------------+--------------------+ +-------+--------+ +-------+--------+ +-------+--------+ | TValidator | | TCollection | | TStream | +-------+--------+ +----------------+ +---------+------+ +---------------------+------------------+ +--------+ +-------+-----------+ +-------+--------+ +-------+--------+ | |TPXPictureValidator| |TFilterValidator| |TLookupValidator| | +-------------------+ +-------+--------+ +-------+--------+ | +-------+---------+ | | | TRangeValidator | +--+ | +-----------------+ | | +----------+-----------+ | |TStringLookupValidator| | +----------------------+ | +-------------------+-------------------+-----------+ +-------+--------+ +-------+--------+ +-------+--------+ | TMemoryStream | | TDosStream | | TEmsStream | +----------------+ +-------+--------+ +----------------+ +-------+--------+ | TBufStream | +----------------+

Рис. 7.2 Иерархия наборов, потоков и проверки допустимости.

Модуль OBJECTS: Модуль VALIDATE:

TObject TValidator TCollection TPXPictureValidator TSortedCollection TFilterValidator TStrCollection TRangeValidator TStringCollection TLookupValidator TStream TStrongLookupValidator TMemoryStream TEmsStream TDosStream TBufStream

Базовый объект

TObject - это базовый объектный тип, общий предок всех объ- ектов ObjectWindows. Он определяет рудиментарный конструктор и деструктор. Потоки ObjectWindows требует, чтобы записанные объек- ты были потомками TObject.

TApplication

Этот тип определяет поведение, необходимое для всех приложе- ний ObjectWindows. Каждое приложение ObjectWindows, которое вы пишете, будет определять объектный тип приложения, производный от TApplication. Объекты приложения подробно описываются в Главе 8 "Объекты приложения".



Интерфейсные объекты

Остальные объекты в иерархии ObjectWindows классифицируются в общем случае как интерфейсные объекты. Они являются интерфейс- ными в том смысле, что представляют элементы и пользовательском интерфейсе Windows и служат интерфейсом между кодом вашей прик- ладной программы и операционной средой Windows. Интерфейсные объ- екты описываются в Главе 9.

Объекты Windows

Объекты Windows представляются не только знакомыми окнами среды Windows, но и большинством визуальных инструментов операци- онной среды, такими как управляющие элементы.

Объекты диалоговых блоков

Объекты диалоговых блоков (окон) обеспечивают временные ок- на для обслуживания специальных функций ввода и вывода. В общем случае они включат в себя текст и управляющие элемента, такие как командные кнопки, блоки списка и полосы прокрутки. Об объектах диалоговых блоков подробно рассказывается в Главе 11.

Объекты управляющих элементов

В диалоговых блоках и некоторых окнах управляющие элементы позволяют пользователям вводить данные и выбирать параметры. Объ- екты управляющих элементов обеспечивают согласованные и простые средства для работы со всеми типами управляющих элементов, опре- деленных в Windows. Объекты управляющих элементов подробно описы- ваются в Главе 12.

Объекты MDI

Windows реализует стандарт для работы с несколькими докумен- тами в рамках одного окна, которое называется множественным доку- ментальным интерфейсом (MDI). ObjectWindows обеспечивает средства для установки окон MDI и работы с ними. Объекты MDI подробно опи- сываются в Главе 14.

Объекты проверки допустимости

ObjectWindows содержит полный набор объектов проверки допус- тимости данных, которые позволяют проверять допустимость данных, введенных пользователем, при выходе пользователя из поля или ког- да пользователь завершает работу с окном. Проверка допустимости данных описывается в Главе 13.

Объекты принтера

Для работы с печатью документов или печати содержимого окна ObjectWindows предусматривает соответствующие объекты. О том, как использовать эти объекты, рассказывается в Главе 15.

Объекты наборов и потоков

Модуль Object включает в себя многочисленные объекты, реали- зующие гибкие структуры данных и потоки, позволяющие считывать и записывать объекты. Наборы описываются в Главе 19, а потоки - в Главе 20.


Имена методов


-----------------------------------------------------------------

Методы реакции на сообщения называются по именам сообщений, на которые они отвечают, но без подчеркиваний. Например, метод, отвечающий на сообщение wm_KeyDown будет называться WMKeyDown.



Имена объектов


-----------------------------------------------------------------

Имена всех объектный типов, предусмотренных в ObjectWindows, начинаются с буквы T. Например, объекты диалоговых окон имеют тип TDialog. Для каждого определения объектного типа имеется соот- ветствующий ссылочный тип, начинающийся с P. Например, указатель на TDialogh имеет тип PDialog. В примерах данного руководства бу- дут создаваться динамические экземпляры объектов, например, с по- мощью PDialog позволяет разместить объекты TDialog в динамически распределяемой области памяти.



Комбинирование констант стилей


-----------------------------------------------------------------

Функции Windows, позволяющие получить интерфейсные элементы, требуют обычно некоторого параметра типа Word или Longint. Иден- тификаторы констант стилей состоят из двухбуквенного мнемоничес- кого префикса, за которым следует подчеркивание и описательное имя. Например, ws_Popup - это константа стиля окна (ws_ означает стиль окна - window style").

Примечание: В Windows определены сотни констант сти- лей, которые перечислены в Главе 21 "Справочник по ObjectWindows".

Часто эти стили комбинируются для получения другого сти- ля. Например, в случае функции MessageBox вы можете передать в качестве параметра стиля mb_YesNo или mb_IconQuestion. Этот стиль дает окно сообщений с двумя командными кнопками Yes и No и пик- торгаммой вопросительного знака. Поразрядная операция or факти- чески комбинирует две константы бит за битом. Полученный в ре- зультате стиль представляет собой комбинацию обоих стилей.

Имейте в виду, что некоторые стили взаимноисключающие. Их комбинирование может дать непредвиденные или нежелательные ре- зультаты.



Константы Windows


-----------------------------------------------------------------

Функции Windows требуют от вас передачи в качестве аргумен- тов разнообразных констант типа Word или Longint. Эти константы представляют стили окна, диалогового блока или управляющего эле- мента, а также возвращаемые значение и др. Если в программе ис- пользуются данные константы, она становится более читаемой, обс- луживаемой и будет более независимой от изменений в последующих версиях Windows, чем программы, использующие числа. Определенные в модуле WinTypes константы описываются в Главе 21 "Справочник по ObjectWindows".



Обзор объектов


-----------------------------------------------------------------



Соглашения Windows


-----------------------------------------------------------------

Соглашения по именам ObjectWindows обеспечивают ясность и содержательность имен.



Типы функций Windows


-----------------------------------------------------------------

Ниже перечислены виды функций Windows, которые вы можете ис- пользовать в своих программах ObjectWindows.

Функции интерфейса с администратором Windows

Эти функции выполняют обработку сообщений, манипулируют ок- нами и диалоговыми блоками и создают системный вывод. Данная ка- тегория включает в себя функции для меню, курсоров и буфера выре- занного изображения.

Функции интерфейса с графическими устройствами (GDI)

Эти функции выводят на разнообразные устройства (включая эк- ран и принтер) текст, графику и битовые массивы. Они не привязаны к конкретному устройству и являются независимыми от устройства.

Функции интерфейса со служебными функциями системы

Эти функции работают с широким диапазоном системных уст- ройств, включая управление памятью, взаимодействие с операционной системой, администрирование ресурсов и коммуникации.



Вызов в ObjectWindows функций API


-----------------------------------------------------------------

Методы ObjectWindows вызывают функции API. Но ObjectWindows - это не дублируемые функциональные возможности; она предоставля- ет в новом пакете объектно-ориентированной библиотеки функции Windows, ее прикладной программный интерфейс (API). Кроме того, ObjectWindows значительно упрощает задачи спецификации многочис- ленных параметров, требуемых в функциях Windows. Часто ObjectWin- dows автоматически подставляет параметры, такие как описатели окон и идентификаторы дочерних окон, которые хранятся в интер- фейсных объектах в виде полей.

Например, многие функции Windows для задания окна, с которым они должны работать, используют описатели окна, и эти функции вы- зываются обычно из методов оконного объекта. Объекты содержат в поле HWindow описатель соответствующего окна и может передавать его, освобождая вас от необходимости каждый раз задавать описа- тель. Таким образом, объектные типы ObjectWindows служат объект- но-ориентированным слоем, реализованным с помощью вызовов необъ- ектно-ориентированных функций API.



Взаимодействие с Windows


-----------------------------------------------------------------

ObjectWindows избавляет вас от многих утомительных и запу- танных частей Windows, но иногда возникает необходимость в непос- редственном взаимодействии с Windows (например, когда вы хотите переопределить некоторое заданное в Windows поведение или выйти за рамки того, что инкапсулировано в ObjectWindows).

Существует два способа, с помощью которых вы можете взаимо- действовать с ObjectWindows: вызов ее функций API и получение со- общений. В данном разделе описываются функции API. Об обработке сообщений рассказывается в Главе 16 "Сообщения Windows".



Записи данных Windows


-----------------------------------------------------------------

Некоторые функции Windows требуют более сложных структур данных, например, шрифтов (TLongFont) или классов окон (TWndClass). Windows и ObjectWindows определяют эти и другие структуры данных. Перечень доступных структур вы можете найти в оперативном справочнике или в файле WINTYPES.PAS. Структуры, не- посредственно используемые в ObjectWindows, вы можете найти в Главе 21 "Справочник по ObjectWindows".

При использовании ObjectWindows все функции Windows доступны также непосредственно и могут вызываться в вашей программе (если в ее операторе uses указывается модуль WinProcs). Например, сле- дующий код для получения окна сообщений вызывает функцию Windows MessageBox:

Reply := MessageBox(HWindow, 'Хотите сохранить?', 'Файл изменен', mb_YesNo or mb_IconQuestion);

MessageBox возвращает целочисленное значение, указывающее, какое действие выбрал пользователь для закрытия окна сообщения. Если пользователь щелкнул "мышью" на командной кнопке Yes (Да), то результат равен определенной в Windows целочисленной константе id_Yes. Если пользователь щелкнул "мышью" на командной кнопке No (Нет), то результат равен id_No.



Деструктор Done


-----------------------------------------------------------------

Done - это деструктор объекта приложения. Перед завершением работы приложения он освобождает память объекта приложения.

+----------------+ +-----------------+ | Init +--+->| InitApplication | +----------------+ | +-----------------+ | | +-----------------+ +----------------+ +->| InitInstance +--->| InitMainWindow | +-----------------+ +----------------+

+----------------+ +-----------------+ | Run +---->| MessageLoop | +----------------+ +-----------------+

+----------------+ | Done | +----------------+

Рис. 8.1 Вызовы методов, управляющие работой приложения.



Объекты приложения


При разработке приложения ObjectWindows вам нужно сначала определить объект приложения, производный от типа ObjectWindows TApplication. Этот объект инкапсулирует следующее поведение при- ложения ObjectWindows:

* Создание и вывод основного окна приложения.

* Инициализацию первого экземпляра приложения для задач, вы- полняемых всеми экземплярами приложения, таких как созда- ние файлов, совместно используемых всеми экземплярами.

* Инициализацию каждого экземпляра приложения, например, загрузку таблицы оперативных клавиш.

* Обработку сообщений Windows.

* Закрытие приложения.

Кроме определения объекта приложения вы должны добавить к нему возможность построения объекта основного окна. Вы имеете также возможность переопределения используемого по умолчанию по- ведения инициализированных экземпляров, закрытия приложения и об- работки сообщений Windows.



Инициализация каждого экземпляра


-----------------------------------------------------------------

Пользователь может одновременно выполнять несколько экземп- ляров ObjectWindows. Метод InitInstance инициализирует каждый эк- земпляр приложения. Он должен инициализировать только само прило- жение, а не его основное окно. Основное окно инициализируйте в InitMaionWindow.

InitInstance вызывает InitMainWindow, а затем создает и вы- водит основное окно. Для модификации стандартной инициализации приложения (например, для загрузки таблицы оперативных клавиш) вам нужно только переопределить InitInstance. Если вы переопреде- ляете для своего приложения InitInstance, убедитесь сначала, что он вызывает метод InitInstance, наследуемый из TApplication.

Приведем метод InitInstance, который перед выполнением при- ложения загружает метод InitInstance. 'MeHotKeys' - это идентифи- катор ресурса таблицы оперативных клавиш, определенный в файле ресурса:

procedure TEditApplication.InitInstance; begin inherited InitInstance; HAccTable := LoadAccelerators(HInstance, 'MyHotKeys'); end;

Вы можете также использовать InitInstance для регистрации экземпляра приложения с внешней DLL (типа Paradox Engine).



Инициализация основного окна


-----------------------------------------------------------------

Вы должны определить метод InitMainWindow, который строит и инициализирует объект основного окна и сохраняет его в поле MainWindow объекта приложения. Ниже показан пример описания объ- екта приложения и метода InitMainWindow.

Данный метод создает новый экземпляр типа TWindow ObjectWindows (PWindow - это указатель на тип TWindow). Обычно ваша программа будет определять для своего основного окна новый оконный тип, а InuitMainWindow будет использовать этот тип вмес- то TWindow.

Примечание: Объекты окон подробно описываются в Главе 10.

Следующее простое приложение ObjectWindows объединяет в себе новый тип TMyApplication и старое приложение MinApp. Оно отлича- ется от MinApp только тем, что основное окно имеет заголовок:

program TestApp; uses OWindows;

type TMyApplication = object(TApplication) procedure InitMainWindow; virtual; end;

procedure TMyApplication.InitMainWindow; begin MainWindow := New(PWindow, Init(nil, 'Основное окно')); end;

var MyApp: TApplication; begin MyApp.Init('TestApp'); MyApp.Run; MyApp.Done; end;

Программа TestApp выводит окно с заголовком 'Основное окно'. Вы можете легко перемещать это окно и изменять его размер, мини- мизировать его, восстанавливать или максимизировать. Закрытие ок- на завершает приложение. Короче, TestApp - это полнофункциональ- ный "скелет" приложения, оснащенный только простейшим основным окном.



Инициализация первого экземпляра


-----------------------------------------------------------------

Если пользователь запускает приложение в отсутствии уже ра- ботающих других экземпляров этого приложения, вы можете опреде- лить для такого первоначального запуска некоторую обработку. Эта обработка называется инициализацией первого экземпляра. Если пользователь начинает и завершает ваше приложение, затем запуска- ет его снова и т.д., каждый экземпляр будет первым экземпляром (поскольку другие экземпляры в этот момент не выполняются).

Если текущим экземпляром является первый экземпляр, то конс- труктор Init вызывает InitApplication. TApplication определяет заместитель метода InitApplication, который вы можете для выпол- нения специальной инициализации первого экземпляра переопреде- лить.

Например, вы можете модифицировать TestApp таким образом, чтобы о первом экземпляре сообщалось в заголовке основного окна. Для этого добавьте в тип приложения TMyApplication булевское поле с именем FirstApp, затем переопределите метод InitApplication, чтобы он устанавливал FirstApp в True. Наконец, модифицируйте InitMainWindow для проверки FirstApp и вывода в основном окне приложения соответствующего заголовка (см. Рис. 8.2).

################################################################# #+----------------------------------------------------+-+-+###### #|#=###############Первый экземпляр###################|^|v|###### #+----------------------------------------------------+-+-|###### #| |###### #| +----------------------------------------------------+-+-+### #| |#=###############Дополнительный экземпляр###########|^|v|### #| +----------------------------------------------------+-+-|### #| | |### #| | +----------------------------------------------------+-+-+ #| | |#=#XXXXXXXXXXXXДополнительный экземплярXXXXXXXXXXXXX|^|v| #| | +----------------------------------------------------+-+-| #| | | | #| | | | #+--| | | ####| | | ####+--| | #######| | #######+--------------------------------------------------------+

Рис. 8.2 Новая инициализация приложения.

program TestApp; uses OWindows;

type TMyApplication = object(TApplication) FirstApp: Boolean; procedure InitMainWindow; virtual; procedure InitApplication; virtual; end;

procedure TMyApplication.InitMainWindow; begin if FirstApp then MainWindow := New(PWindow, Init(nil, 'Первый экземпляр')) else MainWindow := New(PWindow, Init(nil, 'Дополнительный экземпляр')); end;

procedure TMyApplication.InitApplication; begin FirstApp := True; end;

var MyApp; TMyApplication; begin MyApp.Init('TestApp'); MyApp.Run; MyApp.Done; end.



Инициализация приложения


-----------------------------------------------------------------

Выполнение методов, инициализирующих объект приложения, поз- воляет вам настроить части процесса путем переопределения отдель- ных методов. Один из методов, требующий переопределения, чтобы приложения получило конкретный смысл - это InitMainWindow. Вы мо- ете также переопределить InitInstance и InitApplication.



Конструктор Init


-----------------------------------------------------------------

Первый оператор - это вызов конструктора Init приложения. Этот вызов делает следующее:

* Строит объект.

* Инициализирует поля данных объекта.

* Устанавливает глобальную переменную Application на объект (@Self).

* Выполняет два вида инициализации:

- Вызывает InitApplication при отсутствии других выполняе- мых экземпляров данного приложения.

- Всегда вызывает InitInstance, устанавливая вызовом InitMainWindow основное окно.

Когда Init завершает работу, основное окно вашего приложения находится на экране. В большинстве случаев вам нужно только пере- определить InitMainWindow.



Метод Run


-----------------------------------------------------------------

Второй оператор вызывает метод Run приложения, который реа- лизует работу приложения, вызывая MessageLoop. MessageLoop обра- батывает сообщения от Windows и выполняет инструкции, которые уп- равляют работой приложения. MessageLoop - это цикл, который про- должает работу до закрытия приложения.

MessageLoop вызывает несколько методов, обрабатывающих конк- ретные поступающие сообщения (см. далее).



Методы Init, Run и Done


-----------------------------------------------------------------

Так как в основном блоке программы ObjectWindows каждый раз используются одни и те же операторы, вы должны понимать, что де- лает каждый из них.



Минимальное приложение


-----------------------------------------------------------------

Приведем пример минимального приложения ObjectWindows:

program MinApp; uses OWindows; var MyApp: TApplication; begin MyApp.Init('TtstApp'); MyApp.Run; MyApp.Done; end;

MinApp - это абсолютный минимум приложения ObjectWindows. Эта программа не требует определения других объектов. Обычно вы определяете новый объектные типы как минимум для приложения и ос- новного окна.



Минимальные требования


-----------------------------------------------------------------

Чтобы ваше программа ObjectWindows стала рабочим приложением Windows, она должна в своем основном блоке beginend делать сле- дующее:

- выполнять инициализацию;

- обрабатывать сообщения;

- по требованию завершать работу.

Используемый по умолчанию объект выполняет эти задачи путем вызова трех его методов: Init, Run и Done. Основной блок любой программы ObjectWindows содержит обычно эти три метода. Чтобы из- менить поведение по умолчанию, методы нужно переопределить.



Модификация поведения при закрытии


-----------------------------------------------------------------

Все оконные объекты, наследуют булевский метод CanClose, возвращающий по умолчанию True (что указывает на подтверждение закрытия, то есть TestApp закрывается немедленно). Для изменения поведения при закрытии вы можете переопределить методы CanClose приложения или основного типа окна. Если какие-либо объекты возв- ращают из CanClose значение False, то приложение завершиться не может. Обычно вы можете изменить поведение при закрытии объектно- го типа основного окна. Например, перед завершением можно убе- диться, что приложение сохранило файлы.

Механизм CanClose

Механизм CanClose дает основному окну, объекту приложения и другим окнам возможность подготовиться с закрытию или предотвра- тить его. В итоге объект приложения должен разрешить закрытие приложения. По умолчанию он проверяет основное окно. Обычная пос- ледовательность закрытия выглядит следующим образом:

1. Windows посылает основному окну приложения сообщение wm_Close.

2. Объект основного окна вызывает метод CanClose объекта приложения.

3. Объект приложения вызывает метод CanClose.

4. Объект основного окна вызывает метод CanClose для каждого из дочерних окон и возвращает True только в том случае, если методы CanClose дочерних окон возвращают True.

Модификация CanClose

Методы CanClose редко возвращают значение False. Вместо это- го они должны выполнять некоторые действия, позволяющие им возв- ращать True. Метод CanClose должен возвращать False только в том случае, если он не может выполнить действий, необходимых для нор- мального завершения, или пользователь показывает, что он хочет продолжать выполнение программы.

Например, метод CanClose окна редактора может проверять из- менение редактируемого текста, а затем выводить диалоговое окно с запросом, нужно ли сохранить текст перед закрытием, и восприни- мать ответ Yes (Да), No (Нет) или Cancel (Отмена). Cancel будет указывать, что пользователь пока не хочет закрывать приложение, так что CanClose должен возвращать False. CanClose следует также возвращать False при обнаружении ошибки в сохранении текста, пре- доставляя пользователю другую возможность сохранения данных перед закрытием.

Если метод CanClose не переопределяется, тип основного окна наследует его от TWindowsObject, где CanClose возвращает True после вызовов методов CanClose дочерних окон. Чтобы модифициро- вать поведение при закрытии основного окна, переопределите метод CanClose. Например, для проверки того, что пользователь собирает- ся закрыть окно, он может проверять открытые файлы.



Поиск объекта приложения


-----------------------------------------------------------------

Когда программа выполняется, ObjectWindows поддерживает гло- бальную переменную Application - указатель на объект приложения. Этот указатель позволяет подпрограммам вне объекта приложения об- ращаться к его полям и методам. По умолчанию Application устанав- ливается в @Self конструктором объекта приложения и в nil дест- руктором объекта.



Специальный вывод основного окна


-----------------------------------------------------------------

Начальный вывод основного окна управляется переменной CmdShow модуля System. CmdShow содержит одну из констант sw_ и передается в качестве параметра функции API Windows ShowWindow, когда приложение создает свое основное окно.

С помощью CmdShow вы легко можете вывести основное окно в нормальном виде (по умолчанию), распахнутым до размеров полного экрана, минимизированным в пиктограмму или скрытым. Лучшим местом присваивания значения CmdShow является конструктор объекта основ- ного окна. Это обеспечивает передачу выбранного значения при соз- дании элемента экрана.



Выполнение приложений


-----------------------------------------------------------------

Метод Run вашего приложения вызывает метод MessageLoop, ко- торый вызывает цикл обработки сообщений вашей программы. Во время выполнения вашей программы в цикле обработки сообщений обрабаты- ваются поступающие от Windows сообщения. Программы ObjectWindows наследуют цикл MessageLoop, который работает автоматически. До- полняйте цикл обработки сообщений только специальными диалогами, оперативными клавишами или обработкой MDI.

Метод MessageLoop для обработки сообщений Windows вызывает три метода. ProcessDlgMsg работает с безрежимными диалоговыми ок- нами, ProcessAccels - обрабатывает оперативные клавиши, а ProcessMDIAccels - оперативные клавиши для приложений MDI. Для приложений, не использующих командные клавиши или безрежимные ди- алоговые окна или не являющихся приложениями MDI MessageLoop мож- но несколько упростить. См. методы TApplication в Главе 21.



Закрытие приложений


-----------------------------------------------------------------

Вызов деструктора Done в основной программе вашего приложе- ния уничтожает объект приложения. Однако, перед тем как это про- исходит, программа должна прервать цикл обработки сообщения. Это может произойти, когда пользователь пытается закрыть основное ок- но приложения. Мы говорим, что это может произойти, так как ObjectWindows предусматривает механизм изменения поведения прило- жения при закрытии: вы можете задавать условия закрытия.