Makaveli84
Makaveli84

Reputation: 494

Creating, Raising, and Handling Custom Events in C++ Builder (FMX)

I am trying to implement the following pattern and I need some help filling in the blanks:

// In a class, declare (and initialize?) a custom event
class Foo
{
public:
    TEvent* Bar = nullptr;
        
    Foo::Foo()
    {
        Bar = new TEvent(false);
    }
        
private:
    // This event trigger method could be called during the lifetime of a Foo object
    void Foo::TriggerMethod()
    {
        // ??? Raise Bar (pun intended :) here
    }
};
// In a separate Unit (FMXMainForm):
private:
    Foo* myFoobject = nullptr;
    
    // An event handler method
    void FMXMainForm::BarTender(TObject* Sender)
    {
        // Do stuff with event data via Sender
        ShowMessage("Bar Mitzvah!");
    }
    
public:
    // And when constructing FMXMainForm:
    __fastcall FMXMainForm::FMXMainForm(TComponent* Owner)
    {
        // Create Foo object
        myFoobject = new Foo();
        
        // Assign an event handler to myFoobject's Bar event
        // ??? myFoobject->Bar = &(this->BarTender); // This doesn't work
    }

Can this be done in C++ Builder/FMX? And if so, would it be via TEvents? What pieces am I missing? I need some help, please.

Upvotes: 0

Views: 854

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595792

Yes, it is quite doable in C++Builder, but it is done a little bit differently than what you have shown, ie utilizing Classes::TNotifyEvent instead of Syncobjs::TEvent (which deals only with thread synchronization and nothing to do with object events), eg:

#include <fmx.h>
#include <System.Classes.hpp>
...

// In a class, declare (and initialize?) a custom event
class Foo : public TObject
{
public:
    TNotifyEvent Bar = nullptr;

private:
    // This event trigger method could be called during the lifetime of a Foo object
    void TriggerMethod()
    {
        if (Bar) Bar(this);
    }
};
// In a separate Unit (FMXMainForm):
private:
    Foo* myFoobject = nullptr;
    
    // An event handler method
    void __fastcall BarTender(TObject* Sender)
    {
        // Do stuff with event data via Sender
        ShowMessage("Bar Mitzvah!");
    }
    
public:
    // And when constructing FMXMainForm:
    __fastcall FMXMainForm(TComponent* Owner)
    {
        // Create Foo object
        myFoobject = new Foo();
        
        // Assign an event handler to myFoobject's Bar event
        myFoobject->Bar = &BarTender;
    }

Later on, if you want to add additional parameters to the event, simply replace TNotifyEvent with a custom type that includes the desired parameters, eg:

#include <fmx.h>

typedef void __fastcall (__closure *TMyEventType)(... param types here ...);
// or:
// using TMyEventType = void __fastcall (__closure *)(... param types here ...);

...

// In a class, declare (and initialize?) a custom event
class Foo : public TObject
{
public:
    TMyEventType Bar = nullptr;

private:
    // This event trigger method could be called during the lifetime of a Foo object
    void TriggerMethod()
    {
        if (Bar) Bar(... param values here ...);
    }
};
// In a separate Unit (FMXMainForm):
private:
    Foo* myFoobject = nullptr;
    
    // An event handler method
    void __fastcall BarTender(... params here ...)
    {
        // Do stuff with event data via Sender
        ShowMessage("Bar Mitzvah!");
    }
    
public:
    // And when constructing FMXMainForm:
    __fastcall FMXMainForm(TComponent* Owner)
    {
        // Create Foo object
        myFoobject = new Foo();
        
        // Assign an event handler to myFoobject's Bar event
        myFoobject->Bar = &BarTender;
    }

Note that the above approach does rely on a number of Embarcadero-specific compiler extensions, namely:

  • the __fastcall calling convention.

  • the __closure keyword (used in the declaration of TNotifyEvent), which allows a pointer-to-member-function to point at a member function of any class type, unlike a standard C++ pointer-to-member-function, which is class-specific.

  • a non-standard operator& that creates a pointer-to-member-function via an object pointer/reference, which is compatible with __closure.

  • the Delphi-based TObject base class.

If you want a pure C++ solution, try std::function instead, eg:

#include <functional>

// In a class, declare (and initialize?) a custom event
class Foo
{
public:
    std::function<void()> Bar;

private:
    // This event trigger method could be called during the lifetime of a Foo object
    void TriggerMethod()
    {
        if (Bar) Bar();
    }
};
// In a separate Unit (FMXMainForm):
private:
    Foo* myFoobject = nullptr;
    
    // An event handler method
    void BarTender()
    {
        // Do stuff with event data via Sender
        ShowMessage("Bar Mitzvah!");
    }
    
public:
    // And when constructing FMXMainForm:
    __fastcall FMXMainForm(TComponent* Owner)
    {
        // Create Foo object
        myFoobject = new Foo();
        
        // Assign an event handler to myFoobject's Bar event
        myFoobject->Bar = std::bind(&FMXMainForm::BarTender, this);
        or:
        myFoobject->Bar = [this]{ BarTender(); };
    }

Or, using additional parameters:

#include <functional>

// In a class, declare (and initialize?) a custom event
class Foo
{
public:
    std::function<void(... param types here ...)> Bar;

private:
    // This event trigger method could be called during the lifetime of a Foo object
    void TriggerMethod()
    {
        if (Bar) Bar(... param values here ...);
    }
};
// In a separate Unit (FMXMainForm):
private:
    Foo* myFoobject = nullptr;
    
    // An event handler method
    void BarTender(... params here ...)
    {
        // Do stuff with event data via Sender
        ShowMessage("Bar Mitzvah!");
    }
    
public:
    // And when constructing FMXMainForm:
    __fastcall FMXMainForm(TComponent* Owner)
    {
        // Create Foo object
        myFoobject = new Foo();
        
        // Assign an event handler to myFoobject's Bar event
        using namespace std::placeholders;
        myFoobject->Bar = std::bind(&FMXMainForm::BarTender, this, ... _1, _2, etc ...);
        or:
        myFoobject->Bar = [this](... params here ...){
            BarTender(... params here ...);
        };
    }

Upvotes: 3

Related Questions