你能在WINAPI中将函数指针连接到HMENU项吗

Can you connect a function pointer to a HMENU item in WINAPI?

本文关键字:连接 指针 HMENU 项吗 函数 WINAPI      更新时间:2023-10-16

我创建了一个菜单:

HMENU subm = CreateMenu();
AppendMenuA(subm, MF_STRING, NULL, "SubItem1");
AppendMenuA(subm, MF_STRING, NULL, "SubItem2");
AppendMenuA(subm, MF_STRING, NULL, "SubItem2");
HMENU menu = CreateMenu();
AppendMenuA(menu, MF_STRING, (UINT_PTR)subm, "Item1");
AppendMenuA(menu, MF_STRING, NULL,           "Item1");
AppendMenuA(menu, MF_STRING, NULL,           "Item1");
SetMenu(hwnd, menu);

现在,我希望能够将这些菜单项中的每一个连接到特定的功能。

根据我的理解,经典的方法是不将NULL作为第三个参数发送,而是发送一个标识号,以便在windowsWndProc函数中处理WM_COMMAND消息。

然而,由于我的项目是一个基于C++的WinAPI库,我更喜欢隐藏原始数字的实现细节,而不是用函数指针连接每个菜单项。最终目标是用户应该能够看到以下内容:

MenuStrip subMenuStrip;
subMenuStrip.Add("SubItem1", std::bind(&Window1::SubItem1_Click, this));
subMenuStrip.Add("SubItem2", std::bind(&Window1::SubItem2_Click, this));
subMenuStrip.Add("SubItem3", std::bind(&Window1::SubItem3_Click, this));
MenuStrip menuStrip;
menuStrip.Add("Item1", subMenuStrip);
menuStrip.Add("Item2", std::bind(&Window1::Item2_Click, this));
menuStrip.Add("Item3", std::bind(&Window1::Item3_Click, this));
window.MainMenu(menuStrip);

所以,我的问题是:

如何将HMENU中的每个项连接到函数指针

性能效率越高,方法越简单越好。

编辑:

我完全理解我可能想做的事情完全没有意义,但这也是我问这个问题的原因。那么,谁能给我指引正确的方向吗?如果我们看看其他GUI库/框架,例如C#中的Windows窗体,它们完全支持为不同的事件分配函数,例如菜单单击。在C++中实现这一点的正确方法是什么?

我创建了一个菜单:

现在,我希望能够将这些菜单项中的每一个连接到特定的功能。

需要创建一个捕获WM_COMMAND消息的窗口(在我的实现中,我使用仅消息窗口)(以此为起点)。可以通过获得wParamLOWORD来找到与特定菜单项相关联的ID。您可以通过使用lParam(如果使用重复的ID)来标识菜单实例(通常是封装菜单的类的指针)。然后,您的封装程序需要将每个ID与回调(源自std::function)(例如typedef std::map<EventID, std::function<void()>> CallbackMap)相关联。

我的第一步是允许将普通窗口消息(例如WM_COMMAND)与成员函数相关联,其中成员函数可以订阅WM消息。然后封装处理特定消息的类,例如WM_TIMERWM_COMMAND

My Menu类有一个到MessageHandlingWindow的接口,通常这样调用:

//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是一个看起来像这样的接口(目前是在仅消息窗口中实现的)。您是如何实现它的(就仅消息或普通窗口而言)并不重要,但我更喜欢尽可能少的重复(讨厌重新实现窗口处理程序)。你会注意到,我使用了std::bind来完成繁重的任务:

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;
};

我将把实施作为OP 的练习

编辑:

您不一定要使用仅消息窗口(您可以通过调用SetMenu将任何窗口与菜单关联,尽管在我的实现中,我只有一个消息处理函数,并且它与仅消息窗口关联(这是我与所有事件处理程序关联的窗口)。也许不是每个人都这样做,但这意味着只需要编写一个事件处理程序。

另一种方法可能是封装所有窗口(就像我有消息窗口一样),并提供/注入单个处理程序。

有一些框架可以让你更轻松。你可以看看WTL