Event handling

G
2004-07-11
2004-07-21
  • G
    G
    2004-07-11

    This seems like a nice way of doing gui:s! I've played a bit with it, but I don't really understand  the way event handling is implemented. I can see that it works, but how?

    Could you elaborate a bit on how the mapping between the message and it's handler is done?

     
    • John Torjo
      John Torjo
      2004-07-12

      I'm gonna show you an exctract from an email I sent a while ago to Luke Wagner:

      About the events: they're indeed tricky  ;)
      But the idea behind them is quite simple: in the event handler function itself, specify 2 things:
      - the handled event (or command/notification)
      - the function itself (At runtime, the function will have to register itself).

      They both need to be compile-time parameters. I need to create a templated class which contains both, which will have a static variable. The static variable will then register the function.

      Something like this:
      template<int event_id, pointer_to_member_function> struct handler {
          // when this static var gets created, it will register the handler function
          static hanler_impl<event_id,pointer_to_member_function> impl;
      };

      So, in your code, when you say:

      handle_event on_ok() {
          retunn command<IDOK>().HANDLED_BY(&me::on_ok);
      }

      Invisibly, a static variable gets created, which at construction registers this handler function: me::on_ok for command IDOK.

      Now, the tricky part: specifying the event as a templated parameter is extremely simple. But the C++ syntax makes it extremely difficult to specify a pointer-to-member function. For instance, instead of one, you need at least two template parameters:

      template<class T, some_args, handle_event (T::*)(some_args)> struct handler {};

      Using this in code just plain sucks.

      So, I created a proxy object - which knows T and some_args:

      get_proxy(&me::on_ok) -> this is a function, and from its argument, it will extract T and some_args. Then, I can actually specify &me::on_ok as template parameter.

      Thus, HANDLED_BY(&me::on_ok) is equivalent to
      get_proxy(&me::on_ok).handled_by<&me::on_ok>().

      Hope this cleared things up a bit ;) Sorry if there are any unclear things - really busy. Please let me know if there are things I should explain better.

      Best,
      John

      Hope this helps. If not, please ask again ;)

       
    • G
      G
      2004-07-12

      I see, the sole purpose of the returned object is to create the mapping. To me it turns thing upside down a bit, it's not until the end of the body of the handler function you actually see what is handled.

      What about using the same trick in this way:

      void on_ok()
      {
         command<IDOK>().HANDLED_BY(&me::on_ok);

         /* event handling here...*/
      }

      A consequense would be that a more traditional message map would be possible:

      void msgMap()
      {
         command<IDOK>().HANDLED_BY(&me::on_ok);
         command<IDCANCEL>().HANDLED_BY(&me::on_cancel);
         //...
      }

      This would require adapations of the event handler function types, but there are perhaps downsides to this?

       
      • John Torjo
        John Torjo
        2004-07-13

        I don't like traditional ;)

        It always screws me up. I need to modify in multiple places. Also, I don't enjoy Wizards in C++ too much, since most of the time they screw up when the project gets to be non-trivial.

        Also, there are other considerents, such as:
        void on_ok()
        {
        command<IDOK>().HANDLED_BY(&me::on_ok);

        /* event handling here...*/
        }

        The user might simply forget to set the handler.

        But most importantly, the compiler could optimize away the whole command<IDOK>()....etc.

        By forcing it on the return command line, this will not happen (as far as I know ;)).

        Best,
        John

         
        • G
          G
          2004-07-13

          I'm not sure if I like the traditional way either, wizards and MFC doesn't make me sleep good at night. But you wouldn't have to chose, you could still define each mapping in its handler function (at the beginning or at the end). At the same time it would support a msg map should you prefer that.

          As far as I can tell there is no way of mapping one handler to several events.

          I know ther is support for ranges but for me they're just wrong, at least in MFC. They rely on resource IDs which are hidden by macro defines in the client. The actual IDs might change as the gui evolves and the range could be detroyed. But there is no way of detecting that in client macros....

           
          • John Torjo
            John Torjo
            2004-07-13

            At this point, there's no way to map one handler to several events (you can map one to ALL commands or ALL events,etc. by using something like command<0>().... or event<0>()...).

            I do plan to support in the near future multiple events  with the && syntax. Something like:
            return event<WM_1>().HANDLED_BY(&me::on_myfunc) && event<WM_2>().HANDLED_BY(&me::on_myfunc) ...etc.

            As for command ranges, I think they're a very good thing if you use them right - don't rely on the resource.h IDs, and create your own IDs. Hopefully I'll have time to write about it some day ;)

             
      • John Torjo
        John Torjo
        2004-07-13

        P.S. Also, by specifying the event/command/notif on the last line, forces (or should force) you to come up with good function names, like:

        on_ok, on_cancel, on_lbutton_down, on_name_change, etc.

        They are self explanatory - you could almost forget to look for the event/command/notif it handles.

         

      • Anonymous
        2004-07-21

        It's good John Torjo did it the way he did.
        In your code, you could do something like this:

        #define EVT_MSG    WM_LBUTTONDOWN
        #define EVT_HNDLR  on_WM_LBUTTONDOWN
        handle_event EVT_HNDLR( hwnd_param hWnd,
                w_param<WPARAM> wParam,
                l_param<LPARAM> lParam ) {
            g_main_pAppBase->OnMiscMouseEvent(
                (HWND)hWnd, (UINT)EVT_MSG,
                (WPARAM)wParam, (LPARAM)lParam );
            return event<EVT_MSG>()
                .HANDLED_BY(&me::EVT_HNDLR);
        } //event
        #undef EVT_MSG
        #undef EVT_HNDLR

        The message handling is so efficient
        you can use this in developing computer games.

        CP