Duilib的多级菜单实现(网易云信版本)

完整代码见:https://github.com/netease-im/NIM_Duilib_Framework/tree/master/ui_components/menu

核心代码:

ui_menu.h

#ifndef __UIMENU_H__
#define __UIMENU_H__

#pragma once

namespace nim_comp {

using namespace ui;
enum MenuAlignment
{
    eMenuAlignment_Left = 1 << 1,
    eMenuAlignment_Top = 1 << 2,
    eMenuAlignment_Right = 1 << 3,
    eMenuAlignment_Bottom = 1 << 4,
    eMenuAlignment_Intelligent = 1 <<5    //智能的防止被遮蔽
};

enum MenuCloseType
{
    eMenuCloseThis,  //适用于关闭当前级别的菜单窗口,如鼠标移入时
    eMenuCloseAll     //关闭所有菜单窗口,如失去焦点时
};

//增加关闭事件的传递。
/*
点击某一菜单,获取该菜单窗口句柄,通知该菜单窗口可以关闭子菜单项了。
即某子菜单项目的父窗口等于该窗口,该子菜单关闭。
由于菜单的父子关系,会自动关闭其所有子孙菜单窗口
这里的事件传递设计拷贝原生Duilib的MenuDemo,不过Redrain的Menu功能更好,支持菜单复选,这里暂未实现
*/
#include "observer_impl_base.hpp"   //copy from menuDemo
struct ContextMenuParam
{
    MenuCloseType wParam;
    HWND hWnd;
};

typedef class ObserverImpl<BOOL, ContextMenuParam> ContextMenuObserver;
typedef class ReceiverImpl<BOOL, ContextMenuParam> ContextMenuReceiver;

/////////////////////////////////////////////////////////////////////////////////////
//


extern const TCHAR* const kMenuElementUIInterfaceName;// = _T("MenuElement);
class CMenuElementUI;
class CMenuWnd : public ui::WindowImplBase, public ContextMenuReceiver
{
public:
    enum PopupPosType  //鼠标点击的point属于菜单的哪个位置    1.-----.2       1左上 2右上
    {                                                 //      |     |
        //这里假定用户是喜欢智能的                            3.-----.4       3左下 4右下
        RIGHT_BOTTOM = eMenuAlignment_Right | eMenuAlignment_Bottom | eMenuAlignment_Intelligent,
        RIGHT_TOP = eMenuAlignment_Right | eMenuAlignment_Top | eMenuAlignment_Intelligent,
        LEFT_BOTTOM = eMenuAlignment_Left | eMenuAlignment_Bottom | eMenuAlignment_Intelligent,
        LEFT_TOP = eMenuAlignment_Intelligent | eMenuAlignment_Top | eMenuAlignment_Intelligent,
        //这里是normal,非智能的
        RIGHT_BOTTOM_N = eMenuAlignment_Right | eMenuAlignment_Bottom,
        RIGHT_TOP_N = eMenuAlignment_Right | eMenuAlignment_Top,
        LEFT_BOTTOM_N = eMenuAlignment_Left | eMenuAlignment_Bottom,
        LEFT_TOP_N = eMenuAlignment_Intelligent | eMenuAlignment_Top
    };
    CMenuWnd(HWND hParent = NULL);
    void Init(STRINGorID xml, LPCTSTR pSkinType, POINT point, PopupPosType popupPosType = LEFT_TOP, bool no_focus = false, CMenuElementUI* pOwner = NULL);
    void Show();
    // 重新调整菜单的大小
    void ResizeMenu();
    // 重新调整子菜单的大小
    void ResizeSubMenu();

    static ContextMenuObserver& GetMenuObserver()
    {
        static ContextMenuObserver s_context_menu_observer;
        return s_context_menu_observer;
    }
    BOOL Receive(ContextMenuParam param) override;
    
    virtual Control* CreateControl(const std::wstring& pstrClass) override;
    virtual std::wstring GetSkinFolder() override {
        return L"menu";
    }
    virtual std::wstring GetSkinFile() override {
        return m_xml.m_lpstr;
    }
    std::wstring GetWindowClassName() const override;

public:
    HWND m_hParent;
    POINT m_BasedPoint;
    PopupPosType m_popupPosType;
    STRINGorID m_xml;
    bool no_focus_;
    CMenuElementUI* m_pOwner;
    ListBox* m_pLayout;
private:
    virtual void InitWindow() override;
    void CMenuWnd::OnFinalMessage(HWND hWnd) override;
    LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);

};

