有个坑爹的说法:其实Direct UI只是一个思想,要实现这个思想,还要靠自己。

采用windowless方式用api或gdi实现ui的绘制。

DirectUI意为直接在父窗口上绘图(Paint on parent dc directly)。

子窗口不以窗口句柄的形式创建,只是逻辑上的窗口,绘制在父窗口之上。

DirectUI技术的实现步骤和难点:
1、窗口的子类化,截获窗口的消息。
2、封装自己的控件,并将自己的控件绘制到该窗口上
3、封装窗口的消息,并分发到自己的控件上,让自己的控件根据消息进行相应和绘制
4、根据不同的行为发送自定义消息给窗口,以便程序进行调用。
5、一般窗口上控件的组织使用XML来描述

首先,要实现一个好的DirectUI,必须设计出一套逻辑性、可编程性及可扩展性非常强的层布局器(Layout Manager ),为什么这样说呢?
因为DirectUI的核心设计思想就是界面自绘,既然很多控件都要用自绘的形式表现在界面上,那么控件与控件之间,一定存在某些逻辑布局上的依赖关系,我们可以把一个app分解成3D构架,横向为X,纵向为Y,内外为Z,那么好了,A控件跟B控件的X、Y相同,但是我要实现成B为A打底,成为A的背景(当然A会绘制成透明背景模式),那么A与B之间只存在Z的关系,即层次性。由此可以看出,一个好的Layout Manager是DirectUI绘制必不可少的关键点,可以将UI所要表达的所有控件(windowless,全部需要绘制出来的)层次分明的一一展现。(题外话:你可以通过任何手段将控件的逻辑位置等信息解析到LM中)

其次,ReDraw,第一点我已经阐明了控件之间层的关系,接下来自然是要通过LM(Layout Manager)将控件一一表现到界面上。如果在你的项目中木有LM,那么你就惨了,你无法判断应该用什么顺序依次往你的程序界面上绘制控件:(,就像一张空白的画纸,而你想往上面画很多漂亮的猫猫狗狗,那究竟是先画猫还是先画狗呢。。。打住,通过LM,你完全很清楚先画什么后画什么,LM设计的时候可以要求传入控件要有父控件节点,哦可!那么在ReDraw的时候,先遍历LM,绘制父节点,再绘制子节点,这样一来,界面就基本具有逻辑雏形了。

最后,该绘制的控件已经都绘制出来了,剩下唯一需要你实现的就是如何响应用户对这些控件的操作,这个应该不用多说了,无非就是捕获用户当前LButtonDone/LButtonUp等鼠标Click事件及rect,然后根据LM反馈出属于那种控件,以及该控件对应的事件即可。

以下转载地址:http://blog.sina.com.cn/s/blog_4c3b2dc20100s8w6.html

我对Direct UI里面的消息循环的理解。首先Direct UI是在一个大的窗体里面自绘所有的控件,而这里的控件不同与Windows标准控件,因为他们不是CreateWindow出来的,所以就没有句柄。然后,这个大的窗体还是有消息循环的,我们只把这个窗体的系统消息提取出来,进行处理。因为这个窗体里没有系统控件,所以系统消息就相对单一了,只有鼠标,键盘,还有就是这个大窗体的create paint消息等。我们可以通过一个鼠标单击事件来跟踪一下DUI是如何处理消息的。

一个DUI程序启动,先进入 CPaintManagerUI::MessageLoop, 然后在!CPaintManagerUI::TranslateMessage(&msg)这个函数中提取出本窗体的消息,然后进入所有CPaintManagerUI::PreMessageHandler函数,这里我们需要截获快捷键的信息,比如说TAB、ESC等等。接着进入大窗体注册的callback函数__WndProc,处理一下uMsg == WM_NCCREATE 的问题,这段我没看懂,然后跳到CWindowWnd::HandleMessage 这个都是被用户实现类所重载掉,来实现WM_CREATE等一些消息,比如

   if( uMsg == WM_CREATE ) {
m_pm.Init(m_hWnd);
CDialogBuilder builder;
CControlUI* pRoot = builder.Create(GetDialogResource());
ASSERT(pRoot && "Failed to parse XML");
m_pm.AttachDialog(pRoot);
m_pm.AddNotifier(this);
Init();
return 0;
}
LRESULT lRes = 0;
if( m_pm.MessageHandler(uMsg, wParam, lParam, lRes) ) return lRes;
return CWindowWnd::HandleMessage(uMsg, wParam, lParam);


这里的GetDialogResource()就是返回的XML的页面构成代码,然后就进入了CPaintManagerUI::MessageHandle 现在,我们就要从这里开始处理消息了。处理消息之前,我们调用m_aMessageFilters[i])->MessageHandler去过滤一些消息。然后这里会处理鼠标移动,size paint等一些消息。我们先看一下WM_LBUTTONDOWN的消息处理,首先,获取到鼠标坐标,然后根据这个坐标来找到那个坐标下的控件,如果没有,直接返回,如果有,我们会建一个 event 的结构体,包含消息的type wParam lParam Mousepoint timestamp wKeyState 还有一个pSender的,估计是tooltip之类的,需要获取两次消息。而我们的WM_LBUTTONDOWN里面没有,信息保存完后,我们会调用找到的控件的Event事件。接着,我们来到了

