Docu/ru/Tut-2.odc

2 Составные документы
ENGLISH

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

2.1 Перманентность

Мы называем объект, который содержится в окне, отображением. Отображение отражает себя в окне, и оно может интерпретировать пользовательский ввод в этой области, такой как щелчок мышкой. В наиболее общем случае отображение можно открыть и сохранить как документ. Другие отображения могут быть созданы динамически или загружены из файлов ресурсов. Элементы управления диалогового окна "Найти и заменить"  пример последнего случая.

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



Рисунок 2-1. Соотношения подтипов между визуальными представлениями и хранилищами.

В BlackBox Component Framework хранилища представлены как тип Store в модуле Stores; отображения представлены как тип View в модуле Views; и существует множество различных специфичных отображений, такие как тип View в модуле TextViews, или тип View в модуле FormViews, или тип Control в модуле Controls (см. рис. 2-2). Это только малая часть, в реальности имеется много больше типов отображений. Формат файлов механизма хранилища является независимым от платформы, т.е. скомпенсированы различия в порядке байтов.



Рисунок 2-2. Соотношения подтипов между визуальными представлениями и хранилищами в BlackBox

Часто копирование хранилища эквивалентно записи его во временный файл и последующему его прочтению (выгрузка старого объекта в файл, затем создание и загрузка нового объекта из файла). Это делает удобным объединение поддержки перманентности и копирования в одной и той же абстракции хранителя. Тем не менее, хранилища в BlackBox способны изменить свою сущность или "исчезнуть" при выгрузке в файл, в этом случае копирование не эквивалентно более выгрузке/загрузке. Например, отображения маркера ошибки, которые указывают синтаксическую ошибку, убирают себя при выгрузке  хотя видимый маркер ошибки может быть скопирован с помощью механизма drag-n-drop, так же как другие отображения. Чтобы позволить такую гибкость, BlackBox не трактует автоматически копирование как пару выгрузка/загрузка. Хранилища имеют явные методы Externalize, Internalize и CopyFrom. При выгрузке оно получает возможность подставить вместо себя другое хранилище, возможно, NIL. Это механизм, позволяющий изменить свою сущность на другую, т.н. заместитель [proxy].

2.2. Независимость от устройства отображения

Вычислительный мир гетерогенен, с различными аппаратными и операционными платформами на рынке. Интернет, с его миллионами платформ на любой вкус, окончательно сделал ясным, как важна независимость кода и данных от платформы. Этого нелегко достичь, потому что программное и аппаратное обеспечение может широко изменятся между платформами. Устройства отображения, такие как мониторы и принтеры,  хороший пример. Их пространственное и цветовое разрешения могут чрезвычайно различаться.
Для того чтобы достичь независимого от платформы кода, отображения и их перманентное состояние не должны ссылаться на конкретные устройства. Вместо этого пространство документа и пространство устройства отображения должны быть четко разделены, и подходящее отображение между ними должно обеспечиваться средой. Например, в пространстве документа расстояния измеряются в хорошо определенных единицах, таких как дюймы или миллиметры, тогда как в пространстве устройства отображения расстояния измеряются в пикселях, каким бы ни был их реальный размер.

Для моделирования разделения между пространствами устройства отображения и документа, где пространство документа сформировано перманентными отображениями, нам нужна абстракция устройства отображения. Мы называем такой объект фреймом [frame]. Фрейм  это прямоугольная поверхность для рисования, которая обеспечивает множество операций рисования. Когда он делается видимым, отображение получает фрейм и через этот фрейм он может нарисовать на экране (или принтере). Фрейм выполняет отображение между единицами пространства документа и пикселями на устройстве отображения, так что визуальному представлению никогда не нужно иметь дело с размерами пикселов напрямую.



Рисунок 2-3. Разделения пространств устройства отображения и документа

В BlackBox Component Framework единицы пространства документа измеряются в 1/36000 мм. Это значение выбрано так, чтобы минимизировать ошибки округления для типичных разрешений экрана. Фрейм BlackBox'а обеспечивает локальную систему координат для своего визуального представления. Пока вы можете рассматривать фрейм как окно, а отображение  как документ. Отображение, которое не изображено, не имеет фрейма. Фреймы  это "легкие" объекты, которые создаются и удаляются по необходимости. Они создаются и разрушаются каркасом, и нет необходимости реализовывать или расширять их программисту отображений.
Единственный случай, где необходимо расширять фреймы, это когда для ависимых от платформы элементов управления или OLE-объектов нужно создать свернутые отображения BlackBox'а. Т.к. написание таких отображений  "грязная" работа, каркас обеспечивает стандартные сверкти OLE (версия для Windows) и свертки для важных (до OLE) стандартных элементов управления.