class ListContainerElement;
class CMenuElementUI : public ui::ListContainerElement
{
    friend CMenuWnd;
public:
    CMenuElementUI();
    ~CMenuElementUI();

    virtual bool ButtonUp(EventArgs& msg) override;
    virtual bool MouseEnter(EventArgs& msg) override;

    virtual void PaintChild(IRenderContext* pRender, const UiRect& rcPaint) override;

    bool CheckSubMenuItem();
private:
    
    void CreateMenuWnd();
    CMenuWnd*    m_pSubWindow;
};

} // namespace nim_comp

#endif // __UIMENU_H__

ui_menu.cpp

#include "stdafx.h"
#include "ui_menu.h"

namespace nim_comp {

/////////////////////////////////////////////////////////////////////////////////////
//

Control* CMenuWnd::CreateControl(const std::wstring& pstrClass)
{
    if (pstrClass == kMenuElementUIInterfaceName)
    {
        return new CMenuElementUI();
    }
    return NULL;
}

BOOL CMenuWnd::Receive(ContextMenuParam param)
{
    switch (param.wParam)
    {
    case eMenuCloseAll:
        Close();
        break;
    case eMenuCloseThis:
    {
        HWND hParent = GetParent(m_hWnd);
        while (hParent != NULL)
        {
            if (hParent == param.hWnd)
            {
                Close();
                break;
            }
            hParent = GetParent(hParent);
        }
    }
        break;
    default:
        break;
    }

    return TRUE;
}

CMenuWnd::CMenuWnd(HWND hParent) :
    m_hParent(hParent),
    m_xml(_T("")),
    no_focus_(false),
    m_pOwner(nullptr),
    m_pLayout(nullptr)
{
}

void CMenuWnd::Init(STRINGorID xml, LPCTSTR pSkinType, POINT point, PopupPosType popupPosType, bool no_focus, CMenuElementUI* pOwner)
{
    m_BasedPoint = point;
    m_popupPosType = popupPosType;

    m_xml = xml;
    no_focus_ = no_focus;
    m_pOwner = pOwner;

    CMenuWnd::GetMenuObserver().AddReceiver(this);

    Create(m_hParent, L"NIM_DUILIB_MENU_WINDOW", WS_POPUP, WS_EX_TOOLWINDOW | WS_EX_TOPMOST, true, UiRect());
    // HACK: Don't deselect the parent's caption
    HWND hWndParent = m_hWnd;
    while (::GetParent(hWndParent) != NULL) hWndParent = ::GetParent(hWndParent);
    ::ShowWindow(m_hWnd, no_focus ? SW_SHOWNOACTIVATE : SW_SHOW);
    if (m_pOwner)
    {
        ResizeSubMenu();
    }
    else
    {
        ResizeMenu();
    }
    ::SendMessage(hWndParent, WM_NCACTIVATE, TRUE, 0L);
}

void CMenuWnd::OnFinalMessage(HWND hWnd)
{
    Window::OnFinalMessage(hWnd);
    RemoveObserver();
    if (m_pOwner != NULL) {
        m_pLayout->SelectItem(-1);
        for (int i = 0; i < m_pLayout->GetCount(); i++) {
            CMenuElementUI* pItem = static_cast<CMenuElementUI*>(m_pLayout->GetItemAt(i)); //这里确定是CMenuElementUI*,static_cast效率高
            if (pItem)
            {
                pItem->SetOwner(dynamic_cast<IListOwner*>(m_pOwner->GetParent()));//这里的父控件可能仍然是menuitem,那么置空即可
                pItem->SetWindow(m_pOwner->GetWindow(), m_pOwner, false);         //更改item的归属
//                 pItem->SetVisible(false);
                 pItem->SetInternVisible(false);
            }
        }
        m_pLayout->RemoveAll();
        m_pOwner->m_pSubWindow = NULL;
        //m_pOwner->m_uButtonState &= ~UISTATE_PUSHED;  这里可能需要替换,暂时注释
        m_pOwner->Invalidate();
    }
    ReapObjects(GetRoot());
    delete this;
}

std::wstring CMenuWnd::GetWindowClassName() const
{
    return _T("MenuWnd");
}

LRESULT CMenuWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
     case WM_KILLFOCUS:
     {
        HWND hFocusWnd = (HWND)wParam;

        BOOL bInMenuWindowList = FALSE;
        ContextMenuParam param;
        param.hWnd = GetHWND();

        ContextMenuObserver::Iterator<BOOL, ContextMenuParam> iterator(GetMenuObserver());
        ReceiverImplBase<BOOL, ContextMenuParam>* pReceiver = iterator.next();
        while (pReceiver != NULL) {
            CMenuWnd* pContextMenu = dynamic_cast<CMenuWnd*>(pReceiver);
            if (pContextMenu != NULL && pContextMenu->GetHWND() == hFocusWnd) {
                bInMenuWindowList = TRUE;
                break;
            }
            pReceiver = iterator.next();
        }

        if (!bInMenuWindowList) {
            param.wParam = eMenuCloseAll;
            GetMenuObserver().RBroadcast(param);

            return 0;
        }
    }
        break;
    case WM_KEYDOWN:
        if (wParam == VK_ESCAPE || wParam == VK_LEFT)
            Close();
        else if (wParam == VK_RIGHT)
        {
            if (m_pLayout)
            {
                int index = m_pLayout->GetCurSel();
                CMenuElementUI* pItem = dynamic_cast<CMenuElementUI*>(m_pLayout->GetItemAt(index));
                if (pItem)
                {
                    pItem->CheckSubMenuItem();
                }
            }
        }
        else if (wParam == VK_RETURN || wParam == VK_SPACE)
        {
            if (m_pLayout)
            {
                int index = m_pLayout->GetCurSel();
                CMenuElementUI* pItem = dynamic_cast<CMenuElementUI*>(m_pLayout->GetItemAt(index));
                if (pItem)
                {
                    if (!pItem->CheckSubMenuItem())
                    {
                        ContextMenuParam param;
                        param.hWnd = m_hWnd;
                        param.wParam = eMenuCloseAll;
                        CMenuWnd::GetMenuObserver().RBroadcast(param);
                    }
                }
            }
        }
        break;
    case WM_RBUTTONDOWN:
    case WM_CONTEXTMENU:
    case WM_RBUTTONUP:
    case WM_RBUTTONDBLCLK:
        return 0L;

