ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

VS2015 + Qt图形界面开发:从版本配到Hello World实战

VS2015 + Qt图形界面开发:从版本配到Hello World实战

前几天有人私信我,说机器上装的是 Visual Studio 2015,老师又要求交一个带图形界面的 C++ 小作业,问我能不能用 VS2015 配 Qt 来做。当然能,而且 VS2015 + Qt 这套组合,是国内很多老项目、教材和实验课上绕不开的一条路。这篇就把从零跑通图形化 Hello World 的每一步讲透:版本怎么配、插件怎么装、项目怎么建、报错怎么治,最后再顺手做一个能输入名字的小交互。适合刚接触 Qt,或者平时只在命令行里写过 C++ 的朋友。

1. 版本搭配先过关:VS2015该配哪个Qt

1.1 先搞懂Qt安装包的命名密码

很多人一上来就栽在下载环节:去 Qt 官网看到一堆目录,msvc2015、msvc2017、mingw73_64……直接懵了。

其实 Qt 官方安装包的名字已经把答案写脸上了:

  • msvc2015表示它用 VS2015 的编译器(MSVC v140 工具集)编译;
  • msvc2015_64表示 64 位版本;
  • mingw53_32这种带 MinGW 的是另一套编译器体系,跟 VS 是两路人。

Qt 是靠底层库分发的,你用msvc2015编译出来的库,配合 VS2015 的 v140 运行时,兼容性才是最稳的。如果你强行装一个msvc2017的 Qt 包,再用 VS2015 去链接,最常见的后果就是编译阶段报一堆头文件缺失、链接阶段报LNK2019,或者即使编译过了,运行时弹 "0xc000007b",非常劝退。

1.2 我的推荐组合与理由

VS2015 对应的 Qt 版本,我一般推荐Qt 5.11.2或者Qt 5.9(LTS 长期版)。原因很简单:这两个版本是官方最后一波还提供msvc2015预编译包的版本。到 Qt 5.12 以后,官方预编译包的主打编译器换成了 msvc2017,VS2015 用户就得自己折腾源码编译,那个复杂度对新手来说完全是灾难。

另外提醒一句:如果选的是 MinGW 版本,就要搭配 Qt Creator 使用,而不是 VS。所以用 VS2015 做开发,安装 Qt 时一定要勾选 MSVC 系列的包,别顺手勾了 MinGW,装完发现模板里面啥都没有,又得回头折腾。

如果你只是练手,32 位和 64 位怎么选?我的建议是:第一次做就先装 32 位(msvc2015),VS 里用默认的 Win32 平台,少一层位数不匹配的烦恼。64 位后面会了再补也不迟。

2. 安装与配置:组件勾选、插件注册、Qt Versions指路

2.1 Qt在线安装器里到底要勾什么

安装 Qt 时,我建议用离线安装包,省去注册账号和网络的等待。安装路径建议用D:\Qt这种短路径,尽量不要带空格和中文,比如C:\Program Files (x86)\Qt。虽然 Qt 也能处理,但后面你用命令行工具、拼环境变量路径的时候,空格经常会给你整出意想不到的幺蛾子。

进入组件选择页面之后,展开Qt 5.11.2这一项,勾上msvc2015或msvc2015_64(看你前面决定装哪个)。下面那一长串 Qt Charts、Qt 3D、Qt WebEngine 之类,Hello World 阶段一律不勾,装了纯属占硬盘。等你真的需要某个模块,以后单独维护也是可以的。

工具链那块,除非你想顺便试试 Qt Creator,否则不用额外装。装完后你会在D:\Qt\5.11.2\msvc2015_64下面看到 include、lib、bin 这些目录,这就算成了。

这里要习惯性地做一个动作:设置环境变量。

  • 新建QTDIR,值填D:\Qt\5.11.2\msvc2015_64;
  • 把%QTDIR%\bin追加到Path变量里。

这步不是必须,但做了之后,后面在命令行里跑windeployqt、或者排查“找不到 Qt5Widgets.dll”时会省事很多。

2.2 VS2015侧的Qt插件与Qt Versions设置

VS 本身不认识 Qt,它俩之间需要一个插件来桥接。VS2015 上一般装的是 Qt Visual Studio Tools,老一些的资料里叫 Qt Add-in。打开 VS2015,进工具 -> 扩展和更新 -> 联机,搜索 “Qt Visual Studio Tools”,下载安装后重启 VS。

装完插件,菜单栏会多出一个Qt VS Tools(或老版本的Qt)菜单。进去找到Qt VS Tools -> Qt Options,左侧选Qt Versions,点Add按钮,名称随便填,路径选到D:\Qt\5.11.2\msvc2015_64。填完设成默认。

