当前位置:首页 > 技术 > 正文内容

uCGUI窗口对象的创建机制详解

访客 技术 2026年6月29日 1

一、核心数据结构与属性定义

窗口对象结构

/* 窗口对象结构 共30字节 */
typedef struct {
  GUI_RECT winRect;           //窗口区域(x0,y0,x1,y1)     8字节
  GUI_RECT dirtyRect;         //失效区域(x0,y0,x1,y1)     8字节
  WM_CALLBACK* pfCallback;    //回调函数指针               4字节
  
  WM_HWIN hNextSibling;       //兄弟窗口链表指针           2字节
  
  WM_HWIN hParentWin;         //父窗口句柄                 2字节
  WM_HWIN hFirstChildWin;     //第一个子窗口句柄           2字节
  WM_HWIN hPrevSibling;       //前一个兄弟窗口             2字节
  
  U16 wmFlags;                //状态标志                   2字节
} WM_OBJECT;

窗口创建标志位定义

#define WM_CF_TRANSPARENT      (1<<0)  /* 透明窗口标志。不填充整个客户区的窗口需要定义此标志 */
#define WM_CF_HIDE             (0<<1)  /* 创建后隐藏窗口(默认) */
#define WM_CF_SHOW             (1<<1)  /* 创建后显示窗口 */
#define WM_CF_MEMDEV           (1<<2)  /* 重绘时使用内存设备 */
#define WM_CF_STAYONTOP        (1<<3)  /* 窗口置顶 */
#define WM_CF_DISABLED         (1<<4)  /* 禁用状态:不接收触摸和鼠标输入 */
/* 仅用于创建过程的标志 */
#define WM_CF_ACTIVATE         (1<<5)  /* 创建后自动激活窗口 */
#define WM_CF_FOREGROUND       (0<<6)  /* 创建后置于前台(默认) */
#define WM_CF_BACKGROUND       (1<<6)  /* 创建后置于后台 */

/* 锚点定位标志 */
#define WM_CF_ANCHOR_RIGHT     (1<<7)  /* 右锚点:父窗口调整大小时,与右边距保持不变 */
#define WM_CF_ANCHOR_BOTTOM    (1<<8)  /* 下锚点:父窗口调整大小时,与下边距保持不变 */
#define WM_CF_ANCHOR_LEFT      (1<<9)  /* 左锚点:父窗口调整大小时,与左边距保持不变 */
#define WM_CF_ANCHOR_TOP       (1<<10) /* 上锚点:父窗口调整大小时,与上边距保持不变 */

#define WM_CF_FIXED_OUTLINE    (1<<11) /* 固定轮廓。仅对透明窗口有意义,若透明窗口无固定轮廓,
                                       则重绘时清除背景而非窗口本身,会增加计算开销 */
#define WM_CF_LATE_CLIP        (1<<12)
#define WM_CF_MEMDEV_ON_REDRAW (1<<13)
#define WM_CF_RESERVED3        (1<<14)
#define WM_CF_RESERVED4        (1<<15)

在实际开发中,WM_CF_SHOW、WM_CF_STAYONTOP、WM_CF_HIDE、WM_CF_ACTIVATE这几个标志使用频率最高。

二、窗口创建流程深度解析

1、WM_CreateWindowAsChild 函数实现