    default:
        break;
    }

    return __super::HandleMessage(uMsg, wParam, lParam);
}

void CMenuWnd::ResizeMenu()
{
    Control* pRoot = GetRoot();
    MONITORINFO oMonitor = {};
    oMonitor.cbSize = sizeof(oMonitor);
    //点击在哪里,以哪里的屏幕为主
    ::GetMonitorInfo(::MonitorFromPoint(m_BasedPoint, MONITOR_DEFAULTTOPRIMARY), &oMonitor);
    UiRect rcWork = oMonitor.rcWork;

    CSize szAvailable = { rcWork.right - rcWork.left, rcWork.bottom - rcWork.top };
    szAvailable = pRoot->EstimateSize(szAvailable);   //这里带上了阴影窗口
    SetInitSize(szAvailable.cx, szAvailable.cy);
    UiRect rcCorner = GetShadowCorner();
    CSize szInit=szAvailable;   
    szInit.cx -= rcCorner.left + rcCorner.right;
    szInit.cy -= rcCorner.top + rcCorner.bottom; //这里去掉阴影窗口,即用户的视觉有效面积 szInit<=szAvailable
    
    CPoint point = m_BasedPoint;  //这里有个bug,由于坐标点与包含在窗口内,会直接出发mouseenter导致出来子菜单,偏移1个像素
    if (m_popupPosType & eMenuAlignment_Right)
    {
        point.x += -szAvailable.cx + rcCorner.right + rcCorner.left;
        point.x -= 1;
    }
    else if (m_popupPosType & eMenuAlignment_Left)
    {
        point.x += 1;
    }
    if (m_popupPosType & eMenuAlignment_Bottom)
    {
        point.y += -szAvailable.cy + rcCorner.bottom + rcCorner.top;
        point.y += 1;
    }
    else if (m_popupPosType & eMenuAlignment_Top)
    {
        point.y += 1;
    }
    if (m_popupPosType&eMenuAlignment_Intelligent)
    {
        if (point.x < rcWork.left)
        {
            point.x = rcWork.left;
        }
        else if (point.x + szInit.cx> rcWork.right)
        {
            point.x = rcWork.right - szInit.cx;
        }
        if (point.y < rcWork.top)
        {
            point.y = rcWork.top ;
        }
        else if (point.y + szInit.cy > rcWork.bottom)
        {
            point.y = rcWork.bottom - szInit.cy;
        }
    }
    if (!no_focus_)
    {
        SetForegroundWindow(m_hWnd);
        SetFocus(m_pLayout);
    }
    SetWindowPos(m_hWnd, HWND_TOPMOST, point.x - rcCorner.left, point.y-rcCorner.top,
        szAvailable.cx, szAvailable.cy,
        SWP_SHOWWINDOW | (no_focus_ ? SWP_NOACTIVATE : 0));
}

