这是针对 Linux Wayland (wlcom) 平台模态问题的完整解决方案。

一、问题分析

在 Wayland 平台上,窗口管理机制比 X11 更为严格。 QQuickWidget 或 QML 窗口与 QWidget 对话框属于不同的窗口层级。如果 一个 QDialog / QWidget 没有正确设置 Wayland 层面的父窗口(Transient Parent),Wayland 合成器(Compositor)就无法识别它们之间的模态关系,导致模态失效。

二、解决方案

核心修复方案是在对话框显示( exec() )之前,强制创建其底层窗口句柄,并手动设置其 Transient Parent 为 QML 的主窗口。

关键步骤:

  1. 调用 dialog.winId() 确保对话框创建了原生窗口句柄。
  2. 调用 dialog.windowHandle()->setTransientParent(qml_parent) 建立模态关系。

三、什么是Transient

在 Wayland(以及 X11)的窗口协议中, “Transient”(瞬态) 这个词有特定的技术含义,不仅仅是字面上的“短暂”。

简单来说, Transient Parent 是窗口管理器(Compositor)用来识别“ 辅助窗口属于哪个主窗口 ”的唯一凭证。

以下是详细的技术分析:

1. 什么是 Transient(瞬态)窗口?

在窗口系统中,窗口分为两类:

  • Toplevel(顶层窗口) :独立存在的窗口,比如你的主程序界面( main.qml 对应的窗口)。它们在任务栏有图标,可以独立最小化。
  • Transient(瞬态窗口) : 依附于 某个顶层窗口存在的辅助窗口,比如对话框(Dialog)、提示框(Alert)、工具栏等。
    “Transient Parent” 就是指这个辅助窗口所依附的那个“父亲”。

2. 为什么 Wayland 必须显式设置它?

Wayland 与 X11 的最大区别在于 安全性 和 隔离性 。

  • X11 的“混乱” :在 X11 时代,任何程序都可以通过全局坐标得知其他窗口的位置,甚至拦截其他窗口的输入。窗口管理器通常比较“宽容”,只要你设了模态(Modal)标志,它可能就会尝试阻塞输入,哪怕它不知道你属于谁。
  • Wayland 的“严格” :在 Wayland 中,Compositor(合成器,如 KWin, Mutter 或 wlcom)默认认为每个窗口都是独立的、沙箱化的。
    • 如果你弹出一个对话框( WritingAddItemDialog ),但没有告诉 Compositor “我是 main.qml 的孩子”。
    • Compositor 就会把它当成另一个独立的顶层窗口。
    • 结果 :既然是两个独立的程序窗口,它们之间当然没有模态阻塞关系。用户依然可以点击后面的主窗口,模态失效。
      只有当你调用 setTransientParent 时,底层才会发送协议(如 xdg_toplevel_set_parent )给 Compositor,正式“认亲”。Compositor 确认关系后,才会执行模态策略(变暗背景、拦截点击)。

3. Qt 中的两个 “Parent” 的区别

这是最容易混淆的地方:

  1. QObject Parent / QWidget Parent(内存与对象树层面)

    • 代码: new WritingAddItemDialog(parentWidget)
    • 作用:负责 内存管理 (父对象析构时自动析构子对象)和 Qt 内部的事件传递 。
    • 局限:对于 QQuickWidget 和 QWidget 混用的情况,或者跨越了 QML/C++ 边界,Qt 有时无法自动将这个对象树关系映射到操作系统的窗口关系上。
  2. Window Transient Parent(操作系统窗口层面)

    • 代码: windowHandle()->setTransientParent(qmlWindow)
    • 作用:直接告诉操作系统(Wayland Compositor)窗口层级关系。
    • 这就是我们修复的核心 :我们绕过了 Qt 的对象层级,直接在底层窗口句柄(QWindow)级别建立了父子关系。

在 Wayland 上, “模态”是一种窗口管理器提供的服务 ,而 Transient Parent 是申请这项服务的“入场券”。没有这张入场券,对话框就是个路人,无权阻塞主窗口。

四、Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//适用于QML为父窗口,widgets子窗口需要显示模态的场景

/*========================第1步=================================*/
//中介类
//Msg是普通的QObject类

//中介类暴露
Msg *msgTemplate = new Msg();
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
engine->rootContext()->setContextProperty("msg",Msg);

/*========================第2步=================================*/
//主界面传递,QML中
Component.onCompleted:
{
msgTemplate.setParent(root);
}

//C++中
QQuickWindow *m_parent = nullptr;
public slots: void MsgTemplate::setParent(QObject *parent)
{
m_parent = qobject_cast<QQuickWindow*>(parent);
}

/*========================第3步=================================*/
QDialog dlg;
// --- 修复模态问题的核心代码 ---
if(m_parent)
{
dlg.winId(); // 1. 强制创建原生窗口句柄
if (dlg.windowHandle())
{
dlg.windowHandle()->setTransientParent(m_parent); // 2. 设置 Wayland 瞬态父窗口
}
}
dlg.exec();