2.3 Редактирование нескольких отображений

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



Рисунок 2-4. Прокрутка текстового отображения

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



Рисунок 2-5. Различные типы отображений, представляющие одни и те же данные

Объект, представляющий данные, которые могут быть представлены в различных отображениях, называется моделью. Разделение на отображение и модель возвращается к проекту Smalltalk в Xerox PARC. В терминологии проектных схем отображение  это наблюдатель модели.

В Компонентном Каркасе BlackBox разделение модель/отображение опционально. Маленькие фиксированного размера отображения, которые легко умещаются в окне, обычно не нуждаются в раздельных моделях, тогда как большие или более чистые нуждаются. Это может происходить в ситуации, подобной следующей, где отображение имеет раздельную модель, и оба вместе они формируют документ, который изображается в окне:



Рисунок 2-6. Разделение моделей и отображений в BlackBox.

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



Рисунок 2-7. Множество отображений для одной и той же модели

Только одно из двух отображений выгружается, когда документ сохраняется в файл. Чтобы избежать путаницы, одно окно четко различается как главное окно, когда все другие являются так наз. дочерними окнами [subwindow]. Если вы закроете дочернее окно, только это окно закроется. Но если вы закроете главное окно, оно само и все другие дочерние окна закроются одновременно.

2.4 Распространение изменений 

Редактирование из нескольких представлений создает проблему согласованности. Редактирование отображения значит, что отображение заставляет измениться свою модель. Как результат, все отображения, представляющие эту модель, должны быть обновлены, если это необходимо, не только отображение, в котором пользователь сейчас работает. Например, если пользователь изменяет значение в отображении таблицы рисунка 2-5, то круговая диаграмма в другом отображении должна соответственно обновиться, потому что их общая модель изменилась.
Такое распространение изменений легко реализовать. После того, как модель выполнила операцию над данными, которые она представляет, она уведомляет все отображения, которые ее представляют, посылая им сообщение об обновлении, описывающее операцию, которая была выполнена.
Уведомление может быть легко сделано с помощью прохода по линейному списку отображения, фиксированных в модели. Такая функциональность может быть обеспечена раз и навсегда подходящей инкапсуляцией ее в класс. Это класс распространения изменений обеспечивает интерфейс, состоящий из двух частей. Первая часть позволяет регистрировать новые отображения, которые хотят прослушивать сообщения об обновлении ("подписка"). Вторая часть позволяет посылать сообщения об обновлении ("публикация"):



Рисунок 2-8. Механизм распространения изменений

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

Иногда механизм, описанный выше, делает слишком много. Он посылает сообщения об обновлении всем отображениям, независимо от того, нужно их обновлять или нет. Например, когда слово удалено в текстовой модели, то некоторое отображения текста может отображать часть текста, на которую совершенно не повлияло удаление. Это отображения не должно ничего делать. Конечно, все отображения, которые отражают измененную часть текста, должны быть соответственно обновлены.

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


2.5 Вложенные отображения

До сих пор мы принимали, что отображение (возможно с моделью)  это документ, и фрейм  это окно. В действительности, все немного сложнее. Причина в том, что отображения могут быть вложенными. Некоторые отображения могут содержать ("внедрять") другие отображения; по этой причине они называются отображениями контейнеров. Некоторые контейнеры очень общие и позволяют внедрять любое число и любые типы других отображений. Например, отображение текста может внедрять отображения таблиц,  рисунков и т.д. Оно может даже внедрять другое отображение текста, имеется в виду то, что можно создать иерархии вложенностей любой глубины.
Как это влияет на то, о чем мы до сих пор говорили? Очевидно, нам нужно обобщить нашу нотацию отображений и фреймов. Отображение может содержать другие отображения, которые могут содержать другие отображения и т.д.; т.е. отображения становятся иерархичными. В принципе, эти иерархии могут даже не быть деревьями, они могут быть произвольными ориентированными ацикличными графами. Это значит, что некоторые отображения в документе могут ссылаться на одну и ту же модель, но ссылки не могут идти "вверх" в иерархии документа, потому что это привело бы к бесконечной рекурсии при прорисовке документа, сродни телевизору, изображающего себя.
Фреймы представляют подмножество иерархии отображений. Т.к. фрейм всегда полностью содержится в своем родительском фрейме, иерархия фреймов строго является деревом.
Документ можно теперь рассматривать как корень ориентированного ацикличного графа отображений, и окно можно рассматривать как корень дерева фреймов.



