如何从鼠标处理程序中调用成员函数?

时间:2016-06-15 12:38:10

标签: c++ design-patterns event-handling

我想说我的课程如下:

class IWavePlayer : public IControl
{
private:
    WDL_String mWavePath;

public:
    IWavePlayer() { 
        // some task
    }

    void LoadWave() {
        mWavePath = PromptForFile();
        // some task with mWavePath
    }
};

我使用IWavePlayer pWavePlayer;在主要实例中添加了它。

现在,我需要调用(在另一个控件中"处理"鼠标单击)LoadWave()的函数pWavePlayer

class ICustomButton : public IControl
{
private:


public:
    ICustomButton()  {
        // some task
    }

    void ICustomButton::OnMouseDown(int x, int y, IMouseMod *pMod) {
        pWavePlayer.LoadWave();
    }
};

我无法在此致电pWavePlayer.LoadWave();,因为很明显它并不知道" pWavePlayer。我无法将pWavePlayer的实例传递给ICustomButton(例如,因为它仅适用于1个按钮)。

您通常如何处理这种情况?这样做的正确模式是什么?

2 个答案:

答案 0 :(得分:1)

当您需要从另一个对象的无关成员函数调用对象上的成员函数时,您需要为调用者提供一个引用或指向目标的指针。这通常在构造函数中完成:

class ICustomButton : public IControl {
private:
    IWavePlayer *pWavePlayer;
public:
    ICustomButton(IWavePlayer *wp) : pWavePlayer(wp)  {
        // some task
    }
    void ICustomButton::OnMouseDown(int x, int y, IMouseMod *pMod) {
        pWavePlayer->LoadWave();
    }
};

这也可以通过提供某种"定位服务来实现。或者#34;注册表"通过它可以找到指向IWavePlayer的指针而不提供任何引用。例如,如果系统中只有一个IWavePlayer,则可以将其设为单个,这意味着它可以作为IWavePlayer::instance在整个系统中访问。

如果您不想在IWavePlayer中引用ICustomButton,可以使用std::function添加另一个抽象层:

class ICustomButton : public IControl {
private:
    std::function<void(void)> action;
public:
    ICustomButton(std::function& f) : action(f)  {
        // some task
    }
    void ICustomButton::OnMouseDown(int x, int y, IMouseMod *pMod) {
        action();
    }
};

现在,创建按钮的来电者可以提供std::function<void(void)>的操作,可以在其中嵌入对pWavePlayer的引用。

以下是说明该方法的完整示例:

class Button {
    function<void(void)> action;
public:
    Button(function<void(void)> f) : action(f) {}
    void click() {
        action();
    }
};

class WavePlayer {
public:
    void load() {
        cout << "loaded" << endl;
    }
};

int main() {
    WavePlayer player;
    Button b([&] {
        player.load();
    });
    b.click();
    return 0;
}

Demo.

答案 1 :(得分:1)

这是Observer Pattern的典型问题。

在C ++中,您可以使用模板轻松实现此功能。您可以使用我为OSS项目编写的一个。您可以从此observer.h

中提取它

基本上,您将鼠标处理程序对象声明为Dispatcher,并将任何对象想要将其作为Listener接收。在鼠标处理程序中,调用notify方法,然后将通过您的事件通知所有观察者(或监听器)。这种方法的优点是,两个对象之间没有依赖关系,因此您可以轻松添加可能对此事件感兴趣的不同对象,而无需更改对象。

这是一个简单的演示(使用VS2015编译,但也应该在gcc上工作,因为这是我最初开发它的地方)。

#include <iostream>
#include <vector>

template <typename... T> class Dispatcher;
template <typename... T> class Listener;

#define ListenerList        std::vector
#define UNUSED(x) (x)

template <typename... T>
class Listener
{
public:
    Listener(void)
    {
    }

    virtual ~Listener(void)
    {
    }

    virtual void handleNotification(Dispatcher<T...> *oSource, T... /* oEvent */)
    {
        // Default implementation does nothing
        // which can be used as a null listener where
        // a listener is expected but doesn't have
        // a meaningful implementation.
        UNUSED(oSource);
    }

    /**
    * The invalidateDispatcher() call is sent when the dispatcher
    * should no longer be accessed anymore. After this call, the listener
    * will no longer receive any notifications and the dispatcher is
    * destroyed, so the listener should not unregister with
    * removeListener().
    */
    virtual void invalidateDispatcher(Dispatcher<T...> const *oDispatcher)
    {
        UNUSED(oDispatcher);
    }
};

template <typename... T>
class Dispatcher
{
public:
    Dispatcher(void)
    {
        mAllowDuplicates = false;
    }

    virtual ~Dispatcher(void)
    {
        invalidate();
    }

    void allowDuplicates(bool bAllowDuplicates = true)
    {
        mAllowDuplicates = bAllowDuplicates;
    }

    /**
    * After the invalidate() message is sent to the listeners,
    * they will no longer receive any notifications and they should
    * no longer access the dispatcher pointer as the object became invalid.
    * When this call is sent, the listener also shouldn't
    * unregister via removeListener().
    */
    virtual void invalidate(void)
    {
        for (Listener<T...> * &listener : mListeners)
            listener->invalidateDispatcher(this);
    }

