这是一个可直接编译运行 的完整项目示例。我们将创建一个传统的 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 (推荐)
-
创建一个构建目录:
bashmkdir build cd build -
配置项目 (确保
qt-cmake或cmake能找到 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 -
编译:
bashcmake --build . -
运行:
确保main.qml文件在生成的可执行文件同一目录下(CMakeLists.txt 里已经加了复制命令)。bash./WindowContainerExample
常见问题排查
- QML 加载失败 (空白) :
- 检查
setSource的路径。如果是QUrl::fromLocalFile("main.qml"),确保程序运行目录下真的有main.qml。 - 建议使用 Qt 资源系统 (.qrc) 。创建一个
qml.qrc文件,然后在代码里用QUrl("qrc:/main.qml"),这样最稳妥。
- 检查
- 鼠标点不动 :
- 检查是否设置了
container->setFocusPolicy(Qt::StrongFocus);。 - 检查
quickView->setResizeMode(...)是否设置。
- 检查是否设置了
- 界面闪烁 :
- 这是
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。
希望这个完整示例能帮你顺利跑通第一个混合界面!