
1. 项目概述为什么需要手动创建QT Quick项目在QT的世界里使用Qt Creator的向导“新建项目”无疑是最高效、最无脑的方式。它会自动生成.pro文件、main.cpp、qml文件甚至帮你配置好构建套件。但作为一名有追求的C/QT开发者如果你只停留在点鼠标的阶段那可能永远无法真正理解一个QT Quick项目是如何从零开始“组装”起来的。当你的项目结构需要高度定制或者你需要集成到复杂的CMake构建系统中又或者你只是想彻底搞明白那些配置文件里每一行代码的意义时手动创建就成了必经之路。手动创建项目意味着你将从最原始的文本文件开始一步步定义项目的骨骼、血肉和灵魂。这个过程会让你深刻理解QT Quick应用的启动流程、QML引擎的初始化、C与QML的绑定机制以及构建系统如何将它们打包成一个可执行文件。这不仅仅是“创建”更是一次深度的“解构”与“重构”。无论是为了应对更复杂的项目需求还是为了在面试中能清晰阐述QT应用的底层原理掌握手动创建项目的能力都至关重要。2. 核心思路与项目结构设计2.1 技术栈选型与决策逻辑一个典型的QT Quick with C项目其技术栈是清晰分层的。最底层是C业务逻辑层中间是QT框架提供的QML引擎和一系列模块最上层是QML/JS描述的UI界面。手动创建时我们需要明确每一层需要哪些组件。首先C编译器与构建工具。虽然Visual Studio功能强大但对于追求轻量化和跨平台一致性的开发我更倾向于使用MinGW-w64或MSVC命令行工具链配合CMake。CMake作为元构建系统可以生成适用于不同IDE如VS、Qt Creator或构建工具如Ninja、Make的项目文件灵活性远超Qt Creator自带的qmake。这也是为什么很多大型开源QT项目如KDE系列都转向CMake的原因。其次QT模块的选择。一个基础的QT Quick应用至少需要以下模块QtCore: 核心的非GUI类如信号槽、容器、线程管理。QtGui: 窗口系统集成、OpenGL封装、图像处理等基础GUI功能。QtQml: 提供QML引擎和运行时支持。QtQuick: 提供声明式UI框架和一系列基本QML类型如Rectangle, Text。QtQuickControls2或QtQuick.Controls: 提供一套现成的、风格化的UI控件如Button, TextField。QtQuickControls2是更新、更现代的实现通常作为首选。在手动创建时我们需要在构建配置文件如CMakeLists.txt中显式地声明对这些模块的依赖。2.2 项目目录结构规划一个清晰、可扩展的目录结构是项目可维护性的基石。手动创建给了我们完全的自由来设计它。我推荐以下结构它分离了源代码、资源、构建产物和文档MyQtQuickApp/ ├── CMakeLists.txt # 项目根CMake配置文件 ├── src/ # C源代码目录 │ ├── CMakeLists.txt # 源代码子目录的CMake配置 │ ├── main.cpp # 程序入口 │ └── backend/ # C后端逻辑可选 │ ├── CMakeLists.txt │ ├── AppEngine.h │ └── AppEngine.cpp ├── qml/ # QML界面资源目录 │ ├── main.qml # 根QML文件 │ ├── components/ # 可复用的自定义QML组件 │ │ └── CustomButton.qml │ ├── pages/ # 不同的页面/视图 │ │ └── HomePage.qml │ └── qmldir # QML模块定义文件高级用法 ├── resources/ # 其他资源图片、字体、翻译文件等 │ └── images/ │ └── logo.png ├── build/ # 构建输出目录通常.gitignore └── README.md # 项目说明这个结构的关键在于分离关注点。src/纯Cqml/纯QML/JSresources/放静态资源。CMakeLists.txt负责告诉构建系统如何找到并处理这些分散的文件最终将它们链接成一个整体。注意build/目录通常不纳入版本控制如Git。你可以在项目根目录创建一个.gitignore文件里面加上一行build/来忽略它。这是保持仓库清洁的好习惯。3. 从零开始手动创建核心文件详解3.1 编写CMakeLists.txt项目的构建蓝图CMakeLists.txt是项目的总指挥。我们将创建一个最简版本并逐行解释其含义。在项目根目录创建CMakeLists.txt。# 1. 指定CMake最低版本要求。使用较新的版本可以支持更多便利特性。 cmake_minimum_required(VERSION 3.16) # 2. 定义项目名称、版本和使用的编程语言。 project(MyQtQuickApp VERSION 1.0.0 LANGUAGES CXX) # 3. 设置C标准。C17是现代QT项目的一个良好起点。 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 4. 自动包含当前目录和src目录到头文件搜索路径。 # 这样在src/里的.cpp文件就可以用#include “AppEngine.h”来包含同目录的头文件了。 set(CMAKE_AUTOMOC ON) # 自动处理QT的元对象编译器moc必须开启 set(CMAKE_AUTORCC ON) # 自动处理资源文件.qrc必须开启 set(CMAKE_AUTOUIC ON) # 自动处理UI文件.ui本项目未使用但可开启。 # 5. 查找所需的QT模块。COMPONENTS后面列出我们需要的模块。 # REQUIRED表示如果找不到这些模块CMake配置将失败。 find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick QuickControls2) # 6. 添加可执行文件目标并指定源文件。 # 这里先添加主入口文件其他源文件稍后在src/目录的CMakeLists中添加。 add_executable(${PROJECT_NAME} src/main.cpp ) # 7. 将找到的QT模块链接到我们的可执行目标上。 # Qt6::Core等是CMake导入的目标包含了所有必要的头文件路径、库文件和编译定义。 target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Gui Qt6::Qml Qt6::Quick Qt6::QuickControls2 ) # 8. 在Windows上为了避免运行时需要一堆DLL我们可以静态链接运行时库可选。 # 但这会增大最终可执行文件的体积。 if (WIN32) set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$$CONFIG:Debug:Debug) endif() # 9. 添加src子目录该目录有自己的CMakeLists.txt来管理更多C源文件。 add_subdirectory(src)接下来在src/目录下创建另一个CMakeLists.txt# 这个文件管理src/目录下的所有源代码。 # 1. 创建一个库目标也可以是直接添加到父目录的可执行目标。 # 这里我们选择将后端逻辑编译成一个静态库结构更清晰。 add_library(app_backend STATIC backend/AppEngine.cpp backend/AppEngine.h ) # 2. 为这个库目标同样链接必要的QT模块。 target_link_libraries(app_backend PRIVATE Qt6::Core Qt6::Qml # 因为AppEngine可能会暴露给QML所以需要Qml模块 ) # 3. 将生成的库链接到主可执行文件。 # ${PROJECT_NAME}是在根CMakeLists.txt中定义的目标。 target_link_libraries(${PROJECT_NAME} PRIVATE app_backend) # 4. 设置此库的头文件包含目录这样main.cpp或其他文件可以找到它。 target_include_directories(app_backend PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/backend )3.2 编写C入口点main.cppmain.cpp是应用的起点负责初始化QT应用、加载QML引擎并启动主界面。在src/目录下创建main.cpp。#include QGuiApplication // 对于纯Quick应用QGuiApplication足够。如果需要窗口菜单栏等则用QApplication。 #include QQmlApplicationEngine // QML应用引擎用于加载和运行QML文件。 #include QIcon // 用于设置应用图标 // 引入我们自定义的C后端类如果存在 // #include “backend/AppEngine.h” int main(int argc, char *argv[]) { // 1. 启用高DPI缩放让界面在不同分辨率的屏幕上显示清晰。 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // 2. 设置应用的一些属性例如组织名和应用名这会影响设置存储的位置。 QCoreApplication::setOrganizationName(“MyCompany”); QCoreApplication::setApplicationName(“MyQtQuickApp”); // 3. 创建应用实例。 QGuiApplication app(argc, argv); // 4. 设置应用窗口图标可选。需要先将图标文件如app.ico添加到资源系统。 app.setWindowIcon(QIcon(“:/images/logo.png”)); // 5. 创建QML应用引擎。 QQmlApplicationEngine engine; // 6. 可选将C对象暴露给QML上下文。 // AppEngine backend; // engine.rootContext()-setContextProperty(“backend”, backend); // 7. 加载主QML文件。这里使用QUrl从资源系统加载。 // qrc:/qml/main.qml 是资源路径我们需要创建对应的.qrc文件来映射。 const QUrl url(QStringLiteral(“qrc:/qml/main.qml”)); // 8. 连接一个信号当QML加载的对象被创建后如果根对象是null加载失败则退出应用。 QObject::connect(engine, QQmlApplicationEngine::objectCreated, app, [url](QObject *obj, const QUrl objUrl) { if (!obj url objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); // 9. 加载QML文件。 engine.load(url); // 10. 进入应用的主事件循环。 return app.exec(); }关键点解析QGuiApplicationvsQApplication: 如果你的应用不需要传统的菜单栏、工具栏等这些是QWidget的特性那么QGuiApplication更轻量适用于纯QT Quick应用。反之若需要混合QWidget和QQuickView则使用QApplication。qrc:/路径这是QT资源系统的前缀。它允许我们将QML、图片等文件编译进可执行文件本身避免发布时文件散落。下一步我们就需要创建这个资源文件。3.3 创建QT资源文件 (.qrc)资源文件是一个XML格式的文件它告诉QT构建系统哪些文件需要被嵌入到最终的程序中。在项目根目录创建resources.qrc名字可自定。RCC qresource prefix“/” !-- 将qml目录下的所有.qml文件添加为资源 -- file alias“qml/main.qml”qml/main.qml/file file alias“qml/pages/HomePage.qml”qml/pages/HomePage.qml/file !-- 添加图片资源 -- file alias“images/logo.png”resources/images/logo.png/file /qresource /RCC重要提示你需要修改根CMakeLists.txt将这个资源文件添加到可执行目标以便AUTORCC能处理它。在add_executable那一行加入resources.qrcadd_executable(${PROJECT_NAME} src/main.cpp resources.qrc # 添加这一行 )3.4 编写QML界面main.qmlQML是声明式语言用于描述UI。在qml/目录下创建main.qml。import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 ApplicationWindow { id: window width: 800 height: 600 visible: true title: qsTr(“My Qt Quick App”) // qsTr用于国际化翻译 // 使用菜单栏可选 menuBar: MenuBar { Menu { title: qsTr(“File”) Action { text: qsTr(“New”); onTriggered: console.log(“New action triggered”) } Action { text: qsTr(“Open”); onTriggered: console.log(“Open action triggered”) } MenuSeparator {} Action { text: qsTr(“Exit”); onTriggered: Qt.quit() } } } // 主内容区域 Page { anchors.fill: parent padding: 20 ColumnLayout { anchors.centerIn: parent spacing: 15 Label { text: qsTr(“Welcome to My Qt Quick App!”) font.pixelSize: 24 Layout.alignment: Qt.AlignHCenter } Button { text: qsTr(“Click Me!”) Layout.alignment: Qt.AlignHCenter onClicked: { statusLabel.text qsTr(“Button clicked at “) new Date().toLocaleTimeString(Qt.locale(), “hh:mm:ss”) } } Label { id: statusLabel text: qsTr(“Ready.”) Layout.alignment: Qt.AlignHCenter } // 一个简单的输入示例 TextField { id: nameInput placeholderText: qsTr(“Enter your name”) Layout.fillWidth: true onAccepted: statusLabel.text qsTr(“Hello, “) text “!” } } } }这个QML文件创建了一个带菜单栏的窗口中间有一个标签、一个按钮、一个状态标签和一个文本框。它展示了QT Quick的基本组件和信号槽如onClicked,onAccepted的使用。4. 构建、运行与调试配置4.1 命令行构建与运行假设你已经在系统上安装好了QT例如通过官方安装程序或包管理器并且将QT的bin目录包含qmake,cmake等添加到了PATH环境变量。同时确保有合适的C编译工具链如MinGW或MSVC。生成构建系统打开终端CMD, PowerShell, bash等导航到你的项目根目录MyQtQuickApp/。# 创建一个构建目录并进入 mkdir build cd build # 运行CMake生成构建文件。-G指定生成器例如“Ninja”或“Visual Studio 17 2022”。 # -DCMAKE_PREFIX_PATHpath_to_qt 至关重要告诉CMake在哪里找QT。 # 例如你的QT安装在 C:\Qt\6.5.0\msvc2019_64 cmake .. -G “Ninja” -DCMAKE_BUILD_TYPERelease -DCMAKE_PREFIX_PATH“C:\Qt\6.5.0\msvc2019_64”实操心得CMAKE_PREFIX_PATH是成功的关键。如果CMake报错找不到Qt6十有八九是这个路径没设对。你可以通过Qt安装目录下的bin\qmake.exe所在路径向上推两级来找到它。编译项目# 使用生成的构建系统进行编译 cmake --build . --config Release如果使用Ninja也可以直接运行ninja。运行程序# 在build目录下可执行文件通常在子目录如Release/或直接就在当前目录。 # 例如 ./Release/MyQtQuickApp.exe # Windows ./MyQtQuickApp # Linux/macOS首次运行时你可能需要将QT的运行时DLL所在目录如C:\Qt\6.5.0\msvc2019_64\bin添加到PATH或者直接将必要的DLL复制到可执行文件同目录。更专业的做法是使用CMake的windeployqt工具QT自带自动收集依赖。4.2 集成开发环境配置在Visual Studio Code中开发安装扩展C/C(Microsoft),CMake(Microsoft),CMake Tools(Microsoft)。用VSCode打开项目根文件夹。按下CtrlShiftP输入CMake: Configure选择你的工具链如Visual Studio Community 2022 Release - amd64或GCC。配置cmake.configureSettings在VSCode的settings.json中添加“cmake.configureSettings”: { “CMAKE_PREFIX_PATH”: “C:/Qt/6.5.0/msvc2019_64” }底栏会出现CMake的构建、调试按钮。你可以选择构建目标MyQtQuickApp和构建类型Debug/Release然后进行构建和调试。在Qt Creator中开发打开Qt Creator选择“打开文件或项目”打开项目根目录的CMakeLists.txt。Qt Creator会自动识别CMake项目并弹出配置界面。确保“Kit”选择了正确的QT版本和编译器。配置CMake参数在“Initial CMake parameters”中添加-DCMAKE_PREFIX_PATH“C:/Qt/6.5.0/msvc2019_64”如果自动检测不到QT。点击“Configure Project”。之后就可以像普通Qt项目一样进行构建、运行和调试了。踩坑记录有时在Qt Creator中打开CMake项目QML文件没有代码高亮和自动补全。这通常是因为Qt Creator没有正确关联QML模块。检查“项目”模式下的“构建和运行”设置确保QT版本正确并且QML的Import Path包含了QT的安装路径下的qml目录。5. C与QML交互的几种核心模式手动创建项目的一个巨大优势是你可以完全掌控C与QML的交互方式。以下是几种最常用、最核心的模式。5.1 上下文属性注册这是最简单直接的暴露方式适合暴露全局性的单例或主要数据模型。我们在main.cpp中已经见过示例。C端// 在main.cpp中创建引擎后 MyDataModel dataModel; // 假设这是一个继承自QObject的类 engine.rootContext()-setContextProperty(“dataModel”, dataModel);QML端// 在任何QML文件中可以直接使用dataModel Text { text: dataModel.someProperty } Button { onClicked: dataModel.someSlot() }优缺点优点简单快捷无需在QML中import任何模块。缺点污染了全局命名空间。如果注册多个对象容易产生命名冲突。不利于模块化和测试。5.2 注册QML类型这是更模块化、更推荐的方式。你可以将C类注册为一个QML元素类型然后在QML中像使用内置类型如Rectangle,Button一样使用它。C类定义(src/backend/MyItem.h/cpp)// MyItem.h #pragma once #include QObject #include QQmlEngine // 用于qmlRegisterType class MyItem : public QObject { Q_OBJECT Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) // 定义属性 QML_ELEMENT // 这是一个重要的宏简化注册需要Qt 6.2 public: explicit MyItem(QObject *parent nullptr); QString text() const; void setText(const QString newText); signals: void textChanged(); private: QString m_text; };// MyItem.cpp #include “MyItem.h” MyItem::MyItem(QObject *parent) : QObject(parent) {} QString MyItem::text() const { return m_text; } void MyItem::setText(const QString newText) { if (m_text newText) return; m_text newText; emit textChanged(); }注册类型 在main.cpp中或者更好的做法是在一个专门的注册函数中// 在main.cpp中include头文件后 #include “backend/MyItem.h” // ... QQmlApplicationEngine engine; // 注册MyItem类到QML中URI为“MyCompany.MyModule”版本1.0名为“MyItem” qmlRegisterTypeMyItem(“MyCompany.MyModule”, 1, 0, “MyItem”); // ...在QML中使用import MyCompany.MyModule 1.0 // 导入我们注册的模块 Item { width: 200; height: 200 MyItem { // 像使用原生组件一样使用 id: myItem text: “Hello from C” } Text { text: myItem.text } }5.3 使用单例模式注册对于全局管理器、工具类等只需要一个实例的对象注册为单例非常合适。C类定义同样需要是QObject派生类并使用QML_SINGLETON宏。// AppSettings.h #pragma once #include QObject #include QQmlEngine #include QJSEngine class AppSettings : public QObject { Q_OBJECT Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged) QML_SINGLETON // 声明为单例 QML_ELEMENT public: static AppSettings *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) { // 这个静态函数会被QML引擎调用以获取单例实例 Q_UNUSED(qmlEngine) Q_UNUSED(jsEngine) static AppSettings instance; return instance; } // ... 其他属性和方法 };注册由于使用了QML_SINGLETON和QML_ELEMENT宏通常只需要在main.cpp中包含头文件CMake的AUTOMOC和Qt的元对象系统会自动处理。但为了确保模块被加载你可以在main.cpp中import一下对应的QML模块虽然不直接使用或者确保在QML中import了该模块。在QML中使用import MyCompany.MyModule 1.0 Item { Component.onCompleted: { console.log(“Current theme:”, AppSettings.theme) // 直接通过类型名访问 AppSettings.theme “dark” } }6. 高级主题项目配置与优化6.1 管理多平台与不同构建类型CMake可以方便地管理不同平台Windows、Linux、macOS和构建类型Debug、Release的差异。# 在根CMakeLists.txt中 # 平台特定设置 if(WIN32) # Windows特定设置如设置子系统、图标等 set(CMAKE_EXE_LINKER_FLAGS “${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS”) # 定义Windows专用宏 add_compile_definitions(MY_WIN32_PLATFORM) elseif(APPLE) # macOS特定设置 set(CMAKE_OSX_DEPLOYMENT_TARGET “10.15”) add_compile_definitions(MY_MACOS_PLATFORM) elseif(UNIX AND NOT APPLE) # Linux特定设置 add_compile_definitions(MY_LINUX_PLATFORM) endif() # 构建类型特定设置 if(CMAKE_BUILD_TYPE STREQUAL “Debug”) add_compile_definitions(QT_QML_DEBUG) # 启用QML调试 # 添加调试信息关闭优化 add_compile_options(-g -O0) else() # 发布模式开启优化可能去除调试符号 add_compile_options(-O2 -s) endif()6.2 部署与打包手动创建项目后部署也需要手动处理。QT提供了windeployqtWindows、macdeployqtmacOS和linuxdeployqtLinux等工具来帮助收集运行时依赖。Windows下使用windeployqt在Release模式下编译你的项目。打开QT自带的命令行如“Qt 6.5.0 (MSVC 2019 64-bit)”导航到你的可执行文件所在目录。运行命令windeployqt MyQtQuickApp.exe该工具会自动扫描可执行文件将其依赖的QT DLL、插件、QML模块等复制到当前目录。你可能还需要手动复制一些额外的资源或者使用NSIS、Inno Setup等工具制作安装包。使用CMake的安装目标 你可以定义install规则让CMake在构建后自动将文件复制到标准位置。# 安装可执行文件 install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin BUNDLE DESTINATION . # macOS .app ) # 安装QML文件如果需要保持外部文件 install(DIRECTORY qml/ DESTINATION qml) # 安装资源文件 install(FILES resources.qrc DESTINATION .)然后通过cmake --install .命令执行安装。7. 常见问题与调试技巧实录7.1 QML文件修改后界面不更新现象修改了QML文件重新运行程序界面还是老样子。排查检查资源系统确保修改的QML文件在.qrc资源文件中被正确列出并且路径无误。如果文件不在.qrc中程序运行时将找不到它。清理构建有时构建系统会有缓存。尝试清理构建目录删除build文件夹或执行cmake --build . --target clean然后重新构建。QML引擎缓存QT Quick会缓存编译后的QML文件以提升性能。在调试时可以通过设置环境变量QML_DISABLE_DISK_CACHE1来禁用磁盘缓存。在main.cpp的main函数开头添加qputenv(“QML_DISABLE_DISK_CACHE”, “1”);。检查导入路径确保QML文件中的import语句版本和路径正确。如果导入失败该QML组件将无法创建。7.2 C类暴露给QML后属性更改但界面不刷新现象C对象的属性通过Q_PROPERTY暴露在C中修改了属性值但QML中绑定的文本或颜色没有更新。排查信号未发射这是最常见的原因。确保在属性的setter函数中当值真正改变时发射了对应的NOTIFY信号。void setValue(int newVal) { if (m_value ! newVal) { // 必须做判断 m_value newVal; emit valueChanged(); // 必须发射信号 } }QML绑定上下文确保在QML中你是通过属性绑定如text: myCppObj.value来显示值而不是在Component.onCompleted中一次性赋值。一次性赋值不会建立响应式连接。线程问题如果你在非GUI线程非主线程中修改了属性需要确保通过信号槽或QMetaObject::invokeMethod将调用 marshalling 到主线程。直接在子线程中修改UI相关属性是未定义行为。7.3 运行时错误module “QtQuick.Controls” is not installed现象程序启动崩溃控制台输出类似错误。排查CMake链接缺失检查CMakeLists.txt中的target_link_libraries是否包含了Qt6::QuickControls2或Qt6::QuickControls。QML导入语句错误检查QML文件顶部的import语句。对于Qt 6通常使用import QtQuick.Controls 2.15。版本号需要与你安装的QT版本匹配。如果不确定可以尝试写import QtQuick.Controls不指定版本但指定版本是更好的实践。插件路径问题在部署时需要确保QT的Quick Controls插件通常是qtquickcontrols2插件目录被正确部署到可执行文件旁边。windeployqt工具通常会处理这个。7.4 如何在QML中调用C函数并传递复杂参数除了属性你还可以将C的Q_INVOKABLE函数或公共槽函数暴露给QML调用。C端class DataProcessor : public QObject { Q_OBJECT public: Q_INVOKABLE QVariantList processData(const QVariantMap input) { // QVariantMap对应QML的object QVariantList result; // ... 处理逻辑 return result; // QVariantList对应QML的数组 } };QML端Button { onClicked: { var input { “name”: “Alice”, “score”: 95 }; var output dataProcessor.processData(input); console.log(JSON.stringify(output)); } }关键点QT提供了QVariant作为C和QML/JS之间的通用数据容器。QVariantMap对应QML的JavaScript对象QVariantList对应数组。在函数参数和返回值中使用它们可以方便地传递复杂数据。手动创建QT Quick项目的过程就像亲手搭建一座房子的框架。虽然比使用向导耗时但你对每一块砖、每一根梁的位置和作用都了然于胸。当项目遇到诡异问题或者需要实现一些向导无法生成的复杂构建流程时这份亲手搭建的经验就会成为你最有力的调试和解决问题的武器。从.pro/qmake切换到CMake从全局上下文属性切换到模块化类型注册每一步的深入都让你对QT框架的理解更加透彻。下次当你再使用Qt Creator的向导时你看到的将不再是一个黑盒而是一个个你可以随意拆解和重组的标准部件。