这步千万别跳过。插件靠这个路径去定位头文件、库文件,还要调用moc、uic、windeployqt这些 Qt 自带工具。路径填错或者没填,后面新建工程后一编译就是一堆“工具找不到”的错。

3. 第一次新建Qt Widgets项目:每个人的第一个带界面的C++程序

3.1 选对模板之后,项目里这几个文件是干嘛的

打开 VS2015,文件 -> 新建 -> 项目,左侧选 Visual C++,如果插件正常,你会看到Qt Widgets Application(旧版插件可能叫Qt Application)。给项目起名HelloWorld,点确定。

系统会生成一个 Qt 项目,核心文件就这几样:

  • HelloWorld.pro:qmake 工程文件,以后你想脱离 VS、单纯用命令行构建,它就有用了;
  • main.cpp:整个程序的入口,创建应用对象和主窗口,进入事件循环;
  • mainwindow.h/mainwindow.cpp:主窗口类的声明和实现;
  • mainwindow.ui:用 Qt Designer 可视编辑的界面文件。

新手容易犯的错是把mainwindow.ui当普通文本乱改,或者去翻生成的ui_mainwindow.h。这个文件是在编译前由uic工具根据.ui自动生成的,你改它没有任何意义,下次重新生成又会覆盖。

main.cpp 默认长这样,先混个脸熟:

#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }

a.exec()是事件循环,别问为什么窗口能一直挂着不退出,就是这个函数在持续接收鼠标、键盘、绘制等消息。这是 Qt 应用生命的核心。

3.2 在Qt Designer里拖出按钮和标签

双击项目里的mainwindow.ui,会打开 Qt Designer 的可视化编辑界面。左边是一堆控件面板,右侧是属性编辑器。

先拖一个QLabel(标签)和一个QPushButton(按钮)到窗体上。拖完第一件事,改对象名。选中控件,在右侧属性编辑器的最上面找到objectName:

  • 标签改成labelHello;
  • 按钮改成btnSayHello;
  • 按钮的text属性改成“点我试试”这类文字。

对象名这件事一定别偷懒。默认的label_2、pushButton_3这种名字,写代码的时候根本分不清谁是谁,等到项目稍微大一点,那就是给自己埋雷。

再把两个控件都选中,在窗体空白处右键,选栅格布局。不布局的后果是,窗口一拉伸,控件原地不动,非常丑。布局是 Qt 界面开发里最容易被忽略的习惯,早点养成。

3.3 用信号槽让按钮真正干一件事

界面拖好了,接下来才是 Qt 的精髓——信号槽。

打开mainwindow.cpp,在构造函数里补一段:

#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->btnSayHello, &QPushButton::clicked, this, [this]() { ui->labelHello->setText("Hello World"); }); } MainWindow::~MainWindow() { delete ui; }

connect的意思就是:当按钮的clicked信号发射时,执行后面 lambda 里的代码。你点按钮,标签的文字就变成 “Hello World”。

这里我刻意用了 Qt5 推荐的函数指针写法,而不是老的SIGNAL/SLOT宏。差别在于函数指针写法在编译期就能检查信号和槽是否存在、参数是否匹配,老写法跑起来炸了才提示你,排查半天,对新人非常不友好。

快捷键Ctrl+Shift+B生成解决方案,再按F5启动调试。弹出一个窗口,点一下按钮,标签显示 Hello World。到这里,你的第一个图形化 C++ 程序就跑通了。

顺便说一嘴,Qt 还有一种“自动连接”的写法:如果槽函数名刚好叫on_btnSayHello_clicked(),setupUi会自动帮你连上,不用写 connect。这个技巧项目里很常见,但我更建议新手先从手动 connect 学起,看代码时逻辑更直白。

4. 连点三次“调试”的完整排错链路:从报错到能跑

4.1 常见编译错误的排查链路

头一次跑,报错一两回很正常。我把最常见的几类按症状整理了排查顺序:

症状常见原因最先排查的地方
fatal error C1083:找不到 QMainWindow.h插件没注册 Qt 版本,或包含目录缺失Qt VS Tools -> Qt Options -> Qt Versions里的路径
LNK2019 无法解析的外部符号Qt 库没链接,或模块没勾全确认项目是 Qt 模板创建;Debug/Release 附加依赖项是否正确
报错找不到 ui_mainwindow.huic没跑,或项目不是 Qt 工程删除后重新用 Qt 模板新建,不要从空项目硬改
运行时弹“0xc000007b”32/64 位不匹配检查 VS 平台(Win32/x64)与 Qt 包位数是否一致

