{
reference_to<Lines_window>(pw).next();
}
Функция
next()
определяет действие, которое действительно выполняется после щелчка на кнопке
Next point: она считывает пару координат, обновляет объект
Open_polyline
и позицию считывания, а также перерисовывает окно.
void Lines_window::next()
{
int x = next_x.get_int();
int y = next_y.get_int();
lines.add(Point(x,y));
// обновляем текущую позицию считывания:
ostringstream ss;
ss << '(' << x << ',' << y << ')';
xy_out.put(ss.str());
redraw();
}
Все это совершенно очевидно. Функция
get_int()
позволяет получить целочисленные координаты из объектов класса
In_box
; поток
ostringstream
форматирует строки для вывода в объект класса
Out_box
; функция-член
str()
позволяет вставить строку в поток
ostringstream
. Финальная функция,
redraw()
, необходима для представления результатов пользователю; старое изображение остается на экране, пока не будет вызвана функция
redraw()
из класса
Window
.
А что нового в этой программе? Посмотрим на ее функцию
main()
.
#include "GUI.h"
int main()
try {
Lines_window win(Point(100,100),600,400,"lines");
return gui_main();
}
catch(exception& e) {
cerr << "Исключение: " << e.what() << '\n';
return 1;
}
catch (...) {
cerr << "Какое-то исключение\n";
return 2;
}
Так ведь здесь, по существу, ничего нет! Тело функции
main()
содержит лишь определение нашего окна
win
и вызов функции
gui_main()
. Ни других функций, ни операторов
if
или
switch
, ни цикла — ничего из того, чтобы изучали в главах 6–7, — только определение переменной и вызов функции
gui_main()
, которая сама вызывает функцию
run()
из библиотеки FLTK. Изучая программу далее, увидим, что функция
run()
— это просто бесконечный цикл.
while(wait());
За исключением некоторых деталей реализации, описание которых вынесено в приложение Д, мы просмотрели весь код, запускающий программу рисования линий. Мы увидели всю логику этой программы. Что же произошло?
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)
. Очевидно, что функция, активизированная обратным вызовом, может делать все, что угодно: открывать файлы, связываться с сетью веб и т.д. Однако пока мы рассмотрим простой случай, когда данные хранятся в окне.