C++实现带成员函数回调的Button

nbnkbykc  于 2023-03-09  发布在  其他
关注(0)|答案(2)|浏览(102)

我正在尝试用C++实现一个简单的GUI组件。“Button”类存在于一个与主程序完全解耦的库中。我希望能够传递一个函数指针到一个单击后可以运行的按钮。
我取得了一些成功,直到我把“button”从一个结构体移到了类中,以提高可读性和可扩展性。我认为它的工作纯粹是偶然的,因为现在我遇到了非常随机的问题。
我基本上有这样的东西:

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

从我的子类BaseMenu的类中,我做了如下操作:

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

并设置:

ClickAreaCallback to &cb (as well as funObj)

然后,我通过执行以下操作在“单击”时运行它:

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

这显然是错误的,因为我读到过传递非静态成员函数并期望它们运行的问题。
那么,我正在做的事情是不可能的吗?我想要的是通过使用普通C而不使用boost或使用-std=c11来实现的吗?我将自己限制在非常基础的东西上,这样我仍然可以在许多平台上编译。
简而言之:我想要一个简单的方法,从一个对它所调用的类一无所知的类中调用函数。
先谢谢你了。

cu6pst1q

cu6pst1q1#

原则上,指向成员的指针没有错,例如,见以下代码:

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

但这看起来像是一个概念上的错误。你不应该需要回调。按钮应该派生自基类,并有虚拟成员函数来做特定的事情。
下面的例子演示了继承而不是回调的用法。注意,ButtonToggle是一个在按钮内部存储信息的例子,ButtonNotify是一个通知菜单的按钮的例子。

#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:
 */
7ajki6be

7ajki6be2#

您可以使用std::function实现此目的,例如:

// gui_element.cpp
#include "gui_element.h"

GUI_Element::GUI_Element(int x, int y, int w, int h, std::function<void()> on_release) : 
    x(x), y(y), w(w), h(h), on_release(on_release)
{
}

“on_release”参数是可选的,默认值为不执行任何操作的lambda函数。

// gui_element.h
#indef GUI_ELEMENT_H
#define GUI_ELEMENT_H

#include <functional>
class GUI_Element {
public:
    GUI_Element(int x, int y, int w, int h, std::function<void()> on_release = [](){});

    std::function<void()> on_release;
protected:
    int x,y,w,h;
};

#endif

下面是如何使用一行代码创建这样的元素(使用lambda函数):

//main.cpp
GUI_Element *elem = new GUI_Element(0, 0, 10, 10, [](){Serial.println("Element was clicked");});

相关问题