Docu/ru/Tut-4.odc

Часть II: Библиотека
ENGLISH

    В части I учебника по BlackBox представлено введение в шаблоны проектирования, применяемые в BlackBox. Знание этих шаблонов облегчает понимание и запоминание более детальных проектных решений в различных модулях BlackBox.
    Часть II учебника BlackBox поясняет применение наиболее важных компонентов библиотек: элементов управления, форм и текстовых элементов.
    Часть III учебника BlackBox демонстрирует разработку  отображений, рассматривая серию постепенно усложняющихся примеров.



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

    4.1 Введение

    Мы хотим как можно скорее начать с конкретных примеров, но небольшие вводные замечания все же необходимы. Ожидается, что читатель уже знаком с основами программирования, желательно на одном их диалектов Паскаля. Более того, ожидается, что он знаком с платформой (Windows или Macintosh) и основными принципами пользовательского интерфейса платформы. 
    В BlackBox Component Builder показывается типичное применение различных элементов программ. Содержание книги может быть применено как для Windows, так и для Macintosh-версии BlackBox, за исключением экранных снимков, сделанных в основном с применением Windows 95. Для минимизации платформенной специфики в тексте, применяются следующие соглашения:
* Mac OS folders называются "каталогами",
* В путях применяется символ "/" как разделитель каталогов как в Unix и World-Wide Web,
* Наименования файлов и каталогов могут содержать как прописные, так и строчные буквы,
* Наименования файлов документов даются без расширения ".odc", применяемого в Windows. Так, наименование файла "Text/Rsrc/Find" соответствует "Text\Rsrc\Find.odc" в Windows 95 и "Text:Rsrc:Find" в MacOS,
* Клавиша-модификатор: в Windows  - Ctrl, в MacOS - Option,
* Команды меню: M->I - сокращение для пункта I из меню M, например File->New
    При работе в оболочке BlackBox полезно помнить следующее: практически все изменения в документах BlackBox могут быть отменены, что делает безопасным эксперименты с различными тонкостями. В общем, доступно многократная отмена, то есть не только одна, но и несколько команд могут быть отменены, столько сколько позволяет объем памяти.
    Эта книга не заменяет руководство пользователя по BlackBox. В ней задействовано минимальное число специфических средств BlackBox, поэтому требуется описание этих средств. Текст предполагает концентрацию на программировании в ущерб описанию средств. Там, где это необходимо, разъяснения особенностей средств даются при первом упоминании.
    Для читателей, не владеющих Component Pascal свободно, в приложении b описываются различия между Pascal и Component Pascal.
    Окно помощи BlackBox дает прямой или косвенный доступ к полной и обширной интерактивной документации, такой как руководство пользователя, все примеры ("Overview by example") и прочее. Во всемирной паутине дополнительные источники могут быть найдены по адресу http://www.oberon.ch.


    4.2 Пример "Телефонная книга"

    На протяжении всей части книги мы рассмотрим изменения и дополнения характерного примера. Этот пример - чрезвычайно упрощенная база данных. Идея не в представлении полноценного приложения со всеми возможными "примочками", а в минимальном примере, который бы не прятал объясняемые концепции за большими кусками кода. Тем не менее, идеи как добавить "примочки" компонент за компонентом видна все время.
    Наша база данных телефонов содержит следующие фиксированные значения:
   
    Daffy Duck       310-555-1212
    Wile E. Coyote   408-555-1212
    Scroodge McDuck  206-555-1212
    Huey Lewis       415-555-1212
    Thomas Dewey     617-555-1212

Таблица 4-1. Состав базы данных телефонов
    
    В базе данных может быть найден телефонный номер по имени. Напротив, в базе данных можно отыскать имя по телефонному номеру, или содержимое базы данных может быть доступно по индексу. Мы уже видели возможную реализацию такой базы данных в разделе 3.2.
    Наша задача - создать интерфейс для базы данных. Пользовательский интерфейс представляет собой диалоговое окно, содержащее два поля; одно под имя, второе под телефонный номер. У каждого поля есть заголовок, поясняющий назначение. Более того, диалоговое окно содержит переключатель, который позволяет определить, по какому из полей: имени или телефонному номеру, осуществляется поиск, и, наконец, кнопку, которая вызывает команду поиска. Следующий экранный снимок показывает, как будет выглядеть диалоговое окно окончательно:



Рисунок 4-2. Образец окна поиска в базе данных

Для построения окна поиска начнем с создания пустой формы диалогового окна, используя команду Contorls->New Form. В результате будет вызвано диалоговое окно:



Рисунок 4-3. Диалог новой формы

Щелчок на кнопке "Empty" открывает пустую форму:



Рисунок 4-4. Пустая форма

Используя команды из меню Controls, мы вставляем те элементы управления, которые необходимы, а именно: два заголовка, два однострочных редактора, переключатель и кнопку. Вставленные элементы управления имеют обобщенные названия вроде "untitled" (безымянный) или "Caption" (заголовок). Для изменения этих визуальных свойств применяется инспектор свойств. Он открывается путем выбора элемента управления и затем: Edit->Object Properties... (Windows) или Edit->Part Info (Mac OS) соответственно. Отредактируйте поле "label" в на форме для изменения заголовка выбранного элемента управления и щелкните на кнопке по умолчанию для сохранения изменений.



Рисунок 4-5. Редактор свойств компонента

   Измените поле "label" для всех элементов управления так, чтобы в конце вы получили макет подобно рисунку 4-2. При переходе в порядке табуляции поля "label" будут следующими (слева-направо, сверху-вниз):

Тип элемента управления   Заголовок
Caption             Name
Text Field
Caption             Number
Text Field
Check Box           Lookup by Name
Command Button      Lookup

Таблица 4-6. Список элементов управления диалога телефонной книги

Элементы управления можно разместить по-другому, используя мышь или команды разметки из меню Layout. После изменения разметки макета, обязательно вызовите Layout->Sort Views; эта команда отсортирует элементы управления в том порядке, в котором они будут обходиться, перемещения по клавише табуляции между ними будут происходит в традиционном порядке, то есть слева-направо и сверху-вниз.



Рисунок 4-7. Окончательный макет диалога телефонной книги

Когда вы останетесь довольны макетом, следует сохранить диалоговое окно как и любой другой документа, используя команду File->Save. По соглашению, макеты диалоговых окон всех примеров сохранены в каталоге Obx/Rsrc. В нашем случае, мы сохраним диалоговое окно как Obx/Rsrc/PhoneUI.
    Каталог под именем Rsrc необходим для "ресурсов". Ресурсы - это документы, необходимые для работы программ. В частности таких как макеты диалоговых окон и строковых ресурсов. Строковые ресурсы позволяют вынести строки за пределы программного кода в отдельный редактируемый документ. 
    Ресурсы могут быть отредактированы без перекомпиляции чего-либо. Например, вы можете изменить все заголовки в представленном выше диалоговом окне с английского на немецкий без необходимости доступа с источнику кода или инструментов разработки.


    4.3 Интеракторы

    Создание диалогового окна - это хорошо, но необходим еще способ добавить линию поведения для диалогового окна. В нашем случае пользовательский интерфейс диалогового окна должен обеспечивать операции поиска в базе данных телефонов. Чтобы это выполнить, нужно реализовать это для базы данных. Мы уже упоминали выше, что подходящая реализация уже существует. Не должно вызывать удивление, так как в данной части книги рассказывается о сборке компонентов, что имеется ввиду соединени имеющихся компонентов и подходящих экземпляров объектов, которые они реализуют.
    В нашем примере, база данных телефонов представлена в модуле Component Pascal под именем ObxPhoneDB. Мы уже знакомы с этим модулем по главе 3. Сейчас мы хотим создать наш первый новый модуль, назначение которого состоит в построении моста между модулем базы данных ObxPhoneDB и диалоговым окном, созданным нами ранее. Новый модуль назовем ObxPhoneUI, где "UI" означает "Пользовательский Интерфейс". Это типичный модуль с программным кодом, основной целью которого является обеспечение работы составного документа, который может использоваться как представление логики приложения, такого, как в нашем случае, как простая база данных телефонов. 
    Новый модуль использует, то есть импортирует, два существующих модуля. Один - это ObxPhoneDB, другой - Dialog:


    Рисунок 4-8. Важные связи между ObxPhoneUI и ObxPhoneDB

    Dialog - часть основополагающего каркаса, имеющегося в BlackBox Component Builder. Этот модуль обеспечивает различные службы для поддержки пользовательского интерфейса. С наиболее важными мы уже встречались в этой главе. 

MODULE ObxPhoneUI;

	IMPORT Dialog, ObxPhoneDB;

	VAR
		phone*: RECORD
			name*, number*: ObxPhoneDB.String;
			lookupByName*: BOOLEAN
		END;

	PROCEDURE Lookup*;
	BEGIN
		IF phone.lookupByName THEN
			ObxPhoneDB.LookupByName(phone.name, phone.number);
			IF phone.number = "" THEN phone.number := "not found" END
		ELSE
			ObxPhoneDB.LookupByNumber(phone.number, phone.name);
			IF phone.name = "" THEN phone.name := "not found" END
		END;
		Dialog.Update(phone)
	END Lookup;

END ObxPhoneUI.

