Итак, вы создали два типа окон - основное окно (объект TStepWindow) и режимные дочерние окна, которые создаются и уничтожаются каждый раз, когда они необходимы (например, блоки сообщений). Однако, в полноценной программе Windows дочерние окна часто требуется сохранять активными в течении неопределенного периода времени (в качестве примера можно привести окно оперативной полосы SpeedBar в работающей под Windows интегрированной среде IDE).
    До сих пор все дочерние окна в Steps имели фиксированный размер и создавались из шаблонов ресурсов. В шагах 10 - 12 вы будете делать следующее:
    Наконец, мы дадим предложения по дальнейшему расширению программы Steps.
    Создание и уничтожение окон и диалоговых блоков прекрасно
подходит, когда они используются не часто. Но в некоторых случаях
желательно иметь дочернее окно, доступное большую часть времени.
Примером такого окна является инструментальная палитра.
    В этом шаге вы будете делать следующее:
Палитра пера, которая выводится при выборе пользователем
команды Palette|Show (Палитра|Вывод) показана на Рис. 6.1.
    Поскольку это первое "новое" окно, которое вы создаете и
которое будет создаваться автоматически, неплохо рассмотреть, как
создаются и выводятся на экран объекты и элементы окна.
    Режимными дочерними окнами, которые вы до сих пор
использовали, управлять легко. Вы можете их создать, использовать и
отменить. Но здесь нам нужно работать с диалоговым окном, которое не
является режимным. Например, оно должно закрываться, если
закрывается приложение, и становиться скрытым, если основное окно
минимизируется.
    В основном поведение диалоговых окон (такое как закрытие или
когда окно временно становится скрытым) автоматическое.
Единственный случай, когда вам нужно делать что-то особенное - это
когда вы хотите сделать дочернее окно независимым от его
порождающего окна. Например, окно палитры, которое вы собираетесь
выводить, сможет скрываться и выводиться, пока основное окно остается
видимым. Окна, которые могут перемещаться или выводиться
независимо от своих порождающих окон, называются независимыми дочерними
окнами. На следующем шаге вы будете создавать зависимые окна,
присоединенные к независимому окну палитры.
    Так как основное окно должно посылать команды окну палитры,
потребуется указатель на это окно, поэтому добавьте в TStepWindow
его палитры пера. TStepWindow содержит теперь следующие поля:
    Осталось только построить объект дочернего окна и присвоить
его PenPalette. Этому посвящен следующий раздел.
    Объекты дочернего окна строятся обычно в конструкторах своих