void CMenuWnd::ResizeSubMenu()
{
    // Position the popup window in absolute space
    RECT rcOwner = m_pOwner->GetPos();
    RECT rc = rcOwner;

    int cxFixed = 0;
    int cyFixed = 0;

    MONITORINFO oMonitor = {};
    oMonitor.cbSize = sizeof(oMonitor);
    ::GetMonitorInfo(::MonitorFromPoint(m_BasedPoint, MONITOR_DEFAULTTOPRIMARY), &oMonitor);
    UiRect rcWork = oMonitor.rcWork;
    CSize szAvailable = { rcWork.right - rcWork.left, rcWork.bottom - rcWork.top };

    for (int it = 0; it < m_pLayout->GetCount(); it++) {
        //取子菜单项中的最大值作为菜单项
        CMenuElementUI* pItem = dynamic_cast<CMenuElementUI*>(m_pLayout->GetItemAt(it));
        if (pItem)
        {
            SIZE sz = pItem->EstimateSize(szAvailable);
            cyFixed += sz.cy;

            if (cxFixed < sz.cx)
                cxFixed = sz.cx;
        }
    }
    UiRect rcCorner = GetShadowCorner();
    RECT rcWindow;
    GetWindowRect(m_pOwner->GetWindow()->GetHWND(), &rcWindow);
    //去阴影
    {
        rcWindow.left += rcCorner.left;
        rcWindow.right -= rcCorner.right;
        rcWindow.top += rcCorner.top;
        rcWindow.bottom -=  rcCorner.bottom;
    }

    ::MapWindowRect(m_pOwner->GetWindow()->GetHWND(), HWND_DESKTOP, &rc);
    
    rc.left = rcWindow.right;
    rc.right = rc.left + cxFixed;
    rc.bottom = rc.top + cyFixed;

    bool bReachBottom = false;
    bool bReachRight = false;
    LONG chRightAlgin = 0;
    LONG chBottomAlgin = 0;

    RECT rcPreWindow = { 0 };
    ContextMenuObserver::Iterator<BOOL, ContextMenuParam> iterator(GetMenuObserver());
    ReceiverImplBase<BOOL, ContextMenuParam>* pReceiver = iterator.next();
    while (pReceiver != NULL) {
        CMenuWnd* pContextMenu = dynamic_cast<CMenuWnd*>(pReceiver);
        if (pContextMenu != NULL) {
            GetWindowRect(pContextMenu->GetHWND(), &rcPreWindow);  //需要减掉阴影

            bReachRight = (rcPreWindow.left + rcCorner.left) >= rcWindow.right;
            bReachBottom = (rcPreWindow.top + rcCorner.top) >= rcWindow.bottom;
            if (pContextMenu->GetHWND() == m_pOwner->GetWindow()->GetHWND()
                || bReachBottom || bReachRight)
                break;
        }
        pReceiver = iterator.next();
    }
    if (bReachBottom)
    {
        rc.bottom = rcWindow.top;
        rc.top = rc.bottom - cyFixed;
    }

    if (bReachRight)
    {
        rc.right = rcWindow.left;
        rc.left = rc.right - cxFixed;
    }

    if (rc.bottom > rcWork.bottom)
    {
        rc.bottom = rc.top;
        rc.top = rc.bottom - cyFixed;
    }

    if (rc.right > rcWork.right)
    {
        rc.right = rcWindow.left;
        rc.left = rc.right - cxFixed;
    }

    if (rc.top < rcWork.top)
    {
        rc.top = rcOwner.top;
        rc.bottom = rc.top + cyFixed;
    }

    if (rc.left < rcWork.left)
    {
        rc.left = rcWindow.right;
        rc.right = rc.left + cxFixed;
    }

    SetWindowPos(m_hWnd, HWND_TOPMOST, rc.left-rcCorner.left, rc.top-rcCorner.top,
        rc.right - rc.left, rc.bottom - rc.top,
        SWP_SHOWWINDOW);

    SetForegroundWindow(m_hWnd);
    SetFocus(m_pLayout);
}