Листинг 4-9. Первая версия ObxPhoneUI

    ObxPhoneUI экспортирует глобальную запись phone, которая содержит два строковых и одно логическое поля. В зависимости от текущего значения логического поля, процедура Lookup берет phone.name для поиска соответствующего номера или phone.number для поиска соответствующего имени. Если поиск неудачен, то есть возвращена пустая строка, то результат переключается на "не найдено". Оба результата помещаются в phone, то есть phone несет одновременно входной и выходной параметр поиска в базе данных. 
    Когда пользователь желает изменить содержимое phone изменяя содержимое элементов управления, например, заполняя текстовые поля, используется глобальная переменная типа phone, называемая интерактором. Элементы управления отображают содержимое полей интерактора и, если возможно, позволяют изменять их интерактивно. Для этого каждый элемент управления вначале должен быть присоединен к соответствующему полю интерактора. Это выполняется с помощью редактора свойств элементов управления, который мы видели ранее. Поле "Link" должна содержать имя поля, например ObxPhoneDB.phone.name или имя процедуры, например ObxPhoneDB.Lookup. Используйте инспектор свойств элемента управления для выставления связей согласно таблице 4-10:

  Control  type	     Label	         Link
	Caption	           Name
	Text     Field		                  ObxPhoneUI.phone.name
	Caption	           Number
	Text     Field		                  ObxPhoneUI.phone.number
	         Check Box	Lookup by Name	ObxPhoneUI.phone.lookupByName
	Command  Button	   Lookup	        ObxPhoneUI.Lookup
Таблица 4-10. Связи телефонной базы и диалогового окна.

    Обратите внимание, что элемент управления бывший до этого недоступным сейчас стал доступным. Что это значит? Когда элемент управления связан, как это обычно случается при чтении формы из файла или, как в нашем случае, связь изменена из инспектора, модуль, связанный с этим элементом должен быть загружен. Если модуль уже загружен, ничего не требуется делать дополнительно. Если он еще не был загружен (это можно проверить по Info->Loaded Modules), загрузка происходит немедленно. Если загрузка оборвалась, например, код модуля еще не существует, связка элемента управления нарушается и он остается недоступным. Связывание так же оканчивается неудачей, когда элемент управления и поле не совместимы по типу, например кнопка независимой фиксации связана со строковым полем. 
    Для достижения такого уровня функциональности (и безопасности против неверного применения), BlackBox обеспечивает ряд улучшенных служб "метапрограммирования", а именно динамическая загрузка модулей по требованию и проверка типов переменных (typesafe lookup of variables). Последнее требование расширяет информацию о типе на этапе выполнения (RTTI), что сравнительно необычно для полностью компилирующих языков. 
    Связи всех элементов управления переопределяются каждый раз, когда модуль выгружается. Это гарантирует, что элементы управления никогда не ссылаются на незагруженные модули. 
    Одна из целей дизайна BlackBox и состоит в разделении деталей пользовательского интерфейса и программной логики. По этой причине модуль ObxPhoneUI ничего не знает об элементах управления, формах и т.п. Например, связка по кнопке "ObxPhoneUI.Lookup" говорит о необходимости активировать процедуру ObxPhoneUI.Lookup, когда кнопка будет нажата. В свою очередь, процедура ничего не знает о командной кнопке (или даже о нескольких), а только оповещает возможные элементы управления, вызывая Dialog.Update(phone) в конце процедуры. Dialog.Update инициирует обновление всех элементов управления, которые это требуют. Например, если Lookup получило значение "не найдено" для phone.number, то связанные текстовые поля и подобные элементы управления должны быть перерисованы соответствующим образом. 
    Как параметр для Dialog.Update должен быть передан интерактор (interactor). Вызов Dialog.Update необходим после программного изменения одного или нескольких полей интерактора. В случае модификации нескольких полей, Dialog.Update следует вызывать только одни раз из соображений эффективности. Необходимо отметить, что элементы управления вызывают Dialog.Update самостоятельно, когда пользователь изменяет поля интерактора, необходимость в принудительном вызове возникает только когда интерактор изменяется программным кодом.
    Строгое разделение интерфейса пользователя и программной логики весьма необычно. Преимущество разделения - в простоте: как только мы узнаем как определять процедуры и как объявлять записи и глобальные переменные, вы уже можете создавать графический интерфейс пользователя для модуля. Это возможно даже для тех, кто только начал заниматься программированием. Другое преимущество: вам не требуется писать код для простого пользовательского интерфейса. Создание пользовательского интерфейса осуществляется посредством редактора форм (то есть установкой места и размера элементов управления) и инспектора (то есть, установкой выравнивания текста и собственно текста для строковых полей). Это позволяет легко изменять пользовательский интерфейс под различные требования без затрагивания логики приложения. Только если вы хотите получить больший контроль над пользовательским интерфейсом, например доступность элементов управления или реакция на особые события, такие как печать с клавиатуры, необходимо написать небольшой кусок кода, который может быть очень просто отделен от логики непосредственно приложения. Необходимые концепции, так же называемые охранники (guards) и уведомления (notifiers) будут обсуждаться в следующих двух разделах. Если вам все еще нужны другие элементы управления, можно изучить самостоятельно, как рассказано в разделе 4.9.
    В настоящее время к недостаткам BlackBox Component Builder можно отнести необходимость применения глобальных переменных в качестве интерактора для связи с элементами управления. Если имеется несколько элементов управления с одинаковыми полями интерактора, все из них отображают одинаковое значение. Сами по себе элементы управления не могут имеют самостоятельного статуса.
    Следует отметить интересную особенность BlackBox: если написан модуль, подобный ObxPhoneUI из листинга 4-9, то возможна автоматическая генерация формы с подходящими элементами управления по умолчанию. Это происходит при нажатии кнопки "Create" в диалоге "New Form" вместо кнопки "Empty". Эта особенность полезна при создании тестовых или отладочных интерфейсов в процессе разработки, когда нецелесообразны затраты времени на ручное построение форм.
    Сейчас уже имеется дизайн диалогового окна с элементами управления, связанными с модулем ObxPhoneUI и этот модуль импортирует механизм управления базой данных ObxPhoneDB. Сейчас все еще отсутствует способ применения диалогового окна вместо простого редактирования его дизайна. В процессе редактирования очень полезно сразу же попробовать диалоговое окно, даже если оно еще не совершенно. Для того, чтобы опробовать, убедитесь, что макет является самым верхним окном и затем выполните Controls->Open As Aux Dialog. Новое открывшееся окно содержит такой же диалог, но может быть отредактировано содержимое элементов управления вместо изменения разметки этих элементов. Окно ведет себя как маска ввода. Сейчас можно напечатать, например, "Huey Lewis" в строке имени, отметить флаг "Lookup by Name" и щелкнуть по кнопке "Lookup". Можно увидеть, что соответствующий номер появился в поле "Number".


Рисунок 4-11. Дизайн (слева) и работа (справа) для одной и той же формы.

    Следует отметить, что то же имя и номер так же отразились и в окне разметки. Более того, если вы измените разметку в дизайнере, например, подвинете кнопку независимой фиксации, можно увидеть, что изменения немедленно отразятся и в соседнем окне. Такое поведение так же называется Model-View-Controller, реализованной в BlackBox. В Части II книги этот шаблон проектирования уже детально обсуждался. Сейчас достаточно сказать, что несколько просмотров могут совместно использовать одни и те же данные, то есть дизайн и рабочий диалог могут отображать одну и ту же форму; и разметка, и диалог явления одного порядка, хотя и в разных формах. Переключение между двумя режимами осуществляется посредством команд: Dev->Layout Mode или Dev->Mask Mode.
    Два режима работы различаются способом обработки выделения и активности. В режиме разметки можно выделить внедренный просмотр и отредактировать выделение, но нельзя сделать активным элемент просмотра. В режиме работы вы можете активизировать просмотр, но не можете выделить сам элемент управления (только его содержимое) и поэтому не можете изменить его разметку, то есть его размер, местоположение и т.п. Другими словами: режим разметки запрещает активацию, режим работы запрещает выделение.
    Открытие второго окна формы в рабочем режиме удобно в процессе разработки, но не желательно, чтобы окно разметки открывалось в момент реальной работы программы. В таком случае следует открывать диалоговое окно в рабочем режиме применяя подходящие команды меню.
    Новая команда меню может появиться после редактирования текста конфигурации. Можно открыть этот текст (расположенный в System/Rsrc/Menus) выполнив команду Info->Menus. Допишите следующий текст в конец файла:

MENU "Priv"
		"Open..."	""	"StdCmds.OpenAuxDialog('Obx/Rsrc/PhoneUI', 'Phonebook')"	""
END

Затем следует выполнить Info->Update Menus. Появится сообщение, что появилось новое меню "Priv". Выполните пункт меню "Open..." В результате будет выполнена команда

