Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Программирование с использованием gtkmm 3. Сигналы

Оригинал: Programming with gtkmm 3
Авторы: Murray Cumming, Bernhard Rieder, Jonathon Jongsma, Ole Laursen, Marko Anastasov, Daniel Elstner, Chris Vine, David King, Pedro Ferreira, Kjell Ahlstedt
Дата публикации: 15 Октября 2013 г.
Перевод: А.Панин
Дата перевода: 25 Апреля 2014 г.

Приложение B. Сигналы

B.1. Установка обработчиков сигналов

Классы виджетов gtkmm имеют такие методы для доступа к сигналам, как метод Gtk::Button::signal_clicked(), который позволяет вам установить собственный обработчик сигналов. Благодаря гибкости используемой gtkmm библиотеки для работы с функциями обратных вызовов libsigc++, обработчик сигналов может быть представлен практически любым типом функции, но вы, скорее всего, захотите использовать метод класса. При этом использующие тулкит GTK+ совместно с языком программирования C разработчики обычно применяют для этих целей именованные функции обратного вызова.

Ниже приведен пример установки обработчика сигналов для определенного типа сигналов:

В ходе рассмотрения данного (малофункционального) кода обнаруживаются нюансы, над которыми следует подумать. Во-первых, давайте идентифицируем участвующие в обработке сигналов стороны:
  • Обработчик сигналов, представленный функцией on_button_clicked().
  • Мы связываем этот обработчик сигналов с объектом button на основе класса виджета кнопки Gtk::Button.
  • В момент, когда виджет кнопки генерирует сигнал нажатия "clicked", будет вызвана функция on_button_clicked().
Теперь давайте еще раз рассмотрим код установки обработчика сигнала:
    ...
    button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
    ...

Заметьте, что мы не передавали указатель на функцию on_button_clicked() непосредственно методу установки сигнала connect(). Вместо этого мы вызываем метод sigc::ptr_fun() и передаем результат в качестве параметра метода connect().

Метод sigc::ptr_fun() генерирует объект слота типа sigc::slot. Объект слота является примитивом, который выглядит и ведет себя как функция, но на самом деле является объектом. Эти объекты слотов известны также как объекты функций или функторы (functors). Метод sigc::ptr_fun() генерирует слот для обособленной функции или статического метода. Метод sigc::mem_fun() генерирует слот для метода определенного экземпляра класса.

Ниже приведен более подробный пример использования слотов:

Первый вызов метода connect() аналогичен тому, который мы видели ранее; здесь нет ничего нового.

Следующий вызов представляет больший интерес. При вызове метода sigc::mem_fun() используются два аргумента. Первый аргумент some_object представляет объект, на который будет указывать наш слот. Второй аргумент является указателем на один из его методов. Данная версия метода sigc::mem_fun() создает слот, который при "вызове" будет вызывать указанный метод заданного объекта, в данном случае some_object.on_button_clicked().

Другой заслуживающий упоминания аспект данного примера заключается в двукратном вызове метода connect() одного и того же объекта сигнала. В этом нет ничего плохого - при нажатии кнопки будут вызваны оба установленных обработчика сигналов.

Мы уже говорили о том, что при обработке сигнала нажатия кнопки "clicked" ожидается вызов метода без аргументов. Все сигналы предъявляют аналогичные требования к методам - вы не сможете связать вызов функции с двумя аргументами с сигналом, не предполагающим использование аргументов (конечно же, в том случае, если не используете такой адаптер, как sigc::bind()). Таким образом, важно знать о том, какой тип обработчика сигналов должен быть установлен для определенного сигнала.

B.2. Разработка обработчиков сигналов

Для выяснения того, какой тип обработчика сигналов следует использовать для конкретного сигнала, вы можете обратиться к справочной документации или к заголовочному файлу. Ниже приведен пример объявления сигнала, которое вы можете встретить в заголовочных файлах из состава gtkmm:
Glib::SignalProxy1<bool, Gtk::DirectionType> signal_focus()

Помимо названия сигнала (focus) важны две вещи: число, следующее после слова SignalProxy в начале объявления (в данном случае 1) и список типов аргументов (bool и Gtk::DirectionType). Число указывает количество аргументов, которые должны приниматься обработчиком сигналов; первый тип bool относится к возвращаемому обработчиком сигналов значению; следующий тип Gtk::DirectionType является типом первого и единственного аргумента данного обработчика сигналов. Обратившись к справочной документации, вы можете также выяснить и имена аргументов.

По такому же принципу объявляются сигналы с большим количеством аргументов. Ниже приведен пример объявления сигнала с тремя аргументами (взятый из заголовочного файла <gtkmm/textbuffer.h>):
Glib::SignalProxy3<void, const TextBuffer::iterator&, const Glib::ustrin&, int> signal_insert();
Для этого объявления был использован все тот же формат. Число 3 в конце имени типа указывает на то, что наш обработчик сигналов должен принимать три аргумента. Первым типом из списка типов является void, поэтому наш обработчик сигналов также должен использовать его в качестве типа возвращаемого значения. Следующие три типа являются типами аргументов, записанными в порядке их следования. Исходя из вышесказанного, прототип нашего обработчика сигналов должен выглядеть аналогичным образом:
void on_insert(const TextBuffer::iterator& pos, const Glib::ustring& text, int bytes)