void CButtonUI::Event(TEventUI& event)
{
if( event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK )//按下
{
if( ::PtInRect(&m_rcItem, event.ptMouse) && IsEnabled() ) { //判断是否在区域内,调用IsEnable
m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED; //修改外观
Invalidate();
}
} 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();
}
}

Invalidate()是触发一次改区域的重绘,在重绘之前,我们先判断鼠标是否在当前区域,然后,修改 一些外观。当UIEVENT_BUTTONUP 事件后,我们会进入Activate()

bool CButtonUI::Activate()
{
if( !CControlUI::Activate() ) return false;
if( m_pManager != NULL ) m_pManager->SendNotify(this, _T("click"));
return true;
}


这里的notify就是Direct UI 内部去相应系统消息所传递的消息了。然后我们看看SendNotify函数


void CPaintManagerUI::SendNotify(CControlUI* pControl, LPCTSTR pstrMessage, WPARAM wParam , LPARAM lParam )
{
TNotifyUI Msg;
Msg.pSender = pControl;
Msg.sType = pstrMessage;
Msg.wParam = 0;
Msg.lParam = 0;
SendNotify(Msg);
} void CPaintManagerUI::SendNotify(TNotifyUI& Msg)
{
// Pre-fill some standard members
Msg.ptMouse = m_ptLastMousePos;
Msg.dwTimestamp = ::GetTickCount();
// Allow sender control to react
Msg.pSender->Notify(Msg);
// Send to all listeners
for( int i = 0; i < m_aNotifiers.GetSize(); i++ ) {
static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg); //发送给所有接口
}
}

我们会看到,系统向所有的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);
}

这里我们要保证name的唯一,因为这是对所有的接口转发的,否则其他相同name的控件也会相应这个消息。

最后,我们来和MFC的消息循环比较一下。没有了标准控件,DUI里面的系统消息就少了很多,而且每个控件也没有自己的消息循环,这样相互操作起来更加容易。而MFC也是想把所有的系统消息统一管理。首先,MFC用CBT的hook截取了标准控件里面的所有消息,然后用偷天换狗日的方法,改写了这个控件的callback函数。这样不同控件的系统消息就可以在一个平台上交流了,而不是只存在在自己的消息循环里面。然后用struct+macro来实现,我们在设计窗口双击一下,就可以添加click的函数。

这样处理click事件的方法确实非常方便,相比Win32的switch,或者说上面DUI的Notify方式的来说。但是,这种的方便,设计起来是非常的麻烦。比如说我们实现click的处理函数,我们要把这个控件的ID和click函数指针,还有相应的参数放在那个struct里面,然后,把所有这样的struct连成一张网。当MFC截获到这个click事件后,遍历这张网,找到对应的函数,传入相应的参数。

大家知道MFC都是基于继承,那么这个消息的传递也要继承,比如说我自己继承的button,我不写onFocus事件,他也会有这个事件。如果消息真的像class一样继承,那就坑爹了。因为要继承,就要用class,而且会有虚函数表,那虚函数表的内存占用就是个问题。所以,MFC用struct,并且,通过一张网来处理继承这样的关系处理得非常巧妙。而从这点我们可以看出,面向对象只是一种思想,与语言无关。你也可以在C里面通过struct来描述面向对象的继承的思想。

说到最后,我也就是对DUI的消息循环简单的描述一下,对于DUI可以控件套控件,还有一些类的组织,还是相当模糊的。以后有时间还会继续了解的。