void CMenuWnd::Show()
{
    MONITORINFO oMonitor = {};
    oMonitor.cbSize = sizeof(oMonitor);
    ::GetMonitorInfo(::MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTOPRIMARY), &oMonitor);
    UiRect rcWork = oMonitor.rcWork;
    UiRect monitor_rect = oMonitor.rcMonitor;
    ui::CSize szInit = { rcWork.right - rcWork.left, rcWork.bottom - rcWork.top };
    szInit = GetRoot()->EstimateSize(szInit);
    szInit.cx -= GetShadowCorner().left + GetShadowCorner().right;
    szInit.cy -= GetShadowCorner().top + GetShadowCorner().bottom;
    if (m_popupPosType == RIGHT_BOTTOM)
    {
        if (m_BasedPoint.y + szInit.cy > monitor_rect.bottom)
        {
            m_BasedPoint.y -= szInit.cy;
        }
    }
    else if (m_popupPosType == RIGHT_TOP)
    {
        if (m_BasedPoint.y - szInit.cy >= monitor_rect.top)
        {
            m_BasedPoint.y -= szInit.cy;
        }
    }
    else
    {
        //兼容老版本
        return;
    }
    UiRect rc;
    rc.left = m_BasedPoint.x;
    rc.top = m_BasedPoint.y;
    if (rc.top < monitor_rect.top)
    {
        rc.top = monitor_rect.top;
    }

    //判断是否超出屏幕
    if (rc.left > monitor_rect.right - szInit.cx)
    {
        rc.left = monitor_rect.right - szInit.cx;
    }
    if (rc.left < monitor_rect.left)
    {
        rc.left = monitor_rect.left;
    }
    rc.right = rc.left + szInit.cx;
    rc.bottom = rc.top + szInit.cy;

    SetPos(rc, false, SWP_SHOWWINDOW | (no_focus_ ? SWP_NOACTIVATE : 0), HWND_TOPMOST, false);
    if (!no_focus_)
        SetForegroundWindow(m_hWnd);
}


void CMenuWnd::InitWindow()
{
    if (m_pOwner)
    {
        m_pLayout = dynamic_cast<ListBox*>(FindControl(L"submenu"));
        ASSERT(m_pLayout);
        m_pLayout->SetAutoDestroy(false);

        for (int i = 0; i < m_pOwner->GetCount(); i++) {
            CMenuElementUI* subMenuItem = dynamic_cast<CMenuElementUI*>(m_pOwner->GetItemAt(i));
            if (subMenuItem && subMenuItem->IsVisible())
            {
                //此时子菜单item属于2个子菜单,注意生命周期的维护,子菜单窗口退出不能销毁控件,需要归还原控件,
                //此时子菜单item的父控件是准的,但父控件可能不是Vlist,SetOwner的入参是Vlist,这时owner置空
                //见OnFinalMessage
                m_pLayout->Add(subMenuItem); //内部会调用subMenuItem->SetOwner(m_pLayout); 会调用SetWindows,改变了归属窗口、父控件。
            }
        }
    }
    else
    {
        m_pLayout = dynamic_cast<ListBox*>(m_pRoot);
        if (m_pLayout == NULL)
        {
            //允许外面套层阴影
            if (m_pRoot->GetCount()>0)
            {
                m_pLayout = dynamic_cast<ListBox*>(m_pRoot->GetItemAt(0));
            }
        }
        ASSERT(m_pLayout);
    }
}



