0

I've created a menu:

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

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

From what I've understood the classical way is to not send in NULL as the 3rd parameter, but an identification number, in order to handle WM_COMMAND messages in the windows WndProc function.

However, since my project is a C++ based WinAPI library I'd prefer if I could hide this implementation detail of raw numbers and instead connect each menu item with a function pointer. The end goal is that the user should be able to see something along these lines:

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

So, my question is:

How can I connect each item in a HMENU to a function pointer?

The more performance efficient and simple way, the better.

EDIT:

I fully understand that what I might want to do is completely non sense, but that's also why I asked this question. Could anyone please guide me in the right direction then? If we look at other GUI libraries/frameworks, for instance Windows Forms in C#, they have full support for assigning functions to different events, such as menu clicks. What is the correct approach to implement this in C++?

Necu
  • 9
  • 3
  • you can pass here only *static* function pointer. member function require 2 pointers - pointer to function + and pointer to object (`this`). but you have space only for one pointer. you of course can say allocate some memory where place any count of pointers/data and pass this memory pointer to `AppendMenu`. this is your choice how use this – RbMm Oct 23 '17 at 10:55
  • Suggest you have a look at one or more of the frameworks available WTL (Windows Template Library) and MFC (Microsoft Foundation Classes) are the 2 native ones that come to mind. Both use classes and/or templates to handle the routing of Window's messages. – Richard Critten Oct 23 '17 at 10:59
  • It's odd that you regard performance to be important. It isn't. You should look at extant libraries to learn the commonly used solutions to this problem. You could also search on SO for all the other questions on this topic. – David Heffernan Oct 23 '17 at 11:10
  • 1
    [Pointers to member functions are very strange animals](https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713). – IInspectable Oct 23 '17 at 11:15
  • @RbMm I'm not sure I understand what you mean. Do you mean that I can send a static function pointer to the `AppendMenuA` function? I tried that but pressing the button never called the function. Maybe you could give an example with code? – Necu Oct 23 '17 at 11:27
  • @RichardCritten I've tried to download the WTL library source code, but the examples in there won't even compile. I also tried to create a MFC project in Visual Studio 2015, but it seems they either create their menus in resource files (with IDs), or if you create the menu manually, you also have to provide an ID according to [link](https://stackoverflow.com/questions/3673546/dynamic-menu-using-mfc). Any suggestions? – Necu Oct 23 '17 at 11:30
  • @Necu - *but pressing the button never called the function* - but when button pressed - you got `WM_COMMAND` and in `WPARAM` will be low dword of what you set in `AppendMenu` or `InsertMenuItem`. nobody, except you will not call your function. and in 64 bit system you not get your pointer back at all, but only it low part. what your try at all no sense – RbMm Oct 23 '17 at 11:31
  • @DavidHeffernan Why is it odd that I regard performance to be important? And I would appreciate it very much if you could link those extant libraries and other questions that appearently are laying around on the internet but that I havn't been able to find after multiple days of searching :) – Necu Oct 23 '17 at 11:32
  • @RbMm I fully understand that what I might want to do is completely non sense, but could you (or someone else) please guide me in the right direction then? If we look at other GUI libraries/frameworks, for instance Windows Forms in C#, they have full support for assigning functions to different events, such as menu clicks. What is the _correct_ approach to implement this in C++? – Necu Oct 23 '17 at 11:35
  • You've already been given links to other libraries. It's odd that you care about performance, because dealing with UI interaction is never a performance bottleneck. Users are quite a lot slower than computers – David Heffernan Oct 23 '17 at 11:36
  • @Necu - and what you try attain this way ? you can not normally asign even pointer to menu. only `DWORD` and you got this dword back in `WPARAM` on `WM_COMMAND`. you must based on this id yourself call some function. and normally you class is bind to window - so you will be have `this` pointer in window proc already – RbMm Oct 23 '17 at 11:36
  • usual this frameworks use static tables (maps) which have pairs - `(id, func)` and window (hwnd) always somehow bind to c++ class. so when we process `WM_COMMAND` from menu - we already have this pointer (menu bind to some hwnd). so framework walk by table(map) and search id. and call corresponded member function – RbMm Oct 23 '17 at 11:40
  • @DavidHeffernan Maybe because the GUI will be used in a performance critical application? Not all applications sit idle and wait for new messages, if you use `PeekMessage` you don't lock the program, hence the bottleneck is not the interaction of the user but the time it takes for the application to run. I've not got any links, only names, of which I tried to either download or create projects of, and it didn't lead anywhere. Also, as I said, if I'm completely on the wrong approach here, could anyone please guide me how for instance Windows Forms in C# assigns functions to events? – Necu Oct 23 '17 at 11:42
  • @RbMm Thank you. So the "correct" approach here would be to make an dynamic ID during runtime, and then map that ID to a function with `std::map`? – Necu Oct 23 '17 at 11:44
  • I think you have excessive faith in your understanding of performance of gui programs, given your level of experience. You are surely going to make the classic mistake of premature optimization. This gets even more hideous when you get your message loop wrong, as you are doing. Expect pain. – David Heffernan Oct 23 '17 at 11:47
  • @Necu - no, static id. hardcode at compile time usually. but if you not write own framework - the best will be simply switch on wm_command – RbMm Oct 23 '17 at 11:47
  • @DavidHeffernan Sometimes I wonder how people like you can get 474'749 in reputution, because you are not helpful at all. Yes, I want to design it with performance in mind because I'm not going to take the time and rewrite everything later. Designing it with performance in mind and premature optimization isn't really the same thing. Or maybe when you write algorithms you prefer to make it slow on purpose just for the sake of not optimizing prematurely? One could expect to get some help from a person of your level of experience rather than just unhelpful comments. – Necu Oct 23 '17 at 11:55
  • Responding to user interaction is never a cpu bottleneck. So design with ease of programming in mind. If you focus on performance when you design you are likely to end up with harder to maintain and harder to use code. As for the message loop, why do expect more from me. You only said that you were not going to idle and instead call PeekMesage. I'm guessing that you plan to have the main thread working flat out and intersperse calls to PeekMesage. But why should I say more when there's no real detail on that. Pride is the real problem here. Are you prepared to learn? – David Heffernan Oct 23 '17 at 12:02
  • @DavidHeffernan If I wasn't prepared to learn, I wouldn't have asked this question, would I? Yes, of course I'm prepared to learn :) So please: 1) Should I use `GetMessage` or `PeekMessage`, or something else? Or should I run the GUI and the logic on 2 different threads? 2) How does other libraries/frameworks (like for example Windows Forms with C#) connect different events to functions? – Necu Oct 23 '17 at 12:07
  • Use the standard message loop with long running tasks hives off to worker threads. WTL is one example of how to hook up events. But I can't believe you can't find questions here. How hard have you searched? – David Heffernan Oct 23 '17 at 12:10
  • @RbMm: Both `LPARAM` and `WPARAM` are pointer sized, regardless of the target architecture. Besides, you don't get that pointer passed through `WPARAM` anyway. You get the menu entry's ID. Using that, you can get the context pointer from the menu item. Again, irrespective of the target architecture. – IInspectable Oct 23 '17 at 12:11
  • @IInspectable - i know the size of `WPARAM`. and i say that we not get pointer back in `WPARAM`. despite in [`WM_COMMAND`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms647591(v=vs.85).aspx) stated that we get only *low word* (id) and 0 in *high word* - really we got id as dword (32 bit) but not 64bit pointer in x64. yes possible get late full `DWORD_PTR` from menu by id, but this already no big sense. – RbMm Oct 23 '17 at 12:19
  • @DavidHeffernan I've tried to search for [windows template library source](https://www.google.com/search?q=windows+template+library+source) on google, and there I found [http://wtl.sourceforge.net/](http://wtl.sourceforge.net/). I downloaded the source, tried to build it with Visual Studio, but it failed because files are missing. I also checked the "Include" folder full of header files, but it's a mess and I can't even find where they've implemented either menues nor events :( – Necu Oct 23 '17 at 12:22
  • 2
    You don't need to compile source code to read it, do you? – IInspectable Oct 23 '17 at 12:24
  • @IInspectable No, but looking into the "Includes" folder is a mess aswell, so I had hoped that the examples could give me a hint of where to start. But it turns out the examples use menues created by resources and not in the actual code. Reason I tried to compile it was to see if it even worked at all. – Necu Oct 23 '17 at 12:29
  • **Comments are not for extended discussion.** If you have insights to offer, post an answer. – Cody Gray - on strike Oct 23 '17 at 12:41

1 Answers1

1

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

Werner Erasmus
  • 3,988
  • 17
  • 31
  • Message-only windows serve a completely different purpose. Indeed, `WM_COMMAND` messages aren't even passed to it, unless you do so yourself. And if you decide to do so, why don't you handle the `WM_COMMAND` messages right there? – IInspectable Oct 23 '17 at 15:16
  • @IInspectable, I have a popup-menu in mind. I could test the code for menu. Associating a (message only) window with the popup in TrackPopupMenuEx would generate the event. One only needs one "handler", which is why I've used a message window to handle all events, to which I connect with "std::functions" – Werner Erasmus Oct 23 '17 at 15:38