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
делает много полезных действий, чтобы наша программа могла правильно возобновить работу, когда произойдет ожидаемое событие. Например, при работе под управлением системы Microsoft Windows программа должна перерисовать окно, которое было перемещено или ранее перекрыто другим окном. Кроме того, объект класса
Window
должен самостоятельно реагировать на изменение размеров окна. Функция
Fl::wait
выполняет все эти задания так, как это предусмотрено по умолчанию. Каждый раз, когда функция
wait
обрабатывает какое-то событие, она возвращает управление, чтобы наша программа могла выполнить какие-то действия.