За исключением некоторых деталей реализации, описание которых вынесено в приложение Д, мы просмотрели весь код, запускающий программу рисования линий. Мы увидели всю логику этой программы. Что же произошло?
16.6. Инверсия управления
А произошло вот что: мы передали поток управления от самой программы элементам управления окном: теперь программа возобновляет свою работу каждый раз, когда активизируется какой-нибудь из этих элементов. Например, щелкните на кнопке, и программа начнет работать. После возврата обратного вызова программа “отключается”, ожидая, пока пользователь сделает что-нибудь еще. По существу, функция
wait
просит систему
опросить элементы управления окном и активизировать соответствующие обратные вызовы. Теоретически функция
wait
могла бы сообщать, какой элемент управления требует внимания, и предоставить самому программисту вызывать соответствующую функцию. Однако в библиотеке FLTK и в большинстве других систем графического пользовательского интерфейса функция
wait
активизирует соответствующий обратный вызов, освобождая программиста от необходимости писать код для выбора этой функции.
Обычная программа организована следующим образом:
Программа графического пользовательского интерфейса организована иначе.
Одна из сложностей такой инверсии управления проявляется в том, что порядок выполнения программы теперь полностью определяется действиями пользователя. Это усложняет как организацию, так и отладку программы. Трудно себе представить, что сделает пользователь, но еще труднее представить себе возможные результаты случайной последовательности обратных вызовов. Это превращает систематическое тестирование в ночной кошмар (подробнее об этом — в главе 26). Методы решения этой проблемы выходят за рамки рассмотрения нашей книги, но мы просим читателей быть особенно осторожными, работая с кодом, управляемым пользователями с помощью обратных вызовов. Кроме очевидных проблем с потоком управления, существуют проблемы, связанные с видимостью и отслеживанием связей между элементами управления окном и данными. Для того чтобы минимизировать трудности, очень важно не усложнять часть программы, отвечающую за графический пользовательский интерфейс, и создавать ее постепенно, тестируя каждую часть. Работая с программой графического пользовательского интерфейса, почти всегда необходимо рисовать небольшие диаграммы объектов и взаимодействия между ними.
Как взаимодействуют части программы, активизированные разными обратными вызовами? Проще всего, чтобы функции оперировали данными, хранящимися в окне, как показано в примере из раздела 16.5. В нем функция
next
класса
Lines_window
активизировалась щелчком на кнопке Next point, считывала данные из объектов класса
In_box
(
next_x
и
next_y
), а затем обновляла переменную-член
lines
и объект класса
Out_box (xy_out)
. Очевидно, что функция, активизированная обратным вызовом, может делать все, что угодно: открывать файлы, связываться с сетью веб и т.д. Однако пока мы рассмотрим простой случай, когда данные хранятся в окне.
16.7. Добавление меню
Исследуем вопросы управления и взаимодействия, поднятые в разделе “Инверсия управления”, на примере создания меню для программы, рисующей линии. Для начала опишем меню, позволяющее пользователю выбирать цвет всех линий в переменной
Создание всех таких практически идентичных функций обратного вызова и функций “действия” — довольно утомительное занятие. Однако оно не вызывает никаких затруднений, а описание более простых средств выходит за рамки нашей книги. После щелчка на кнопке меню цвет линий изменяется на требуемый.
Кнопки динамически связываются с меню (с помощью функции
attach
) и при необходимости могут быть удалены и/или изменены. Функция
Menu::attach
настраивает размер и место кнопки, а также связывает его с окном. Это все. Теперь мы увидим на экране следующее.
Экспериментируя с этой программой, мы решили, что нам необходимо выпадающее меню; т.е. мы не хотим фиксировать конкретное место на экране, в котором оно будет появляться. Итак, мы добавили кнопку Color menu. Когда пользователь щелкнет на ней, всплывет меню цвета, а после того как выбора меню снова исчезнет, и на экране отобразится кнопка.
Посмотрим сначала на окно, в которое добавлено несколько линий.
Мы видим новую кнопку Color menu и несколько черных линий. Щелкнем на кнопке Color menu, и на экране откроется меню.
Обратите внимание на то, что кнопка Color menu исчезла. Она не нужна, пока открыто меню. Щелкнем на кнопке blue и получим следующий результат.
Теперь линии стали синими, а кнопка Color menu вновь появилась на экране.
Для того чтобы достичь такого эффекта, мы добавили кнопку Color menu и модифицировали функцию “pressed”, настроив видимость меню и кнопки. Вот как выглядит класс
Lines_window
после всех этих модификаций.
struct Lines_window:Window {
Lines_window(Point xy, int w, int h, const string& title );