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渲染?
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最高的窗口
- 缓冲区管理:每个窗口独立分配缓冲区,注意内存占用
screen_set_window_property_iv 显式设置ZORDER,问题就解决了。
还有个容易忽略的点:窗口销毁顺序。我建议先销毁子窗口,再销毁主窗口。否则子窗口可能变成「孤儿窗口」,虽然Screen会帮你清理,但保不齐出什么幺蛾子。
4.5 实战经验总结
做了这么多年QNX仪表盘,我总结了几条铁律:
- 窗口数量别贪多:每个窗口都要消耗缓冲区内存,4个窗口以内最稳妥
- 属性设置要集中:别在代码里东一个
screen_set_window_property西一个,集中管理方便调试 - 事件处理要快:超过16ms的处理时间,用户就能感觉到卡顿
- 善用调试工具:
screeninfo命令可以查看所有窗口信息,我排查问题时必用
最后说一句:Screen窗口管理看似简单,但每个细节都可能成为性能瓶颈。你想想看,一个仪表盘上几十个指针、数字、图标,背后都是窗口和缓冲区的协同工作。把基础打牢了,后面做复杂界面才不慌。
screen_window_t 的API说明,建议通读一遍。虽然文档写得像天书,但结合实战经验看,会有新收获。