Qt WindowContainer 完整实战示例:QWidget 嵌入 QML

这是一个可直接编译运行 的完整项目示例。我们将创建一个传统的 QWidget 窗口,并在其中通过 QWidget::createWindowContainer 嵌入一个 QML 界面。

1. 项目结构

为了清晰起见,我们保持最简单的文件结构:

text 复制代码
MyProject/
├── CMakeLists.txt      # 构建配置文件 (推荐使用 CMake)
├── main.cpp            # C++ 主程序逻辑
└── main.qml            # QML 界面文件

注意 :如果你必须使用 .pro (qmake),我在文章末尾也提供了 .pro 内容。


2. 构建配置 (CMakeLists.txt)

这是 Qt6 的标准 CMake 配置。如果你使用 Qt5,大部分也兼容,但建议 Qt6。

cmake 复制代码
cmake_minimum_required(VERSION 3.16)
project(WindowContainerExample LANGUAGES CXX)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找 Qt 包 (优先找 Qt6,找不到则找 Qt5)
find_package(Qt6 COMPONENTS Core Gui Widgets Quick REQUIRED)
# 如果上面失败,可以尝试 find_package(Qt5 ...)

# 定义可执行文件
add_executable(${PROJECT_NAME}
    main.cpp
    main.qml  # 将 QML 文件加入构建系统
)

# 链接 Qt 模块
target_link_libraries(${PROJECT_NAME} PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Widgets
    Qt6::Quick
)

# 设置 QML 导入路径 (让程序能找到 main.qml)
# 这里简单起见,让 QML 文件在可执行文件同一目录或源码目录
set_target_properties(${PROJECT_NAME} PROPERTIES
    AUTOMOC ON
    AUTORCC ON
)

# 复制 QML 文件到构建目录 (简单粗暴法,方便调试)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
    ${CMAKE_SOURCE_DIR}/main.qml
    $<TARGET_FILE_DIR:${PROJECT_NAME}>/main.qml
)

3. QML 界面文件 (main.qml)

这是一个简单的 QML 界面,包含一个矩形和一个按钮,用来证明交互是正常的。

qml 复制代码
import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    id: root
    width: 400
    height: 300
    color: "#f0f0f0"  // 浅灰色背景

    // 中间的文字
    Text {
        anchors.centerIn: parent
        text: "我是 QML 内容\n嵌入在 QWidget 中"
        font.pixelSize: 20
        horizontalAlignment: Text.AlignHCenter
        color: "#333333"
    }

    // 一个可点击的按钮
    Button {
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 20
        anchors.horizontalCenter: parent.horizontalCenter
        text: "点击我 (QML)"
        onClicked: {
            console.log("QML 按钮被点击了!")
            root.color = "#ffcccc" // 点击后变红
        }
    }
}

4. C++ 主程序 (main.cpp)

这是核心部分。请注意注释中标记为 【关键】 的地方。

cpp 复制代码
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QQuickView>
#include <QWidget> // 用于 createWindowContainer

int main(int argc, char *argv[])
{
    // 1. 初始化 QApplication (混合编程必须用 QApplication 而不是 QGuiApplication)
    QApplication app(argc, argv);

    // 2. 创建主窗口 (传统 QWidget)
    QWidget mainWindow;
    mainWindow.setWindowTitle("Qt WindowContainer 示例");
    mainWindow.resize(800, 600);

    // 3. 创建主布局
    QVBoxLayout *mainLayout = new QVBoxLayout(&mainWindow);
    mainLayout->setContentsMargins(10, 10, 10, 10);

    // 4. 添加一些纯 QWidget 内容 (证明是混合环境)
    QLabel *label = new QLabel("上方是传统 QWidget 区域");
    label->setStyleSheet("background: lightblue; padding: 10px;");
    mainLayout->addWidget(label);

    QPushButton *btn = new QPushButton("我是 QWidget 按钮");
    mainLayout->addWidget(btn);

    // ---------------------------------------------------------
    // 5. 【核心步骤】准备 QQuickView (QWindow 派系)
    // ---------------------------------------------------------
    QQuickView *quickView = new QQuickView();
    
    // 设置 QML 源文件
    // 注意:这里假设 main.qml 在可执行文件运行目录下
    quickView->setSource(QUrl::fromLocalFile("main.qml")); 
    
    // 如果上面加载失败,可能是路径问题,尝试使用资源文件路径:
    // quickView->setSource(QUrl("qrc:/main.qml")); 

    // 【关键】设置调整大小模式:让 QML 内容跟随 View 大小变化
    quickView->setResizeMode(QQuickView::SizeRootObjectToView);
    
    // 设置背景色 (可选,设为透明可穿透)
    quickView->setColor(Qt::transparent);

    // ---------------------------------------------------------
    // 6. 【核心步骤】创建容器 (WindowContainer)
    // ---------------------------------------------------------
    // 将 QWindow (quickView) 包装成 QWidget
    QWidget *container = QWidget::createWindowContainer(quickView, &mainWindow);

    // 【关键】设置大小策略:让容器填满剩余空间
    container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    
    // 【关键】设置最小尺寸 (否则可能缩成一个小点)
    container->setMinimumSize(400, 300);

    // 【关键】设置焦点策略 (否则 QML 里的输入框或按钮可能无法接收键盘事件)
    container->setFocusPolicy(Qt::StrongFocus);

    // 可选:如果焦点仍有问题,设置焦点代理
    // container->setFocusProxy(quickView);

    // 7. 将容器加入布局
    mainLayout->addWidget(container);

    // 8. 连接一个信号,证明 QWidget 能控制 QML
    QObject::connect(btn, &QPushButton::clicked, [=]() {
        // 通过 C++ 修改 QML 属性
        QObject *rootObject = quickView->rootObject();
        if (rootObject) {
            rootObject->setProperty("color", "#ccffcc"); // 变绿
        }
    });

    // 9. 显示主窗口
    mainWindow.show();

    return app.exec();
}