    virtual void notify(T... oEvent)
    {
        for (Listener<T...> * &listener : mListeners)
            listener->handleNotification(this, oEvent...);
    }

    /**
    * Adds a listener to the dispatcher. A listener
    * can attach itself multiple times, in which case
    * it will receive as many notifications as it
    * is registered. When the listener is removed
    * it will remove all instances with a single call
    * so there is no need to balance the addListener()
    * with removeListener() calls.
    */
    virtual void addListener(Listener<T...> *oListener)
    {
        if (!mAllowDuplicates)
        {
            if (listenerIndex(oListener) != -1)
                return;
        }

        mListeners.push_back(oListener);
    }

    virtual void removeListener(Listener<T...> *oListener)
    {
        // The listener may have registered multiple times
        // so we must remove all instances.
        int i;
        while ((i = listenerIndex(oListener)) != -1)
            mListeners.erase(mListeners.begin() + i);
    }

protected:
    ListenerList<Listener<T...> *> &getListeners(void) const
    {
        return mListeners;
    }

    virtual int listenerIndex(Listener<T...> const *oListener) const
    {
        int i = -1;
        for (Listener<T...> * const &listener : mListeners)
        {
            i++;
            if (listener == oListener)
                return i;
        }

        return -1;
    }

private:
    ListenerList<Listener<T...> *> mListeners;
    bool mAllowDuplicates;
};


class Mousehandler : public Dispatcher<bool /* ButtonState */, int /* x Position */, int /* y Position */>
{
public:
    Mousehandler(void) {}

    void buttonePressed(int nButtonState, int x, int y)
    {
        if (nButtonState == 1)      // Button up
            notify(true, x, y);
        else
            notify(false, x, y);        // Button down.
    }
};


class MouseListener : public Listener<bool, int, int>
{
public:
    MouseListener(int id) { mId = id;  }

    void handleNotification(Dispatcher<bool, int, int> *oSource, bool bButtonPress, int nX, int nY) override
    {
        UNUSED(oSource);

        if (bButtonPress)
            std::cout << mId << ": Button was pressed at " << nX << "/" << nY << std::endl;
        else
            std::cout << mId << ": Button was released at " << nX << "/" << nY << std::endl;
    }

private:
    int mId;
};


int main(int argc, char *argv[])
{
    UNUSED(argc);
    UNUSED(argv);

    Mousehandler h;
    MouseListener l1(1);
    MouseListener l2(2);

    h.addListener(&l1);
    h.addListener(&l2);

    h.buttonePressed(true, 10, 15);
    h.buttonePressed(false, 20, 11);

    return 0;
}

如果您使用的是较旧的编译器,则可能没有可变参数,在这种情况下,您必须更改模板以仅接受一个类型名称,并且如果需要发送,则必须使用指向结构或类的指针您的活动不止一个参数。使用C ++ 11,它更容易,IMO更清晰。

这是相同的,但在处理程序上使用了多个事件。

class Mousehandler
    : public Dispatcher<bool /* ButtonState */, int /* x Position */, int /* y Position */>
    , public Dispatcher<int /* x Position */, int /* y Position */>
{
public:
    typedef Dispatcher<bool, int , int > button_handler;
    typedef Dispatcher<int, int > move_handler;
    typedef Listener<bool, int, int > button_listener;
    typedef Listener<int, int > move_listener;

public:
    Mousehandler(void) {}

    void buttonPressed(int nButtonState, int x, int y)
    {
        if (nButtonState == 1)      // Button up
            Dispatcher<bool, int, int>::notify(true, x, y);
        else
            Dispatcher<bool, int, int>::notify(false, x, y);        // Button down.
    }

    void mouseMoved(int x, int y)
    {
        Dispatcher<int, int >::notify(x, y);
    }

    void addButtonListener(button_listener *pListener)
    {
        button_handler::addListener(pListener);
    }

    void addMoveListener(move_listener *pListener)
    {
        move_handler::addListener(pListener);
    }
};


class MouseListener 
    : public Listener<bool, int, int>
    , public Listener<int, int>
{
public:
    MouseListener(int id) { mId = id;  }

    void handleNotification(Mousehandler::button_handler *oSource, bool bButtonPress, int nX, int nY) override
    {
        UNUSED(oSource);

        if (bButtonPress)
            std::cout << mId << ": Button was pressed at " << nX << "/" << nY << std::endl;
        else
            std::cout << mId << ": Button was released at " << nX << "/" << nY << std::endl;
    }

    void handleNotification(Mousehandler::move_handler *oSource, int nX, int nY) override
    {
        UNUSED(oSource);

        std::cout << mId << ": Mouse moved to " << nX << "/" << nY << std::endl;
    }

private:
    int mId;
};


int main(int argc, char *argv[])
{
    UNUSED(argc);
    UNUSED(argv);

    Mousehandler h;
    MouseListener l1(1);
    MouseListener l2(2);

    h.addButtonListener(&l1);
    h.addMoveListener(&l1);

    // No need for movements on the second listener.
    h.addButtonListener(&l2);

    h.buttonPressed(true, 10, 15);
    h.buttonPressed(false, 20, 11);

    h.mouseMoved(12, 20);
    h.mouseMoved(21, 23);

    return 0;
}