4. Screen窗口创建与管理:从零搭建你的显示世界

说实话,Screen窗口管理这块,是QNX图形开发里最基础也最容易踩坑的地方。我刚开始接触时,以为不就是创建个窗口嘛,结果被各种属性配置折腾得够呛。今天咱们就把它彻底讲透。

4.1 窗口创建流程:三步走,稳得很

创建Screen窗口,说白了就三步:连接Screen服务、创建窗口对象、配置窗口属性。我习惯把这过程比作「租房」——先找中介(连接服务),再选房子(创建窗口),最后装修(设置属性)。

先看个最简示例:

#include <screen/screen.h>

screen_context_t screen_ctx;
screen_window_t   screen_win;

// 第一步:连接Screen服务
screen_create_context(&screen_ctx, SCREEN_APPLICATION_CONTEXT);

// 第二步:创建窗口
screen_create_window(&screen_win, screen_ctx);

// 第三步:设置基本属性
int usage = SCREEN_USAGE_NATIVE;
screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, &usage);

int size[2] = {800, 480};
screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_SIZE, size);

// 最后:显示窗口
screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_VISIBLE, &(int){1});

嗯,这里要注意:screen_create_context 的参数类型决定了你的窗口权限。我遇到过有人用了 SCREEN_INPUT_CONTEXT 结果窗口死活显示不出来——因为输入上下文不能创建可见窗口。

核心要点:
  • 上下文类型选 SCREEN_APPLICATION_CONTEXT 最通用
  • 窗口创建后默认不可见,需要手动设置 VISIBLE 属性
  • 先设属性再显示,顺序别搞反

4.2 窗口属性设置:每个参数都有故事

窗口属性这块,我踩过的坑能写满一页纸。咱们挑几个最关键的说说。

属性名 类型 说明 我的经验
SCREEN_PROPERTY_SIZE int[2] 窗口宽高 单位是像素,别和物理尺寸搞混
SCREEN_PROPERTY_POSITION int[2] 窗口位置 坐标原点在屏幕左上角
SCREEN_PROPERTY_USAGE int 缓冲区用途 这个最坑,后面细说
SCREEN_PROPERTY_ZORDER int Z轴顺序 值越大越靠上
SCREEN_PROPERTY_ALPHA int 透明度 0-255,255不透明

关于 SCREEN_PROPERTY_USAGE,我曾经犯过一个低级错误:做仪表盘时只设置了 SCREEN_USAGE_NATIVE,结果OpenGL ES渲染上去全是黑的。后来才发现,需要加上 SCREEN_USAGE_OPENGL_ES2 才行。你想想看,缓冲区用途没告诉Screen,它怎么知道你要用GPU渲染?

我的小技巧: 做仪表盘开发,USAGE 属性我一般这样配:
int usage = SCREEN_USAGE_NATIVE | SCREEN_USAGE_OPENGL_ES2 | SCREEN_USAGE_ROTATION;
screen_set_window_property_iv(win, SCREEN_PROPERTY_USAGE, &usage);
加上 ROTATION 是为了后面做旋转动画方便。

4.3 窗口事件处理:别让用户等你

事件处理这块,说白了就是Screen把用户操作(触摸、按键)告诉你,你做出响应。我见过很多新手在事件循环里做耗时操作,结果界面卡成PPT。

标准的事件处理流程:

screen_event_t event;
screen_create_event(&event, screen_ctx);

while (1) {
    // 等待事件
    screen_get_event(screen_ctx, event, -1);
    
    int type;
    screen_get_event_property_iv(event, SCREEN_PROPERTY_TYPE, &type);
    
    switch (type) {
        case SCREEN_EVENT_CREATE:
            // 窗口创建完成
            break;
            
        case SCREEN_EVENT_CLOSE:
            // 窗口关闭请求
            break;
            
        case SCREEN_EVENT_PROPERTY:
            // 属性变化
            break;
            
        case SCREEN_EVENT_POINTER:
            // 触摸/鼠标事件
            int pos[2];
            screen_get_event_property_iv(event, SCREEN_PROPERTY_POSITION, pos);
            // 处理触摸位置
            break;
            
        case SCREEN_EVENT_KEYBOARD:
            // 按键事件
            break;
    }
}

screen_destroy_event(event);
注意: screen_get_event 的第三个参数是超时时间(毫秒)。传 -1 表示阻塞等待,传 0 表示非阻塞。我建议在UI线程里用 -1,别用轮询——CPU不是用来空转的。

这里有个坑:事件处理里千万别做内存分配或文件读写。我曾经在触摸事件里写日志,结果触摸响应延迟从5ms飙到200ms。后来改成异步队列处理,问题就解决了。

4.4 多窗口管理:让它们和谐共处

仪表盘开发中,多窗口是常态。比如一个窗口显示车速,一个窗口显示导航,一个窗口显示告警。怎么管理它们?我总结了一套「窗口树」的思路。

先看个多窗口创建示例:

// 创建主窗口
screen_window_t main_win;
screen_create_window(&main_win, screen_ctx);
// 设置主窗口属性...

// 创建子窗口(车速表)
screen_window_t speed_win;
screen_create_window(&speed_win, screen_ctx);
// 设置子窗口为透明背景
int alpha = 0;
screen_set_window_property_iv(speed_win, SCREEN_PROPERTY_ALPHA, &alpha);
// 设置子窗口位置
int pos[2] = {100, 200};
screen_set_window_property_iv(speed_win, SCREEN_PROPERTY_POSITION, pos);

// 创建告警窗口(顶层)
screen_window_t alert_win;
screen_create_window(&alert_win, screen_ctx);
int zorder = 100;  // 高优先级
screen_set_window_property_iv(alert_win, SCREEN_PROPERTY_ZORDER, &zorder);

多窗口管理有几个关键点:

  • Z轴顺序:告警窗口永远在最上面,ZORDER设大值
  • 透明窗口:子窗口可以透明,只绘制需要的内容
  • 事件分发:触摸事件默认发给ZORDER最高的窗口
  • 缓冲区管理:每个窗口独立分配缓冲区,注意内存占用
避坑指南: 我曾经做过一个三窗口的仪表盘,结果发现切换窗口时画面闪烁。排查了半天,原来是窗口创建顺序导致ZORDER冲突。后来我统一用 screen_set_window_property_iv 显式设置ZORDER,问题就解决了。

还有个容易忽略的点:窗口销毁顺序。我建议先销毁子窗口,再销毁主窗口。否则子窗口可能变成「孤儿窗口」,虽然Screen会帮你清理,但保不齐出什么幺蛾子。

4.5 实战经验总结

做了这么多年QNX仪表盘,我总结了几条铁律:

  1. 窗口数量别贪多:每个窗口都要消耗缓冲区内存,4个窗口以内最稳妥
  2. 属性设置要集中:别在代码里东一个 screen_set_window_property 西一个,集中管理方便调试
  3. 事件处理要快:超过16ms的处理时间,用户就能感觉到卡顿
  4. 善用调试工具screeninfo 命令可以查看所有窗口信息,我排查问题时必用

最后说一句:Screen窗口管理看似简单,但每个细节都可能成为性能瓶颈。你想想看,一个仪表盘上几十个指针、数字、图标,背后都是窗口和缓冲区的协同工作。把基础打牢了,后面做复杂界面才不慌。

推荐阅读: QNX官方文档中关于 screen_window_t 的API说明,建议通读一遍。虽然文档写得像天书,但结合实战经验看,会有新收获。