5. 备选:.pro 文件 (如果你不用 CMake)

如果你的环境比较旧,或者习惯用 qmake,可以使用这个 WindowContainer.pro

qmake 复制代码
QT += core gui widgets quick

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = WindowContainer
TEMPLATE = app

SOURCES += main.cpp

# 将 qml 文件作为资源处理,或者单独复制
# 这里简单处理,运行时会去找同级目录的 main.qml
files_to_copy.files = main.qml
files_to_copy.path = $$OUT_PWD
QMAKE_EXTRA_TARGETS += files_to_copy
PRE_TARGETDEPS += files_to_copy

6. 编译与运行

使用 CMake (推荐)

  1. 创建一个构建目录:

    bash 复制代码
    mkdir build
    cd build
  2. 配置项目 (确保 qt-cmakecmake 能找到 Qt6):

    bash 复制代码
    # Windows (PowerShell 示例,需调整 Qt 路径)
    cmake .. -G "Ninja" -DCMAKE_PREFIX_PATH="C:/Qt/6.5.0/msvc2019_64"
    
    # Linux / Mac
    cmake .. -DCMAKE_PREFIX_PATH=/path/to/Qt/6.5.0/gcc_64
  3. 编译:

    bash 复制代码
    cmake --build .
  4. 运行:
    确保 main.qml 文件在生成的可执行文件同一目录下(CMakeLists.txt 里已经加了复制命令)。

    bash 复制代码
    ./WindowContainerExample

常见问题排查

  1. QML 加载失败 (空白)
    • 检查 setSource 的路径。如果是 QUrl::fromLocalFile("main.qml"),确保程序运行目录下真的有 main.qml
    • 建议使用 Qt 资源系统 (.qrc) 。创建一个 qml.qrc 文件,然后在代码里用 QUrl("qrc:/main.qml"),这样最稳妥。
  2. 鼠标点不动
    • 检查是否设置了 container->setFocusPolicy(Qt::StrongFocus);
    • 检查 quickView->setResizeMode(...) 是否设置。
  3. 界面闪烁
    • 这是 createWindowContainer 的固有特性(原生窗口合成)。尝试调整窗口大小看是否缓解。如果严重,考虑进阶版提到的 QQuickRenderControl 方案。

7. 代码关键点解析 (复习)

代码行 作用 为什么重要
QApplication app 创建应用实例 混合编程必须用 QApplication,不能用 QGuiApplication
QQuickView 创建 QML 引擎窗口 它是 QWindow 的子类,不能直接放入 QLayout
setResizeMode 设置大小同步 保证主窗口变大时,QML 内容跟着拉伸,否则会出现留白或裁剪。
createWindowContainer 核心函数 创建一个"壳"Widget,把 QQuickView 的原生窗口句柄塞进去。
setSizePolicy(Expanding) 设置大小策略 告诉布局管理器:这个容器应该尽可能抢占空间。
setFocusPolicy 设置焦点策略 保证键盘事件(如 Tab 切换、输入框打字)能传进 QML 内部。

8. 进阶提示:QQuickWidget

如果你发现 createWindowContainer 太麻烦,或者遇到了无法解决的闪烁/层级问题,Qt 官方提供了一个更简单的类:QQuickWidget

它本质上封装了 createWindowContainer 的逻辑,但作为一个标准的 QWidget 提供给你。

替换方案 (更简单):

cpp 复制代码
// 不用 QQuickView + createWindowContainer
// 直接用 QQuickWidget
#include <QQuickWidget>

QQuickWidget *quickWidget = new QQuickWidget(&mainWindow);
quickWidget->setSource(QUrl("qrc:/main.qml"));
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
mainLayout->addWidget(quickWidget);

建议

  • 学习原理、需要精细控制窗口句柄时 -> 用 createWindowContainer (本例)。
  • 快速开发、追求稳定、不需要底层 hack 时 -> 用 QQuickWidget

希望这个完整示例能帮你顺利跑通第一个混合界面!

相关推荐
小白学大数据11 小时前
面向大规模爬取:Python 全站链接爬虫优化(过滤 + 断点续爬)
开发语言·爬虫·python
良木生香12 小时前
【C++初阶】STL——List从入门到应用完全指南(1)
开发语言·数据结构·c++·程序人生·算法·蓝桥杯·学习方法
Alice-YUE12 小时前
【无标题】
开发语言·javascript·ecmascript
todoitbo12 小时前
WHERE 子句中的函数执行顺序与副作用风险分析
数据库·时序数据库·函数
jiayong2312 小时前
MySQL 8.0 Root 用户远程登录配置完整指南
数据库·mysql
数智化管理手记12 小时前
设备总停机?找准根源+TPM核心逻辑,筑牢零故障基础
数据库·人工智能·低代码·制造
叼烟扛炮12 小时前
C++ 知识点17 友元
开发语言·c++·算法·友员
计算机安禾12 小时前
【c++面向对象编程】第2篇:类与对象(一):定义第一个类——成员变量与成员函数
开发语言·c++
Dxy123931021612 小时前
Python Pillow库:`img.format`与`img.mode`的区别详解
开发语言·python·pillow
亿牛云爬虫专家13 小时前
深度解析:数据采集场景下的 Java 代理技术实战
java·开发语言·数据采集·动态ip·动态代理·代理配置·连接池复用