完整代码见: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 = << ,
eMenuAlignment_Top = << ,
eMenuAlignment_Right = << ,
eMenuAlignment_Bottom = << ,
eMenuAlignment_Intelligent = << //智能的防止被遮蔽
}; 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(-);
for (int i = ; 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 ;
}
}
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 -= ;
}
else if (m_popupPosType & eMenuAlignment_Left)
{
point.x += ;
}
if (m_popupPosType & eMenuAlignment_Bottom)
{
point.y += -szAvailable.cy + rcCorner.bottom + rcCorner.top;
point.y += ;
}
else if (m_popupPosType & eMenuAlignment_Top)
{
point.y += ;
}
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 : ));
} void CMenuWnd::ResizeSubMenu()
{
// Position the popup window in absolute space
RECT rcOwner = m_pOwner->GetPos();
RECT rc = rcOwner; int cxFixed = ;
int cyFixed = ; 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 = ; 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 = ;
LONG chBottomAlgin = ; RECT rcPreWindow = { };
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 : ), 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 = ; 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()>)
{
m_pLayout = dynamic_cast<ListBox*>(m_pRoot->GetItemAt());
}
}
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(-, 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 = ; 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) = ;
virtual void RemoveReceiver(ReceiverImplBase<ReturnT, ParamT>* receiver) = ;
virtual ReturnT Broadcast(ParamT param) = ;
virtual ReturnT RBroadcast(ParamT param) = ;
virtual ReturnT Notify(ParamT param) = ;
}; template <typename ReturnT, typename ParamT>
class ReceiverImplBase
{
public:
virtual void AddObserver(ObserverImplBase<ReturnT, ParamT>* observer) = ;
virtual void RemoveObserver() = ;
virtual ReturnT Receive(ParamT param) = ;
virtual ReturnT Respond(ParamT param, ObserverImplBase<ReturnT, ParamT>* observer) = ;
}; 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(), 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

Duilib的多级菜单实现(网易云信版本)的更多相关文章

  1. Duilib的圆环形 进度条 实现(网易云信版本)

    /** @file CircleProgress.h* @brief 圆环型进度条控件,圆环中间可以有文本(如85%)* @copyright (c) 2019-2022, NetEase Inc. ...

  2. Duilib的控件拖拽排序,支持跨容器拖拽(网易云信版本)

    完整代码见:https://github.com/netease-im/NIM_Duilib_Framework/pull/151 核心代码(思路): appitem.h #pragma once # ...

  3. 网易云信Duilib开发实践和Windows应用界面开发框架源码开源介绍

    序言 Duilib介绍 Duilib是windows平台下的一款轻量级directUI开源库(遵循BSD协议),完全免费,可用于商业软件开发,只需在软件包里附上协议文件即可.Duilib可以简单方便地 ...

  4. 类似微信聊天小程序-网易云信,IM DEMO小程序版本

    类似微信聊天小程序-网易云信,IM DEMO小程序版本 代码地址: https://github.com/netease-im/NIM_Web_Weapp_Demo 云信IM DEMO 小程序版本 ( ...

  5. Jquery多级菜单插件Slimmenu使用说明

    Jquery多级菜单插件Slimmenu使用说明 现在扁平化设计逐渐的成为了趋势,不管是pc web,还是移动互联网的应用开发,都在研究和设计Flat ui, 这里有一篇文章说明扁平化的设计的一些想法 ...

  6. 子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

    本文原文内容来自InfoQ的技术分享,本次有修订.勘误和加工,感谢原作者的分享. 1.前言 自从2018年8月20日子弹短信在锤子发布会露面之后(详见<老罗最新发布了“子弹短信”这款IM,主打熟 ...

  7. 网易云信&七鱼市场总监姜菡钰:实战解读增长黑客在B端业务的运用

    近些年 ,随着互联网的迅速崛起,“增长黑客”一词逐渐映入大众的眼帘,并成为了最热门的话题之一.从2018年开始,线上流量触达天花板,引流之争的激烈程度空前高涨,企业为了获得更多的关注,产品的比拼.流量 ...

  8. 微信小程序开发中的二三事之网易云信IMSDK DEMO

    本文由作者邹永胜授权网易云社区发布. 简介 为了更好的展示我们即时通讯SDK强悍的能力,网易云信IM SDK微信小程序DEMO的开发就提上了日程.用产品的话说就是: 云信 IM 小程序 SDK 的能力 ...

  9. jquery自定义插件-参数化配置多级菜单导航栏插件

    1 自定义菜单导航栏插件的必要性 看图说话,下面是利用自定义的菜单导航栏插件simpleMenu创建的网站导航示例: 插件默认提供的是如上图的导航栏样式,即一二级菜单为横向分布:三四级菜单为纵向分布. ...

随机推荐

  1. luogu 3857 [TJOI2008]彩灯 线性基

    可以将每一个开关控制的灯的序列看作是0/1组成的二进制. 由于灯的开和关是满足异或的性质的,所以直接求一下线性基大小即可. 答案为 $2^{size}.$ #include <cstdio> ...

  2. 错误/异常:org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save() 的解决方法

    1.错误/异常视图 错误/异常描述:id的生成错误,在调用save()方法之前,必须先生成id. 2.解决方法     在对应的实体类的主键(id)的get方法上加上:@GeneratedValue( ...

  3. javascript中“use strict”的好处和坏处

    1.为什么使用严格模式? 消除javascript语法的一些不合理.不严谨之处,减少一些怪异行为: 消除代码运行的不安全之处,保证代码的运行: 提高编译效率,增加运行效率: 为未来新版本的javasc ...

  4. GUI输入数据并保存

    from tkinter import * def write_to_file(): fileContent = open("deliveries.txt","a&quo ...

  5. 找出所有从根节点到叶子节点路径和等于n的路径并输出

    //找出所有从根节点到叶子节点路径和等于n的路径并输出 Stack<Node> stack = new Stack<Node>(); public void findPath( ...

  6. vue 引入jQuery

    http://blog.csdn.net/cly153239/article/details/53067433 vue-cli webpack全局引入jquery 首先在package.json里加入 ...

  7. JDBC接口核心的API

    java.sql.*   和  javax.sql.* Driver接口: 表示java驱动程序接口.所有的具体的数据库厂商要来实现此接口. |- connect(url, properties):  ...

  8. Django之模型的高级用法

    from django.db import models class Publisher(models.Model): name = models.CharField(max_length=30) a ...

  9. 运维之思科篇——NAT基础配置

    一. NAT(网络地址转换) 1. 作用:通过将内部网络的私有IP地址翻译成全球唯一的公网IP地址,使内部网络可以连接到互联网等外部网络上. 2. 优点: 节省公有合法IP地址 处理地址重叠 增强灵活 ...

  10. 在Excel工作表单元格中引用当前工作表名称

    在Excel工作表单元格中引用当前工作表名称 有多份Excel表格表头标题都为"××学校第1次拉练考试××班成绩表",由于工作表结构都是一样的,所以我每次都是复制工作表然后编辑修改 ...