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

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

相关推荐
xiaoliuliu123452 小时前
Android Studio 2025 安装教程:详细步骤+自定义安装路径+SDK配置(附桌面快捷方式创建)
java·前端·数据库
l1t2 小时前
这个插件使postgresql能访问ducklake数据湖。
数据库·postgresql
samson_www2 小时前
用nssm部署FASTAPI服务
数据库·python·fastapi
MyBFuture2 小时前
Halcon条形码与二维码识别全攻略
开发语言·人工智能·halcon·机器视觉
@insist1232 小时前
数据库系统工程师-分布式数据库与数据仓库核心考点及应用体系
数据库·数据仓库·分布式·软考·数据库系统工程师·软件水平考试
电商API&Tina2 小时前
唯品会数据采集API接口||电商API数据采集
java·javascript·数据库·python·sql·json
AI+程序员在路上2 小时前
新手进入嵌入式行业方法与方向选择
c语言·开发语言·单片机·嵌入式硬件
dovens2 小时前
GO 快速升级Go版本
开发语言·redis·golang
回到原点的码农3 小时前
maven导入spring框架
数据库·spring·maven