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


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

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

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

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


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

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

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


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

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

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


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

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

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

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

    Осталось только построить объект дочернего окна и присвоить его 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):

     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: добавление специализированных управляющих элементов


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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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


    Любой тип окна, который имеет объекты управляющих элементов (или другое дочернее окно) должен определять для построения своих объектов управляющих элементов конструктор 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 мы определим реакцию на события управляющего элемента.

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


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

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

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

    Обычно наличие дочернего окна, которое всегда возвращает из 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 палитры пера.


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

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


    Единственная сложная часть в определении графических изображений для командных кнопок - это присваивание идентификаторов ресурсов. Управляющие элементы 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}

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


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

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

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


    Так как каждое перо, которое вы сохраняете в палитре, имеет один и тот же размер (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.

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


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

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

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

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

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


    Как и в случае методов реакции на сообщения, имена которым присваиваются по сообщениям, методы, основанные на дочерних идентификаторах, также должны именоваться по идентификаторам сообщений. Так как две командные кнопки, на которые вы хотите реагировать, имеют идентификаторы 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.

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


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

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

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


    Так как окно палитры пера может изменять свой размер, палитра в окне может фактически оставаться фиксированной. Чтобы показать только часть палитры, в которой отображаются перья, вы можете использовать возможности отсечения 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.

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


    Построение и уничтожение объекта палитры выполняется достаточно просто. Конструктор 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;

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


    Как вы могли заметить, 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.

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


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

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


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

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


    Наконец. 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;

Что дальше?


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

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

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


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

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


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

Отмена


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

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

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


    Вы можете также отметить, что при щелчке "мышью" между палитрой и основным окном, окно, где вы нажимаете кнопку, становится активным (получает активную рамку), а другие окна становятся неактивными. Если вы часто перемещаетесь между двумя окнами (что может иметь место при работе с палитрой), это может показаться весьма раздражающим. Чтобы предотвратить это явление, вам нужно перехватывать передачу в окна сообщений 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 прокручивает изображение, так что вы будете видеть другие части рисунка.