StdCmds.OpenAuxDialog('Obx/Rsrc/PhoneUI', 'Phonebook')
    
    Она окрывает макет Obx/Rsrc/PhoneUI, переключает его в режим ввода и открывает его с заголовком "Phonebook". Если необходимо, чтобы изменения меню стали постоянными, сохраните текст меню перед его закрытием.
    Необходимо отметить, что форма не сохраняется в режиме ввода (это было бы неудобно для последующего редактирования), она только временно переключается в режим ввода командой StdCmds.OpenAuxDialog.


    4.4 Охранники

    В рамках чистой функциональности мы узнали все необходимое о стандартных наборах элементов управления. Позднее будет рассказано о палитре стандартных компонентов более подробно. Тем не менее вначале необходимо обсудить важные моменты стандартных элементов управления, которые, строго говоря, не добавляют функциональности приложению, а необходимы только для удобства пользователя, то есть для дружественности к пользователю.
    В разделе 1.2 уже было обсуждено что значит дружественный к пользователю. Например, это означает уход от модальности везде, где это возможно. По этой причине BlackBox не поддерживает модальных диалоговых окон. Подобный способ действия необходим, если действия пользователя в одних случаях возможны, а в других случаях - нет. Например, если буфер обмена пуст, его содержимое не может быть вставлено в текст. Если он не пустой и содержит текст, то вставка возможна. С этим ничего не поделаешь, поэтому текущий статус должен быть виден или легко получен пользователем. Например, если буфер обмена пуст, команда меню Paste должна быть визуально отмечена недоступной. Видимые различия дают пользователю раннее предупреждение, что данная команда не доступна. Это гораздо лучше, чем позволить пользователю выполнить команду, а затем получить сообщение об ошибке. Следующий пример показывает диалоговое окно с двумя кнопками. Кнопка Empty доступна, только когда кнопка Create недоступна. Кнопка Create становится доступной только в случае, если что-то было напечатано в текстовом поле диалогового окна:



Рисунок 4-12. Доступные и недоступные кнопки.

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

PROCEDURE XyzGuard* (VAR par: Dialog.Par);
BEGIN
	par.disabled := ...some Boolean expression...
END XyzGuard;

Охранник описывается следующим типом:
GuardProc = PROCEDURE (VAR par: Dialog.Par);
Для процедуры охранника Lookup из нашего примера мы дополним модуль ObxPhoneUI следующим образом:

MODULE ObxPhoneUI;

IMPORT Dialog, ObxPhoneDB;

VAR
	phone*: RECORD
		name*, number*: ObxPhoneDB.String;
		lookupByName*: BOOLEAN
	END;

PROCEDURE Lookup*;
BEGIN
	IF phone.lookupByName THEN
		ObxPhoneDB.LookupByName(phone.name, phone.number);
		IF phone.number = "" THEN phone.number := "not found" END
	ELSE
		ObxPhoneDB.LookupByNumber(phone.number, phone.name);
		IF phone.name = "" THEN phone.name := "not found" END
	END;
	Dialog.Update(phone)
END Lookup;

PROCEDURE LookupGuard* (VAR par: Dialog.Par);
BEGIN	(* disable if input string is empty *)
	par.disabled := phone.lookupByName & (phone.name = "") OR
								~phone.lookupByName & (phone.number = "")
END LookupGuard;

END ObxPhoneUI.

Листинг 4-13. ObxPhoneUI с LookupGuard

    Что случится после компиляции приведенного выше модуля? Его символьный файл на диске заменится новой версией, так как интерфейс модуля поменялся по сравнению с предыдущей версией. Так как изменения заключаются в простом добавлении глобальной процедуры (то есть новая версия совместима со старой версией), возможные зависимые модули, импортирующие модуль ObxPhoneUI не теряют актуальности и не требуют перекомпиляции.
    Компиляция так же породит новый файл кода на диске. Тем не менее, старая версия ObxPhoneUI по-прежнему остается загруженной в памяти! Другими словами: однажды будучи загруженным, модуль остается в памяти ("выполнить и оставаться в памяти"). Это не проблема, так как модули чрезвычайно малы и занимают мало памяти. Тем не менее программист конечно же должен иметь возможность выгрузит модули без необходимости выхода из оболочки BlackBox Component Builder для того, чтобы опробовать новый версии модулей. Для этой цели служит команда Dev->Unload, выполняющая выгрузку модуля, код которого в настоящее время активен. 
    Необходимо отметить, что компиляция не выгружает модули автоматически, что часто бывает неудобно. В частности, если ведется работа с несколькими взаимосвязанными модулями одновременно, выгрузка одного из них раньше перед тем как другие корректно обновятся может привести к тому, что весь набор модулей придет в противоречивое состояние.
    Для таких случаев, когда немедленная выгрузка модулей после компиляции существенна, как в простом обсуждаемом примере, существует команда Dev->Compile And Unload.
    Ее можно использовать для проверки новой версии модуля ObxPhoneUI. Необходимо отметить, что кнопка Lookup недоступна, когда поле Name пусто (если выбран режим "Поиск по имени") или когда поле Number пусто (если выбран режим "Поиск по номеру"). Ввод каких-либо символов в поле делает кнопку доступной, удаление всех символов - недоступной. В конце этого раздела будет более подробно разъяснено, когда именно срабатывает охранник, сейчас достаточно знать, что охранник срабатывает после ввода каждого символа в элемент управления.
    Охранники чаще всего применяются для обеспечения доступности/недоступности элементов интерфейса, таких как элементы управления или пункты меню. Тем не менее, иногда охранники выступают в более общей роли. Например, элемент управления может быть не только недоступен, но еще и быть только для чтения или не определен. 
    "Только для чтения" означает, что элемент управления не может быть модифицирован. Например, следующий охранник выставляет "Только для чтения" для полей выходного параметра. Первый охранник выставляет "только для чтения", если поиск идет не по имени, то есть по номеру. Очевидно, в этом случае номер - входной, а имя - выходной параметры. Параметр для чистого вывода должен быть только для чтения. Таким образом, первый охранник может использоваться как охранник для поля Name. Другой охранник может быть применен для поля Number, так как выставляет только для чтения, если поиск возвращает номер. 

PROCEDURE NameGuard* (VAR par: Dialog.Par);
	BEGIN	(* make read-only if lookup is by number *)
		par.readOnly := ~phone.lookupByName
	END NameGuard;

	PROCEDURE NumberGuard* (VAR par: Dialog.Par);
	BEGIN	(* make read-only if lookup is by name *)
		par.readOnly := phone.lookupByName
	END NumberGuard;

Листинг 4-14. Охранники "Только для чтения" для ObxPhoneUI

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

    par.undef := TRUE

    Неопределенное значение может быть приравнено к значению "только для записи", так как значение элемента управления не может быть прочитано пользователем, так как оно еще не определено, но может быть изменено, тем самым выставляется какое-то значение.
    Из полей disabled, readOnly и undef, только одно может быть установлено охранником в TRUE. Это приводит к тому, что возможно четыре состояния элемента управления: одно из указанных выше полей или ни одного имеет установленный статус. Когда охранник вызывается оболочкой, все три логических поля устанавливаются в FALSE. Это одно из общих правил в BlackBox: логические поля по умолчанию выставляются в FALSE.
    В таблице 4-15 представлены подходящие охранники для макета с рисунка 4-2:

Control type	                        Link	                      Guard
	Caption
	Text Field	ObxPhoneUI.phone.name	ObxPhoneUI.NameGuard
	Caption
	Text Field	ObxPhoneUI.phone.number	ObxPhoneUI.NumberGuard
	Check Box	ObxPhoneUI.phone.lookupByName
	Command Button	ObxPhoneUI.Lookup	ObxPhoneUI.LookupGuard

Таблица 4-15. Охранники диалогового окна базы данных телефонов

    Охранник может применятся к процедуре, к полю интерактора или к нескольким процедурам или полям интерактора. Если он применяется в большей части к одной процедуре или к полю, то имя охранника формируется добавлением слова "Guard" к (с большой буквы) имени процедуры или поля. Например, охранник для процедуры Lookup называется LookupGuard, охранник для поля phone.name называется NameGuard и т.п. Такое простое соглашение о наименование делает более легким определение связи между охранником и подопечным элементом. Соглашение об именовании "мягкое", то есть не навязывается оболочкой, фактически оболочка не знает об этом вовсе.
    Экспорт охранников не просто соглашение, а необходимость. Доступ к охранникам осуществляется так называемым механизмом метапрограммирования BlackBox, который по соображениям безопасности оперирует только набором экспортируемых элементов, то есть интерфейсов, доступных извне. Если охранник не экспортируется, элемент управления не может его вызвать. Подобная ситуация с полями интерактора, которые так же должны быть экспортированы, если интерактор будет связан с ними. 
    Если заглянуть в описание типа Dialog.Par (применяя Info->Interface...), то можно увидеть что еще не описаны два поля: label и checked:

Par = RECORD 
		disabled, checked, undef, readOnly: BOOLEAN;
		label: Dialog.String
	END

    Поле label позваляет изменить заголовок элемента управления. Например, вместо заголовка кнопки "Toggle" можно применить заголовки "Switch On" и "Switch Off" в зависимости от текущего состояния системы. Этого можно добиться подобной процедурой:

PROCEDURE ToggleGuard* (VAR par: Dialog.Par);
	BEGIN
		IF someInteractor.isOn THEN
			par.label := "Switch Off"
		ELSE
			par.label := "Switch On"
		END
	END ToggleGuard;

