4. 多屏环境认知

在 Qt Quick 应用进入多屏场景之前,必须先建立一套清晰的“屏幕世界观”。很多适配问题并不是 QML 写法错误,而是开发者对操作系统如何组织多块显示器、如何分配坐标、如何决定窗口落点缺乏准确认知。本节从多显示器拓扑、主屏与副屏、扩展与镜像模式、屏幕坐标系统四个角度,建立后续窗口定位、缩放适配和跨屏拖拽的基础模型。

一、多显示器拓扑:屏幕不是“一个更大的桌面”

操作系统会把每一块物理显示器抽象为一个独立的屏幕对象,并按照用户在显示设置中的排列方式,组成一个虚拟桌面。这个虚拟桌面可以横向排列、纵向排列、错位排列,甚至形成 L 形或品字形。每块屏幕都有自己的分辨率、缩放比例、刷新率、物理尺寸和相对位置。Qt 通过 QScreen 和 QGuiApplication::screens() 暴露这些信息,QML 中则可以通过 Screen 附加属性或 Qt.application.screens 访问。

拓扑关系决定了窗口从一个屏幕移动到另一个屏幕时的行为。例如,当两块屏幕顶部对齐时,鼠标从主屏右侧进入副屏左侧是连续的;如果副屏被放置在主屏右上方,那么鼠标路径会出现“跳跃”或“斜向”移动。窗口管理器通常依据拓扑决定最大化、全屏和吸附行为,因此理解拓扑是排查“窗口跑到屏幕外”“全屏覆盖错误屏幕”等问题的第一步。

多显示器拓扑与虚拟桌面坐标系统 主屏 Primary 1920 × 1080 · scale 1.0 虚拟坐标原点 (0, 0) geometry: 0,0 1920×1080 devicePixelRatio: 1.0 主窗口默认落点 副屏 Secondary 2560 × 1440 · scale 1.25 虚拟坐标 (1920, 0) geometry: 1920,0 2560×1440 devicePixelRatio: 1.25 扩展模式:可拖入窗口 副屏 2 1280 × 720 (4480, -200) 错位拓扑 虚拟桌面 X 轴 → Y 轴 原点 (0,0) 镜像模式:多块屏幕共享同一逻辑坐标区域,窗口内容被复制显示,坐标系统不扩展。

二、主屏与副屏:谁决定默认落点

主屏(Primary Screen)是操作系统指定的默认屏幕,通常也是任务栏、桌面图标和大多数应用首次启动时的落点。Qt 中可以通过 QGuiApplication::primaryScreen() 获取主屏,QML 中则常用 Screen.width、Screen.height 获取当前窗口所在屏幕的信息。需要注意的是,Screen 附加属性反映的是“窗口当前所在屏幕”,而不是“主屏”。如果窗口被拖到副屏,Screen.width 会随之变化。

主屏与副屏的区分在工程上有三个典型影响:第一,应用首次启动时窗口默认出现在主屏;第二,全屏窗口通常默认使用主屏;第三,某些平台的主屏缩放比例会作为全局参考。若应用需要“始终在副屏显示”,不能依赖默认行为,而应显式指定屏幕和坐标。

// QML 中获取屏幕列表并打印关键信息
import QtQuick
import QtQuick.Window

Window {
    visible: true
    width: 640
    height: 480

    Component.onCompleted: {
        var screens = Qt.application.screens
        for (var i = 0; i < screens.length; ++i) {
            var s = screens[i]
            console.log("Screen", i,
                        "name=", s.name,
                        "geometry=", s.x, s.y, s.width, s.height,
                        "dpr=", s.devicePixelRatio,
                        "primary=", (s === Qt.application.primaryScreen))
        }
    }
}

三、扩展模式与镜像模式:两种完全不同的坐标语义

扩展模式(Extend)下,多块屏幕拼成一个更大的虚拟桌面,每块屏幕拥有独立的坐标区域,窗口可以从一块屏幕移动到另一块屏幕。镜像模式(Mirror / Clone)下,多块屏幕显示相同内容,逻辑上只存在一个坐标区域,窗口坐标不会因为物理屏幕数量而扩展。两者对 Qt Quick 的影响截然不同:扩展模式下需要处理跨屏坐标和不同 DPI;镜像模式下则主要关注分辨率取交集和缩放一致性。

对比维度 扩展模式 镜像模式
虚拟桌面大小 各屏坐标区域相加,可能很大 等于单块屏幕逻辑区域
窗口跨屏 支持,窗口可拖到任意屏幕 不支持,所有屏幕显示同一窗口
DPI 差异 各屏可不同,需分别适配 通常取共同缩放,差异被隐藏
典型问题 窗口落点错误、跨屏模糊 分辨率不匹配、黑边或拉伸
Qt 关注点 screen.geometry、devicePixelRatio primaryScreen 的有效分辨率

四、屏幕坐标系统:逻辑坐标与物理像素

Qt 使用逻辑坐标(device-independent pixels)描述窗口和控件位置,再通过 devicePixelRatio 映射到物理像素。多屏环境下,每块屏幕可以有不同的缩放比例。例如主屏 1920×1080、缩放 100%,副屏 2560×1440、缩放 125%,那么副屏的逻辑尺寸约为 2048×1152。窗口从主屏移动到副屏时,逻辑坐标会重新映射,若应用使用固定像素值布局,就可能出现控件变小、模糊或错位。

核心公式:物理像素 = 逻辑坐标 × devicePixelRatio。跨屏移动窗口时,应优先使用逻辑坐标和锚点布局,避免直接写死物理像素。

五、典型场景与排障思路

场景一:应用启动后窗口出现在副屏,用户找不到。排查时先确认 primaryScreen 是否符合预期,再检查是否在代码中硬编码了 x/y。场景二:窗口拖到高 DPI 副屏后界面模糊。检查是否使用了固定尺寸图片或未开启高 DPI 缩放。场景三:全屏窗口覆盖了错误的屏幕。应使用 Window.screen 显式指定目标屏幕,而不是依赖默认行为。

常见误区:把 Screen.width 当作主屏宽度;在扩展模式下假设所有屏幕 DPI 相同;用物理像素计算跨屏坐标;忽略镜像模式下逻辑桌面不扩展的事实。这些都会导致窗口定位和缩放异常。

建立正确的多屏认知后,后续的窗口定位、屏幕选择、缩放适配和跨屏拖拽才有稳定的基础。建议在项目初期就打印所有屏幕的 name、geometry、devicePixelRatio 和 primary 信息,形成一份“屏幕环境日志”,作为排查多屏问题的第一手依据。