// MenuElementUI
const TCHAR* const kMenuElementUIInterfaceName = _T("MenuElement");

CMenuElementUI::CMenuElementUI() :
m_pSubWindow(nullptr)
{
    m_bMouseChildEnabled = false;
}

CMenuElementUI::~CMenuElementUI()
{}

bool CMenuElementUI::ButtonUp(EventArgs& msg)
{
    std::weak_ptr<nbase::WeakFlag> weakFlag = m_pWindow->GetWeakFlag();
    bool ret = __super::ButtonUp(msg);
    if (ret && !weakFlag.expired()) {
        //这里处理下如果有子菜单则显示子菜单
        if (!CheckSubMenuItem())
        {
            ContextMenuParam param;
            param.hWnd = GetWindow()->GetHWND();
            param.wParam = eMenuCloseAll;
            CMenuWnd::GetMenuObserver().RBroadcast(param);
        }
    }

    return ret;
}


bool CMenuElementUI::MouseEnter(EventArgs& msg)
{
    std::weak_ptr<nbase::WeakFlag> weakFlag = m_pWindow->GetWeakFlag();
    bool ret = __super::MouseEnter(msg);
    if (ret && !weakFlag.expired()) {
        //这里处理下如果有子菜单则显示子菜单
        if (!CheckSubMenuItem())
        {
            ContextMenuParam param;
            param.hWnd = GetWindow()->GetHWND();
            param.wParam = eMenuCloseThis;
            CMenuWnd::GetMenuObserver().RBroadcast(param);
            //m_pOwner->SelectItem(GetIndex(), true);  有些老版本attachselect会触发
            //这里得把之前选中的置为未选中
            m_pOwner->SelectItem(-1, false);
        }
    }

    return ret;
}

void CMenuElementUI::PaintChild(IRenderContext* pRender, const UiRect& rcPaint)
{
    UiRect rcTemp;
    if (!::IntersectRect(&rcTemp, &rcPaint, &m_rcItem)) return;

    for (auto it = m_items.begin(); it != m_items.end(); it++) {
        //尝试转CMenuElementUI
        CMenuElementUI* subMenuItem = dynamic_cast<CMenuElementUI*>(*it);
        if (subMenuItem)
        {
            continue;
        }
        Control* pControl = *it;
        if (!pControl->IsVisible()) continue;
        pControl->AlphaPaint(pRender, rcPaint);
    }
}

bool CMenuElementUI::CheckSubMenuItem()
{
    bool hasSubMenu = false;
    for (int i = 0; i < GetCount(); ++i)
    {
        CMenuElementUI* subMenuItem = dynamic_cast<CMenuElementUI*>(GetItemAt(i));
        if (subMenuItem )
        {
            //subMenuItem->SetVisible(true);
            subMenuItem->SetInternVisible(true);
            hasSubMenu = true;
        }
    }
    if (hasSubMenu)
    {
        m_pOwner->SelectItem(GetIndex(), true);
        CreateMenuWnd();
    }
    return hasSubMenu;
}

void CMenuElementUI::CreateMenuWnd()
{
    if (m_pSubWindow) return;
    m_pSubWindow = new CMenuWnd(GetWindow()->GetHWND());

    ContextMenuParam param;
    param.hWnd =GetWindow()->GetHWND();
    param.wParam = eMenuCloseThis;
    CMenuWnd::GetMenuObserver().RBroadcast(param);

    m_pSubWindow->Init(_T("submenu.xml"), _T(""), CPoint(), CMenuWnd::RIGHT_BOTTOM, false, this);
}

} // namespace ui

observer_impl_base.hpp

#ifndef OBSERVER_IMPL_BASE_HPP
#define OBSERVER_IMPL_BASE_HPP

#include <map>

template <typename ReturnT, typename ParamT>
class ReceiverImplBase;

template <typename ReturnT, typename ParamT>
class ObserverImplBase
{
public:
    virtual void AddReceiver(ReceiverImplBase<ReturnT, ParamT>* receiver) = 0;
    virtual void RemoveReceiver(ReceiverImplBase<ReturnT, ParamT>* receiver) = 0;
    virtual ReturnT Broadcast(ParamT param) = 0;
    virtual ReturnT RBroadcast(ParamT param) = 0;
    virtual ReturnT Notify(ParamT param) = 0;
};

