Продолжение. Начало - здесь: Обработка событий с помощью таблицы событий.
Динамическая обработка событий
- Замечание:
- Когда не используется C++ RTTI
Очень широкие возможности.
Начнем с рассмотрения синтаксиса: сразу ясно, что больше не нужно использовать ни wxDECLARE_EVENT_TABLE(), ни wxBEGIN_EVENT_TABLE(), ни макросы связывания.
Вместо этого, в любом месте кода - обычно в коде класса, определяющего сам обработчик (и уж точно не в глобальной области как для таблицы событий), вызываем метод Bind<>(), примерно как здесь:
MyFrame::MyFrame(...)
{
Bind(wxEVT_COMMAND_MENU_SELECTED, &MyFrame::OnExit, this, wxID_EXIT);
}
Рассмотрим разницу в семантике:
- Обработчики могут быть назначены в любой момент. К примеру, можно что-то сначала инициализировать, а разрешить выполнять обработку события лишь потом, в случае успешной инициализации. Сие позволит избежать в обработчике необходимости проверки - "а был ли объект правильно инициализирован?". В случае использования Bind<>(), обработчики просто не будут вызваны в случае неправильной инициализации.
- Кроме того, в любой момент времени могут быть отключены с помощью Unbind<>() (и, если нужно, подключены снова позднее). Конечно, такое поведение можно эмулировать и с помощью классических статических (т.е., назначенных с помощью таблицы событий) обработчиков сообщений с помощью внутренних флагов, определяющих, разрешен ли в данный момент этот обработчик и завершения обработки в случае, если флаг сброшен, но использование динамически назначенных обработчиков требует меньше кодирования и более понятно.
- Еще одна громадная, повышающая гибкость разница - возможность назначить событие:
- Методу другого объекта.
- Обычной функции типа статического метода или глобальной функции.
- Простому функтору, вроде boost::function<> или, в C++11, std::function<> или ламбда - выражению.
Все это невозможно делать с таблицей событий, так как событию невозможно назначить такие обработчики. Следовательно, событие должно быть обработано в том же самом объекте, который сгенерировал событие. А с помощью Bind<>() можно использовать все перечисленные выше обработчики. В простом примере рассмотрим вопрос "как получить события перемещения от мыши", происходящие, когда курсор мыши перемещается над одним из окно, являющихся дочерними по отношению к главному окну. Попытка сделать это наивным методом не работает.- Строка
EVT_LEAVE_WINDOW(MyFrame::OnMouseLeave)
в таблице событий окна не дает никакого эффекта от перемещения мыши (в том числе при входе и выходе курсора мыши в область окна), события не передаются родительскому окну выше (по крайней мере, не по умолчанию). - Размещение такой же строки в дочерней таблице событий вызовет крах в рантайме, так как метод MyFrame будет вызван не для того объекта, – легко убедиться, что единственным объектом, который может быть здесь использован, является указатель на дочерний объект вроде wxWidgets и ничего другого. Но вызов метода окна применительно к указателю на дочернее окно вместо указателя на само окно вызовет крах.
Однако, если написать:- то все будет работать так, как ожидается. Отметим, что мы можем получить объект, который генерирует событие, и этот объект - не тот же самый, что и само окно – это делается с помощью метода wxEvent::GetEventObject() применяемого к аргументу, передаваемому в обработчик события. - В действительности такой фокус является следствием предыдущего: из-за увеличения гибкости от использования Bind(), система обработки событий стала также более безопасна, так как стало невозможно использовать метод другого класса. Вместо краха в рантайме мы получаем ошибку компиляции Bind().
Теперь рассмотрим два случая использования перегруженной Bind(): для методов объекта и для обычных функторов (вызываемых объектов, в том числе и простых функций):
Дополнительно к случаю использования метода объекта, который сам и генерирует событие, в качестве обработчика события можно использовать метод совершенно другого объекта:
void MyFrameHandler::OnFrameExit( wxCommandEvent & )
{
// Делаем что-то полезное.
}
MyFrameHandler myFrameHandler; // Класс - обработчик события
MyFrame::MyFrame() // Конструктор класса окна MyFrame
{
Bind( wxEVT_COMMAND_MENU_SELECTED, &MyFrameHandler::OnFrameExit,
&myFrameHandler, wxID_EXIT );
}
Заметим, что
MyFrameHandler
не обязан быть наследником от wxEvtHandler. Но следует иметь в виду, что время жизни myFrameHandler
должно быть бо'льшим, чем у объекта MyFrame, иначе как минимум придется откреплять
его (обработчик) от события перед уничтожением.
Чтобы использовать обычную функцию или статический метод в качестве обработчика, нужно написать что-то вроде этого:
void HandleExit( wxCommandEvent & )
{
// Делаем что-то полезное.
}
MyFrame::MyFrame() // Конструктор класса окна MyFrame
{
Bind( wxEVT_COMMAND_MENU_SELECTED, &HandleExit, wxID_EXIT );
}
Наконец, обработчиком события может быть назначен обычный функтор:
struct MyFunctor
{
void operator()( wxCommandEvent & )
{
// Делаем что-то полезное.
}
};
MyFunctor myFunctor;
MyFrame::MyFrame()
{
Bind( wxEVT_COMMAND_MENU_SELECTED, myFunctor, wxID_EXIT );
}
В C++11 можно просто использовать лямбда-выражения, без определения отдельного класса-функтора:
MyFrame::MyFrame()
{
Bind(wxEVT_COMMAND_MENU_SELECTED,
[](wxCommandEvent&) {
// Делаем что-то полезное
},
}
Другим распространенным примером является использование обобщенного функтора boost::function<>, или, в C++11, std::function<>:
#if __cplusplus >= 201103L || wxCHECK_VISUALC_VERSION(10)
using namespace std;
using namespace std::placeholders;
#else // Pre C++11 compiler
using namespace boost;
#endif
void MyHandler::OnExit( wxCommandEvent & )
{
// Делаем что-то полезное
}
MyHandler myHandler;
MyFrame::MyFrame()
{
function< void ( wxCommandEvent & ) > exitHandler( bind( &MyHandler::OnExit, &myHandler, _1 ));
Bind( wxEVT_COMMAND_MENU_SELECTED, exitHandler, wxID_EXIT );
}
С помощью
bind<>()
можно использовать даже методы, не имеющие правильной сигнатуры:
{
// Делаем что-то полезное
}
MyHandler myHandler;
MyFrame::MyFrame()
{
function< void ( wxCommandEvent & ) > exitHandler(
bind( &MyHandler::OnExit, &myHandler, EXIT_FAILURE, _1, "Bye" ));
Bind( wxEVT_COMMAND_MENU_SELECTED, exitHandler, wxID_EXIT );
}
Таким образом, использование Bind<>() требует немного больше кодирования, но дает гораздо большую гибкость по сравнению с использованием статичных таблиц событий, поэтому не стесняемся использовать его там, где требуется эта дополнительная мощность. С другой стороны, таблицы событий все еще отлично справляются со свое задачей там, где такая гибкость не требуется.
Продолжение - здесь: Как обрабатываются события.
Комментариев нет:
Отправить комментарий