关于C#:可以将函数指针连接到WINAPI中的HMENU项吗?

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

我已经创建了一个菜单:

1
2
3
4
5
6
7
8
9
10
11
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作为第三个参数,而是发送一个标识号,以便处理Windows WndProc函数中的WM_COMMAND消息。

但是,由于我的项目是一个基于C的WinAPI库,所以如果我可以隐藏此原始数字的实现细节,而是将每个菜单项与一个函数指针连接,则希望使用。最终目标是使用户能够看到以下内容:

1
2
3
4
5
6
7
8
9
10
11
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中实现此目标的正确方法是什么?


I've created a menu:

Now I want to be able to connect each of these menu items to specific
functions.

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

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

"我的菜单"类具有MessageHandlingWindow的接口,通常这样称呼:

1
2
3
4
5
6
7
8
9
10
11
12
//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来完成繁重的工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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