template <typename ReturnT, typename ParamT>
class ReceiverImplBase
{
public:
    virtual void AddObserver(ObserverImplBase<ReturnT, ParamT>* observer) = 0;
    virtual void RemoveObserver() = 0;
    virtual ReturnT Receive(ParamT param) = 0;
    virtual ReturnT Respond(ParamT param, ObserverImplBase<ReturnT, ParamT>* observer) = 0;
};

template <typename ReturnT, typename ParamT>
class ReceiverImpl;

template <typename ReturnT, typename ParamT>
class ObserverImpl : public ObserverImplBase<ReturnT, ParamT>
{
    template <typename ReturnT, typename ParamT>
    friend class Iterator;
public:
    ObserverImpl()
    {}

    virtual ~ObserverImpl()    {}

    virtual void AddReceiver(ReceiverImplBase<ReturnT, ParamT>* receiver)
    {
        if (receiver == NULL)
            return;

        receivers_.push_back(receiver);
        receiver->AddObserver(this);
    }

    virtual void RemoveReceiver(ReceiverImplBase<ReturnT, ParamT>* receiver)
    {
        if (receiver == NULL)
            return;

        ReceiversVector::iterator it = receivers_.begin();
        for (; it != receivers_.end(); ++it)
        {
            if (*it == receiver)
            {
                receivers_.erase(it);
                break;
            }
        }
    }

    virtual ReturnT Broadcast(ParamT param)
    {
        ReceiversVector::iterator it = receivers_.begin();
        for (; it != receivers_.end(); ++it)
        {
            (*it)->Receive(param);
        }

        return ReturnT();
    }

    virtual ReturnT RBroadcast(ParamT param)
    {
        ReceiversVector::reverse_iterator it = receivers_.rbegin();
        for (; it != receivers_.rend(); ++it)
        {
            (*it)->Receive(param);
        }

        return ReturnT();
    }

    virtual ReturnT Notify(ParamT param)
    {
        ReceiversVector::iterator it = receivers_.begin();
        for (; it != receivers_.end(); ++it)
        {
            (*it)->Respond(param, this);
        }

        return ReturnT();
    }

    template <typename ReturnT, typename ParamT>
    class Iterator
    {
        ObserverImpl<ReturnT, ParamT> & _tbl;
        DWORD index;
        ReceiverImplBase<ReturnT, ParamT>* ptr;
    public:
        Iterator( ObserverImpl & table )
            : _tbl( table ), index(0), ptr(NULL)
        {}

        Iterator( const Iterator & v )
            : _tbl( v._tbl ), index(v.index), ptr(v.ptr)
        {}

        ReceiverImplBase<ReturnT, ParamT>* next()
        {
            if ( index >= _tbl.receivers_.size() )
                return NULL;

            for ( ; index < _tbl.receivers_.size(); )
            {
                ptr = _tbl.receivers_[ index++ ];
                if ( ptr )
                    return ptr;
            }
            return NULL;
        }
    };

protected:
    typedef std::vector<ReceiverImplBase<ReturnT, ParamT>*> ReceiversVector;
    ReceiversVector receivers_;
};


template <typename ReturnT, typename ParamT>
class ReceiverImpl : public ReceiverImplBase<ReturnT, ParamT>
{
public:
    ReceiverImpl()
    {}

    virtual ~ReceiverImpl()    {}

    virtual void AddObserver(ObserverImplBase<ReturnT, ParamT>* observer)
    {
        observers_.push_back(observer);
    }

    virtual void RemoveObserver()
    {
        ObserversVector::iterator it = observers_.begin();
        for (; it != observers_.end(); ++it)
        {
            (*it)->RemoveReceiver(this);
        }
    }

    virtual ReturnT Receive(ParamT param)
    {
        return ReturnT();
    }

    virtual ReturnT Respond(ParamT param, ObserverImplBase<ReturnT, ParamT>* observer)
    {
        return ReturnT();
    }

protected:
    typedef std::vector<ObserverImplBase<ReturnT, ParamT>*> ObserversVector;
    ObserversVector observers_;
};

#endif // OBSERVER_IMPL_BASE_HPP
原文地址:https://www.cnblogs.com/xuhuajie/p/12029446.html