B.4. Отсоединение обработчиков сигналов

Давайте еще раз рассмотрим метод connect объекта сигнала:
sigc::signal<void,int>::iterator signal<void,int>::connect( const sigc::slot<void,int>& );

Обратите внимание на тип возвращаемого значения sigc::signal<void,void>::iterator. Этот тип может быть неявно преобразован к типу соединения sigc::connection, а объекты этого типа, в свою очередь, могут использоваться для управления соединением. Сохраняя объект соединения, вы можете отсоединять ассоциированный с ним обработчик сигналов, используя метод sigc::connection::disconnect().

B.5. Перекрытие стандартных обработчиков сигналов

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

Вместо последовательной установки обработчиков для сигналов вы можете просто создать новый унаследованный от класса виджета, скажем, от класса кнопки Gtk::Вutton класс, после чего перекрыть стандартный обработчик сигналов, такой, как Gtk::Button::on_clicked(). Такой подход может оказаться более простым, чем установка обработчиков для всех сигналов.

Создание подклассов не всегда является лучшим способом решения поставленных задач. Такая стратегия полезна только тогда, когда вы хотите, чтобы виджет сам обрабатывал свои сигналы. В том же случае, если вы хотите, чтобы сигнал обрабатывался каким-либо другим классом, вам придется устанавливать отдельный обработчик для этого сигнала. Это ограничение становится еще более актуальным в том случае, когда вы хотите обрабатывать сигналы от нескольких объектов с помощью одного обработчика сигналов.

Классы gtkmm спроектированы с учетом возможности перекрытия методов; они содержат виртуальные методы, которые предназначены специально для перекрытия.

Давайте рассмотрим пример перекрытия метода:

Здесь мы объявляем новый класс под названием OverriddenButton, который наследуется от класса Gtk::Button. Единственным изменением является метод on_clicked(), который вызывается всегда, когда виджет кнопки на основе класса Gtk::Button генерирует сигнал нажатия "clicked". Этот метод записывает в стандартный поток вывода (stdout) строку "Hello World", после чего вызывает оригинальный перекрытый метод для того, чтобы виджет кнопки на основе класса Gtk::Button выполнил все необходимые операции таким же образом, как и в том случае, когда метод не перекрывается.

Вам не всегда придется вызывать метод родительского класса; вполне реальны случаи, когда вы предпочтете не вызывать его вообще. Обратите внимание на то, что мы вызвали метод родительского класса после записи строки "Hello World" в стандартный поток вывода, но мы могли вызвать его и до этой записи. В данном простом примере порядок вызовов не очень важен, но в некоторых случаях этот порядок будет иметь значение. При использовании обработчиков сигналов достаточно сложно изменить механизм обработки сигналов подобным образом, причем в данном случае вы можете выполнить такие преобразования механизма обработки сигналов, которые просто невозможно осуществить при работе с установленным обработчиком сигналов: вы можете вызвать метод родительского класса из середины вашего кода обработки сигналов.

B.6. Добавление дополнительных аргументов

В том случае, если вы используете обработчик сигналов для захвата одного и того же сигнала от нескольких виджетов, у вас может возникнуть желание передать в обработчик сигналов некоторую дополнительную информацию. Например, вам может потребоваться информация о том, какая кнопка была нажата. Вы можете добавить такую информацию, воспользовавшись методом sigc::bind(). Ниже приведен небольшой фрагмент кода из примера под названием helloworld2:
m_button1.signal_clicked().connect( sigc::bind<Glib::ustring>( sigc::mem_fun(*this, &HelloWorld::on_button_clicked), "button 1") );
В данном примере показано то, что мы хотим передать дополнительный аргумент типа Glib::ustring в обработчик сигнала, а также то, что в качестве значения этого аргумента должна использоваться строка "button 1". Конечно же, нам придется добавить дополнительный аргумент в объявление нашего обработчика сигналов:
virtual void on_button_clicked(Glib::ustring data);

Разумеется, обычный обработчик сигнала нажатия на кнопку "clicked" не принимает аргументов.

Метод sigc::bind() используется не часто, но иногда может оказаться очень полезным для вас. Если вы имеете опыт разработки приложений с использованием GTK+, вы, скорее всего, отметили схожесть этой методики с использованием дополнительного аргумента gpointer data, предоставляемого всеми функциями обратного вызова из состава GTK+. Данные аргументы используются повсеместно в GTK+ для передачи информации, которая должна храниться в переменных, являющихся членами унаследованного класса виджета, но такой подход не используется из-за того, что наследование классов виджетов при использовании языка программирования C является достаточно сложной процедурой. При этом потребность в описанных приемах при работе с gtkmm значительно снижается.

B.7. Сигналы, соответствующие событиям оконной системы X