Листинг 4-16. Пример охранника заголовка

    Следует отметить, что охранник перекрывает все заголовки, которые были установлены для элемента управления из инспектора. Это верно для всех элементов управления (и для всех пунктов меню также, смотри ниже).
    Настоятельно не рекомендуется размещать строковые литералы в коде программы, как в примере выше, так как при изменении языка, например с английского на немецкий (см. так же раздел 1.4) потребуется принудительная перекомпиляция. В Черном Ящике строки, относящиеся к пользовательскому интерфейсу, такие как заголовки или сообщения чаще всего заносятся в отдельный файл параметров, так же называемый ресурсом строк. Для каждой из подсистем может существовать один ресурсный файл, например Text/Rsrc/Strings или Form/Rsrc/Strings. Ресурс строк представляет из себя текстовый документ Черного Ящика, начинающийся с ключевого слова STRING и нескольких пар вида <ключ, строка>. Ключ представляет из себя строку, отделенную символом табуляции от собственно строки, которую заменяет. Каждая пара <ключ, строка> должна заканчиваться символом возврата каретки. Пары не должны быть упорядочены каким-либо образом, но полезно отсортировать их в алфавитном порядке ключей, так как тогда будет удобней искать обыкновенный ключ в процессе редактирования строкового ресурса.
    Например, System/Rsrc/Strings начинается так:

STRINGS
About	About BlackBox
AlienAttributes	alien attributes
AlienCause	alien cause
AlienComponent	alien component
AlienControllerWarning	alien controller (warning)

Таблица 4-17. Ресурс строк System/Rsrc/Strings

    Для использования строковых ресурсов в нашем примере, необходимо использовать специальный синтаксис, который показывает, что строка в действительности является ключем, который должен быть заменен подходящим значением из строкового ресурса. Таким образом, в подсистеме Obx в строковом ресурсе имеются ключи "On" и "Off", следующий код их использует:

PROCEDURE ToggleGuard* (VAR par: Dialog.Par);
	BEGIN
		IF someInteractor.isOn THEN
			par.label := "#Obx:Off"
		ELSE
			par.label := "#Obx:On"
		END
	END ToggleGuard;

Листинг 4-18. Охранник заголовка с применением карты строк

    Ведущий символ "#" показывает, что необходима подстановка строк. Затем следует название подсистемы, в нашем случае "Obx". Затем следует двоеточие, после которого идет ключ для замены. Кнопка с таким охранником будет отображать заголовки "Switch Off" или "Switch On" в английской версии BlackBox, "Ausschalten" или "Einschalten" в немецкой версии и т.п.
    Если нет подходящего строкового ресурса, ключ заменяется на собственное значение. Например, если не будет ключа "Off" в Obx/Rsrc/Strings, то "#Obx:Off" будет заменено на "Off".
    Из Dialog.Par осталось не рассмотренным поле под названием checked. В действительности, оно никогда не применяется для элементов управления BlackBox. Оно применяется для пунктов меню. Пункты меню подобны элементам управления: они могут запускать какие-то действия, могут быть доступными/недоступными, могут иметь заголовки. По этой причине к ним применяется такой же механизм охранников. Охранник для меню определяется в тексте Menus как строка после метки пункта меню и клавиатурного эквивалента. Например, следующий фрагмент представляет определение охранника меню Dev:

  "Edit Mode"	""	"StdCmds.SetEditMode"	"StdCmds.SetEditModeGuard"

    Отличительная черта элемента меню состоит в том, что он может быть отмечен. Например, в следующей меню пункт Edit Mode отмечен:



Рисунок 4-19. Пункт меню с отметкой

    Отметка показывает, какой пункт был выбран самым последним, и какой статус был установлен по данному выделению. Состояние может быть изменено вовлечением других пунктов меню, например Mask Mode, как на рисунке выше. В общем, четыре пункта меню образут группу, из которых только один может быть выбран. 
    Процедура охранника для пункта меню может выставить значения disabled, checked и label для Dialog.Par. Другие поля для меню игнорируются.
    Процедура охранника может выставлять несколько полей для выходного параметра par одновременно, то есть может быть установлено поле disabled и label для кнопки. Тем не менее, охранник может выставлять как минимум одно логическое поле из par и никогда не должен изменять состояние вне рамок par, то есть поля интерактора или что-то подобное, то есть не должно быть побочных эффектов. Так же не должно быть вызовов других процедур, имеющих побочный эффект. Это сделано по причине того, что программа не может знать когда (а особенно когда "не") оболочка вызывает охранника. Охранник может использовать интерактор или несколько интеракторов как входные параметры, или текущее выделение или фокусировку. Последнее часто используется для охранников меню. Текущую фокусировку или выделение применяется в охранниках только для элементов управления так же называемых диалоговый панели инструментов, то есть диалоги, оперирующие содержимым нижеследующего документа. Диалоговое окно Find & Replace, работающее с текущим окном текста - типичный пример инструментального диалогового окна. Другие ока являеются самодостаточными и называются вспомогательными диалоговыми окнами. Маска ввода данных - типичный пример вспомогательного диалогового окна.
    Когда срабатывает охранник? Существует четыре причины вызова охранника: когда установлена связь с элементом управления, когда изменяется содержимое интерактора, когда изменяется иерархия окна или когда пользователь щелкает на меню. 
    Связь элемента управления устанавливается после вставки в контейнер, после загрузки из файла, после выгрузки модуля или после того как связь была изменена в инспекторе свойств или другими средствами. Когда кусок кода изменяет содержимое интеркатора, необходимо вызвать Dialog.Update для этого интерактора. В результате каждый визуальный элемент управления получает уведомление (см. раздел 2.9). В ответ элементы управления сравнивают текущие состояния со значениями полей связанного интерактора. Если поля интерактора изменились, элемент управления соответствующим образом перерисовывается. После того, как все элементы управления обновили свое содержимое, вычисляются охранники всех визуальных элементов управления. По этой причине охранники должны быть оптимальными и не занимать много времени на обработку.
    Охранники так же вычисляются, когда изменяется иерархия окон, то есть когда нижнее окно перемещается наверх. Это необходимо, так как многие команды зависят от текущего фокуса и выделения, которые могут исчезнуть, когда другое окно перемещается наверх. 
    Другая причина вызова охранников - меню. Когда пользователь щелкает на меню, все охранники этого меню или охранник меню целиком пересчитывается.
    Чаще всего охранник имеет форму:

PROCEDURE SomeGuard* (VAR par: Dialog.Par)

    Другая форма охранника
PROCEDURE SomeGuard* (n: INTEGER; VAR par: Dialog.Par)

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

    StdCmds.Color(00000FFH)
	StdCmds.Color(000FF00H)
	StdCmds.Color(0FF0000H)

Для этих команд могут быть применены следующие охранники
    StdCmds.ColorGuard(00000FFH)
	StdCmds.ColorGuard(000FF00H)
	StdCmds.ColorGuard(0FF0000H)

Имеющаяся реализация заголовка StdCmds.ColorGuard:
  PROCEDURE ColorGuard (color: INTEGER; VAR par: Dialog.Par)


    4.5. Уведомления

    Охранник элемента управления выставляет текущее состояние элемента, например: доступен или нет. Других действий не производится. Не изменяются какие-либо данные интерактора. Не добавляется какой-либо функциональности. Лишь добавляется обратная связь для элемента управления о текущей функциональности или ее возможности. Охранник пересчитывается только когда пользователь что-либо изменит в элементе управления. Такое положение может быть представлено как простая "косметическая" особенность, служащая исключительно для увеличения дружественности интерфейса.
    Тем не менее, иногда действия пользователя должны инициировать больше, чем простое срабатывание охранника. В частности иногда необходимо изменить какое-либо состояние интерактора в ответ на действия пользователя. Например, изменение выделения в окне может повлечь необходимость обновления соответствующего счетчика, который показывает число выделенных элементов в окне. Или требуется реализация какой-либо последующей обработки пользовательского ввода, как будет показано на примере ниже. Это будет альтернативная реализация примера ObxPhoneUI. Вместо кнопки Lookup, новая версия имеет всего два текстовых поля. После каждого ввода символа база данных выполняет поиск, пока не будет найден правильный ключ. Необходимо отметить, что неудачный поиск в ObxPhoneDB возвращает пустую строку.

MODULE ObxPhoneUI1;

	IMPORT Dialog, ObxPhoneDB;

	VAR
		phone*: RECORD
			name*, number*: ObxPhoneDB.String
		END;

	PROCEDURE NameNotifier* (op, from, to: INTEGER);
	BEGIN
		ObxPhoneDB.LookupByName(phone.name, phone.number);
		Dialog.Update(phone)
	END NameNotifier;

	PROCEDURE NumberNotifier* (op, from, to: INTEGER);
	BEGIN
		ObxPhoneDB.LookupByNumber(phone.number, phone.name);
		Dialog.Update(phone)
	END NumberNotifier;

END ObxPhoneUI1.

Листинг 4-20. Модуль ObxPhoneUI1 с уведомлениями.

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

PROCEDURE XyzNotifier* (op, from, to: INTEGER);
	BEGIN
		...
	END XyzNotifier;

Тип уведомления:

