Direct UI

2023-05-16

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


采用windows方式用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可以控件套控件,还有一些类的组织,还是相当模糊的。以后有时间还会继续了解的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Direct UI 的相关文章

随机推荐

  • k8s: InitContainer

    apiVersion v1 kind Pod metadata name myapp pod labels app kubernetes io name MyApp spec initContainers name init myservi
  • k8s指定ceph,实现动态持久化存储

    一 使用持久化存储StorageClass xff08 ceph挂载 一 准备好ceph服务器 和创建k8s secret 第三步用到 在k8s集群中所有节点安装 ceph common 需要使用kubelet使用rdb命令map附加rbd
  • 慢查询如何优化?(实战慢查询)

    慢查询如何优化 xff1f 1 数据库中设置SQL慢查询2 分析慢查询日志3 常见的慢查询优化3 1 索引没起作用的情况3 2 分拆关联查询3 4 limit偏移量过大3 4 1 解决方案 3 3 分库分表3 4 使用专业查询的中间件 ES
  • springboot项目,localhost:port 可以访问,但是外网IP:port不行

    springboot 项目启动后 xff0c localhost port 可以访问 xff0c 但是外网IP port不行 现象原因及解决方法解决方法 xff1a 规则1 xff1a 规则2 xff1a 规则3 xff1a 现象 spri
  • springcloud项目,localhost:port 可以访问,但是外网IP:port不行

    springcloud项目启动后 xff0c localhost port 可以访问 xff0c 但是外网IP port不行 现象原因及解决方法解决方法 xff1a 规则1 xff1a 规则2 xff1a 规则3 xff1a 现象 spri
  • Springboot项目(PC)启动后如何对外访问?

    64 toc 比如这是本地地址 比如这是外部手机设备访问 如有错误欢迎指正
  • linux配置vnc

    1 yum install tigervnc server tigervnc 2 vim etc sysconfig vncservers xff08 SUSE企业版不用配置此文件 xff09 再最后面加入如下两行 xff1a VNCSER
  • tensor和numpy互相转化

    span class token keyword import span torch span class token keyword import span numpy span class token keyword as span n
  • vmbox主机和虚拟机无法共通网络服务 主机无法使用虚拟机的网络服务 虚拟机无法使用主机的网络服务

    问题描述 xff1a 我在主机开启了一个phpstudy的服务 xff0c 在虚拟机中开启了一个weblogic的服务 主机可以访问主机开启的phpstudy服务 xff0c 虚拟机不可以 虚拟机可以访问其开启的weblogic服务 xff
  • Linux下的stratis高级存储

    Linux下的stratis高级存储 一 stratis存储介绍1 stratis存储简介2 stratis的架构图3 stratis存储的高级特性4 stratis的专业术语5 stratis支持的设备 二 配置stratis精简池1 系
  • Linux系统之部署Go语言开发运行环境

    Linux系统之部署Go语言开发运行环境 一 Go语言介绍1 Go语言简介2 Go语言的撰写风格 二 本地环境检查1 检查系统版本2 检查系统内核版本 三 yum直接安装go1 检查yum仓库2 安装go3 创建环境变量 四 二进制安装Go
  • 解决vnc view看不到图形界面的问题

    有时候用VNC View打开远程linux桌面时 xff0c 桌面显示不出来 xff0c 只有一个灰色背景加上一个命令行 这时候需要在vnc server上设置一下 xff1a 编辑 root vnc xstartup root 64 lo
  • C# delegate、event、Action、Func使用案例解析

    C 中 delegate event Action Func使用案例解析 一 xff1a delegate与event配合使用 public static class Evt Test public static string strEqu
  • Nginx 使用 logrotate 进行日志滚动

    Nginx 日志滚动 xff08 官方 xff09 向 Nginx 主进程发送 USR1信号 USR1信号量被 Nginx 自定义了 xff0c 为重新打开日志 xff1b 当 kill 命令发送 USR1时 xff0c nginx 会重新
  • Softmax()函数的溢出问题笔记

    首先 xff0c 回顾softmax函数的定义 xff1a 按照这个定义 xff0c softmax 在Python解释器中可以这样实现 xff1a import numpy as np def softmax a exp a 61 np
  • 进公司一年,怎么跟老板提涨工资?

    网友提问 xff1a 进公司一年 xff0c 怎么跟老板提涨工资 xff1f 无忧专家 xff1a 薪资不是 你想加 xff0c 想加就能加的 xff0c 好多人鼓足勇气开口说出了加薪理由 xff0c 却被老板轻描淡写的一句话给噎住了喉咙
  • what is Cardinality?

    在数学意义上 xff0c cardinality 基数或者势 指集合内元素的个数 在数据库的相关资料中 xff0c 往往会看到cardinality这个术语 Base cardinality is the number of rows in
  • CAS单点登录开源框架解读(六)--CAS单点登录服务端认证之用户认证跳转

    用户认证之后如何执行后续跳转 在上一章节中 xff0c 我们知道了默认CAS服务端是如何通过配置文件实现用户登录名和密码的认证 xff0c 下面我们将继续对认证之后的动作处理进行分析 1 发送TGT之行为状态sendTicketGranti
  • cocos creator新缓动系统-cc.tween

    前言 一直对于cocos creator的action系统有着深深的埋怨 xff0c 原因是用起来太麻烦了 习惯了Unity的Tween插件的用法 xff0c 我也试着自己封装了下action系统 xff0c 用起来像Tween那样 xff
  • Direct UI

    有个坑爹的说法 xff1a 其实Direct UI只是一个思想 xff0c 要实现这个思 想 xff0c 还要靠自己 采用windows方式用api或gdi实现ui的绘制 DirectUI意为直接在父窗口上绘图 Paint on paren