Класс виджета Gtk::Widget содержит реализации нескольких специальных сигналов, которые соответствуют сигналам лежащего в основе виджета окна оконной системы X. Эти сигналы имеют суффикс _event; к примеру, Gtk::Widget::signal_button_press_event().

Иногда, в случаях, когда нет возможности использовать обычные сигналы, обработка событий оконной системы X может оказаться достаточно полезной. Класс кнопки Gtk::Button, например, не передает координаты курсора мыши обработчику своего сигнала нажатия "clicked", но вы можете обработать сигнал "button_press_event" в том случае, если вам требуется подобная информация. События оконной системы X также обычно используются для обработки нажатий клавиш клавиатуры.

Принцип работы описанных сигналов значительно отличается от принципа работы обычных сигналов. Возвращаемое из обработчика сигналов значение указывает на то, было ли событие "обработано" в полном объеме. В том случае, если возвращается логическое значение false, gtkmm передаст событие следующему обработчику сигналов. Если же обработчик сигналов возвращает логическое значение true, необходимость в вызове других обработчиков сигналов отпадет.

Обработка событий оконной системы X никоим образом не влияет на обработку других сигналов виджета. В том случае, если вы обрабатываете сигнал "button_press_event" экземпляра класса Gtk::Button, вы все еще можете обрабатывать его сигнал "clicked". Эти сигналы генерируются одновременно (практически).

Учтите, что не все виджеты по умолчанию обрабатывают все события оконной системы X. Для приема дополнительных событий оконной системы X вы можете использовать метод Gtk::Widget::set_events() перед показом виджета или метод Gtk::Widget::add_events() после его показа. Однако, некоторые виджеты должны для начала помещаться в контейнерный виджет для приема событий (EventBox). Обратитесь к главе "Виджеты без окон оконной системы X".

Ниже приведен простой пример:
bool on_button_press(GdkEventButton* event);
Gtk::Button button("Название");
button.signal_button_press_event().connect( sigc::ptr_fun(&on_button_press) );

При перемещении указателя мыши над виджетом кнопки и нажатии кнопки мыши будет вызван метод on_button_press().

Структура GdkEventButton содержит такие параметры события, как координаты указателя мыши в момент нажатия кнопки мыши. Существует несколько различных типов структур GdkEvent, которые используются при наступлении различных событий.

B.6.1. Последовательность обработчиков сигналов

По умолчанию ваши обработчики сигналов будут вызываться после любых предварительно установленных обработчиков сигналов. Однако, такой принцип работы сигналов может привести к возникновению проблем в случае работы с сигналами событий оконной системы X. К примеру, существующие обработчики сигналов или стандартный обработчик сигналов может возвращать логическое значение true для предотвращения вызова других обработчиков сигналов. Для указания на то, что ваш обработчик сигналов должен быть вызван перед другими обработчиками сигналов, т.е., должен быть вызван в любом случае, вы можете передать логическое значение false в качестве значения необязательного параметра after. Например, это может быть сделано следующим образом:
button.signal_button_press_event().connect( sigc::ptr_fun(&on_mywindow_button_press), false );

Событие доставляется в первую очередь виджету, благодаря которому оно наступило. В том случае, если все обработчики сигналов этого виджета возвращают логическое значение false (указывающее на то, что событие не было обработано), сигнал будет распространен на родительский виджет и сгенерирован от его имени. В том случае, если ни один из виджетов не обрабатывает сигнал, его распространение продолжится до достижения виджета верхнего уровня.

B.7. Исключения в обработчиках сигналов

При завершении программы из-за генерации необработанного исключения языка программирования C++ иногда полезно использовать отладчик для уточнения расположения строки кода, благодаря которой было сгенерировано это исключение. Но данная операция значительно усложняется в том случае, если исключение генерируется в обработчике сигналов.

В данном разделе будет показано стандартное поведение Linux-системы при использовании отладчика gdb.

Для начала давайте рассмотрим простой пример, в котором исключение генерируется в обычной функции (не являющейся обработчиком сигналов).

Ниже приведен фрагмент сессии отладки gdb. В этом фрагменте показаны только наиболее существенные данные отладки.

Вы можете видеть, что исключение было сгенерировано в строке 6 файла исходного кода without_signal.cc (throw "Something";).

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

И снова приведем фрагмент сессии отладки gdb.

Исключение захватывается в glibmm и программа завершается на вызове функции g_error(). Другие исключения могут привести к отличному поведению программы, но в любом случае исключение из обработчика сигналов будет захватываться в glibmm или gtkmm, причем отладчик gdb не позволит обнаружить расположение строки, с помощью которой оно было сгенерировано.

Для установления расположения строки, благодаря которой исключение было сгенерировано, вы можете использовать команду catch throw отладчика gdb.

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

Эти команды позволяют выводить данные обратной трассировки для каждой инициируемой с помощью оператора throw операции генерации исключения и продолжать отладку. Данные обратной трассировки для последней (или, возможно, последней и единственной) инициированной с помощью оператора throw операции генерации исключения перед завершением программы и являются интересующими вас данными.


Следующий раздел : Приложение C. Создание собственных сигналов.