NotifierProc = PROCEDURE (op, from, to: INTEGER);

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


    4.6 Стандартные элементы управления

    В данном разделе представлены различные элементы управления, принятые за стандарт в BlackBox Component Builder. Для каждого элемента управления приводится список возможных типов данных, с которыми можно связать элемент, и смысл и значения параметров уведомления.
    Элементы управления могут иметь различные свойства, например заголовки или начертание заголовка стандартным системным шрифтом или более бледным, тонким. Тем не менее, подобные косметические свойства могут быть не реализованы на всех или части платформ, поэтому иногда просто игнорируются.
    Заголовки могут иметь специальные метки (keyboard shortcut), которые позволяют на некоторых платформах перемещаться между элементами без применения мыши. Символ метки определяется по предшествующему знаку "&" (амперсанд). Два последовательных символа "&" обозначают нормальный "&" без присвоения последующему символу признака метки. На один заголовок допустима только одна метка. Например: "Clear Text &Field" определяет "F" в качестве метки, в то время как "Find && &Replace"  определяет "R" как клавиатурную метку. Подобное описание меток применяется и для команд меню.
    Свойства заголовка для всех элементов управления могут быть изменены в процессе выполнения посредством охранника, как было показано в листинге 4-16. Для элементов управления без видимого заголовка не будет видимого эффекта.
    Два наиболее важных свойства элементов управления: охранник и уведомление - не обязательны. Если не определен охранник элемента, будет применяться значение по умолчанию (доступно, для чтения и записи, определено). Если не определено уведомление для элемента, ничего не будет вызываться. 
    В BlackBox сделана попытка минимизировать число различных свойств для облегчения их использования. Это согласуется с общими направлениями развития, как в Windows, так и в Mac OS, а так же в рамках общего развития элементов управления. Это означает, что для каждого отдельного элемента управления не следует специально назначать свойства цвета, шрифтов и т.п. Вместо этого пользователь должен назначить системные установки и конфигурацию этих свойств.
    Тем не менее, если существуют весомые причины, можно назначит специальный шрифт для элементов управления простым выделением этого элемента и выполнением команд меню Font, Attributes или Characters. Если установлен шрифт по умолчанию, то будут выставлены общесистемные настройки. Это нормальное явление. Следует отметить, что различные атрибуты шрифтов, такие как стиль, жирность, размер, могут быть зафиксированы в некоторых платформах. Например, в Mac OS системный шрифт всегда 12 пунктов.
    В нижеследующем тексте описаны стандартные элементы управления BlackBox в следующем порядке: кнопки (command buttons), редакторы (edit fields), кнопки независимой фиксации (check boxes), кнопки зависимой фиксации (radio buttons), поля ввода даты (date fields), поля ввода времени (time fields), поля ввода цвета (color fields), заголовки (captions), группы (groups), выпадающие списки (list boxes), списки (selection boxes), комбинированные списки (combo boxes). Для каждого элемента управления представлены его свойства, возможные типы переменных для связи, значения параметров op, from и to в уведомлениях. 
    Следует отметить, что не существует специального типа для ввода валюты, взамен следует использовать редактор, связанный с типом Dialog.Currency.
    В приведенном описании применяются следующие сокращения:
pressed - Dialog.pressed
released - Dialog.released
changed - Dialog.changed
included - Dialog.included
excluded - Dialog.excluded
undefined означает, что параметр from или to уведомления не содержит определенного значения ( в котором можно быть уверенным).
modifier означает 0 для простого щелчка и 1 для двойного щелчка
link, label, guard, notifier и level определены для соответствующего элемента управления в модуле Controls.
Может быть до 5 дополнительных логических свойств. В зависимости от элемента управления они могут называться по-разному, например: default font, default, cancel, sorted, left, right, multiLine, password.

Кнопка (Command Button)



Рисунок 4-21. Кнопка
    
    Кнопка предназначена для выполнения непараметризированной команды Component Pascal или целой последовательности команд. Кнопка по умолчанию отличается по внешнему виду от обычных кнопок; нажатие клавиши "Enter" соответствует щелчку мыши по кнопке по умолчанию. Для кнопки со свойством cancel, нажатие клавиши "ESC" соответствует щелчку мыши на данной кнопке. Кнопка не может быть одновременно "По умолчанию" и "Отмена". Следует отметить, что выставление свойств "По умолчанию" и "Отмена" выполняется для каждого элемента управления, то есть установка одной кнопки "по умолчанию" не превращает предыдущую кнопку со свойством "по умолчанию" в нормальную. Для диалогового окна следует выставлять не больше одной кнопки по умолчанию и не больше одной кнопки cancel. Кнопка отмены или любая другая кнопка, предназначенная для закрытия диалогового окна должна содержать команду StdCmds.CloseDialog.

properties:	link, label, guard, notifier, font, default, cancel
linkable to:	parameterless procedure, command sequence
op:	pressed, released
from:	undefined, modifier
to:	undefined

Редактор (Text Field)



Рисунок 4-22. Редактор
    
    Редактор отражает значение глобальной переменной, которая может иметь строковый, целый, дробный типы или различные типы Dialog.Currency или Dialog.Combo. Значение переменной может быть изменено в редакторе. 
    При любом изменении содержимого связанного поля интерактора вызывается уведомление. Нажатие клавиш, которые не изменяют состояние интерактора, таки как нажатие стрелок, ввод ведущих нулей для чиловых полей, не приводит к вызову уведомления. Изменение выделения содержимого поля так же не приводит к вызову уведомления.
    Если изменение происходит для поля, связанного с типом строка, дробное число, валюта или переменной комбинированного списка, вызывается уведомление с параметрами (change, undefined, undefined). Если поле связано с целочисленным значением, вызывается уведомление с параметрами (change, oldvalue, newvalue).
    Недопустимые символы, например, буквы для полей связанных с числовыми типами, не вводятся.
    Редактор может иметь заголовок, но он не показывается. Причина - метка клавиши может быть определена в заголовке, это может быть полезно для полей ввода.
    Если значение свойства level 0 (по умолчанию) или элемент не связан с числовым полем или связан с  типом Dialog.Currency, то свойство level не имеет эффекта. Если связь установлена с целочисленной переменной, level определяет масштаб для отображения числа, то есть связанное число отображается в разделенном на 10 в степени level. Например, если текущее значение 42 и level 2, то отображается значение 0,42. Для действительных чисел level показывает формат следующим образом:
level > 0: экспоненциальный (научный) формат, при этом не менее level цифр в мантиссе
level = 0: формат с фиксированной или плавающей точкой в зависимости от x
level < 0: фиксированный формат с level цифр после запятой.
    Свойства left и right определяют режим выравнивания содержимого поля. Возможны следующие комбинации:
left & ~right выравнивание по левому краю
left & right выравнивание по содержимому (может не срабатывать на некоторых платформах)
~left & ~right выравнивание по центру
~left & right выравнивание по правому краю

    По умолчанию принято left & ~right (выравнивание по левому краю)
    Свойство multiLine определяет возможность ввода символа возврата каретки и, соответственно, появление разрыва строки в указанное поле, которое должно быть связано со строковой переменной.
    Свойство password определяет необходимость замены содержимого поля на символы снежинок при печати. Это свойство позволяет использовать строку для ввода паролей.

properties:	link, label, guard, notifier, level, font, left, right, multiLine,
	password
linkable to:	ARRAY const OF CHAR,
	BYTE, SHORTINT, INTEGER, LONGINT, SHORTREAL, REAL,
	Dialog.Currency, Dialog.Combo
op:	pressed, released, changed
from:	undefined, old value, modifier
to:	undefined, new value

Кнопка независимой фиксации (Check Box)



Рисунок 4-23. Кнопка независимой фиксации

    Кнопка независимой фиксации отображает значение глобальной переменной логического типа или типа элемента множества. Щелчок на элементе управления изменяет его статус.
    Если элемент связан с логическим типом, то при изменении элемента вызываетя уведомление с параметрами (changed, undefined, undefined). Если связь установлена с элементом из множества, то вызываемые параметры будут (included, level, undefined) или (excluded, level, undefined) в зависимости от того, устанавливается или сбрасывается фиксация. Значение level соответствует элементу из множества, с которым элемент управления связан. Связь может быть определена из инспектора свойств элемента управления. Значение может находиться в пределах 0..31.
    Только последнее и завершающее изменение статуса элемента управления приводит к вызову уведомления, возможные промежуточные изменения (перемещение мыши за пределы границ окна и обратно в область) не возымеют действия.

properties:	link, label, guard, notifier, font, level
linkable to:	BOOLEAN, SET
op:	pressed, released, changed, included, excluded
from:	undefined, level value, modifier
to:	undefined
    
Кнопка с зависимой фиксацией (Radio Button)



Рисунок 4-24. Кнопка с зависимой фиксацией.

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

properties:	link, label, guard, notifier, font, level
linkable to:	BYTE, SHORTINT, INTEGER, LONGINT, BOOLEAN
op:	pressed, released, changed
from:	undefined, old value, modifier
to:	undefined, new value = level value
     
    
Поле ввода даты (Date Field)



Рисунок 4-25. Поле ввода даты
    
    Поле ввода даты отображает дату из глобальной переменной типа Dates.Date. При любом изменении поля связанного интерактора вызывается уведомление с параметрами (change, undefined, undefined). Нажатие клавиш, которые не изменяют значение интерактора, таких как стрелки влево, вправо, не приводят к вызову уведомления. Клавиши стрелок вверх, вниз изменяют дату. Изменение выделения не дает никакого эффекта. Неверная дата не может быть введена.

properties:	link, label, guard, notifier, font
linkable to:	Dates.Date
op:	pressed, released, changed
from:	undefined, modifier
to:	undefined

Поле ввода времени (Time Field)



Рисунок 4-26. Поле ввода времени

    Поле ввода времени отображает время из глобальной переменной типа Dates.Time. При любом изменении связанного интерактора вызывается уведомление с параметрами (change, undefined, undefined). Нажатия клавиш, которые не приводят к изменению значений интерактора, таких как стрелки влево, вправо, не не приводит в вызову уведомления. Стрелки вверх, вниз изменяют время. Изменения выделения в поле не оказывает влияния. Неверное значение времени не может быть введено.