Рисунок 2-9. Вложенные отображения и фреймы

Это понятное обобщение, которое не добавляет неожиданной сложности. Это упрощенная реализация проектной схемы компоновщик [composite]. Она была упрощена, потому что типовая безопасность Компонентного Паскаля делает ненужным абстрактный тип отображения со специфичным для контейнера операциями, которые бессмысленны для неконтейнеров.
К сожалению, больше сложности появляются, когда мы рассматриваем комбинацию вложенных отображений и редактирование нескольких отображений. Прежде всего, редактирование нескольких отображений означает разделение отображений и моделей. Внедренное отображение принадлежит модели, т.к. это часть данных, которые отображает отображение контейнера. Например, пользователь, редактирующий отображение текста, может удалить внедренные отображения, также как он может удалить внедренные символы. Рисунок 2-10 иллюстрирует ситуацию двух отображений контейнера с моделью контейнера, в который внедрено другое отображение:



Рисунок 2-10. Отображения контейнеров с моделью, контекстом и внедренным отображением

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

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



Рисунок 2-11. Дочернее окно на внедренном отображении.

Составные документы привносят дополнительную сложность. Для корневых отображений состояние прокрутки не сохраняется, когда документ сохраняется на диске. По крайней мере, большинство
пользователей предпочитают, чтобы состояние прокрутки отображения не было сохраняемым. Но все же для внедренных отображений ситуация другая. Установка начальной позиции внедренного отображения не столько для удобства (дочерние окна лучше подходят для этого), сколько для печати: пользователь выбирает, какая часть отображения должна быть видима в контейнере.
То же верно для механизма отмены/повтора: прокрутка в корневом отображения не является редактированием документа, и поэтому механизм отмены/повтора добавил бы больше неудобства, чем пользы. Тем не менее, прокрутка внедренного отображения должна рассматриваться как истинная операция редактирования, и поэтому должна быть отменяемой/повторяемой. В итоге, это значит, что реализатор отображения должен трактовать неперсистентное состояние отображения различно, в зависимости от того, является ли оно корневым или внедренным.

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



Рисунок 2-12. Несколько фреймов, отображающих одно и то же отображение.

Такая ситуация не может возникнуть без вложенных отображений потому что самое внешнее отображение всегда копируется (чтобы позволить независимую прокрутку или другие специфичные для него операции). Но когда отображение внедрено в контейнер, изображаемый в двух различных окнах, тогда это отображения изображается в двух различных фреймах. Это делает управление ссылками между фреймами и отображениями более сложным; но, что более важно, это делает более сложным распространение изменений и обновление экрана.
Почему? Давайте предположим, что имеется n фреймов, которые отображают одно и то же отображение. Теперь модель отображения изменилась, и это изменение распространено всем отображениям, которые отображают модель. Т.к. есть только одно отображение для модели, то механизм распространения изменений, который мы обсудили ранее, пошлет отображению сообщение об обновлении ровно один раз. Тем не менее, есть n мест на экране, которые нужно обновить. Как результат, отображение должно выполнить n обновлений, пройдя по всем фреймам, которые его отображают.

Для отображения корректно поддерживать список фреймов, в которых оно отображается, сложная задача. Поэтому Компонентный Каркас BlackBox освобождает реализатора отображения от этой склонной к ошибкам задачи. Отображение в BlackBox'е не содержит список фреймов. Вместо этого каркас поддерживает деревья фреймов автоматически. Это было причиной, по которой на рисунке выше стрелки от фреймов к отображениям направлены только в одну сторону, потому что в BlackBox'е отображение не знает свой фрейм(ы). Корректные фреймы передаются отображению, когда каркас просит отображение сделать что-то, что может включать в себя рисование. (Такой подход связан с проектной схемой приспособленец [flyweight].)
Когда отображение получает сообщение об обновлении и решает, что нужно обновить свои фрейм(ы), оно просто спрашивает каркас послать другое сообщение об обновлении всем своим фреймам. Каждый из этих фреймов попросит отображение обновить фрейм, когда это необходимо. Этот механизм будет более подробно обсужден в главах, посвященных построению отображений.

Т.к. теперь больше нет отношения один к одному между отображениями и фреймами, полезно подытожить различия между фреймами и отображениями:

аспект	фрейм	отображение
живет в	пространстве устройства отображения	пространстве документа

persistent	нет	да

расширяется реализатором	обычно нет	да

может содержать зависимые 		
от устройства данные	да	нет

иерархия	дерево	ацикличный орграф

связан с	одним отображением	0..n фреймами

обновление провоцируется	его отображением	его моделью (если есть)

полностью содержится в контейнере	да	не обязательно

Таблица 2-13. Различия между фреймами и отображениями


2.6 Отложенное обновление

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

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



Рисунок 2-14. Область обновления выделения, которое было перемещено

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

2.7 Режимы контейнера

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

Компонентный каркас BlackBox использует свою поддержку редактирования нескольких отображений, чтобы сделать шаг вперед. Т.к. документ можно открыть в нескольких  отображениях одновременно, возможно открыть строение формы в двух окнах. BlackBox позволяет использовать отображения форм в различных режимах: строения и маски. Эти режимы соответствуют режиму разработки и режиму исполнения, но с одним важным улучшением: они могут использоваться одновременно. Например, одно окно может отображать форму в режиме строения, а другое окно  в режиме маски (см. рис. 2-15). Когда разработчик редактирует строение формы в окне режима строения, любое изменение строения немедленно отображается в окне режима маски. Наоборот, пользовательский ввод в окне режима маски немедленно становится видимым в окне строения. Это результат стандартного механизма распространения изменений BlackBox'а.



Рисунок 2-15. Форма ввода данных в режиме строения (позади) и в режиме маски (впереди)

В действительности, BlackBox более общий. В дополнение к режимам строения и маски, он поддерживает еще несколько режимов. Режим редактирования позволяет изменять расположение и содержимое элементов управления одновременно, а режим просмотра делает форму доступной только для чтения за исключением выделения и копирования частей документа.
Эти общие режимы доступны не только в графических формах. Они  возможности, разделяемые всеми общими отображениями контейнеров, включая представления текстов. Это значит, что возможно (и, иногда, более удобно для разработчика) использовать отображение текста вместо отображения формы при сборке диалогового окна. Пользователь не заметит разницы. Чтобы упростить построение таких мощных контейнеров, каркас обеспечивает исчерпывающую абстракцию контейнера с абстрактным классом модели контейнера, с абстрактным классом отображения контейнера и с абстрактным классом контроллера контейнера. Контроллер можно рассматривать как отделившуюся часть отображения. Он выполняет все взаимодействие с пользователем, включая обработку ввода с клавиатуры и манипуляций мышью, и он также управляет выделениями. Отношение между отображением и контроллером во время исполнения  один к одному. Выделение части визуального представления в объект контроллера повышает расширяемость (различные контроллеры могут быть реализованы для одного и того же отображения, и один и тот же контроллер работает со всеми конкретными реализациями отображения), и это также позволяет уменьшить сложность: контроллер сложного отображения, такого как отображение контейнера, может стать большим, что делает разделение вещей на различные типы (и, обычно, в различные модули) хорошей идеей. В терминах проектных схем контроллер  это объект стратегии. 
Абстракции контейнеров BlackBox'а были разработаны, чтобы абстрагироваться от специфичных пользовательских интерфейсов контейнеров. Это значит, что детали внешнего вида контейнера (такие как обозначение фокуса OLE-объекта пунктирным прямоугольником) спрятаны от разработчика. В свою очередь, это позволяет реализовать контейнеры различным образом для различных платформ, без влияния на разработчика контейнеров. В частности, реализован внешний вид контейнеров, похожих на OLE и Aplle OpenDoc. Тогда как OpenDoc более неуместен, некоторые принципы пользовательского интерфейса, разработанные для него, полезны и сегодня.

2.8 Обработка событий

