http://blog.csdn.net/wangchyz/article/details/6795419 简单的dui例子实现
http://bbs.csdn.net/topics/340239295 dui的简单帖子总结
http://bbs.pediy.com/showthread.php?t=124957 dui的例子实现
http://www.buguw.com/%e8%bd%ac-directui%e7%9a%84%e5%88%9d%e6%ad%a5%e5%88%86%e6%9e%90.html dui的函数总结
http://sogoodlife.blog.163.com/blog/static/45850260201261154229868/ dui对xml文件的解析过程
http://sogoodlife.blog.163.com/blog/static/458502602012610508332/ dui的消息处理过程
http://blog.csdn.net/jofranks/article/details/11853507 dui的处理流程
http://blog.sina.com.cn/s/blog_4c3b2dc20100s8w6.html dui的处理流程
以viksoe的duilib为例总结下。
分两步分析direct ui的原理,dui程序的启动和对xml布局文件的解析
一。dui程序的启动
窗口create之后,进入CPaintManagerUI::MessageLoop.这个消息类似mfc的消息循环机制。
先在 CPaintManagerUI::TranslateMessage中取出本窗口的消息,然后进入所有的CPaintManagerUI::PreMessageHandler函数,过滤出快捷按键,alt键
的消息.接着进入大窗口注册的CWindowWnd::__WndProc窗口过程,处理WM_NCCREATE消息。在这个窗口过程中进入每个窗口注册的MessageHandler函数消息,即每个类重载的HandleMessage函数,比如duilib的例子
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if( uMsg == WM_CREATE ) {
m_pm.Init(m_hWnd);
CDialogBuilder builder;
CControlUI* pRoot = builder.Create(_T("test1.xml"), (UINT)0, NULL, &m_pm);
ASSERT(pRoot && "Failed to parse XML");
m_pm.AttachDialog(pRoot);
m_pm.AddNotifier(this);
Init();
return 0;
}
xxxx
}
可以看到这里调用CDialogBuilder::Create用来加载解析xml布局文件。
在这个HandleMessage最后调用CPaintManagerUI::MessageHandler来处理各种WM_CLOSE,WM_PAINT,WM_SIZE,
WM_MOUSEMOVE,WM_LBUTTONDOWN等各种按键和鼠标消息。比如处理WM_LBUTTONDOWN,
先获取鼠标坐标位置,根据这个坐标通过FindControl找到拥有鼠标位置的控件。找到后,给找到的控件设置焦点,
然后通过 pControl->Event,给控件发一个消息,类似mfc的msg,包含type、ptMouse、wParam、chKey、lParam等。
接着进入控件的CButtonUI::DoEvent函数,
void CButtonUI::DoEvent(TEventUI& event)
{
if( !IsMouseEnabled() && event.Type > UIEVENT__MOUSEBEGIN && event.Type < UIEVENT__MOUSEEND ) {
if( m_pParent != NULL ) m_pParent->DoEvent(event);
else CLabelUI::DoEvent(event);
return;
}
xxxx
if( event.Type == UIEVENT_BUTTONUP )
{
if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
if( ::PtInRect(&m_rcItem, event.ptMouse) ) Activate();
m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);
Invalidate();
}
return;
}
if( event.Type == UIEVENT_KILLFOCUS )
xxxxxx
}
Invalidate是触发一次重绘,在重绘前先通过SendNotify去注册的控件中通知消息的发生。
void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/)
{
xxx
for( int i = 0; i < m_aNotifiers.GetSize(); i++ ) {
static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
}
xxx
}
可以看出系统向所有注册INotifyUI接口的发送消息。所以如果我们想处理这种鼠标,按键消息的话,需要实现
INotifyUI接口,然后注册。比如
void CSearchPageWnd::Notify(TNotifyUI& msg)
{
if( msg.sType == _T("click") )
{
if( msg.pSender->GetName() == _T("ok") ) {
CStandardPageWnd* pWindow = new CEditPageWnd;
pWindow->Create(m_hWnd, NULL, UI_WNDSTYLE_FRAME, 0L);
}
if( msg.pSender->GetName() == _T("cancel") ) Close();
}
CStandardPageWnd::Notify(msg);
}
处理单击消息,消息来源是ok按键,
从上看出,自己实现注册消息处理函数主要从Notify和HandleMessage函数中。
二。对xml布局文件的解析
上面跟踪到调用CDialogBuilder::Create用来加载解析xml布局文件.展示窗口。进入该函数,
CControlUI* CDialogBuilder::Create(xxx)
{
CMarkupNode root = m_xml.GetRoot();
for( CMarkupNode node = root.GetChild() ; node.IsValid(); node = node.GetSibling() ) {
pstrClass = node.GetName();
if( _tcscmp(pstrClass, _T("Image")) == 0 ) {
pstrName = root.GetAttributeName(i);
pstrValue = root.GetAttributeValue(i);
if( _tcscmp(pstrName, _T("size")) == 0 ) {
pManager->SetInitSize(cx, cy);
xxxxxxxx
可以看出是解析xml文件,取出节点,然后看节点的名字pstrClass,匹配时image,Font,Default,Window等各种控件。
取出控件名字后,取出pstrName属性名,根据属性名字调用对应的函数,比如上面进入SetInitSize函数后
::SetWindowPos(m_hWndPaint, NULL, 0, 0, cx, cy, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
设置窗口大小。
最后递归的调用 CDialogBuilder::_Parse函数解析子节点,在这个函数中,根据节点名称的字符个数,在堆中new 出对应的控件,
没有新的窗口句柄,如下所示。
SIZE_T cchLen = _tcslen(pstrClass);
switch( cchLen ) {
case 4:
if( _tcscmp(pstrClass, _T("Edit")) == 0 ) pControl = new CEditUI;
else if( _tcscmp(pstrClass, _T("List")) == 0 ) pControl = new CListUI;
else if( _tcscmp(pstrClass, _T("Text")) == 0 ) pControl = new CTextUI;
break;
case 5:
if( _tcscmp(pstrClass, _T("Combo")) == 0 )
子控件创建出来后通过pContainer->Add(pControl) 添加到父容器控件中,然后处理子控件的属性信息,如下
if( node.HasAttributes() ) {
xxxx
for( int i = 0; i < nAttributes; i++ ) {
pControl->SetAttribute(node.GetAttributeName(i), node.GetAttributeValue(i));
}
解析完xml文件后,对应的窗口就创建出来了,然后注册notify接口,ShowWindow就可以响应消息了。
通过分析,自己实现dui的例子时,三步做。先包含dui的库,然后实现notify和HandleMessage响应各种消息函数。
最后编写xml布局文件,在程序入口处create然后ShowWindow.
wtl的界面实现,类似direct ui,需要继承CFrameWindowImpl,CMessageFilter等类。
在CFrameWindowImpl中实现了消息循环的过程,只需要create和showWindow就可以展现窗口。
通过DECLARE_FRAME_WND_CLASS实现窗口资源和窗口类的绑定,然后通过继承覆盖,父类调用自己的实现类的窗口处理函数。
---写于2013.11.17