properties:	link, label, guard, notifier, font
linkable to:	Dates.Time
op:	pressed, released, changed
from:	undefined, modifier
to:	undefined


Поле выбора цвета (Color Field)



Figure 4-27. Поле выбора цвета
    
    Поле выбора цвета отображает цвета. Оно может быть связано с переменной типа INTEGER или Dialog.Color. Тип Ports.Color является переопределением для INTEGER, поэтому так же может быть использован. 
    При выборе любого другого цвета вызывается уведомление с параметрами (change, oldval, newval). Старое и новое значение являются целыми числами, для переменной типа Dialog.Color имеется поле val.

properties:	link, label, guard, notifier, font
linkable to:	Dialog.Color, Ports.Color = INTEGER
op:	pressed, released, changed
from:	undefined, old value, modifier
to:	undefined, new value
    
Промотка (Up/Down Field)



Рисунок 4-28. Промотка

    Такое поле связано с целочисленной переменной. Значение так же может быть изменено с помощью клавиш со стрелками. При любом изменении связанного поля интерактора происходит вызов уведомления с параметрами (change, oldvalue, newvalue).

properties:	link, label, guard, notifier, font
linkable to:	BYTE, SHORTINT, INTEGER, LONGINT
op:	pressed, released
from:	undefined, old value, modifier
to:	undefined, new value


Заголовок (Caption)



Рисунок 4-29. Заголовок
    
    Заголовок обычно применяется в сочетании с редактором для описания его содержимого. Заголовок пассивен, то есть не может быть отредактирован, поэтому не имеет уведомления. 
    Заголовок может быть связан к тем же типам переменных, что и редактор, и может иметь охранника. Это полезно (в зависимости от платформы) при отображении заголовка с разными визуальными эффектами, если он (или в равной степени его соответствующий редактор) недоступен, только для чтения и т.п. Это означает, что заголовок может быть недоступным вместе с соответствующим редактором, если их связать с одним и тем же полем интерактора. Охранник заголовка может изменить заголовок элемента управления, что актуально для элементов с видимым заголовком. 

properties:	link, label, guard, font, right, left
linkable to:	ARRAY const OF CHAR,
	BYTE, SHORTINT, INTEGER, LONGINT, SHORTREAL, REAL,
	Dialog.Currency, Dialog.Combo

    Свойства left и right определяют режим выравнивания заголовка. Возможны следующие комбинации:
left & ~right выравнивание по левому краю
left & right выравнивание по содержимому (может не срабатывать на некоторых платформах)
~left & ~right выравнивание по центру
~left & right выравнивание по правому краю

    По умолчанию принято left & ~right (выравнивание по левому краю)

Группа (Group)



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

properties:	label, guard, font

Выпадающий список (List Box)



Рисунок 4-31. Выпадающий список (свернутая и распахнутая формы)

    Выпадающий список позволяет выбрать одно значение из списка выбора. Он связан с переменной типа Dialog.List (смотри следующий раздел). Если высота списка не достаточна, отображается список с полосой прокрутки. Если и этого недостаточно, список сворачивается во всплывающее меню.
    Выделение может быть изменено пользователем. Выделение может состоять из одного элемента списка или не содержать элементов. Когда пользователь изменяет выделение, вызывается уведомление с параметрами (changed, oldvalue, newvalue). Старое/новое значение соответствует индексу выделенного элемента списка. Самый верхний элемент имеет индекс 0, следующий элемент - индекс 1 и т.п. Если ничего не выделено, индекс равен -1.
    Свойство sorted определяет необходимость сортировки строк в алфавитном порядке (не действует в Mac OS).

properties:	link, label, guard, notifier, font, sorted
linkable to:	Dialog.List
op:	pressed, released, changed
from:	undefined, old value, modifier
to:	undefined, new value

Список (Selection Box)



Рисунок 4-32. Список

    Список позволяет выделить несколько элементов из предложенного набора выбора. Он связан с переменной типа Dialog.Selection (смотри следующий раздел).
    Выделение может быть изменено пользователем. Каждый элемент может быть выделен отдельно. Когда пользователь изменяет выделение, вызывается уведомление следующими тремя возможными способами:
(included, from, to): диапазон from..to сечас выделен; не то, что выделено до этого
(excluded, from, to): диапазон from..to сейчас не выделено; было выделено до того
(set, from, to): диапазон from..to сейчас выделено; до этого не были выделены.
    Три кода included, excluded, set выставляются на месте changed и применяются в большинстве других элементов управления. Уведомление вызывается по мере необходимости при включении и/или исключении всех необходимых элементов в диапазоны.
    Значения from, to соответствуют индексам элементов. Первый элемент соответствует значению 0, следующий 1 и т.п.
    Свойство sorted определяет необходимость сортировки строк в алфавитном порядке (не действует в Mac OS).

properties:	link, label, guard, notifier, font, sorted
linkable to:	Dialog.Selection
op:	pressed, released, included, excluded, set
from:	undefined, lowest element of range, modifier
to:	undefined, highest element of range

Комбобокс (Combo Box)



Рисунок 4-33. Комбобокс
    
    Комбобокс - это текстовый редактор, содержащий еще и множество значений в стиле всплывающего меню. В отличие от настоящих всплывающих меню и списков, могут быть введены и значения не содержащиеся в списке всплывающего меню. Элемент управления связывается с переменной типа Dialog.Combo (смотри следующий раздел).
    При изменении содержимого комбобокса вызывается уведомитель с параметрами (changed, undefined, undefined).
    Свойство sorted определяет необходимость сортировки строк в алфавитном порядке (не действует в Mac OS).

properties:	link, label, guard, notifier, font, sorted
linkable to:	Dialog.Combo
op:	pressed, released, changed
from:	undefined, modifier
to:	undefined 
    
    Каждый интерактивный элемент управления (за исключением заголовка или группы) вызывает уведомление (если оно есть) когда пользователь в первый раз щелкнет на элементе управления с параметром op = Dialog.pressed и затем с параметром op = Dialog.released, когда кнопка мыши освобождается. Эта особенность используется по большей части для отображения какой-либо строки в статусной строке диалогового окна. Это можно сделать с помощь вызова Dialog.ShowStatus или Dialog.ShowParamStatus. Например, следующее уведомление показывает функцию кнопки пользователю:

PROCEDURE ButtonNotifier* (op, from, to: LONGINT);
  BEGIN
    IF op = Dialog.pressed THEN
      Dialog.ShowStatus("This button causes the disk to spin down")
    ELSIF op = Dialog.released THEN
      Dialog.ShowStatus("")	(* clear the status message again *)
    END
  END ButtonNotifier;

Листинг 4-34. Уведомление для отображения данных в статусной строке.
    
    На некоторых платформах, таких как Mac OS, нет статусной строки, поэтому указанный выше код не дает результата.
    Часто бывает полезным определить двойной щелчок, например, двойной щелчок на списке может выделить элемент и запустить кнопку по умолчанию. Двойной щелчок распознается уведомлением следующим образом:

IF (op = Dialog.pressed) & (from = 1) THEN ...	(* double-click *)

    Для упомянутого выше случая, в котором реакция на двойной щелчок сводится к простому выполнению команды кнопки по умолчанию, доступно более подходящее стандартное уведомление:

StdCmds.DefaultOnDoubleClick

    Как уже упоминалось ранее, в BlackBox Component Builder обеспечивается возможность подмены строк с помощью карты ключей к собственно строкам, с применением ресурсного файла. Эта особенность так же поддерживается Dialog.ShowStatus. Например, вызов

Dialog.ShowParamStatus("This ^0 causes the ^1 to ^2", control, object, verb)

позволяет обеспечить разные строки для символов-заполнителей ^0, ^1, ^2. В действительности будут использованы строки из трех дополнительных параметров control, object и verb. Следует отметить, что строки подменяются перед их сцеплением в первую строку. Особенность подмены строк можно использовать исключительно в вызове процедуры Dialog.MapParamString.


    4.7 Составные элементы управления и интеракторы
    
    Выпадающий список имеет содержание 2-х видов: элемент в строке, который взят из списка и текущее выделение. Как правило, список элементов более постоянен, чем выделение, но и он так же может меняться в процессе использования. В случает списка или выделения приложению малоинтересен сам список элементов, так как он является только подсказкой для пользователя. Для приложения интересно выделение, установленное пользователем. Для списка выделение определяется индексом выделенного элемента. Для списка с множественным выбором выделение определяется множеством индексов выделенных элементов. Для комбобокса допустимо не только выделение, но и введенная строка. 
    Для всех трех типов списочных структур список элементов должен быть как-то построен. Для этой цели в модуле Dialog определяется подходящий интерактор: Dialog.List для списка, Dialog.Selection для списка, Dialog.Combo для комбобокса. Эти типы определяются следующим способом:

