简体   繁体   中英

C++ Implementing a Button with member-function callback

I am trying to implement a simple GUI component in C++. The 'Button' class exists in a library that is completely de-coupled from the main program. I want to be able to pass a function-pointer to a button that can be ran upon clicking.

I had some success until I moved the 'button' from being a struct to class for readability and expandability. I reckon that it only worked by pure chance as now I get very random problems.

I basically have something like this:

   typedef void(BaseMenu::*ClickAreaCallback)(Button*);
   struct Message{
            ClickAreaCallback func;
            Button* clickArea;
            BaseMenu* funObj;
        };

The from my classes that subclass BaseMenu, I do something like this:

   cb = (ButtonContainer::ClickAreaCallback)&TileSelectorScreen::setTileMode;

and set:

   ClickAreaCallback to &cb (as well as funObj)

I then run it upon 'click' by doing:

m->funObj->*m->func)(m->clickArea);

This is obviously wrong as I've read there are problems passing non-static member functions and expecting them to run.

So, is what I am doing impossible? Is what I want possible by using plain C++ without boost or using -std=c++11. I'm limiting myself to the very basics so I can still compile for many platforms.

In short: I want a simple method of calling functions from a class that knows nothing of the class it's calling.

thanks in advance.

In principle there is nothing wrong with pointers to members. See, eg, the following code:

#include <iostream>

/** Some API */
struct Button {
    virtual void OnClick() = 0;
};

struct BaseMenu {

    void f1(Button* b)  {
        std::cout << "f1(Button*)\n";
        b->OnClick();
    }

    void f2(Button* b)  {
        std::cout << "f2(Button*)\n";
        b->OnClick();
    }

    void Update() {     
    }
};

typedef void(BaseMenu::*ClickAreaCallback)(Button*);

struct Message{
    ClickAreaCallback func;
    Button* clickArea;
    BaseMenu* funObj;
};

/** Usage */
class OKButton : public Button {
    void OnClick() {
        std::cout << "OKButton::OnClick()\n";
    }
};

int main(int nArg, char* args[]) {
    // Fill message:
    BaseMenu menu;
    OKButton ok;
    Message m1, m2;
    m1.func = &BaseMenu::f1;
    m1.funObj = &menu;
    m1.clickArea = dynamic_cast<Button*>(&ok);

    m2.func = &BaseMenu::f2;
    m2.funObj = &menu;
    m2.clickArea = dynamic_cast<Button*>(&ok);

    (m1.funObj ->* m1.func)(m1.clickArea);
    (m2.funObj ->* m2.func)(m2.clickArea);
}

But it looks like a conceptional error. You should not need the callback. The buttons should be derived from a base class and have virtual member functions that do the specific stuff.

There follows an example demonstrating the usage of inheritance instead of callbacks. Note, that ButtonToggle is an example for storing the information inside the button and ButtonNotify is an example for the button notifying the menu.

#include <iostream>
#include <vector>

/** Some API */
struct Button {
    double _area[4]; // rectangle x1,y1,x2,y2
    Button(std::initializer_list<double> area) {
        std::copy(area.begin(),area.begin()+4,_area);
    }
    virtual void OnClick() = 0;
};

class BaseMenu {
protected:
    std::vector<Button*> _buttons;
public:
    void Register(Button* btn) {
        _buttons.push_back(btn);
    }
    void OnClick(double pt[2]) {
        for(auto iBtn = _buttons.begin(); iBtn!=_buttons.end(); iBtn++) {
            if( (*iBtn)->_area[0] <= pt[0] && pt[0] <= (*iBtn)->_area[2]
                && (*iBtn)->_area[1] <= pt[1] && pt[1] <= (*iBtn)->_area[3] ) {
                (*iBtn)->OnClick();
            }
        }
    }
};

struct MyMenu : public BaseMenu {
    struct ButtonToggle: public Button {
        bool _val;
        ButtonToggle() :
            Button( {0.0,0.0,1.0,1.0} )
        {
            _val = false;
        }
        void OnClick()
        {
            std::cout << "ButtonToggle::OnClick()\n";
            _val = not(_val);
        }
    } buttonToggle;

    void DoSomething() {
        std::cout << "DoSomething()\n";
    }

    struct ButtonNotify: public Button {
        MyMenu& _myMenu;
        ButtonNotify(MyMenu& myMenu) :
            Button( {2.0,0.0,3.0,1.0} ),
            _myMenu(myMenu)
        {}

        void OnClick() {
            _myMenu.DoSomething();
        }
    } buttonNotify;

    MyMenu() :
        buttonNotify(*this)
    {
        Register(&buttonToggle);
        Register(&buttonNotify);
    }
};

int main(int nArg, char* args[]) {
    MyMenu menu;
    double pt[2];

    while(( std::cout << "\nCoordinates (end: -1 -1):",
            std::cin >> pt[0] >> pt[1],
            not( pt[0] == -1.0 and pt[1] == -1.0 ) )) {
        menu.OnClick(pt);
    }
}

/*
    Local Variables:
    compile-command: "g++ -g -std=c++11 test1.cc"
    End:
 */

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM