вторник, 3 июля 2018 г.

00.04.02 События и их обработка. Динамическая обработка событий.

Продолжение. Начало - здесь: Обработка событий с помощью таблицы событий.

Динамическая обработка событий

Замечание:
Когда не используется C++ RTTI
Очень широкие возможности.
Начнем с рассмотрения синтаксиса: сразу ясно, что больше не нужно использовать ни wxDECLARE_EVENT_TABLE(), ни wxBEGIN_EVENT_TABLE(), ни макросы связывания.
Вместо этого, в любом месте кода  - обычно в коде класса, определяющего сам обработчик (и уж точно не в глобальной области как для таблицы событий), вызываем метод Bind<>(), примерно как здесь:
MyFrame::MyFrame(...)
{
Bind(wxEVT_COMMAND_MENU_SELECTED, &MyFrame::OnExit, this, wxID_EXIT);
}
Заметим, что здесь должен быть использован указатель this.


Рассмотрим разницу в семантике:
  • Обработчики могут быть назначены в любой момент. К примеру, можно что-то сначала инициализировать, а разрешить выполнять обработку события лишь потом, в случае успешной инициализации. Сие позволит избежать в обработчике необходимости проверки - "а был ли объект правильно инициализирован?". В случае использования Bind<>(), обработчики просто не будут вызваны в случае неправильной инициализации.
  • Кроме того, в любой момент времени могут быть отключены с помощью Unbind<>() (и, если нужно, подключены снова позднее). Конечно, такое поведение можно эмулировать и с помощью классических статических (т.е., назначенных с помощью таблицы событий) обработчиков сообщений с помощью внутренних флагов, определяющих, разрешен ли в данный момент этот обработчик и завершения обработки в случае, если флаг сброшен, но использование динамически назначенных обработчиков требует меньше кодирования и более понятно.
  • Еще одна громадная, повышающая гибкость разница - возможность назначить событие:
    • Методу другого объекта.
    • Обычной функции типа статического метода или глобальной функции.
    • Простому функтору, вроде boost::function<> или, в C++11, std::function<> или ламбда - выражению.
    Все это невозможно делать с таблицей событий, так как событию невозможно назначить такие обработчики. Следовательно, событие должно быть обработано в том же самом объекте, который сгенерировал событие. А с помощью Bind<>() можно использовать все перечисленные выше обработчики. В простом примере рассмотрим вопрос "как получить события перемещения от мыши", происходящие, когда курсор мыши перемещается над одним из окно, являющихся дочерними по отношению к главному окну. Попытка сделать это наивным методом не работает.
    • Строка EVT_LEAVE_WINDOW(MyFrame::OnMouseLeave) в таблице событий окна не дает никакого эффекта от перемещения мыши (в том числе при входе и выходе курсора мыши в область окна), события не передаются родительскому окну выше (по крайней мере, не по умолчанию).
    • Размещение такой же строки в дочерней таблице событий вызовет крах в рантайме, так как метод MyFrame будет вызван не для того объекта,  – легко убедиться, что единственным объектом, который может быть здесь использован, является указатель на дочерний объект вроде wxWidgets и ничего другого. Но вызов метода окна применительно к указателю на дочернее окно вместо указателя на само окно вызовет крах.
    Однако, если написать:
    MyFrame::MyFrame(...)
    {
    m_child->Bind(wxEVT_LEAVE_WINDOW, &MyFrame::OnMouseLeave, this);
    }
    - то все будет работать так, как ожидается. Отметим, что мы можем получить объект, который генерирует событие, и этот объект - не тот же самый, что и само окно  – это делается с помощью метода 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,
// Делаем что-то полезное
},
}
Другим распространенным примером является использование обобщенного функтора  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<>() можно использовать даже методы, не имеющие правильной сигнатуры:
void MyHandler::OnExit( int exitCode, wxCommandEvent &, wxString goodByeMessage )
{
// Делаем что-то полезное
}
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<>()  требует немного больше кодирования, но дает гораздо большую гибкость по сравнению с использованием статичных таблиц событий, поэтому не стесняемся использовать его там, где требуется эта дополнительная мощность. С другой стороны, таблицы событий все еще отлично справляются со свое задачей там, где такая гибкость не требуется.

Продолжение - здесь: Как обрабатываются события.

Комментариев нет:

Отправить комментарий