List = RECORD
		index: INTEGER;	(* индекс текущего выбранного элемента *)
		len-: INTEGER;	(* число элементов списка *)
		PROCEDURE (VAR l: List) SetLen (len: INTEGER), NEW;
		PROCEDURE (VAR l: List) SetItem (index: INTEGER; IN item: ARRAY OF CHAR), NEW;
		PROCEDURE (VAR l: List) GetItem (index: INTEGER; OUT item: String), NEW;
		PROCEDURE (VAR l: List) SetResources (IN key: ARRAY OF CHAR), NEW
	END;

	Selection = RECORD
		len-: INTEGER;	(* число выделенных элементов *)
		PROCEDURE (VAR s: Selection) SetLen (len: INTEGER), NEW;
		PROCEDURE (VAR s: Selection) SetItem (index: INTEGER; IN item: ARRAY OF CHAR), NEW;
		PROCEDURE (VAR s: Selection) GetItem (index: INTEGER; OUT item: String), NEW;
		PROCEDURE (VAR s: Selection) SetResources (IN key: ARRAY OF CHAR), NEW;
		PROCEDURE (VAR s: Selection) Incl (from, to: INTEGER), NEW;
																	(* выбрать диапазон [от..до] *)
		PROCEDURE (VAR s: Selection) Excl (from, to: INTEGER), NEW;
																	(* отменить выделение диапазона [от..до] *)
		PROCEDURE (VAR s: Selection) In (index: INTEGER): BOOLEAN, NEW
																	(* проверить, выделен ли элемент с номером index *)
	END;

	Combo = RECORD
		item: String;	(* текущая введенная или выделенная строка *)
		len-: INTEGER;	(* число элементов *)
		PROCEDURE (VAR c: Combo) SetLen (len: INTEGER), NEW;
		PROCEDURE (VAR c: Combo) SetItem (index: INTEGER; IN item: ARRAY OF CHAR), NEW;
		PROCEDURE (VAR c: Combo) GetItem (index: INTEGER; OUT item: String), NEW;
		PROCEDURE (VAR c: Combo) SetResources (IN key: ARRAY OF CHAR), NEW
	END;

Листинг 4-35. Определения для списка, выделения и комбобокса

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

list.SetLen(5);	(* определить длину списка *)
	list.SetItem(0, "Daffy Duck");
	list.SetItem(1, "Wile E. Coyote");
	list.SetItem(2, "Scrooge Mc Duck");
	list.SetItem(3, "Huey Lewis");
	list.SetItem(4, "Thomas Dewey");
	Dialog.UpdateList(list);	(* должен быть вызван после любых изменений элемента списка *)

Листинг 4-36. Явное построение списка
    
    В случае применения спискоориентированной структуры (выпадающий список, список, комбобокс), Dialog.Update обновляет только выделение или текстовый ввод элемента управления, но не содержимое списка. Если изменяется структура, то есть элементы списка, то требуется вызывать процедуру Dialog.UpdateList вместо Dialog.Update.
    Для фиксированных списков рекомендуется сохранять отдельные строки в ресурсах. Это облегчается применением процедур SetResources. Они ищут строки в ресурсном файле. Например, для полной замены вышеприведенного фрагмента может служить выражение:

list.SetResources("#Obx:list")

которое читает файл Obx/Rsrc/Strings и ищет вхождения ключей подобно "list[index]", например:

list[0]	Daffy Duck
	list[1]	Wile E. Coyote
	list[2]	Scrooge Mc Duck
	list[3]	Huey Lewis
	list[4]	Thomas Dewey

Таблица 4-37. Ресурс списка

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


    4.8 Контроль ввода

    Контроль правильности ввода и невведения чего-нибудь противоестественного - один из аспектов немодального пользовательского интерфейса.  Между прочим, немодальность также означает, что пользователь не должен вмешиваться в режим, зависящий от текущего положения символа вставки (курсора) и проверять, правильно ли введены текущие данные или нет.
	В частности это означает, что нельзя заставлять пользователя вводить каки-либо данные прежде чем разрешить дальнейшие действия.
    Существует две основные стратегии проверки правильности введенных данных: ранний и поздний. Ранняя проверка проводится в момент работы с элементом управления. Поздняя проверка проводится после полного ввода данных и попытке совершить над ними какое-либо действие, например: ввод новых данных в базу данных. Поздняя проверка в большинстве случаев подходит при проверке глобально определенных инвариантов, например: во все ли необходимые поля введены данные. Ранняя проверка больше подходит для локальных, использующих особенности элемента управления, инвариантов, например: исправление синтаксиса введенной строки.
    Приведем примеры как ранней, так и поздней проверки для модуля ObxPhoneUI. Примем, что телефонный номер всегда имеет формат: 310-555-1212. Для поздней проверки будет дополнена процедура Lookup (выделенный шрифт) и добавлены несколько вспомогательных процедур. Необходимо отметить, что оператор "$", который используется с LEN возвращает длину строки, а не длину массива.

PROCEDURE Valid (IN s: ARRAY OF CHAR): BOOLEAN;

		PROCEDURE Digits (IN s: ARRAY OF CHAR; from, to: INTEGER): BOOLEAN;
		BEGIN	(* проверить, содержит ли s в диапазоне [от..до] только цифры *)
			WHILE (from <= to) & (s[from] >= "0") & (s[from] <= "9") DO INC(from) END;
			RETURN from > to	(* нет найденных нецифр в диапазоне *)
		END Digits;

	BEGIN	(*  проверить синтаксис телефонного номера *)
		RETURN (LEN(s$) = 12) & Digits(s, 0, 2) & (s[3] = "-") & Digits(s, 4, 6) &
							(s[7] = "-") & Digits(s, 8, 11)
	END Valid;

	PROCEDURE ShowErrorMessage;
	BEGIN
		phone.name := "illegal syntax of number"
	END ShowErrorMessage;

	PROCEDURE Lookup*;
	BEGIN
		IF phone.lookupByName THEN
			ObxPhoneDB.LookupByName(phone.name, phone.number);
			IF phone.number = "" THEN phone.number := "not found" END
		ELSE
			IF Valid(phone.number) THEN
				ObxPhoneDB.LookupByNumber(phone.number, phone.name);
				IF phone.name = "" THEN phone.name := "not found" END
			ELSE
				ShowErrorMessage
			END
		END;
		Dialog.Update(phone)
	END Lookup;

Листинг 4-38. Поздняя проверка правильности ввода.

    Грубо говоря, напоминание показывает сообщение об ошибке. Если открыто окно рабочего журнала, то сообщение пишется в него, а само это окно перемещается на передний план. Если журнал не используется, ShowErrorMessage выполняет следующее выражение:

Dialog.ShowMsg("Please correct the phone number")

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

PROCEDURE Correct (VAR s: ObxPhoneDB.String);

		PROCEDURE CheckMinus (VAR s: ObxPhoneDB.String; at: INTEGER);
		BEGIN
			IF s[at] # "-" THEN s[at] := 0X END	(* clip string *)
		END CheckMinus;

		PROCEDURE CheckDigits (VAR s: ARRAY OF CHAR; from, to: INTEGER);
		BEGIN
			WHILE from <= to DO
				IF (s[from] < "0") OR (s[from] > "9") THEN
					s[from] := 0X; from := to	(* clip string and terminate loop *)
				END;
				INC(from)
			END
		END CheckDigits;

	BEGIN	(* обрезать строку до легального символа, если необходимо *)
		CheckDigits(s, 0, 2);
		CheckMinus(s, 3);
		CheckDigits(s, 4, 6);
		CheckMinus(s, 7);
		CheckDigits(s, 8, 11)
	END Correct;

	PROCEDURE NumberNotifier (op, from, to: INTEGER);
	BEGIN
		Correct(phone.number)
		(* Dialog.Update не вызывается, потому что он может быть вызван после с помощью уведомления *)
	END NumberNotifier;

Листинг 4-39. Ранняя проверка ввода.
  
    Следует отметить, что активное выделение, то есть элемент управления не может предотвратить перевод фокуса ввода на другое выделение. Выделение может только просто изменять собственное отображение, содержание или состояние. Для этой цели выделение получает Contorllers.MarkMsg при изменении фокуса ввода.


    4.9 Прямой доступ к элементам управления

    В большинстве случаев механизм охранников и уведомлений предоставляет достаточный контроль над поведением элементов управления в стиле "Посмотри и почувствуй" (Look&Feel). Тем не менее, иногда необходим прямой доступ к элементам управления на форме. Его описание и представлено в этом разделе. Например, предположим, что необходимо написать свою собственную команду выравнивания и добавиь ее в команды меню Layout. Для выполнения этого необходим доступ к форме представления в текущем редактируемом окне. Форма представления является контейнером для элементов управления, которыми хотим манипулировать. Функция FormControllers.Focus предоставляет ссылку на текущую форму с фокусом ввода в редакторе. Таким образом типичный код доступа выглядит следующим образом (Листинг 4-40): 

VAR c: FormControllers.Controller;
	BEGIN
		c := FormControllers.Focus();
		IF c # NIL THEN
			...

Листинг 4-40. Доступ к элементу управления формы

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

