Author: | Shunsuke Sogame |
---|---|
Contact: | pstade.mb@gmail.com |
License: | Distributed under the Boost Software License Version 1.0 |
Version: | 1.01.7 |
"'Catch up. Ketchup.'"
—Mia Wallace
Four yeas ago, I made a program. Everything WTL originally had other than win32 thin wrappers was almost useless. CUpdateUI was the one of them, so I made a replacement by using macros. Later, I read the book, C++ Template Metaprogramming where I was inspired by a sample code, the finite state machine. It could let me remove BEGIN_MSG_MAP macros and then the result was named Ketchup.
In time, the experience of making Biscuit gave me the way of avoiding compile-time crashes. Now that Ketchup is the type-safe synonym of BEGIN_MSG_MAP to help WTL catch up the modern programming.
Ketchup is a message map generator framework implemented using class templates. The templates allow us to write type-safe BEGIN_MSG_MAP.
A simple BEGIN_MSG_MAP macro snippet:
BEGIN_MSG_MAP(CMainFrame) MESSAGE_HANDLER(WM_CREATE, OnCreate) COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit) COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew) CHAIN_CLIENT_COMMANDS() CHAIN_MSG_MAP(WTL::CFrameWindowImpl<CMainFrame>) END_MSG_MAP()
is approximated using Ketchup's facilities as seen in this code snippet:
begin_msg_map < message_handler<WM_CREATE, &_::OnCreate>, command_id_handler<ID_APP_EXIT, &_::OnFileExit>, command_id_handler<ID_FILE_NEW, &_::OnFileNew>, chain_client_commands<>, chain_msg_map< WTL::CFrameWindowImpl<CMainFrame> > > end_msg_map;
Include Ketchup headers and make CMainFrame a model of Derived:
#include <pstade/ketchup.hpp> class CMainFrame : public pstade::ketchup::message_processor<CMainFrame, WTL::CFrameWindowImpl<CMainFrame>, WTL::CUpdateUI<CMainFrame> >, public WTL::CMessageFilter, public WTL::CIdleHandler {
Define message handlers:
private: LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { // create command bar window HWND hWndCmdBar = m_CmdBar.Create(m_hWnd, rcDefault, NULL, ATL_SIMPLE_CMDBAR_PANE_STYLE); // attach menu m_CmdBar.AttachMenu(GetMenu()); // ... } // ...
Make CMainFrame a model of Message Processor [1]:
public:
begin_msg_map
<
message_handler<WM_CREATE, &_::OnCreate>,
command_id_handler<ID_APP_EXIT, &_::OnFileExit>,
command_id_handler<ID_FILE_NEW, &_::OnFileNew>,
command_id_handler<ID_VIEW_TOOLBAR, &_::OnViewToolBar>,
command_id_handler<ID_VIEW_STATUS_BAR, &_::OnViewStatusBar>,
command_id_handler<ID_APP_ABOUT, &_::OnAppAbout>,
chain_msg_map< WTL::CUpdateUI<CMainFrame> >,
chain_msg_map< WTL::CFrameWindowImpl<CMainFrame> >
>
end_msg_map;
};
Note that declarations of message handlers must be placed before the Entry.
[1] | Standard C++ doesn't allow you to abbreviate the syntax of member function pointers. (See: 5.3.1 Unary operators -3- of Standard C++ Draft) |
A MessageMapContainer [2] is any type that has the member function, whether virtual or not:
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID);
The return value is TRUE if the message is fully handled; otherwise, it is FALSE.
A ChainClass [3] is any base class that is a model of Message Map Container.
A Derived is any type that is derived from ketchup::message_processor<Derived, Chain Class0, Chain Class1, ... >
An Entry is any type that has the static member function:
static bool process(Derived& derived, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID);
An EntryWrapper is a class template that provides a interface for creating an Entry.
A MessageProcessor is any type that has the accessible member variable of Entry named end_msg_map.
An id is any static constant of window message id or command id.
A ValueFunctorClass is any Default Constructible Functor type that has the member function:
unspecified operator()(Derived& derived);
This is for crossing the compile-time/runtime boundary.
[2] | This concept comes from ATL. A type that has BEGIN_MSG_MAP conforms to it. |
[3] | This concept and name come from ATL. |
Ketchup defines the only one function:
template< class DerivedMessageProcessor > BOOL process_window_message(DerivedMessageProcessor& derived, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0);
The return value is TRUE if the message is fully handled by the Message Processor; otherwise, it is FALSE.
ketchup::message_processor is the base class template that provides predefined Entry Wrappers. If a Chain Class is not Default Constructible, you cannot pass it to ketchup::message_processor for the constructor call. Then, your Derived class can't conform to Message Map Container because of the name ambiguity. So you must explicitly add the member function:
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) { return pstade::ketchup::process_window_message(*this, hWnd, uMsg, wParam, lParam, lResult, dwMsgMapID); }
All the predefined Entry Wrappers are in the scope of ketchup::message_processor<Derived>, which defines a nested _ type that refers to the Derived.
message_handler creates an Entry from id and func:
template< UINT id, LRESULT (Derived::*func)(UINT, WPARAM, LPARAM, BOOL&) > struct message_handler;
begin_msg_map creates a sequential Entry from multiple Entries. The maximum number of Entries is thirty. If the number of your Entries comes greater than thirty, you can chain the Entries. Keep in mind that the type created by begin_msg_map is also an Entry:
struct cmd_ui_map_sub; // forward declaration also works fine. // which do you like typedef... typedef begin_msg_map< cmd_ui_handler<ID_BLACK, &_::OnUpdateBlack>, cmd_ui_handler<ID_RED, &_::OnUpdateRed>, cmd_ui_handler<ID_GREEN, &_::OnUpdateGreen>, cmd_ui_map_sub // chain > cmd_ui_map; // or struct? struct cmd_ui_map_sub : begin_msg_map< cmd_ui_handler<ID_BLUE, &_::OnUpdateBlue>, begin_msg_map< // chain cmd_ui_handler<ID_WHITE, &_::OnUpdateWhite>, cmd_ui_handler<ID_CUSTOM, &_::OnUpdateCustom> > > { }; begin_msg_map < cmd_ui_map, // chain msg_wm_paint<&_::OnPaint>, command_range_handler<ID_BLACK, ID_WHITE, &_::OnColor>, command_id_handler<ID_CUSTOM, &_::OnCustomColor> > end_msg_map;
Or you can extract the maximum power from Boost.Preprocessor in exchange for giving credit to VC++7.1 preprocessor:
// stdafx.h // ... extern WTL::CAppModule _Module; #include <atlwin.h> #define PSTADE_KETCHUP_CFG_LIMIT_TEMPLATE_PARAMETERS_NUMBER 64 #include <pstade/ketchup/config/limit_template_parameters_number.hpp> #include <pstade/ketchup.hpp>
The available number of template parameters depends on /Zm option, aside from compiler limitations on the number of template parameters.
alt_msg_map creates an indexed Entry, which is a replacement for ALT_MSG_MAP of ATL:
begin_msg_map < alt_msg_map<0, message_handler<WM_CREATE, &_::OnCreate>, message_handler<WM_KEYDOWN, &_::OnKey>, message_handler<WM_KEYUP, &_::OnKey>, message_handler<WM_LBUTTONDOWN, &_::OnKey> >, alt_msg_map<1, command_id_handler<ID_EDIT_UNDO, &_::OnEditUndo>, command_id_handler<ID_EDIT_CUT, &_::OnEditCut>, command_id_handler<ID_EDIT_COPY, &_::OnEditCopy>, command_id_handler<ID_EDIT_PASTE, &_::OnEditPaste>, command_id_handler<ID_EDIT_CLEAR, &_::OnEditClear>, command_id_handler<ID_EDIT_SELECT_ALL, &_::OnEditSelectAll> >, assert_valid_msg_map_id< ketchup::idset<0, 1> > > end_msg_map;
As a default Entry is not indexed, 0 indexed Entry is always required if Message Map Container must have multiple message map ids.
chain_msg_map creates an Entry from a Chain Class.
chain_msg_map_member creates an Entry from a Value Functor Class:
my_chain_mem m_chain; struct chain_ftor_t { my_chain_mem& operator()(CMainFrame& me) { return me.m_chain; } }; begin_msg_map < message_handler<WM_CREATE, &_::OnCreate>, command_id_handler<ID_VIEW_TOOLBAR, &_::OnViewToolBar>, command_id_handler<ID_VIEW_STATUS_BAR, &_::OnViewStatusBar>, chain_msg_map_member<chain_ftor_t>, // ... > end_msg_map;
As an Entry wrapper is a class template and an object cannot be passed directly, you must write a Value Functor Class for every chained member object.
Every synonym of BEGIN_MSG_MAP macro are fully defined.
Ketchup supports cracked handlers of WTL:
LRESULT OnCreate(LPCREATESTRUCT) { set_msg_handled(false); // pass to the default procedure m_pView = new CHelloView(); RECT rect = { 0, 0, 1, 1 }; m_hWndClient = m_pView->Create(m_hWnd, rect, NULL, WS_CHILD | WS_VISIBLE, WS_EX_CLIENTEDGE); return 1; } begin_msg_map < msg_wm_create<&_::OnCreate>, // cracked! chain_client_commands<>, chain_client_cmd_ui<>, chain_msg_map< CMDIChildWindowImpl<CHelloWnd> > > end_msg_map;
SetMsgHandled of WTL was accepted as set_msg_handled after some experiences. Note that set_msg_handled calls never effect on non-cracked handlers.
Ketchup supports Updating Command UI mechanism of MFC and the limited automatic-disabling:
void OnUpdateViewStatusBar(ketchup::cmd_ui& ui) { ui.set_check(::IsWindowVisible(m_hWndStatusBar)); } virtual BOOL OnIdle() { ketchup::update_toolbar_cmd_ui(m_hWnd, m_wndToolBar); return FALSE; } typedef ketchup::idset< ID_BLACK, ID_RED, ID_GREEN, ID_BLUE, ID_WHITE, ID_CUSTOM, ID_SPEED_SLOW, ID_SPEED_FAST > child_cmd_ids; begin_msg_map < update_menu_cmd_ui<>, cmd_ui_handler<ID_VIEW_TOOLBAR, &_::OnUpdateViewToolBar>, cmd_ui_handler<ID_VIEW_STATUS_BAR, &_::OnUpdateViewStatusBar>, enable_cmd_ui_if_handled<chain_mdi_child_cmd_ui<>, child_cmd_ids>, // ... > end_msg_map;
This is a replacement for CUpdateUI of WTL. update_menu_cmd_ui generates a ketchup::cmd_ui object from WM_INITMENUPOPUP. enable_cmd_ui_if_handled enables a ketchup::cmd_ui object if it is handled; otherwise, disables it.
Ketchup is compatible with BEGIN_MSG_MAP. PSTADE_KETCHUP_CHAIN_MSG makes it:
begin_msg_map < message_handler<WM_CREATE, &_::OnCreate>, chain_client_cmd_ui<> > end_msg_map; BEGIN_MSG_MAP(CBounceWnd) // MESSAGE_HANDLER(WM_CREATE, OnCreate) PSTADE_KETCHUP_CHAIN_MSG(*this) CHAIN_CLIENT_COMMANDS() CHAIN_MSG_MAP(CMDIChildWindowImpl<CBounceWnd>) END_MSG_MAP()
When your Derived class is a template, a conforming compiler cannot find any Entry from ketchup::message_processor without user workaround:
template< class D > struct CMainFrameCommand1 : pstade::ketchup::message_processor< CMainFrameCommand1<D> > { // ... public: typedef CMainFrameCommand1 _; typename _::template // must be added. begin_msg_map < typename _::template command_id_handler<ID_APP_EXIT, &_::OnFileExit>, typename _::template command_id_handler<ID_FILE_NEW, &_::OnFileNew> > end_msg_map; };
VC++ doesn't support two-phase name lookup, but you must add somewhat ugly prefix for Standard C++ [4].
[4] | It is surprising that almost all of WTL is illegal. Note that this workaround cannot work around weird GCC3.4.4. |
The last point is the performance. The program size seems not to be a problem. VC++7.1 generates the same size program as BEGIN_MSG_MAP, because Ketchup's message map is almost same as BEGIN_MSP_MAP. But VC++7.1 can't inline message handlers unlike BEGIN_MSG_MAP. Could this be a problem of the speed?
I did not intend to emulate the appearance of BEGIN_MSG_MAP. It is not just a syntax sugar but the coincidence as the result of naming consistency with ATL/WTL. It was an amazing discovery for me.
By the way, Ketchup might be the first application using Boost.Xpressive, which is used as the source code generator.