Определения:
SWL — оригинал by Kluev.
здесь. Идея специализации аргументов обработчиков для конкретных сообщений и построение отсортированной карты сообщений для бинарного поиска по ней.
SWL2 — вариант by WolfHound.
здесьАвтор: WolfHound
Дата: 27.04.03
. Много разных улучшений, более удобная карта и прочее.
SWL3 — вариант by lboss.
здесьАвтор: lboss
Дата: 08.05.03
. Отсутствие явной карты сообщений. Её роль выполняют хитрые шаблоны возвращаемых значений обработчиков. Этот вариант и будем разбирать дальше.
Существуют несколько проблем:
Необходимость ручного определения processMessage в каждом наследном классе. Не очень приятная альтернатива — запись базового класса в OnMessage<ThisClass,ID,BaseClass>. (OnMessage — это бывший MsgResult)
Нет возможности сделать обработчик, а точнее нотификатор или трейсер, который будет вызываться всегда, вне зависимости от его переопределения в наследном классе. Причём первыми должны получать уведомление базовые классы, а затем наследники.
Понадобилось для создания TopFrame со счётчиком экземпляров. Как только последнее окно TopFrame будет уничтожено, нужно вызвать PostQuitMessage. Трейсеры для WM_CREATE (++count(); ) и WM_DESTROY (if (0 == --count()) PostQuitMessage(0); ).
Отсутствие проверки существования настоящего обработчика на этапе компиляции. Т.е. можно определить OnMessage<This,WM_PAINT> drebeden_den_den(int,char), но ошибка вылезет только на этапе выполнения в отладочной версии, при обработки этого сообщения, если оно вообще придёт за время тестирования.
Вот что я наваял по этому поводу:
Создал маленькую обёртку MsgChain, которая переопределяет processMessage
template <class TThis, class TBase> struct MsgChain : TBase
{
typedef MsgChain<TThis,TBase> Base; // для использования в наследнике
virtual bool processMessage( MsgData & msg )
{
return Internal::MsgMap<TThis>::staticInstance().processMessage(*(TThis*)this, msg)
|| TBase::processMessage(msg);
}
};
Использование:
//Вместо
class MyWnd : public Wnd
//пишем
class MyWnd : public MsgChain<MyWnd,Wnd>
Создал TraceMessage похожий на OnMessage. Регистрирует обработчик в альтернативной карте сообщений. Реализация такая же как и у OnMessage, только используется другой экземпляр карты.
Соответственно, парный TraceChain для traceMessage.
template <class TThis, class TBase> struct TraceChain : TBase
{
typedef TraceChain<TThis,TBase> Base; // для использования в наследнике
virtual void traceMessage( const MsgData & msg ) //const &msg, т.к. не должно влиять на обработку.
{
//обратный порядок
TBase::traceMessage(msg);
Internal::TraceMsgMap<TThis>::staticInstance().processMessage(*(TThis*)this, msg);
}
};
Использование:
#ifndef VERBOSE_LEVEL
//выводить кое-что, самое основное.
#define VERBOSE_LEVEL 1
#endif
#define Verbose(level) if (level<=VERBOSE_LEVEL)
class TopFrame : public TraceChain<TopFrame,Wnd>
{
ThreadSafe::Count& count()
{
static ThreadSafe::Count i(0); //в однопоточной версии можно использовать обычный static int
return i;
}
public:
TraceMessage<TopFrame, WM_CREATE> traceMsg( const Msg<WM_CREATE>&)
{
int i=++count();
Verbose(1) printf("%i: TraceMessage<TopFrame, WM_CREATE> onMsg(Msg<WM_CREATE>&)\n", i);
return false;
}
TraceMessage<TopFrame, WM_DESTROY> traceMsg( const Msg<WM_DESTROY>&)
{
int i=--count();
Verbose(1) printf("%i: TraceMessage<TopFrame, WM_DESTROY> onMsg(Msg<WM_DESTROY>&)\n", i);
if (0==i)
::PostQuitMessage(0);
return true;
}
//...
};
В месте регистрации обработчика сделал проверку его существования с помощью sizeof.
sizeof прямого вызова обработчика (а точнее возвращаемого значения функции) не скомпилируется, если обработчика нет.
template <class TWinClass, UINT _msgID> class OnMessage {
class Initializer { public:
Initializer()
{
//make sure message there is a handler
sizeof(((TWinClass*)0)->onMsg(MsgSpec<TWinClass,_msgID>(_msgID, 0, 0)));
//MsgSpec<class,id> - наследник Msg<id>, от него уже ничего не наследуется.
//если не использовать специализацию взломщика по классу, то можно написать и так:
//sizeof(((TWinClass*)0)->onMsg(Msg<_msgID>(_msgID, 0, 0)));
//продолжить регистрацию...
Какие ещё будут идеи?
Кстати, я не совсем понимаю, что делать с WM_COMMAND и WM_NOTIFY в SWL3. Вручную разбирать, по-старому в обработчике писать switch(ctrlID)/case как-то не хочется. Что можно придумать? Приделать ещё одну карту что ли? И специализировать по id контрола? Что-нибудь вроде OnCommand<ThisClass,id> onCommand(Command<id>&)? Но id ведь может меняться.
P.S. Сейчас пришло в голову, что осталась ещё одна проблема. Можно написать так:
OnMessage<ThisClass,1> onMsg(Msg<1>&);
OnMessage<ThisClass,1> onMsg(Msg<2>&);
и будет дважды зарегистрирован один и тот же обработчик, тогда как второй — вообще не будет.
Проблему можно решить, если проверять отсутствие зарегистрированного обработчика для данного ID при его регистрации. Тогда вторая регистрация выдаст ошибку при создании карты, т.е. на старте программы (ну или как минимум — до создания окна), чего вполне достаточно.
Однако если переделаем так:
struct A
{
OnMessage<ThisClass,1> onMsg(Msg<1>&);
}
struct B : A
{
OnMessage<ThisClass,1> onMsg(Msg<2>&);
}
то ошибка уже не отловится, т.к. во второй карте обработчик ещё не зарегистрирован, но наследуется от базового класса.
Есть идеи?
И вообще, предложения и замечания по SWL* охотно принимаются.