WM_HWIN WM_CreateWindowAsChild( int x0, int y0, int width, int height
                               ,WM_HWIN hParent, U16 Style, WM_CALLBACK* pfCallback
                               ,int nExtraBytes) {
  WM_OBJECT* pWindow;
  WM_HWIN hWindow;
  WM_ASSERT_NOT_IN_PAINT();
  WM_LOCK();
  Style |= WM__CreateFlags;
  
  /* 默认父窗口为桌面窗口 */
  if (!hParent) {
    if (WM__nWindowCount) {
    #if GUI_NUM_LAYERS == 1
      hParent = WM__ahDesktop[0];
    #else
      hParent = WM__ahDesktop[GUI_Context.SelLayer];
    #endif
    }
  }
  
  if (hParent == WM_UNATTACHED) {
    hParent = WM_HWIN_NULL;
  }
  
  if (hParent) {
    WM_OBJECT* pParentObj = WM_H2P(hParent);
    x0 += pParentObj->winRect.x0;
    y0 += pParentObj->winRect.y0;
    if (width==0) {
      width = pParentObj->winRect.x1 - pParentObj->winRect.x0 + 1;
    }
    if (height==0) {
      height = pParentObj->winRect.y1 - pParentObj->winRect.y0 + 1;
    }
  }
  
  if ((hWindow = (WM_HWIN) GUI_ALLOC_AllocZero(nExtraBytes + sizeof(WM_OBJECT))) == 0) {
    GUI_DEBUG_ERROROUT("WM_CreateWindow: 内存不足,无法创建窗口");
  } else {
    WM__nWindowCount++;
    pWindow = WM_H2P(hWindow);
    
    /* 初始化窗口参数 */
    pWindow->winRect.x0 = x0;
    pWindow->winRect.y0 = y0;
    pWindow->winRect.x1 = x0 + width - 1;
    pWindow->winRect.y1 = y0 + height - 1;
    pWindow->pfCallback = pfCallback;
    
    /* 复制可接受的标志位 */
    pWindow->wmFlags |= (Style & (WM_CF_SHOW |
                                  WM_SF_MEMDEV |
                                  WM_CF_MEMDEV_ON_REDRAW |
                                  WM_SF_STAYONTOP |
                                  WM_CF_DISABLED |
                                  WM_SF_CONST_OUTLINE |
                                  WM_SF_HASTRANS |
                                  WM_CF_ANCHOR_RIGHT |
                                  WM_CF_ANCHOR_BOTTOM |
                                  WM_CF_ANCHOR_LEFT |
                                  WM_CF_ANCHOR_TOP |
                                  WM_CF_LATE_CLIP));
    
    /* 添加到链表管理 */
    _AddToLinearList(hWindow);
    WM__InsertToParentChain(hWindow, hParent);
    
    /* 根据窗口样式执行相应操作 */
    if (Style & WM_CF_ACTIVATE) {
      WM_SelectWindow(hWindow);
    }
    
    #if WM_SUPPORT_TRANSPARENCY
      if (Style & WM_SF_HASTRANS) {
        WM__nTransparentCnt++;
      }
    #endif
    
    if (Style & WM_CF_BACKGROUND) {
      WM_BringToBottom(hWindow);
    }
    
    if (Style & WM_CF_SHOW) {
      pWindow->wmFlags |= WM_SF_VISIBLE;
      WM_InvalidateWindow(hWindow);
    }
    
    WM__SendMsgNoData(hWindow, WM_CREATE);
  }
  WM_UNLOCK();
  return hWindow;
}

该函数的执行流程如下:首先根据父窗口坐标计算当前窗口的绝对坐标和尺寸。接着从动态内存池分配窗口对象所需的内存空间,并将窗口参数写入该内存区域。关键步骤包括:将新窗口加入全局窗口管理链表,以及将窗口插入父窗口的子窗口链表中。若创建时指定了WM_CF_SHOW标志,则设置窗口可见状态位并标记为无效,这样在后续GUI_Exec()或WM_Exec()调用时会触发窗口重绘。

2、_AddToLinearList 函数实现

static void _AddToLinearList(WM_HWIN hNewWin) {
  WM_OBJECT* pFirst;
  WM_OBJECT* pNew;
  if (WM__hFirstWindow) {
    pFirst = WM_H2P(WM__hFirstWindow);
    pNew   = WM_H2P(hNewWin);
    
    /*
     * 桌面窗口--->最近创建的窗口1--->较早创建的窗口2~~~--->0
     * 插入新窗口后:
     * 桌面窗口--->新窗口--->最近创建的窗口1--->较早创建的窗口2~~~--->0
     */
    pNew->hNextSibling = pFirst->hNextSibling;
    pFirst->hNextSibling = hNewWin;
  } else {
    WM__hFirstWindow = hNewWin;
  }
}

此函数将新创建的窗口添加到全局线性链表中。该链表基于uCGUI的动态内存机制,通过WM_OBJECT结构体中的hNextSibling成员连接形成单向链表。链表的组织方式保证了最早创建的窗口位于链表末尾,最近创建的窗口靠近头部。

3、WM__InsertToParentChain 函数实现

