One re-usable event for any purpose (pass an std::function)

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
Virchanza
I live to help wx-kind
I live to help wx-kind
Posts: 172
Joined: Sun Jul 19, 2009 6:12 am

One re-usable event for any purpose (pass an std::function)

Post by Virchanza »

Sometimes it's a bit excessive to write a new event class every time I come up with a new purpose to fulfil, and so I've set about writing one kind of event that can be reused for any purpose.

The idea here is that a worker thread creates an object of type "std::function", and then the worker thread passes an event containing this object to the event handler of the main dialog box. Here' s the contents of the header file for the event class:

Code: Select all

#ifndef HPP_WX_EVENT_STD_FUNCTION
#define HPP_WX_EVENT_STD_FUNCTION

#include <functional>  // function

#include <wx/event.h>  // wxEvent

DECLARE_EVENT_TYPE( g_wxevent_std_function, -1 )  // This line *declares* a global object

class wxEvent_std_function : public wxCommandEvent {
public:

    std::function<void(void)> func;

    wxEvent_std_function(wxEventType arg_command_type = g_wxevent_std_function, int arg_id = 0)
      :  wxCommandEvent(arg_command_type, arg_id) { }

    /* No need for copy-constructor, the default supplied one will do */

    // Required for sending with wxPostEvent()
    wxEvent *Clone() const
    {
        return new wxEvent_std_function(*this);
    }
};

typedef void (wxEvtHandler::*wxEventMemberFunctionPointer_std_function)(wxEvent_std_function &);

// This #define simplifies the one below, and makes the syntax less
// ugly if you want to use Connect() instead of an event table.
#define wxEventHandler_std_function(func)                              \
    (wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction)   \
    wxStaticCastEvent(wxEventMemberFunctionPointer_std_function, &func)

#endif

And here's how a worker thread would use this event to do whatever it wants. Let's say we have two dialogs, the main one is "Dialog_Main", and the child dialog is "Dialog_Waiting". Note that the code intended to be executed by the GUI thread can be kept separate from the source file containing all of the main dialog's event handlers. The following is the source code file for the child dialog:

Code: Select all

#include "dialog_waiting.hpp"  // The header file that goes with this source file

#include <thread>  // jthread
#include <stop_token>  // stop_token

#include "dialog_main.hpp"  // Dialog_Main
#include "wxevent_std_function.hpp"  // wxEvent_std_function

DEFINE_EVENT_TYPE(g_wxevent_std_function);

Dialog_Waiting::Dialog_Waiting(wxWindow *const parent) : Dialog_Waiting__Auto_Base_Class(parent)
{
    Dialog_Main *const dm = dynamic_cast<Dialog_Main*>(parent);

    if ( nullptr == dm )  // First make sure that our parent is Dialog_Main
    {
        this->Destroy();
        return;
    }

    dm->Enable(false);  // Disable the main dialog before starting work

    // On the next line is a lambda containing code
    // intended to be executed by the GUI thread
    auto callback = [this,dm](void) -> void
    {
        for ( auto const &e : some_global_lockfree_container )
        {
            int const index = dm->m_listBoxHardware->FindString(e.first);

            if ( wxNOT_FOUND == index ) continue;

            dm->m_listBoxHardware->SetString(index, e.first + "    " + e.second);
        }

        dm->Enable(true);  // Re-enable the main dialog after work is finished
   
        this->Destroy();  // Destroy the 'waiting' dialog and go back to the main dialog
    };
 
    this->worker_thread = std::jthread([this,dm,callback](std::stop_token) -> void
    {
        // Do some work in here while periodically checking the stop_token

        wxEvent_std_function event;

        event.func = callback;

        dm->GetEventHandler()->AddPendingEvent(event);
    });
}

Have I re-invented the wheel here? Is there already something similar inside the wxWidgets C++ library?

Do you think I've gone about this in a good way? To me this seems like an ideal way to write a lambda on-the-fly in a source file belonging to a different dialog box, and the lambda can make the GUI thread do whatever it wants.

Note however, that since the event class contains an 'std::function', it isn't limited to just lambdas -- it can be used with any kind of functor.

Here's a few links to learn about lambdas, functors and std::function:

Lambdas: https://www.learncpp.com/cpp-tutorial/lambda-captures/
Functors: https://www.quantstart.com/articles/Fun ... -C-Part-1/
Using std::function: https://shaharmike.com/cpp/lambdas-and- ... d-function
Virchanza
I live to help wx-kind
I live to help wx-kind
Posts: 172
Joined: Sun Jul 19, 2009 6:12 am

Re: One re-usable event for any purpose (pass an std::function)

Post by Virchanza »

Also, for even more versatility, std::function can store the result returned from "std::bind". See here:

https://thispointer.com/stdbind-tutoria ... e-details/
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: One re-usable event for any purpose (pass an std::function)

Post by doublemax »

I believe wxEvtHandler::CallAfter achieves the same:
https://docs.wxwidgets.org/trunk/classw ... 50b3719519
Use the source, Luke!
Post Reply