порождающих окон. Аналогично тому, как это происходит при
инициализации любого другого поля, вы присваиваете значения любому
указателю дочернего окна. В данном случае:
constructor TStepWindows.Init(AParent: PWindowsObject;
ATitle: PChar);
begin
inherited Init(AParent, ATitle);
.
.
.
PenPalette := New(PPenPalette, Init(@Self, 'Pan Palette');
end;
    Порождающие окна автоматически управляют своими дочерними
окнами, поэтому при создании дочернего окна ему передается
указатель на объект порождающего окна. Поскольку порождающее окно
обычно строит свои дочерние окна, указателем порождающего окна
обычно является @Self.
    Важным исключением является основное окно приложения. Так
как оно не имеет порождающего окна, конструктору основного окна
передается nil.
    Каждый оконный объект имеет список своих дочерних окон,
обеспечивающий создание, вывод, сокрытие и закрытие окон в нужные
моменты. Построение и уничтожение дочерних окон автоматически
обновляет список дочерних окон: построение дочернего окна добавляет
его в список своего порождающего окна; уничтожение окна удаляет
его из списка.
    При построении объекта дочернего окна, ObjectWindows берет
на себя функции по работе с соответствующими объекту элементами
экрана. Это обратно тому, что вы делали в шаге 6 с помощью
InitResource. Тогда вы имели созданный из ресурса элемент экрана
и связывали с ним объект, благодаря чему могли манипулировать
элементом экрана. Теперь вы создали собственный объект, и вам
нужно сообщить Windows о необходимости создания соответствующего
экранного элемента.
    Когда вы в шаге 3 делали это для диалогового окна, то
вызывали ExecDialog. Метод TApplication создает элемент экрана и
выполняет режимное диалоговое окно. Соответствующим методом для
нережимных (или безрежимных) диалоговых окон является
TApplication.MakeWindow. Основным отличием является то, что
MakeWindow не выводит автоматически создаваемый элемент экрана и
не переходит в режимное состояние.
    Примечание: MakeWindow и создание элементов экрана
подробно описываются в Главе 9 "Интерфейсные объекты".
    Тогда процесс построения и вывода окна состоит из следующих
трех этапов:
    К счастью, второй и третий шаги для основного окна
приложения выполняются автоматически. Кроме того, вызов для порождающего
окна MakeWindow автоматически вызывает для любого окна в его
списке дочерних окон MakeWindow, так что дочерние окна основного
окна (такие как палитра пера) автоматически получают элементы
экрана.
    В следующем разделе мы выведем дочернее окно.
    Дочерние окна, отличные от тех, которые были созданы и
выведены по умолчанию их порождающими окнами (этот процесс
управляется с помощью EnableAutoCreate и DisableAutoCreate) в каждом
интерфейсном объекте. Но вы можете скрыть или вывести дочернее окно
по команде. Обе функции метода Show наследуются из
TWindowsObject.
    В зависимости от передаваемых параметров метод Show выводит
либо скрывает окно. Параметр - это одна из констант sw_,
определенная в Windows. В ответ на команды меню Palette|Show
(Палитра|Вывод) или Palette|Hide (Палитра|Сокрытие), которые
генерируют, соответственно, команды cm_PalShow и cm_PalHide, TStepWindow
вызывает метод Show палитры пера (это дополняет STEP10.PAS):
    Если у вас есть поле порождающего окна, указывающее на
дочернее окно, с которым необходимо работать, вы легко можете
задать дочернее окно.
    В шаге 10 вы добавили к основному окну независимое дочернее
окно. Теперь вы добавите зависимые дочерние окна, которые
называются управляющими элементами. Порождающим окном этих управляющих
элементов является окно палитры пера. Нужно помнить о том, что
окно палитры пера является независимым дочерним окном, для
которого порождающим окном является основное окно. Таким образом,
палитра пера является одновременно дочерним окном основного окна и
порождающим окном для управляющих элементов.
    В шаге 6 вы уже имели дело с управляющими элементами и
объектами управляющих элементов, но тогда вы просто связывали
объекты с управляющими элементами, определенными в ресурсе. Построение
управляющего элемента на этапе выполнения несколько сложнее, так
как наряду с типом как вам требуется задать позицию и размер
управляющих элементов.
    Палитра пера, показанная на Рис. 6.1, использует две
специализированные кнопки кисти и последовательность графических
изображений, каждое из которых представляет перо, которое можно
использовать для рисования. Эти перьевые "кнопки" фактически не
являются управляющими элементами, а скорее представляют собой
образы в единственным дочернем окне, которое может их
идентифицировать при щелчке "мышью".
    В данном шаге вы добавите графические кнопки с помощью:
    Для всех управляющих элементов, в целом, поведение задается
типом ObjectWindows TControl и его типом-потомком, позволяющим
работать с каждым типом управляющего элемента. Например, TListBox
определяет объекты блока списка, а TEdit определяет каждый
управляющий объект редактирования. Вы должны также понимать, что
TControl - это потомок TWindow.
    Хотя они ведут себя идентично, между управляющими кнопками
диалоговых блоков (таких как файловое диалоговое окно) и
управляющими элементами окон (таких как окно палитры) имеется
существенное различие. Управляющие элементы диалогового блока вы можете
задать в ресурсе диалогового блока. Они не являются объектами, и
диалоговый блок, которому они принадлежат, полностью отвечает за
управление этими элементами. В Главе 11 показано, как создать из
диалоговых ресурсов свои собственные диалоговые блоки и работать
с их управляющими элементами.
    Управляющие элементы окон задаются определением объекта.
Порождающее окно управляет их поведением через методы, определенные
объектами управляющих элементов ObjectWindows. Например, чтобы
получить следующий элемент, который пользователь выбрал в блоке
списка, вызовите метод GetSelString объекта блока. Аналогично
оконному объекту или объекту диалогового блока, объект
управляющего элемента имеет соответствующий визуальный элемент.
    Объект управляющего элемента и его управляющий элемент
связаны через поле идентификатора объекта управляющего элемента.
Каждый управляющий элемент имеет уникальный идентификатор,
который используется его порождающим окном для идентификации
управляющего элемента при маршрутизации управляющих событий (таких как
щелчок на элементе "мышью"). Для ясности для каждого
идентификатора управляющего элемента следует определить следующие
константы:
    MaxPens задает максимальное число перьев, которые будет
содержать палитра. Значение 9 хорошо подходит для стандартного
экрана VGA.
    Как и в случае других дочерних окон, часто удобно хранить
указатель объекта управляющего элемента в виде поля. Это
необходимо только для дочерних окон, с которыми вы позднее сможете
работать непосредственно, вызывая методы их объектов. TPenPalette
записывает каждый из этих объектов управляющих элементов в
отдельном поле. Приведем часть описания объекта TPenPalette:
    После создания экземпляра этих дочерних объектов управляющих
элементов вы можете манипулировать ими с помощью вызовов методов.
Например, в соответствующие моменты можно разрешать или запрещать
командные кнопки, вызывая их методы Enable и Disable. Используя
метод ChildList порождающего окна, можно получить доступ к
объектам управляющих элементов дочерних окон, не записанных в полях,
но гораздо удобнее делать это с помощью полей.
    Любой тип окна, который имеет объекты управляющих элементов
(или другое дочернее окно) должен определять для построения своих
объектов управляющих элементов конструктор Init. Кроме того, для
задания управляющих элементов перед выводом вы можете
переопределить SetupWindow. Порождающее окно (TPenPalette) автоматически
создает и выводит все свои дочерние окна.
    Приведем в качестве примера метод Init палитры пера. Первое,
что он делает - это установка собственного расположения и
атрибутов размера. Так как метод Init окна отвечает за задание его
атрибутов создания, и поскольку вместе с ним создаются управляющие
элементы окна, вы должны также в методе Init окна построить
управляющие элементы. В каждом вызове конструктора первый параметр
- это @Self (порождающее окно). За ним следует идентификатор
управляющего элемента.
    После создания окна, чтобы задать управляющие элементы окна,
вызывается виртуальный метод TPenPalette.SetupWindow. Поскольку
здесь вы имеете дело только с командными кнопками, инициализация
не требуется, но TPenPalette.SetupWindow первоначально запрещает
одну из командных кнопок. Если бы вы использовали другой
управляющий элемент (например, блок списка), то для инициализации
объекта управляющего элемента потребовалось бы вызывать SetupWindow.
    Примечание: Когда вы переопределяете метод SetupWindow
окна, не забудьте сначала вызывать наследуемый метод
SetupWindow, так как он создает все дочерние управляющие
элементы.
    Вызов методов Init и SetupWindow вполне достаточен для
правильного вывода в окне палитры всех управляющих элементов.
Командные кнопки можно будет активизировать ("нажимать"), но без
каких-либо действий. В шаге 12 мы определим реакцию на события
управляющего элемента.
    Если вы дважды щелкните "мышью" в блоке системного меню
палитры пера, оно исчезнет. Выбор команды Palette|Show не может
больше выводить палитру, так как объект и его экранные элементы
уничтожены. Выводить нечего. Вы можете переопределить это,
добавив метод CanClose, который скрывает окно, а затем запрещает его
закрытие (см. STEP11A.PAS):
    Теперь двойной щелчок "мышью" в блоке системного меню
скрывает окно, но не закрывает его, так что позднее вы можете вывести
его снова.
    Обычно наличие дочернего окна, которое всегда возвращает из
CanClose False, может предотвратить закрытие всего приложения. Но
TStepWindow перед закрытием не проверяет своих дочерних окон, так
как в шаге 1 вы переопределили его метод CanClose.
    Как вы уже знаете, объекты управляющих элементов создают
стандартные управляющие элементы Windows. Например, только что
созданный вами объект TButton дает в результате в палитре окна
стандартную серую кнопку. Однако IDE и диалоговые блоки,
созданные вами из ресурсов, используют кнопки другого типа с
расположенными на них графическими изображениями. ObjectWindows
предоставляет вам простой способ использования в программах командных
кнопок такого вида.
    Использовать специализированные управляющие элементы Borland
для Windows (BWCC) также просто, как использование модуля. Для
этого нужно просто добавить BWCC в оператор uses основной
программы. Это немедленно дает два эффекта. Первый состоит в том, что
все стандартные диалоговые блоки (такое как файловое диалоговое
окно, которое вы уже добавили в программу Steps) используют для
таких общих элементов как кнопки OK или Cancel, а также кнопки с
зависимой и независимой фиксацией, вместо стандартных управляющих
элементов специализированные.
    Примечание: Об использовании и проектировании
специализированных управляющих элементов Borland рассказывается в
Главе 12.
    Фактически, после добавления в оператор uses программы Steps
BWCC вы можете перекомпилировать программу и получить доступ к
диалоговым блокам. Без каких-либо других усилий вы существенно
улучшите внешний вид программы и ее интерфейса.
    Но как насчет кнопок в палитре пера? Они были созданы из
управляющих объектов с BWCC, используемых в программе, но выглядят
как обычные командные кнопки. Ответ, конечно, состоит в том, что
вы еще не определили для кнопок, графические изображения, так что
по умолчанию они просто используют метки, переданные в
конструкторе Init. В следующем разделе вы увидите, как добавлять к
специализированным управляющим элементам графические изображения.
    Хотя вы можете создавать графические изображения (битовые
массивы) для командных кнопок на этапе выполнения, эту задачу
следует решать с помощью ресурсов. Используя пакет разработчика
ресурсов, вам нужно создать для каждой командной кнопки три
различных битовых массива: один для позиционирования сверху, один
для позиционирования снизу и один для позиционирования в фокусе
ввода.
    С этими графическими изображениями вы можете делать что
угодно. Здесь ограничений нет. Вы можете изменять цвет образа в
зависимости от состояния кнопки или перемещать образы (что обычно
используется) при нажатии, а также добавлять вокруг текста кнопки
линию из точек, когда она находится в фокусе (активна). На Рис.
6.2 показано три графических изображения для командной кнопки Add
Pen палитры пера.
    Единственная сложная часть в определении графических
изображений для командных кнопок - это присваивание идентификаторов
ресурсов. Управляющие элементы BWCC знают о том, какое графическое
изображение использовать, основываясь на идентификаторе
конкретного управляющего элемента. Для командных кнопок в системах с VGA
для ресурсов используется 1000 + идентификатор для "верхнего"
образа, 3000 + идентификатор для "нижнего" образа и 5000 +
идентификатор для образа в фокусе.
    Примечание: В системах с EGA используются,
соответственно, ресурсы 2000 + идентификатор, 4000 + идентификатор
и 6000 + идентификатор.
    Так как командная кнопка Add Pen имеет идентификатор 101
(id_Add), разрешение использования BWCC принимает вид ресурсов
1101, 3101 и 5101. В программе STEP11B.PAS, для доступа к
специализированными графическим изображениям, для командных кнопок Add
Pen и Del Pen, используется директива:
    Наиболее интересная часть создания палитры пера - это
создание вашего собственного специализированного окна палитры. Здесь
вы можете, наконец, использовать возможности, предусмотренные
стандартными инструментальными средствами окон, и что-то создать.
    На этом шаге вы сделаете следующее:
    Так как каждое перо, которое вы сохраняете в палитре, имеет
один и тот же размер (40 элементов изображения высотой и 128
шириной), вам нужно убедиться, что окно палитры может увеличиваться
и сжиматься на этот размер каждый раз, когда вы удаляете перо.
Объект TPenPalette определяет два метода, которые позволяют это
делать: Grow и Shrink.
    Оба метода находят координаты границ окна, модифицируют их и
сообщают окну, что нужно использовать новые координаты границ.
Функция API GetWindowRect возвращает структуру TRect, содержащую
верхнюю, нижнюю, левую и правую координату. Grow добавляет в
нижнюю область окна 40 элементов изображения, а Shink вычитает тот
же объем.
    В следующем разделе вы узнаете, как вызывать методы Grow и
Shrink в ответ на нажатие командных кнопок Add Pen и Del Pen.
    Основное различие между окном палитры и режимными
диалоговыми окнами, которые вы использовали ранее, состоит в том, что
режимное диалоговое окно манипулирует управляющими элементами, а
затем считываете результаты, если пользователь щелкает "мышью" на
командной кнопке OK. В данном безрежимном окне палитры вы имеете
дело с активной, динамической частью программы и можете активно
отвечать на каждый используемый управляющий элемент.
    Пока командные кнопки выводятся в окне палитры, но щелчок
кнопкой "мыши" не дает никакого эффекта. Щелчок и выбор "мышью"
являются событиями управляющего элемента. Они аналогичны событиям
меню, на которые вы отвечали в шаге 4.
    Вы отвечали на события меню, определяя методы реакции на
команды. Что-то аналогичное нужно делать с сообщениями управляющих
элементов. События управляющих элементов создают сообщения (на
основе дочернего идентификатора), аналогичные командным
сообщениям, но вместо идентификатора меню содержащие идентификатор
управляющего элемента. Для идентификации заголовка метода на основе
дочернего идентификатора используйте сумму идентификаторов
управляющего элемента и констант id_First.
    Примечание: Подробнее о командных сообщениях и
уведомляющих сообщениях управляющих элементов рассказывается в
Главе 16 "Сообщения окон".
    Как и в случае методов реакции на сообщения, имена которым
присваиваются по сообщениям, методы, основанные на дочерних
идентификаторах, также должны именоваться по идентификаторам
сообщений. Так как две командные кнопки, на которые вы хотите
реагировать, имеют идентификаторы id_Add и id_Del, TPenPalette нужны
методы с именами IDAdd и IDDel.
    Теперь для выполнения соответствующих действий в ответ на
командные кнопки осталось только определить методы IDAdd и IDDel.
Пока что IDAdd должен просто вызывать увеличение окна, а IDDel
его сжатие
    Примечание: Это дополняет содержимое файла
STEP12A.PAS.
    Теперь, когда у вас есть окно палитры, вам необходим просто
способ вывода на экран и выбора перьев в палитре. Для этого вы
можете использовать относительно простой потомок TWindow и набор
объектов пера.
    В данном разделе вы сделаете следующее:
    Так как окно палитры пера может изменять свой размер,
палитра в окне может фактически оставаться фиксированной. Чтобы
показать только часть палитры, в которой отображаются перья, вы
можете использовать возможности отсечения Windows.
    Самой палитре необходимы только несколько полей данных:
набор перьев, указание того, какое перо в данный момент выбрано, и
описатели представляющих перья графических образов. Описатели
графических изображений представляют собой частные поля не
потому, что они должны быть секретными, а для предотвращения их
непреднамеренного изменения другим кодом.
    Приведем описание объекта палитры:
    Объекту TPenPic не требуется очень много методов. Он имеет
простой конструктор для создания набора перьев и деструктор для
их уничтожения. Метод SetupWindow просто перемещает палитру
внутри ее порождающего окна. AddPen и DeletePen включают перо в набор
и удаляют перо из набора, а WMLButtonDown интерпретирует щелчки
"мышью" для выбора перьев из палитры. Наконец, Paint рисует
"кнопки", представляющие перья в наборе.
    Отметим также, что TPenPic является потомком TWindow, а не
TControl. Хотя поведение вашего нового объекта во многом
напоминает поведение управляющего элемента окна, он должен быть
производным от TWindow, так как TControl работает только со
стандартными управляющими элементами, такими как "нажимаемые" командные
кнопки и полосы прокрутки. При создании собственных управляющих
элементов нужно начинать с TWindow.
    Построение и уничтожение объекта палитры выполняется
достаточно просто. Конструктор Init вызывает TWindow.Init, затем
изменяет стиль окна (чтобы оно стало видимым дочерним окном). PenSet
инициализируется как набор фиксированного размера, достаточно
большой, чтобы содержать максимальное число заданных константой
MaxPens перьев, и не возрастающий. Для текущего выбранного пера
CurrentPen устанавливается в -1. Это означает, что выбранного
пера нет.
    Наконец, Init загружает в UpPic и DownPic два графических
образа. Они используются в качестве фона для каждого пера
палитры. DownPic рисуется за выбранным пером, а UpPic - в качестве
фона других перьев.
    Деструктор Done перед вызовом наследуемого деструктора Done
отменяет графические образы. Вызов DeleteObject для уничтожения
графических образов имеет важное значение. Подобно контексту
дисплея, графические образы являются ресурсами Windows,
поддерживаемыми в ограниченной памяти Windows. Если размещаете их в
памяти Windows и не удаляете, то ваша программа (и другие работающие
параллельно с ней программы) потеряют доступ к этой памяти.
    Init и Done объекта палитры выглядят следующим образом:
    Как вы могли заметить, TPenPic не задает свою позицию в
конструкторе так, как это делают большинство управляющих
элементов. Причина здесь в том, что вы не можете полагаться на
координаты окна, пока оно не будет реально существовать. TPenPalette
обходит эту проблему, создавая свои объекты кнопок путем вызова
для определения высоты заголовка окна функции API
GetSystemMetrics. Хотя это будет работать, существует и другой
подход.
    Вместо позиционирования палитры в конкретное место
порождающего окна вы можете поместить его в определенную позицию в
порождающей области клиента. Таким образом, если вы добавляете меню
или изменяете рамку, либо выполняете вне окна какое-либо другое
изменение, ваш объект палитры все равно будет правильно
позиционирован.
    Изменение позиции окна выполняется в SetupWindow, так как
окну палитры требуется использовать описатель своего порождающего
окна, который до вызова собственного метода SetupWindow
недоступен. При достижении SetupWindow дочернее окно может рассчитывать
на допустимость описателя порождающего окна.
    Для возврата координат области клиента окна палитры метод
TPicPen использует функцию API Windows GwetClientRect. Затем он
перепозиционируется с помощью MoveWindow непосредственно под
объекты кнопок, задавая высоту, достаточную для размещения всех
перьев в наборе. Заметим, что последний параметр MoveWindow - это
значение типа Boolean, указывающее, следует ли выполнять
повторное отображение окна после перемещения. Так как палитра на экран
пока не выводилась, то заново отображать ее не имеет смысла,
поэтому TPenPic.SetupWindow передает значение False.
    В последнем разделе вы отвечали на сообщения от Add Pen
(Добавить перо) и Del Pen (Удалить перо) изменением размера окна
палитры. Теперь настало время изменить эту реакцию и фактически
добавлять или удалять перья из палитры, что в свою очередь
указывает окну палитры на необходимость изменения размера. Вместо вызова
собственных методов Grow и Shrink методы объекта палитры AddPen и
DeletePen должны вызывать соответственно, методы IDAdd и IDDel
в TPenPalette.
    Метод AddPen воспринимает передаваемое перо, копирует его в
набор и отмечает перо, как текущее выбранное. Затем он разрешает
кнопку Del Pen в окне палитры пера, запрещает кнопку Add Pen,
если набор полон, и сообщает порождающему окну о необходимости
увеличения размера, чтобы поместить новое перо.
    Примечание: Чтобы использовать преимущества средств,
специфических для TPenPAlette, TPenPic может выполнять
приведение типа поля Parent. Большинство оконных объектов не
связаны так жестко с конкретным порождающим типом, и
поэтому не должны делать никаких предположений относительно типа
порождающих их окон.
    Метод DeletePen по существу изменяет действия AddPen на
обратные. При наличии выбранного в палитре пера оно удаляется из
набора, а набор уплотняется таким образом, чтобы перья
размещались непрерывно. Затем он указывает, что в данный момент
выбранных перьев нет (поскольку выбранное перо только что удалено) и
запрещает командную кнопку Del Pen, так как Del Pen работает с
выбранным пером. Далее он разрешает кнопку Add Pen, поскольку
удаление пера автоматически освобождает место для по крайней мере
еще одного пера. Наконец, он сообщает порождающему окну на
необходимость уменьшения его размера (так выводить теперь нужно
меньше перьев).
    Заметим, что AddPen и DeletePen используют преимущества того
факта, что окна палитры пера для упрощения связи с методами имеет
указатели на свои командные кнопки. Если бы TPenPalette не имел
полей AddBtn и DelBtn, то объекту палитры пришлось бы искать их
по идентификаторам и посылать им сообщения, либо нужно было бы
послать сообщение порождающему окну, которое в свою очередь
должно каким-то образом связываться с командными кнопками.
    За все, что мы насоздавали в объекте палитры, приходится
расплачиваться, когда приходит момент отображать ее на экране.
Поскольку перья хранятся в наборе, для отображения каждого пера
вы легко можете выполнять итерацию. В самом деле, метод Paint
состоит только из инициализации локальной переменной-счетчика, а
затем выполняет итерацию по набору с помощью ForEach:
    Наиболее интересная часть содержится не в Paint, а во
вложенной процедуре ShowPen, которая вызывается для каждого пера в
палитре. На самом деле ShowPen состоит из двух различных частей.
Первая рисует графическое изображение фона, а вторая (которая уже
должна быть вам достаточно знакома) использует объект пера для
изображения по этому фону образца линии.
    Изображение графических образов предусматривает три шага:
создание контекста устройства памяти, выбор в контексте
устройства графического образа и копирование образа в контекст экрана.
    Как вы видели в шаге 8, для различных видов устройств
существует различные контексты устройства. Для работы с
графическими образами (битовыми массивами) Windows позволяет создавать
текст устройства памяти. Фактически, вы можете только выбирать
битовые массивы в контексте устройства памяти, хотя они могут
копироваться в другие контексты устройства.
    Метод CreateMemoryDC создает пустой контекст устройства
памяти, совместимый с текущим PaintDC. Затем, в зависимости от
того, является ли данное конкретное перо выбранным, ShowPen
выбирает в контексте устройства графические образы UpPic или DownPic.
Заметим, что в контексте устройства памяти графический образ
интерпретируется аналогично любому другому изобразительному
средству. Наконец, функция BitBlt копирует заданную часть
контекста устройства памяти в PaintDC. Заметим, что контекст
устройства памяти требуется уничтожать.
    После того как фон будет на месте, ShowPen использует ShowTo
и LineTo аналогично тому, как это делается при рисовании
непосредственно в окне.
    Наконец. TPenPic обеспечивает реакцию на щелчок в
нарисованной области кнопкой "мыши". Заметим, что хотя размер объекта
палитры никогда не изменяется, он получает сообщение от щелчков
"мышью" в области, фактически показываемой на экране. Палитра
отсекается рамкой окна палитры. Это означает, что вы можете щелкать
кнопкой "мыши" только непосредственно в палитре.
    Поскольку каждый элемент в палитре имеет один и тот же
размер, WMLButton для определения того, в каком графическом образе
была нажата кнопка "мыши", может просто разделить y-координату
щелчка "мыши" (которая поступает в LParamHi) на 40 (размер
каждого графического изображения). Затем он делает перо, на котором
была нажата кнопка "мыши" текущим и задает в качестве пера для
рисования копию выбранного пера палитры. Поскольку теперь есть
выбранное перо, он разрешает кнопку Del Pen в окне палитры, затем
запрещает палитру для обеспечения ее повторного отображения для
того, чтобы показать новый выбор.
    Код для WMLButtonDown имеет следующий вид (это дополняет
текст STEP12B.PAS):
    Есть много дополнений и изменений, которые вы можете внести
в программу Steps, чтобы сделать ее более полезной. Версию
программы Steps, которая включает в себя эти изменения, вы можете
найти в файле GRAFFITI.PAS.
    Программа Graffiti содержит следующие изменения:
    TStepWindow может очень легко работать в качестве дочернего
окна MDI. Фактически с небольшими изменениями программа Graffiti
использует в качестве своих дочерних окон те же окна, что Steps
использует для основного окна. Так как владельцем окна палитры
пера является объект TStepWindow, каждый отдельный рисунок имеет
свой собственный отличный набор перьев. О многодокументальном
интерфейсе рассказывается в Главе 14 "Объекты MDI").
    Для сложных рисунков со многими длинными линиями повторное
