I've created a menu:
Now I want to be able to connect each of these menu items to specific
functions.
One needs to create a window that catches WM_COMMAND
messages (In my implementation I use a Message Only Window) (use this as starting point). The ID associated with the particular menu item can be found by getting the LOWORD
of wParam
. You could identify menu instances (typically this pointer of a class encapsulating a menu) by using lParam (if you use duplicate IDs). Your encapsulator would then need to associate each ID with a callback (derived from std::function
) (e.g typedef std::map<EventID, std::function<void()>> CallbackMap
).
My first step would be to allow for associating normal windows messages (e.g WM_COMMAND
) with member functions, where a member function can subscribe to a WM message. Thereafter encapsulate classes handling specific message, e.g WM_TIMER
and WM_COMMAND
.
My Menu class has an interface to the MessageHandlingWindow that is typically called like this:
//Allows for scoping lifetime without knowing type...
struct ScopedResource{virtual ~ScopedResource(){}};
Menu
{
std::unique_ptr<ScopedResource> event_;
//...
Menu()
: event_(msgHandler_.createEvent(
WM_COMMAND,this, &Menu::onWM_Received))
{}
};
MessageHandler is an interface that looks like this (and is implemented in terms of message only window (presently). How you implemented it (in terms of message only or normal window) doesn't really matter, but I prefer as little as possible duplication (hate having to reimplement window handlers). You'll notice that I've used std::bind to do the heavy lifting:
class WindowMessageHandler
{
public:
typedef UINT MessageId;
typedef std::function<bool(WPARAM, LPARAM)> DefaultMessageHandler;
typedef std::function<bool(MessageId, WPARAM, LPARAM)> DefaultUnmappedMessageHandler;
typedef std::function<bool(WPARAM, LPARAM, LRESULT&)> MessageHandler;
typedef std::function<bool(MessageId, WPARAM, LPARAM, LRESULT&)> UnmappedMessageHandler;
template <class EventT> //NOTE: EvenT must be castable to MessageId.
void postMessage(EventT event, WPARAM wParam, LPARAM lParam)
{
PostMessage(getWindowHandle(), static_cast<MessageId>(event), wParam, lParam);
}
virtual HWND getWindowHandle() const = 0;
template <class MessageIdT, class ReceiverT>
std::unique_ptr<ScopedResource> createEvent(MessageIdT messageId, ReceiverT* receiver, bool (ReceiverT::*handler)(WPARAM, LPARAM, LRESULT&))
{
using namespace std::placeholders;
return addEventImpl(static_cast<MessageId>(messageId), MessageHandler{std::bind(handler, receiver, _1, _2, _3)});
}
template <class MessageIdT, class ReceiverT>
std::unique_ptr<ScopedResource> createEvent(MessageIdT messageId, ReceiverT* receiver, bool (ReceiverT::*handler)(WPARAM, LPARAM))
{
using namespace std::placeholders;
return addEventImpl(static_cast<MessageId>(messageId), DefaultMessageHandler{std::bind(handler, receiver, _1, _2)});
}
template <class MessageIdT, class MessageHandlerT>
std::unique_ptr<ScopedResource> createEvent(MessageIdT messageId, MessageHandlerT&& handler)
{
//Create temporary that will be moved...
return addEventImpl(static_cast<MessageId>(messageId), std::forward<MessageHandlerT>(handler));
}
template <class ReceiverT>
std::unique_ptr<ScopedResource> createUnmappedEvent(ReceiverT* receiver, bool (ReceiverT::*handler)(MessageId, WPARAM, LPARAM, LRESULT&))
{
using namespace std::placeholders;
return addUnmappedEventImpl(std::bind(handler, receiver, _1, _2, _3, _4));
}
template <class ReceiverT>
std::unique_ptr<ScopedResource> createUnmappedEvent(ReceiverT* receiver, bool (ReceiverT::*handler)(MessageId, WPARAM, LPARAM))
{
using namespace std::placeholders;
return addUnmappedEventImpl(std::bind(handler, receiver, _1, _2, _3));
}
template <class MessageHandlerT>
std::unique_ptr<ScopedResource> createUnmappedEvent(MessageHandlerT&& handler)
{
//Create temporary that will be moved...
return addUnmappedEventImpl(std::forward<MessageHandlerT>(handler));
}
protected:
virtual ~WindowMessageHandler() {}
private:
std::unique_ptr<ScopedResource> addEventImpl(MessageId messageId, const MessageHandler& messageHandler)
{
//Creating rvalue-ref
return addEventImpl(messageId, MessageHandler{messageHandler});
}
std::unique_ptr<ScopedResource> addEventImpl(MessageId messageId, const DefaultMessageHandler& defaultHandler)
{
//Creating rvalue-ref
return addEventImpl(
messageId,
MessageHandler{[defaultHandler](WPARAM wp, LPARAM lp, LRESULT& result) {
bool handled = defaultHandler(wp, lp);
if (handled) {
result = 0;
}
return handled;
}}
);
}
std::unique_ptr<ScopedResource> addUnmappedEventImpl(const UnmappedMessageHandler& messageHandler)
{
//Creating rvalue-ref
return addUnmappedEventImpl(UnmappedMessageHandler{messageHandler});
}
std::unique_ptr<ScopedResource> addUnmappedEventImpl(const DefaultUnmappedMessageHandler& defaultHandler)
{
//Creating rvalue-ref
return addUnmappedEventImpl(
UnmappedMessageHandler{[defaultHandler](MessageId messageId, WPARAM wp, LPARAM lp, LRESULT& result) {
bool handled = defaultHandler(messageId, wp, lp);
if (handled) {
result = 0;
}
return handled;
}}
);
}
virtual std::unique_ptr<ScopedResource> addEventImpl(MessageId messageId, MessageHandler&& messageHandler) = 0;
virtual std::unique_ptr<ScopedResource> addUnmappedEventImpl(UnmappedMessageHandler&& messageHandler) = 0;
};
I'll leave the implementation as an exercise for the OP
EDIT:
You don't necessarily have to use a Message Only Window (you could associate any window with your menu by calling SetMenu, although in my implementation I only have one message handling function and this is associated with a message only window (this is the window I associate with all my event handlers). This is perhaps not the way everyone does it, but it means one only has to write one event handler.
Another way to do it might be to encapsulate all windows (as I have message window) and provide/inject a single handler.
There are frameworks that make it easier for you. You could look at WTL