如果你真的遇到LNK2019,可以这样检查:打开项目属性 -> 配置属性 -> 链接器 -> 输入 -> 附加依赖项。Qt 插件的正常工程里会自动加Qt5Widgetsd.lib(Debug 版带 d,Release 版不带)。如果你是从普通 C++ 项目改的,这里十有八九是空的。但我不推荐手搓附加依赖项来解决,最干净的办法是重新用 Qt 模板建一个项目,把代码复制进去。Qt 模板已经把所有自定义生成规则都配置好了,手搓很容易顾此失彼。

4.2 windeployqt:告别“缺少Qt5Widgets.dll”

过了编译关,还有一关是部署。

清完解决方案,你到 Debug 目录双击HelloWorld.exe,如果本机没装 Qt,大概率弹窗说找不到Qt5Widgets.dll。这是正常的,因为 Qt 是动态库,exe 运行时要到处找依赖。

Qt 官方提供了一个工具叫windeployqt,专门解决这事。从开始菜单打开Qt 5.11.2 (MSVC 2015 64-bit)这个命令行环境,然后进到 exe 所在目录,执行:

windeployqt HelloWorld.exe

它会自动把 Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll 以及 platforms 目录下的 qwindows.dll 等一堆依赖拷到 exe 旁边。这一步做完,把整个目录拷到别的 Windows 机器上就能直接运行。

如果你生成的是 Debug 版 exe,记得加参数:

windeployqt HelloWorld.exe --debug

不加的话,它默认按 Release 处理,Debug 程序运行时还是会缺带 d 的Qt5Cored.dll之类。

4.3 中文文本乱码的根源与一次性修法

最后一个高频坑是中文乱码。

现象是:标签显示的不是“你好”,而是一串生僻字。原因是 VS2015 把源码文件默认存成了 GBK 编码,而 Qt5 认为源码是 UTF-8,两边各说各话,文字就乱了。

修法很简单:在 VS2015 里打开源文件,文件 -> 高级保存选项,把编码选成Unicode (UTF-8 带签名) - 代码页 65001,保存后重新编译。

还有土办法是在字符串外面包一层:

ui->labelHello->setText(QString::fromLocal8Bit("你好"));

这招在中文 Windows 上能临时救急,但换到别的语言环境立马又出问题,不建议长期用。我后来几乎所有项目都统一把源文件存成 UTF-8,一劳永逸。

5. 把Hello World换成你的名字:一个能互动的扩展练习

5.1 加一个QLineEdit,把输入框联动起来

跑通 Hello World 之后,你可以顺手把它扩展成一个能互动的小程序,顺便多练两个控件。

回到 Qt Designer,再拖一个QLineEdit(单行输入框)到窗体,把objectName改成editName。然后回mainwindow.cpp,把构造函数里的 connect 改成:

connect(ui->btnSayHello, &QPushButton::clicked, this, [this]() { QString name = ui->editName->text(); if (name.isEmpty()) { ui->labelHello->setText(tr("Hello World")); } else { ui->labelHello->setText(tr("Hello, ") + name); } });

text()返回输入框里的内容;isEmpty()是防空的判断。现在你在输入框里填个名字,点按钮,标签就会显示 “Hello, 名字”。这一步走完,你对“界面控件 + 代码逻辑 + 信号槽”三者之间的配合,就有感觉了。

5.2 一个小习惯让以后的Qt代码更好维护

最后分享一个我在多个项目里都坚持的习惯:字符串一律用tr()包起来。

tr()是 Qt 给多语言和编码管理预留的通道。现在的 Hello World 看不出差别,但以后项目要国际化,或者团队里有人要改文案,tr()包过的字符串能统一提取、统一替换,不会散落在代码各处。用它,就是在为未来的自己少踩坑。

另外一个习惯是,lambda 内部只做简单逻辑。如果槽函数里要做的事超过三行,就别硬塞在 lambda 里,拉出来写成一个普通成员函数,再connect到那个函数。代码一大,可读性的差距就出来了。

这套环境搭好之后,其实你以后做很多 Qt 小工具都会沿用同一套流程。我第一次跑通图形化 Hello World 的时候,以为还要写不少底层代码,结果发现 Qt 把窗口管理、事件分发、消息循环这些都封装好了,作为使用者真的只需要关注业务逻辑本身。后面再遇到“窗口卡死”“界面控件找不到”“dll 缺失”这类问题,先回头检查版本匹配和插件路径,八成都是这些基础环节出了岔子。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进