идентифицированный драйвером мыши, становится причиной вызова функции cb_next(). Мы передаем адрес функции cb_next() и адрес нашего объекта класса Simple_window вниз через уровни программного обеспечения; затем какой-то код “где-то внизу” вызывает функцию cb_next(), когда выполняется щелчок на кнопке
Next.
Система графического пользовательского интерфейса (и операционная система) может использоваться программами, написанными на разных языках, но они не могут навязывать всем пользователям стиль языка С++. В частности, ей ничего не известно о наших классах Simple_window и Button. Фактически она вообще ничего не знает о классах и их членах. Тип, требуемый для обратного вызова функции, выбирается так, чтобы его можно было использовать на самых низких уровнях программирования, включая язык C и ассемблер. Функция обратного вызова не возвращает значений и принимает в качестве аргументов два адреса. Мы можем объявить функцию-член так, чтобы она подчинялась этим требованиям.
static void cb_next(Address, Address); // обратный вызов для
// next_button
Здесь ключевое слово static гарантирует, что функция cb_next() может быть вызвана как обычная функция, т.е. не как функция-член, вызываемая через конкретный объект. Если бы функцию-член могла вызывать сама операционная система, было бы намного лучше. Однако интерфейс обратного вызова нужен для программ, написанных на многих языках, поэтому мы используем статическую функцию-член. Аргументы Address указывают на то, что функция cb_next() получает аргументы, имеющие адреса “где-то в памяти”. Ссылки, существующие в языке C++, во многих языках неизвестны, поэтому мы не можем их использовать. Компилятор не знает, какие типы имеют эти аргументы, расположенные “где-то”. Здесь мы снижаемся на уровень аппаратного обеспечения и не можем использовать обычные средства языка. Система вызовет функцию обратного вызова, первый аргумент которой должен представлять собой адрес некоторого элемента графического пользовательского интерфейса (объекта класса Widget), для которого был сделан обратный вызов. Мы не хотим использовать этот первый аргумент, поэтому его имя нам не нужно. Второй аргумент — это адрес окна, содержащего данный объект класса Widget; для функции cb_next() аргументом является объект класса Simple_window.
Эту информацию можно использовать следующим образом:
void Simple_window::cb_next(Address,Address pw)
// вызов Simple_window::next() для окна, расположенного по адресу pw
{
reference_to<Simple_window>(pw).next();
}
Вызов функции reference_to<Simple_window>(pw) сообщает компьютеру, что адрес, хранящийся в переменной pw, должен интерпретироваться как адрес объекта класса Simple_window; иначе говоря, мы можем использовать значение reference_to<Simple_window>(pw) как ссылку на объект класса Simple_window. В главах 17-18 мы еще вернемся к вопросам адресации памяти. Определение функции reference_to (кстати, совершенно тривиальное) мы покажем в разделе Д.1. А пока просто рады наконец получить ссылку на наш объект класса Simple_window и непосредственный доступ к нашим данным и функциям, которые собирались использовать. Теперь поскорее выходим из этого системно-зависимого кода, вызывая нашу функцию-член next().
Мы могли бы привести весь код, который следовало бы выполнить в функции cb_next(), но мы, как и большинство хороших программистов, разрабатывающих графические пользовательские интерфейсы, предпочитаем отделять запутанный низкоуровневый код от нашего превосходного пользовательского кода, поэтому решили обрабатывать обратный вызов с помощью двух функций.
• Функция cb_next() превращает системные соглашения об обратных вызовах в вызов обычной функции-члена next().
• Функция next() делает то, что мы хотели (ничего не зная о запутанном механизме обратного вызова).
Мы используем здесь две функции, руководствуясь общим принципом, гласящим: каждая функция должна выполнять отдельное логическое действие, т.е. функция cb_next() скрывает низкоуровневую системно-зависимую часть программы, а функция next() выполняет требуемое действие. В ситуациях, когда необходим обратный вызов (из системы) в одном из окон, мы всегда определяем пару таких функций; например, см. разделы 16.5–16.7. Перед тем как идти дальше, повторим сказанное.
• Мы определяем наш объект класса Simple_window.
• Конструктор класса Simple_window регистрирует свою кнопку next_button в системе графического пользовательского интерфейса.
• Когда пользователь щелкает на изображении объекта next_button на экране, графический пользовательский интерфейс вызывает функцию cb_next().
• Функция cb_next() преобразует низкоуровневую информацию системы в вызов нашей функции-члена next() для нашего окна.
• После щелчка на кнопке функция next() выполняет требуемое действие.
Это довольно сложный способ вызвать функцию. Однако помните, что мы работаем с основным механизмом, обеспечивающим взаимодействие мыши (или другого устройства) с программой. В частности, следует иметь в виду следующие обстоятельства.
• Как правило, на компьютере одновременно выполняется много программ.
• Программа создается намного позже операционной системы.
• Программа создается намного позже библиотеки графического пользовательского интерфейса.
• Программа может быть написана на языке, отличающемся от того, который используется в операционной системе.
• Описанный метод охватывает все виды взаимодействий (а не только щелчок на кнопке).
• Окно может иметь много кнопок, а программа может иметь много окон.
Однако, поняв, как вызывается функция next(), мы фактически поймем, как обрабатывается каждое действие в программе, имеющей графический пользовательский интерфейс.
16.3.2. Цикл ожидания
Итак, что должна делать функция next() класса Simple_window после каждого щелчка на кнопке в данном (простейшем) случае? В принципе мы хотели бы, чтобы эта операция останавливала выполнение нашей программы в некоторой точке, давая возможность увидеть, что было сделано к этому моменту. Кроме того, мы хотим, чтобы функция next() возобновляла работу нашей программы после паузы.
// создаем и/или манипулируем некоторыми объектами, изображаем
// их в окне
win.wait_for_button(); // работа программы возобновляется с этой
// точки
// создаем и/или манипулируем некоторыми объектами
На самом деле это просто. Сначала определим функцию wait_for_button().
void Simple_window::wait_for_button()
// модифицированный цикл событий:
// обрабатываем все события (по умолчанию),
// выходим из цикла, когда переменная button_pushed становится
// true
// это позволяет рисовать без изменения направления потока
// управления
{
while (!button_pushed) Fl::wait();
button_pushed = false;
Fl::redraw();
}
Как и большинство систем графического интерфейса, библиотека FLTK содержит функцию, приостанавливающую работу программы, пока не произойдет какое-то событие. Версия этой функции в библиотеке FLTK называется wait(). На самом деле функция wait() делает много полезных действий, чтобы наша программа могла