[置顶] Direct UI的更多相关文章

  1. [置顶] Kendo UI开发教程: Kendo UI 示例及总结

    前面基本介绍完Kendo UI开发的基本概念和开发步骤,Kendo UI的示例网站为http://demos.kendoui.com/ ,包含了三个部分 Web DemoMobile DemoData ...

  2. [置顶] 安卓UI组件之ListView详解

    ListView是很常见的一个UI组件,在许多App中都很常用,其意思就是可滚动的列表,使用ListView必须使用Adapter(适配器),常用的适配器友谊ArrayAdapter,SimpleAd ...

  3. [置顶] Android开发笔记(成长轨迹)

    分类: 开发学习笔记2013-06-21 09:44 26043人阅读 评论(5) 收藏 Android开发笔记 1.控制台输出:called unimplemented OpenGL ES API ...

  4. UITableView设置Cell左滑多个按钮(编辑,删除,置顶等)

    一.iOS7不支持cell多个按钮这个时候可以使用一个三方库JZTableViewRowAction,引用类扩展文件并实现其代理方法 JZTableViewRowAction下载地址:http://d ...

  5. [置顶] Silverlight之控件应用总结(一)(3)

    [置顶] Silverlight之控件应用总结(一)(3) 分类: 技术2012-04-02 20:35 2442人阅读 评论(1) 收藏 举报 silverlightradiobuttondatat ...

  6. 在UWP中页面滑动导航栏置顶

    最近在研究掌上英雄联盟,主要是用来给自己看新闻,顺便copy个界面改一下段位装装逼,可是在我copy的时候发现这个东西 当你滑动到一定距离的时候导航栏会置顶不动,这个特性在微博和淘宝都有,我看了@ms ...

  7. WinFrom窗体始终置顶

    调用WindowsAPI使窗体始终保持置顶效果,不被其他窗体遮盖: [DllImport("user32.dll", CharSet = CharSet.Auto)] privat ...

  8. winform窗体置顶

    winform窗体置顶 金刚 winform 置顶 今天做了一个winform小工具.需要设置置顶功能. 网上找了下,发现百度真的很垃圾... 还是必应靠谱些. 找到一个可以链接. https://s ...

  9. 自定义置顶TOP按钮

    简述一下,分为三个步骤: 1. 添加Html代码 2. 调整Css样式 3. 添加Jquery代码 具体代码如下: <style type="text/css"> #G ...

随机推荐

  1. UESTC_Tournament CDOJ 124

    A sports company is planning to advertise in a tournament. It is a single round-robin tournament, th ...

  2. 【转】android 电池(一):锂电池基本原理篇

    关键词:android  电池关机充电 androidboot.mode charger 平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台:S5PV3 ...

  3. IOS 调用拨打电话Api

    // 判断设备是否有通话功能 NSString *deviceType = [UIDevice currentDevice].model; if([deviceType isEqualToString ...

  4. kiki's game

    欢迎参加——BestCoder周年纪念赛(高质量题目+多重奖励) kiki's game Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: ...

  5. powershell 将文本转换成表格的还有一种方式

    $text=" 1 梦幻西游 216406 2014-01-21 资料片 2 炉石传说 15905 2014-01-24 公測 3 新大话西游 214465 2002-08-01 公測 4 ...

  6. hdu 4611

    2013hdu多校联赛二的第一题,当时队友说两个盒子个数的最小公倍数是周期, 如果两个数的最小公倍数比较大的时候(最大是9999900000),如果遍历求的话肯定会超时 当时想找各种规律,都没找到,最 ...

  7. Unity 梯子生成算法

    Unity之生成梯子算法的实现. 1.通过预制物体动态生成角度可设置的梯子形状. 1.1 主要涉及到的数学知识点,角度与弧度的转化. 弧度=角度乘以π后再除以180 角度=弧度除以π再乘以180 1. ...

  8. VLC播放器架构剖析

    VLC采用多线程并行解码架构,线程之间通过单独的一个线程控制所有线程的状态,解码器采用filter模式.组织方式为模块架构 模块简述:libvlc                  是VLC的核心部分 ...

  9. (转)Server Tomcat v6.0 Server at localhost was unable to start within 45 seconds

    仰天长啸 Server Tomcat v6.0 Server at localhost was unable to start within 45 seconds... 当启动tomcat时候出现 S ...

  10. 对获取config文件的appSettings节点简单封装

    转:http://www.cnblogs.com/marvin/archive/2011/07/29/EfficiencyAppSetting.html C#的开发中,无论你是winform开发还是w ...