【问题标题】:C++ Implementing a Button with member-function callbackC++ 使用成员函数回调实现按钮
【发布时间】:2013-10-04 09:49:29
【问题描述】:

我正在尝试用 C++ 实现一个简单的 GUI 组件。 '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++ 或使用 -std=c++11 是我想要的吗?我将自己限制在最基本的范围内,因此我仍然可以针对许多平台进行编译。

简而言之:我想要一个简单的方法,从一个对它所调用的类一无所知的类中调用函数。

提前致谢。

【问题讨论】:

  • 强制转换函数指针永远不能解决任何问题。不要这样做。
  • 我知道,这就是为什么我要问什么是正确的做法。你有什么建议吗?

标签: c++ callback function-pointers


【解决方案1】:

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

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

【讨论】:

  • 但是按钮肯定不应该知道他们正在做什么的细节。他们应该是哑巴,简单地说“我被点击了”。也许我正在尝试做一些 C++ 不打算做的事情,但我觉得这很难相信。不过感谢您的代码,这几乎就是我一直在做的事情,当我将它移到一个单独的类时我遇到了问题。
  • 你写道:“但按钮肯定不应该知道他们正在做什么的细节。”我不太确定。您所说的对于基类 Button 和 BaseMenu 也是如此。但是您可以派生包含更多信息和/或操作的特定按钮。请参阅上面的扩展答案。
猜你喜欢
  • 2010-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-29
  • 1970-01-01
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多