Когда пользователь взаимодействует с составным документом, это взаимодействие всегда происходит в одном отображении в одно время. Это отображение называется текущим фокусом. Щелчком в другом месте пользователь может изменить текущий фокус. Среди других событий фокус обрабатывает события от клавиатуры, т.е. нажатие клавиши интерпретируется отображением, обладающим фокусом. Отображение, обладающее фокусом, и все содержащие его отображения называются путем к фокусу, где самое внутреннее отображение является фокусом.
Есть возможность позволить каркасу пользовательского интерфейса управлять текущим фокусом. Это требует централизованного управления фокусом. Более легкий подход  сделать управление фокусом децентрализованной деятельностью: оставить управление фокусом индивидуальным отображениям контейнеров, которые все равно должны иметь дело с изменениями фокуса и передачей фокуса. Каждый контейнер просто запоминает, какой из его внедренных отображений является текущим фокусом, если такой есть. Контейнер не обращает внимания, находится ли это отображение в текущем пути к фокусу или нет.
В BlackBox пользовательские события посылаются по пути к фокусу как записи сообщений, начиная с самого внешнего отображения. Записи сообщений  статичные (распределяемы в стеке) переменные некоторого подтипа от Controllers.Message. Существуют сообщения контроллера для ввода с клавиатуры, перемещения мыши, механизма drag-n-drop и т.д. Сообщение контроллера продвигается по пути к фокусу, пока одно из отображений либо проинтерпретирует, либо отклонит его. Это отображение является по определению фокусом.
В терминах проектных схем, путь к фокусу  это "цепочка обязанностей".

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

Некоторые операции, такие как перемещающие drag-n-drop (в отличие от обычного копирующего drag-n-drop), изменяют одновременно два документа. В таких редких случаях, создаются две операции: одна для документа-источника (например, операция удаления) и одна для документа-получателя (например, операция вставки).



Рисунок 2-16. Последовательность  операций и получившиеся изменения стеков отмены и повторения.

На рисунке 2-16 показана последовательность операций от 1) до 8). Для последних пяти ситуаций показаны получившиеся стеки отмены и повторения. Например, после операции 5) стек отмены содержит операции Вставка, Установка свойств и Вставка (от вершины к дну стека), а стек повторения содержит Удаление.

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

Компонентный Каркас BlackBox уникален в том, что его механизм отмены/повтора является компонентно-ориентированным: он позволяет осуществлять композицию отменяемых операций в составные операции,  отменяемые как целое. Без этой способности вложенные операции записывались бы как плоская последовательность атомарных операций.
Рассмотрим, что бы это значило для конечного пользователя. Это бы значило, что пользователь мог выполнить команду меню, которая приводит к выполнению иерархии операций. До сих пор хорошо. Но, когда пользователь захочет отменить эту команду, он или она должен выполнить Правка->Отменить индивидуально для каждой отдельной операции команды вместо одной.
По этой причине BlackBox обеспечивает поддержку для произвольно вложенных операций: модули Models и Views оба экспортируют пару процедур BeginScript / EndScript. В этом контексте, "script" означает последовательность или иерархию атомарных операций, отменяемых как целое. Операции модели и отображения можно произвольно смешивать в скрипте. Абстрактные операции очень легкие. Они обеспечивают только одиночную процедуру без параметров, называемую Do. Эта процедура должна быть реализована обратимым образом, так что если она выполнена четное число раз, она не имеет эффекта. Если она выполнена нечетное число раз, она имеет тот же эффект, что и при выполнении один раз. В терминах проектных схем операция называется командой.

2.9 Элементы управления

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

Для BlackBox'а оказалось, что этот подход слишком сложен и неудобен. Вместо этого, была осуществлена специальная реализация проектной схемы наблюдатель, в котором типичная программа никогда не нуждается в прямом доступе к объектам элементов управления. Они совершенно абстрагированы. Программист имеет дело только с наблюдаемыми данными. Чтобы сделать определение и изменение этих данных как можно удобнее, они просто являются глобальными переменными языка Компонентный Паскаль, называемые interactor. Элемент управления имеет символическое имя (например, "TextCmds.find.ignoreCase"), которое позволяет ему найти свою переменную, используя встроенные возможности метапрограммирования BlackBox'а. В самом модуле интерактора нет ссылки на элемент управления.
Когда элемент управления редактируется, он прозрачно использует стандартный механизм распространения изменений BlackBox'а, т.е. он посылает сообщения отображения, которые уведомляют другие элементы управления, которые могут отображать ту же переменную. Хотя модуль интерактора не имеет доступ к своим элементам управления, он все же может влиять на способ отображения элементов управления (например, доступность/недоступность) и способ их взаимодействия друг с другом. Это делается экспортом подходящих процедур охранника и уведомителя. Они присоединяются к элементу управления таким же образом, как и ссылка на интерактор, используя подходящий редактор свойств элементов управления. Важно, что программа не нуждается в прямом доступе к элементам управления, и нет необходимости программировать прямые взаимодействия элементов управления. Это значительно упрощает реализацию и поддержку сложных поведений пользовательского интерфейса. Этот механизм более подробно описан в Главе 4.
В принципе, каркас получает доступ к модулю интерактора (через метапрограммирование) как одиночный посредник.