MODULE ObxControlShifter;

	IMPORT Ports, Views, FormModels, FormControllers;

	PROCEDURE Shift*;
		VAR c: FormControllers.Controller; sel: FormControllers.List;
	BEGIN
		c := FormControllers.Focus();
		IF (c # NIL) & c.HasSelection() THEN
			sel := c.GetSelection();	(* генерируется список со ссылками на выбранные отображения *)
			WHILE sel # NIL DO
				c.form.Move(sel.view, 10 * Ports.mm, 0);	(* сдвинуть вправо *)
				sel := sel.next
			END
		END
	END Shift;

END ObxControlShifter.

Листинг 4-41. Смещение всех выделенных элементов вправо.

    Этот код работает с выделенными элементами (вне зависимости от того являются ли они управляющими). Иногда требуется оперировать с элементами, которые не выделены. В таком случае необходим считыватель форм (FormModels.Reader) для выполнения последовательных действий над всеми элементами формы с текущим фокусом ввода. Следующий пример кода показывает как команды оперируют со всеми элементами из формы (листинг 4-42):

VAR c: FormControllers.Controller; rd: FormModels.Reader;
	BEGIN
		c := FormControllers.Focus();
		IF c # NIL THEN
			rd := c.form.NewReader(NIL);
			rd.ReadView(v);	(* считать первое отображение *)
			WHILE v # NIL DO
				...
				rd.ReadView(v)	(* считать следующее отображение *)
			END;
			...

Листинг 4-42. Последовательный перебор всех элементов на форме

    Элементы управления - это специальные элементы (типа Contorls.Control). Элементы управления могут быть связаны с глобальными переменными. Следующий пример показывает как получить в список все заголовки элементов управления на форме (листинг 4-43):

MODULE ObxLabelLister;

	IMPORT Views, Controls, FormModels, FormControllers, StdLog;

	PROCEDURE List*;
		VAR c: FormControllers.Controller; rd: FormModels.Reader; v: Views.View;
	BEGIN
		c := FormControllers.Focus();
		IF c # NIL THEN
			rd := c.form.NewReader(NIL);
			rd.ReadView(v);	(* считать первое отображение *)
			WHILE v # NIL DO
				IF v IS Controls.Control THEN
					StdLog.String(v(Controls.Control).label); StdLog.Ln
				END;
				rd.ReadView(v)	(* считать следующее отображение *)
			END
		END
	END List;

END ObxLabelLister.

Листинг 4-43. Создание списка заголовков всех элементов управления формы

    Подобные операции могут быть проведены с любыми другими элементами на форме, не только с управляющими элементами. Например, допустим, что есть форма с кнопкой "Clear", которая приводит к очистке точечного просмотра. Кнопка - стандартный элемент управления, точечный просмотр - специальный тип. Команда, сопоставленная кнопке сперва должна найти точечный просмотр в той же форме, что и кнопка. Ниже приведен пример кода, демонстрирующего реализацию этой команды (листинг 4-44):

VAR c: FormControllers.Controller; rd: FormModels.Reader; v: Views.View;
	BEGIN
		c := FormControllers.Focus();
		IF c # NIL THEN
			rd := c.form.NewReader(NIL);
			rd.ReadView(v);	(* считать первое отображение *)
			WHILE v # NIL DO
				IF v IS MyPlotterView THEN
					(* очичстить v(MyPlotterView) *)
				END;
				rd.ReadView(v)	(* считать следующее отображение *)
			END
		END
Листинг 4-44. Поиск обычного просмотра на форме

    Имеется одна потенциальная проблема в приведенном выше коде. Допустим, что имеется диалоговое окно, содержащее кнопку с командой ObxLabelLister.List. Под диалоговым окном имеется окно с фокусом ввода. Теперь, если щелкнуть на кнопке диалогового окна, какие заголовки будут собраны в список? Заголовки собственно с диалогового окна или заголовки с окна с фокусом ввода, расположенного под диалоговым? Другими словами: FormControllers.Focus обрабатывает окно, содержащее кнопку по которой только что щелкнули или форму, имеющую элемент управления с фокусом ввода? В зависимости от того, что делает команда по кнопке, оба варианта могут иметь место. Для команды редактирования формы подобных ObxContorlShifter.Shift будет возвращены данные с формы с фокусом ввода. Это самое верхнее документное окно, только перекрытое диалоговым окном. Наоборот: если требуется найти точечный просмотр, возвращена будет форма диалогового окна. В данном случае ищутся прямые "ближайшие соседи" кнопки.
    Решение для поиска точечного просмотра будет начало поиска с контекста только что нажатой кнопки. Это можно сделать как указано в нижеследующем примере (листинг 4-45):

VAR button: Controls.Control; m: Models.Model; rd: FormModels.Reader; v: Views.View;
	BEGIN
		button := Controls.par;	(* во время клика на кнопку, эта переменная содержит ссылку на кнопку *)
		m := button.context.ThisModel();	(* получить модель контейнера отображения, которое содержит кнопку*)
		IF m IS FormModels.Model THEN	(* контейнер - это форма *)
			rd := m(FormModels.Model).NewReader(NIL);
			rd.ReadView(v);	(* считать первое отображение *)
			WHILE v # NIL DO
				IF v IS MyPlotterView THEN
					(* очистить v(MyPlotterView) *)
				END;
				rd.ReadView(v)	(* считать второе отображение *)
			END
		END

Листинг 4-45. Поиск обычного просмотра в том же окне, что и кнопка

    В этом коде предполагается, что команда будет выполнятся с кнопки. Она не будет работать из меню. В порядке лучшего разделения пользовательского интерфейса и программной логики этого предположения следует избегать. В BlackBox данная проблема решена предоставлением программисту исключительного контроля над поведением FormControllers.Focus. Хитрость состоит в том, что BlackBox поддерживает два вида окна диалоговых форм: инструментальное окно (tool windows) и вспомогательное окно (auxilary windows) в дополнение к обычному окну документа. Если кнопка расположена на инструментальном окне, FormControllers.Focus обрабатывает форму, являющуюся самым верхним окном документа или возвращает NIL, если такого окна документа не имеется или если фокус ввода самого верхнего окна документа не находится на форме. Если кнопка расположена на вспомогательном окне, FormContorllers.Focus обрабатывает форму диалогового окна, на которой эта кнопка расположена. В следующих абзацах приведено дальнейшее разъяснение различий между двумя видами окон.
    Диалоговое окно в виде инструментального окна не является самостоятельным, оно обеспечивает параметры и элементы управления для операций в нижележащем документе. Типичный пример: диалог "Find / Replace", который оперирует с содержимым нижележащего документа. Кнопка диалогового окна запускает команду и эта команда выполняется в представлении, с которым она оперирует. В примере "Find / Replace" - это окно с текстом, имеющим фокус ввода. Под Windows инструментальное окно всегда является самым верхним окном, то есть никогда не перекрывается окнами документов или вспомогательными окнами, только другими инструментальными окнами. В отличие от других окон, инструментальные окна не могут менять размеры или минимизироваться, но могут перемещаться за пределы окна приложения. В Mac OS инструментальные окна частично похожи на обычные, за исключением отсутствия полос прокрутки и возможности изменения размеров. 
    С другой стороны, вспомогательные окна самодостаточны. Они содержат и оперируют со своими собственными данными. Как минимум, они знают как искать данные для своих операций (например в базе данных). Диалог "Phone Database" - типичное вспомогательное окно, как и большинство форм ввода данных. Над вспомогательными окнами можно выполнять все операции как и над обычными окнами, они так же могут перекрываться другими обычными или вспомогательными окнами.
    Оба вида диалоговых окон хранятся как документы в определенном RSRC каталоге. Но для их открытия в модуле StdCmds определено две разных команды: OpenToolDialog и OpenAuxDialog. Каждая из них принимает переносимое имя файла ресурса как первый параметр и заголовок диалогового окна как второй параметр. Например, могут быть использованы следующие команды меню:

"Find / Replace..."	""	"StdCmds.OpenToolDialog('Text/Rsrc/Cmds', 'Find / Replace')"	""

	"Phone Database..."	""	"StdCmds.OpenAuxDialog('Obx/Rsrc/PhoneUI', 'Phone Database')"	""
    Функция FormControllers.Focus возвращает разный результат, в зависимости от того является ли форма инструментальным или вспомогательным окном. 
    Дальнейшие примеры операция с формами можно найти в модуле FormCmds. Он доступен в исходных кодах и основан на подсистеме Form.


    4.10 Резюме

    В этой главе обсуждались важные аспекты реализации графического пользовательского интерфейса и его воплощении в виде кодов в BlackBox Component Builder. Имеется три части приложения: логика приложения, логика интерфейса и ресурсы, то есть документы. Четкое разделение этих частей делает программы более понятными, поддерживаемыми и расширяемыми.
    В качестве примеров применения системы форм и элементов управления смотри встроенные примеры ObxAddress0, ObxAddress1, ObxAddress2, ObxOrders, ObxControld, ObxDialog и ObxUnitConv. Для более опытных программистов могут быть интересны исходные коды всей подсистемы Form.
    За дополнительной информацией об использовании среды разработки BlackBox обращайтесь к документации следующих модулей:
    DevCompiler, DevDebug, DevBrowser, DevInspector, DevReferences, DevMarkers, DevCmds, StdCmds, StdMenuTool, StdLog, FormCmds, TextCmds.

    Для того, чтобы узнать больше о модуле, выделите его имя и выполните команду Info->Documentation. В порядке возрастающего описания были описаны только наиболее важные команды. Тем не менее, имеются другие полезные команды в меню Info, Dev, Controls, Tools и Layout. Например, несколько "мастеров" для создания основы нового кода. Обратитесь к файлам System/Docu/User-Man, Text/Docu/User-Man, Form/Docu/User-Man и Dev/Docu/User-Man за исчерпывающим руководством пользователя по структуре, редактору (подсистема Text), визуальному проектированию (подсистема Form) и средствам разработки (подсистема Dev).