отображение рисунка может потребовать много времени. Graffiti
использует одно из преимуществ графический функций GDI - функцию
PolyLine, которая рисует сегменты каждой линии сразу, а не по
одному. Эффектом будет более быстрое и плавное отображение окна.
    Поскольку рисунок состоит из наборов линий, для стирания
последней нарисованной линии легко использовать методы наборов.
Graffiti связывает элемент меню Edit|Undo (Редактирование|Отмена)
с набором линий метода AtDelete, удаляя последнюю линию в наборе,
которая является также последней нарисованной линией. Повторяя
удаление последней линии в наборе, вы можно эффективно отменять
все изображение.
    Хотя программа Graffiti этого не делает, вы можете добавить
также функцию Redo (Возобновление), сохраняя удаленные линии в
другом наборе, и перемещая их по одной в набор отображаемых
линий, либо даже в другой рисунок.Шаг 10: Добавление всплывающего окна
Рис. 6.1 Палитра пера программы Steps с тремя перьями.Добавление к окну дочернего окна
TStepWindow = object(TWindow)
DragDC: DHC;
ButtonDown: Boolean;
FileName: array[0..fsPathName] of Char;
HasChanged, IsNewFile: Boolean;
Drawing: PCollection;
Printer: PPrinter;
PenPalette: PPenPalette; { окно палитры }
.
.
.
end;
Построение окна палитры
Назначение порождающего окна
Создание элементов экрана
Вывод и сокрытие палитры
procedure TStepWindow.CMPalShow(var Msg: TMessage);
begin
PenPalette^.Show(sw_ShowNA);
end;
procedure TStepWindow.CMPalHide(var Msg: TMessage);
begin
PenPalette^.Show(sw_Hide);
end;
Шаг 11: добавление специализированных управляющих элементов
Добавление к палитре командных кнопок
const
id_Add = 101;
id_Del = 102;
MaxPens = 9;
Объекты управляющих элементов как поля
TPenPalette = object(TWindow)
AddBtn, DelBtn: PButton;
.
.
.
end;
Работа с управляющими элементами
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;
Сокрытие вместо закрытия
function TPenPalette.CanClose: Boolean;
begin
Show(sw_Hide);
CanClose := False;
end;
Разрешение специализированных управляющих элементов
Создание для командных кнопок графических изображений
Рис. 6.2 Графические изображения для специализированной
кнопки.
Нумерация ресурсов графических изображений
{$R PENTAL.RES}
Шаг 12: Создание специализированного управляющего элемента окна
Динамическое изменение размеров палитры
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;
Реакция на события управляющих элементов
Имена методов реакции на сообщения управляющих элементов
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;
procedure TPenPalette.IDAdd(var Msg: TMessage);
begin
Grow;
end;
procedure TPenPalette.IDDel(var Msg: TMessage);
begin
Shrink;
end;
Добавление "кнопок" палитры
Определение объекта палитры
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;
Создание и уничтожение палитры
Рис. 6.3 Фоновые образы палитры пера.
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;
Размещение в порождающем окне
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;
Добавление и удаление перьев
procedure TPenPalette.IDAdd(var Msg: TMessage);
begin
Pens^.AddPen(CommonPen);
end;
procedure TPenPalette.IDDel(var Msg: TMessage);
begin
Pens^.DeletePen;
end;
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;
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;
Отображение содержимого палитры
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;
Выбор перьев с помощью "мыши"
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;
Что дальше?
Многодокументальный интерфейс
Сглаживание линий
Отмена