/*********************************************************************
*
*       WM__InsertToParentChain
*
* 功能说明:
*   将窗口插入指定父窗口的子窗口链表中
*   同一层级的窗口中,新窗口置于最顶层
*/
void WM__InsertToParentChain(WM_HWIN hWin, WM_HWIN hParent) {
  int bStayOnTop;
  WM_HWIN hChild;
  WM_OBJECT* pWin;
  WM_OBJECT* pParent;
  WM_OBJECT* pSibling;

  if (hParent) {
    pWin = WM_H2P(hWin);
    pWin->hNextSibling = 0;
    pWin->hParentWin = hParent;
    pParent = WM_H2P(hParent);
    bStayOnTop = pWin->wmFlags & WM_CF_STAYONTOP;
    hChild = pParent->hFirstChildWin;
    
    /* 父窗口没有子窗口时,直接作为首个子窗口 */
    if (hChild == 0) {
      pParent->hFirstChildWin = hWin;
      return;
    }
    
    /* 如果首个子窗口是置顶窗口,而新窗口不是,则插入到链表头部 */
    pSibling = WM_H2P(hChild);
    if (!bStayOnTop) {
      if (pSibling->wmFlags & WM_SF_STAYONTOP) {
        pWin->hNextSibling = hChild;
        pParent->hFirstChildWin = hWin;
        return;
      }
    }
    
    /* 遍历链表,找到合适的位置插入 */
    do {
      WM_OBJECT* pNext;
      WM_HWIN hNext;
      if ((hNext = pSibling->hNextSibling) == 0) {
        pSibling->hNextSibling = hWin;
        break;
      }
      pNext = WM_H2P(hNext);
      if (!bStayOnTop) {
        if (pNext->wmFlags & WM_SF_STAYONTOP) {
          pSibling->hNextSibling = hWin;
          pWin->hNextSibling = hNext;
          break;
        }
      }
      pSibling = pNext;
    } while (1);
    
    #if WM_SUPPORT_NOTIFY_VIS_CHANGED
      WM__NotifyVisChanged(hWin, &pWin->winRect);
    #endif
  }
}

此函数负责将新窗口添加到父窗口的子窗口链表中。链表基于动态内存,通过hFirstChildWin和hNextSibling成员连接。

插入规则如下:

1、若父窗口尚无子窗口,直接将其作为首个子窗口。

插入前: 父窗口--->0 ====> 插入后: 父窗口--->当前窗口--->0

2、若父窗口已有子窗口,且首个子窗口具有置顶标志,而新窗口没有,则将新窗口插入链表头部。

插入前:父窗口--->长子--->~~~--->0 => 插入后:父窗口--->当前窗口--->长子--->~~~--->0

3、若父窗口已有子窗口,且首个子窗口无置顶标志,新窗口也无此标志,则插入到所有无置顶标志窗口之后。

插入前:

父窗口--->长子(普通)--->次子(普通)--->三子(置顶)--->~~~--->0 => 插入后:

父窗口--->长子(普通)--->次子(普通)--->当前窗口--->三子(置顶)--->~~~--->0

4、若父窗口已有子窗口,且首个子窗口无置顶标志,但新窗口有置顶标志,则插入到链表末尾。

插入前:

父窗口--->长子(普通)--->次子(普通)--->三子(置顶)--->~~~--->0 => 插入后:

父窗口--->长子(普通)--->次子(普通)--->三子(置顶)--->~~~--->当前窗口--->0

三、窗口层级与绘制顺序

1、按照窗口层级组织结构,链表头部的窗口显示在底层,链表尾部的窗口显示在顶层。窗口重绘时,从链表头部依次向尾部遍历执行。

2、子窗口始终显示在父窗口的上层位置。

四、系统桌面窗口的创建

桌面窗口在GUI_Init函数执行过程中创建。当系统启用窗口管理功能时,GUI_Init会调用WM_Init函数进行窗口管理器初始化,此时会自动创建桌面窗口。相关实现代码如下:

/*********************************************************************
*
*       WM_Init
*/
void WM_Init(void) {
     ......
    WM__ahDesktop[0] = WM_CreateWindow(0, 0, GUI_XMAX, GUI_YMAX, WM_CF_SHOW, cbDesktop, 0);
    ......
    WM_InvalidateWindow(WM__ahDesktop[i]);
    ......
    WM_SelectWindow(WM__ahDesktop[0]);
}

桌面窗口的默认回调处理函数

static void cbDesktop( WM_MESSAGE* pMsg) {
  const WM_KEY_INFO* pKeyInfo;
  switch (pMsg->MsgId) {
  case WM_KEY:
    pKeyInfo = (const WM_KEY_INFO*)pMsg->Data.p;
    if (pKeyInfo->PressedCnt == 1) {
      GUI_StoreKey(pKeyInfo->Key);
    }
    break;
  case WM_PAINT:
    {
      int nLayer;
      #if GUI_NUM_LAYERS > 1
        nLayer = _DesktopHandle2Index(pMsg->hWin);
      #else
        nLayer = 0;
      #endif
      if (WM__aBkColor[nLayer] != GUI_INVALID_COLOR) {
        GUI_SetBkColor(WM__aBkColor[nLayer]);
        GUI_Clear();
      }
    }
  default:
    WM_DefaultProc(pMsg);
  }
}

需要特别说明的是:桌面窗口的句柄默认为1,即使用动态内存块数组的第二个槽位。动态内存分配时从数组的第二个元素开始使用。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。