资讯动态

Qt5 C++桌面开发范例:界面、数据、网络与打包实战

发布时间:2026/10/29 21:17:12 来源:尧图企业网站定制
简介面向C开发者与Qt初学者的实践型范例教程围绕Qt5框架从开发环境搭建到控件、组件、文件读写逐层展开重点解决入门阶段缺少可动手实例、知识零散难成体系的问题。整套内容以单个PDF文件交付压缩包约2.83MB按章节编排第1章完整演示Qt Creator的下载安装与Hello World流程第2章覆盖窗体基类说明、大小与初始位置控制、无边框窗体移动、多窗体调用等窗口管理技巧第3章逐一梳理按钮、标签、单行与多行文本、下拉列表、时间日期、列表树表视图等常用控件并讲解横向布局与网格布局的排布方式。后续章节延伸至日历、登录窗口、进度条、文件创建与修改等组件和文件操作场景同时涉及QPainter二维绘图与哈希加密等应用。目前已有256人学习读者可按目录逐例复现代码快速搭建Qt5界面开发的完整脉络也便于日常查阅控件用法。1. 需求一套可运行的 Qt5 范例集顶替的是什么工作量做桌面端 C 项目真正卡住进度的常常是“窗体怎么锁死尺寸”“托盘菜单怎么写”“配置文件放哪”“换台机器就提示缺 dll”这类不起眼却绕不过去的活。Qt5 范例开发之所以一直是高频检索词是因为这些问题都有成熟答案但答案散在官方文档、论坛帖子和别人的工程里拼起来要花掉不少时间。这套《C Qt5 范例开发大全》干的就是把散点收拢成一份能按需取用的代码集从开发环境、窗体、控件、组件、文件操作、图形图像、多媒体、系统操作、注册表、数据库、网络、进程线程、数据安全一路排到打包部署十四个方向都配了可以直接打开运行的 .pro 工程。它面向三类人最实用——赶课程设计或毕设、需要一份完整界面骨架的学生接小项目要快速验证交互的人以及刚转到 Qt、想把“能编译”推进到“能演示”的开发者。下面不逐条念目录而是按一个工程从建项目到交付验收的顺序把这套范例里真正能复用的部分拆开讲。2. 工程骨架与环境从 .pro 到可复用交互层的完整链路大多数人第一次翻车不是代码写错而是项目建完跑不起来。这套范例的第 1 章把环境搭建单独拎出来讲是有道理的Qt Creator 只是一个编辑器外壳真正决定能不能编译的是背后的编译套件MinGW 或 MSVC和 Qt 版本是否匹配。范例默认的组合是 Windows 32 位 MinGW OpenGL 那一套离线安装包安装完成后 Qt Creator 里会同时出现 Qt 版本和编译器两个条目缺一个就会在“构建套件”页看到黄色感叹号。这一步没配干净后面所有例程都是白搭。新建工程时选择“Qt Gui 应用”向导会问项目名、存放路径、构建目录和基类。构建目录这一项默认是在项目同级建一个build-xxx-Desktop_Qt_5_x_x_MinGW_32bit-Debug里面再分debug和release两个子目录。它们不是手写的是 qmake 根据当前构建配置生成的所以你在源码目录里找不到 exe 属于正常现象。整个工程目录真正需要纳入版本管理的只有.pro、.h、.cpp、.ui和.qrc这几类其余都是可再生产物。2.1 三个窗体基类的选型边界向导给出的三个基类不是随便挑的它们决定了窗口出厂时自带哪些家具。QMainWindow 自带菜单栏、工具栏、状态栏和一个中央部件区适合做主窗口QWidget 是最朴素的矩形区域什么装饰都没有适合做自定义控件、弹出面板QDialog 是对话框基类带accept()/reject()和返回值适合做短期交互比如登录框、设置页。判断标准很简单这个窗口需不需要“菜单和状态栏”需要就 QMainWindow需不需要“等用户确认后再继续”需要就 QDialog两个都不需要用 QWidget 最省事。QMainWindow 的骨架代码大致是这样值得先记住结构再往里填东西// mainwindow.cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // 中央部件只能有一个其他控件靠布局容器摊开 QWidget *central new QWidget(this); QVBoxLayout *layout new QVBoxLayout(central); layout-addWidget(new QTextEdit(central)); setCentralWidget(central); // 交给主窗口托管析构由父对象负责 resize(800, 600); // 初始大小用户仍可拖拽改变 }setCentralWidget是关键一步没有它主窗口中间是空的你 addWidget 加进去的控件会飘在工具栏底下。如果换成 QDialog写法就变成在构造函数里调setWindowTitle、在确定按钮的槽里调accept()调用方用if (dlg.exec() QDialog::Accepted)判断用户是点了确定还是取消——这个返回值机制是对话框最好用的地方别自己造标志位。2.2 .pro 文件、构建目录与 qrc 资源编译.pro是 qmake 的输入文件行数很少但每行都有含义。范例里的配置基本是下面这个形态可以直接作为模板QT core gui widgets # widgets 模块必须显式加否则 QWidget 编译不过 CONFIG c11 # 允许 lambda 连接信号槽 TARGET QtDemo # 生成的 exe 名字 TEMPLATE app # 应用类型做库时改成 lib SOURCES main.cpp mainwindow.cpp HEADERS mainwindow.h RESOURCES image.qrc # 资源文件在这里注册TARGET改的是 exe 名TEMPLATE改的是产物类型这两行很多人从模板里抄完就不管了等要做插件时才回头找。RESOURCES那一行决定了 qrc 会不会被 moc 编译进二进制漏了就表现为“图片明明放在目录里图标就是不显示”。qrc 文件本身是 XML前缀和别名是两个最容易混淆的概念RCC qresource prefix/img file aliasicoresource/QT.ico/file /qresource /RCCprefix是虚拟目录alias是别名。加了别名之后代码里引用路径必须写:/img/ico少一个斜杠或者前缀写错都会静默失败——QIcon 拿到空路径不报错只是图标不出现这是新手最常见的困惑之一。改完 qrc 之后记得在 Qt Creator 里执行一次“执行 qmake”否则编译还是旧的资源表。2.3 窗体行为控制的四段代码窗口大小、位置、背景色、无边框拖动这四件事属于每个项目都要做一遍但每次都要查的东西。// 锁定尺寸最大最小值设为同一个数用户就拖不动了 setMaximumSize(300, 300); setMinimumSize(300, 300); // 等价写法setFixedSize(300, 300); // 初始位置相对屏幕左上角默认是系统居中 move(100, 100); // 背景色简单场景用样式表足够复杂绘制走 paintEvent setStyleSheet(background:#c62828;);无边框窗口要两步先去掉系统边框再自己接管拖动// 构造函数里 setWindowFlags(Qt::FramelessWindowHint); // 头文件里声明三个事件和两个成员 protected: void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; private: QPoint m_offset; bool m_dragging false;void MainWindow::mousePressEvent(QMouseEvent *e) { if (e-button() Qt::LeftButton) { m_dragging true; // 记录按下点相对窗体左上角的偏移避免窗体瞬间“跳”到鼠标位置 m_offset e-globalPos() - frameGeometry().topLeft(); } } void MainWindow::mouseMoveEvent(QMouseEvent *e) { if (m_dragging (e-buttons() Qt::LeftButton)) move(e-globalPos() - m_offset); // 必须用全局坐标 } void MainWindow::mouseReleaseEvent(QMouseEvent *) { m_dragging false; }这里globalPos()和pos()的区别就是无数人踩过的坑pos()是相对父控件的坐标一旦窗体被嵌在容器里用它算偏移拖动时窗体就会乱飘globalPos()是屏幕坐标任何层级下都成立。另外m_dragging这个标志位不能省只用buttons() Qt::LeftButton判断的话鼠标在窗体上划过但没按键也会触发移动。2.4 控件选型与布局范例的控件章节列了二十多个类实际项目里真正高频的就那么一批。按输入、展示、容器三类分开记比死记类名有效分类控件适用场景常见误用输入QLineEdit / QTextEdit单行 / 多行文本用 QLineEdit 收长文本换行丢失输入QSpinBox / QDateEdit数值 / 日期手写正则校验忽略控件自带范围限制选择QComboBox / QCheckBox单选下拉 / 多选用 QComboBox 做互斥开关语义不符展示QListView / QTreeView / QTableView列表 / 层级 / 表格数据手动 addItem 而不走 Model数据一多就卡布局QHBoxLayout / QGridLayout / QSplitter横向 / 网格 / 可拖分隔用 setGeometry 写死坐标窗口一缩放全乱布局这一项值得单独强调Qt 的布局管理器会在窗口尺寸变化时重新计算子控件几何所以正确做法是先搭容器再往里塞控件而不是给每个控件定死坐标。// 左侧树 右侧表格用 QSplitter 让用户自己调比例 QSplitter *splitter new QSplitter(Qt::Horizontal, this); splitter-addWidget(treeView); splitter-addWidget(tableView); splitter-setStretchFactor(0, 1); // 左侧占 1 份 splitter-setStretchFactor(1, 3); // 右侧占 3 份 QVBoxLayout *layout new QVBoxLayout(centralWidget()); layout-addWidget(splitter);setStretchFactor的第二个参数是权重而不是像素这一点和写死宽度有本质区别窗口放大时按比例分配控件不会被拉成畸形。QGroupBox 和 QTabWidget 则负责“分组”这个视觉层面的组织前者适合把一组相关选项框起来后者适合把功能切成互斥的页签。2.5 菜单、工具栏与系统托盘菜单栏、工具栏、右键菜单三者的动作可以共用同一个 QAction 对象这是 Qt 设计得比较舒服的地方QAction *actOpen new QAction(QIcon(:/img/open), 打开, this); connect(actOpen, QAction::triggered, this, MainWindow::openFile); menuBar()-addMenu(文件)-addAction(actOpen); // 菜单里出现 addToolBar(常用)-addAction(actOpen); // 工具栏里也出现托盘图标属于“范例里有、实际项目里也常用”的功能写法不复杂但有前提QSystemTrayIcon *tray new QSystemTrayIcon(this); tray-setIcon(QIcon(:/img/ico)); // 图标为空时托盘项不可见且不报错 tray-setToolTip(后台运行中); tray-show();托盘常驻的应用比如下载器、监控工具必须重写closeEvent把“关闭”改成“隐藏”否则用户点叉之后进程还在却找不到窗口void MainWindow::closeEvent(QCloseEvent *e) { if (m_trayVisible) { hide(); e-ignore(); } // 拦截关闭只隐藏 else QMainWindow::closeEvent(e); }e-ignore()是这段的核心不写它窗口照样关掉托盘图标变成孤儿。3. 组件与数据落盘对话框、定时器、INI/XML 与 SQLite控件解决“界面长什么样”组件解决“一次完整的交互动作”。范例把日历、登录窗口、文件浏览、颜色选择、进度条、定时刷新单独归成组件一章本质是在示范怎么把若干控件组装成一个可以复用的功能单元。这一层做好了后面的业务代码才不至于每个窗口都重写一遍选择文件的逻辑。3.1 对话框组件的复用方式文件选择、颜色选择这类系统级对话框Qt 提供的是静态函数一行就能拿到结果不需要自己 new 一个对象QString path QFileDialog::getOpenFileName( this, 选择文件, QDir::homePath(), 文本文件 (*.txt);;所有文件 (*.*)); // 过滤器用 ;; 分隔多组 if (path.isEmpty()) return; // 用户点了取消 QColor color QColorDialog::getColor(Qt::white, this, 选择背景色); if (color.isValid()) setStyleSheet(QString(background:%1).arg(color.name()));注意getOpenFileName在用户取消时返回空字符串getColor则返回一个无效颜色两者的失败判断方式不一样这一点范例里没有明说但实际写代码时必须处理否则用户一点取消程序就走进空路径分支。登录窗口这类需要多个输入项的场景适合做成独立的 QDialog 子类把校验逻辑封在对话框内部调用方只看返回值LoginDialog dlg(this); if (dlg.exec() QDialog::Accepted) { // 模态阻塞到用户操作完成 QString user dlg.userName(); startMainWork(user); }exec()和show()的差别要拎清exec()是模态会开一个局部事件循环调用处会停住等结果show()是非模态立刻返回结果要靠信号通知。进度条这类需要边跑边刷新的组件必须用非模态用exec()会把界面卡死。3.2 QTimer 驱动时间与进度定时器在范例里出现了两次一次是实时更新时间显示一次是驱动进度条。核心就三行QTimer *timer new QTimer(this); connect(timer, QTimer::timeout, this, [this]{ labelTime-setText(QDateTime::currentDateTime() .toString(yyyy-MM-dd HH:mm:ss)); }); timer-start(1000); // 单位毫秒1000 即每秒触发关于精度得有个清醒认识QTimer 的触发时间取决于事件循环的繁忙程度它能保证“不早于设定值”但不保证“正好是设定值”。用start(1000)做秒表跑十分钟能差出几百毫秒所以涉及精确计时的场景应该记录起始时间戳再算差值而不是靠累加次数。另外timeout槽里不要做耗时操作一个卡住 2 秒的槽会让整个界面跟着停顿。3.3 INI 与 XML 两套配置读写的取舍配置持久化在范例里给了两种方案选哪个取决于数据形态。INI 适合扁平的键值对比如窗口位置、上次打开的目录、数据库地址XML 适合有层级和重复节点的结构比如一组服务器列表、一棵菜单树。INI 用 QSettings 写最省事// 第三个参数是组织名第四个是应用名共同决定文件落盘位置 QSettings cfg(QSettings::IniFormat, QSettings::UserScope, DemoOrg, DemoApp); cfg.setValue(window/geometry, saveGeometry()); // 斜杠表示分组 cfg.setValue(db/host, 127.0.0.1); cfg.setValue(db/port, 3306); cfg.sync(); // 立即落盘不等对象析构 // 读取时给缺省值键不存在也不会拿到空字符串 QString host cfg.value(db/host, localhost).toString(); int port cfg.value(db/port, 3306).toInt();sync()这一步很多人省掉程序正常退出没问题一旦崩溃或被强制结束缓冲里的配置就丢了。第二个要点是value()的第二个参数不给缺省值时首次运行会拿到空值转换数字就变成 0界面上的端口号显示成 0 就是这么来的。XML 用 QXmlStreamWriter 生成比老的 DOM 接口轻量QFile f(config.xml); if (f.open(QIODevice::WriteOnly | QIODevice::Text)) { QXmlStreamWriter w(f); w.setAutoFormatting(true); // 带缩进方便人工看 w.writeStartDocument(); w.writeStartElement(config); w.writeAttribute(version, 1.0); w.writeTextElement(host, 127.0.0.1); w.writeEndElement(); w.writeEndDocument(); } // 文件在 f 析构时自动关闭读取端用 QXmlStreamReader 配合readNextStartElement()循环遇到不认识的标签就skipCurrentElement()这样加字段不会让老版本程序崩掉——这种向前兼容的写法做交付项目时值得养成习惯。方案数据形态写入方式适用量级INI / QSettings扁平键值自动跨平台几十项以内XML层级结构手写流式几百节点SQLite表格记录SQL万行以上3.4 SQLite 与 QODBC 的取舍数据库章节里给了两条路SQLite 走内置驱动Access 走 ODBC。SQLite 是零配置的一个文件就是一个库做课程设计或小工具最合适QSqlDatabase db QSqlDatabase::addDatabase(QSQLITE); db.setDatabaseName(demo.db); // 文件不存在会自动创建 if (!db.open()) { qDebug() 打开失败: db.lastError().text(); // 一定要打印原因 return; } QSqlQuery q(db); q.prepare(INSERT INTO users(name, age) VALUES(?, ?)); // 占位符防注入 q.addBindValue(A同学); q.addBindValue(20); if (!q.exec()) qDebug() q.lastError().text();两个容易忽略的点。第一是错误信息lastError().text()会明确告诉你“表不存在”还是“字段名写错”比盯着exec()返回的 false 干瞪眼强得多。第二是连接名addDatabase的第二个参数是连接名同一进程里开多个库必须给不同名字否则后一个会把前一个顶掉。Access 走的是QSqlDatabase::addDatabase(QODBC)setDatabaseName填的是 DSN 名称而不是文件路径需要先在系统里配好数据源。判断系统里到底有哪些驱动可用跑一行qDebug() QSqlDatabase::drivers();就知道了列出来的才是真能用的。4. 图形图像、网络与线程让范例从“能跑”到“能演示”前面几层解决的是界面和存储这一层解决的是“看起来像那么回事”。绘图让界面有定制感网络让例程能演示两台机器之间的通信线程让耗时不至于把窗口卡死加密则是交付时绕不开的一道工序。这四块在范例里分属不同章节但实际项目里往往是同时用上的。4.1 QPainter 绘制与图像特效链所有自定义绘制都从重写paintEvent开始QPainter 是画笔void Widget::paintEvent(QPaintEvent *) { QPainter p(this); p.setRenderHint(QPainter::Antialiasing); // 抗锯齿线条才不毛刺 p.setPen(QPen(Qt::darkCyan, 2)); p.drawLine(20, 50, 260, 50); p.drawEllipse(20, 70, 100, 60); p.drawText(QRect(20, 20, 200, 24), Qt::AlignLeft, Qt5 绘图); // 缩放后叠加文字KeepAspectRatio 保证不变形 QImage img(:/img/photo.png); QImage scaled img.scaled(200, 200, Qt::KeepAspectRatio, Qt::SmoothTransformation); p.drawImage(20, 150, scaled); }SmoothTransformation和FastTransformation的差别在缩小时特别明显前者做插值边缘平滑但慢后者直接抽样快但会有锯齿。范例里的图片缩放、翻转、扭曲、模糊、着色、阴影、透明这一串效果本质上都是在 QImage / QPixmap 上做像素级的加工再交给 QPainter 显示。模糊和阴影如果自己写循环几十万像素的图会明显卡顿通常的做法是用 QGraphicsBlurEffect 之类的现成效果类或者把处理放到线程里做完再回主线程贴图。翻转可以用 QTransform 一步搞定比手写像素交换省事QImage mirrored img.mirrored(true, false); // 水平翻转 QImage rotated img.transformed(QTransform().rotate(90));4.2 点对点聊天与局域网广播的通信骨架网络这部分范例给了三个例子点对点服务端、点对点客户端、局域网广播。前两个是 TCP第三个是 UDP骨架都很短。// 服务端监听端口收到连接后挂上读事件 QTcpServer *server new QTcpServer(this); connect(server, QTcpServer::newConnection, this, [this]{ QTcpSocket *sock server-nextPendingConnection(); connect(sock, QTcpSocket::readyRead, this, [sock]{ QByteArray data sock-readAll(); sock-write(echo: data); // 回写客户端据此确认链路通 }); connect(sock, QTcpSocket::disconnected, sock, QObject::deleteLater); }); if (!server-listen(QHostAddress::Any, 8888)) qDebug() 监听失败: server-errorString();readyRead有一个必须讲清楚的限制它只说明“现在有数据可读”不保证“一条完整消息都到了”。TCP 是字节流一条 1KB 的消息很可能分两次到达直接readAll()然后当完整消息解析就会出现内容被截断的玄学问题。正规做法是消息前面加四字节长度头或者用固定分隔符收不全就先缓存起来等下一次。UDP 广播适合“发现同一局域网内的其他实例”这种场景QUdpSocket *udp new QUdpSocket(this); udp-bind(8889, QUdpSocket::ShareAddress); // 多实例绑同一端口必须加 ShareAddress connect(udp, QUdpSocket::readyRead, this, [udp]{ while (udp-hasPendingDatagrams()) { QByteArray buf; buf.resize(udp-pendingDatagramSize()); udp-readDatagram(buf.data(), buf.size()); qDebug() 收到广播: buf; } }); udp-writeDatagram(HELLO, QHostAddress::Broadcast, 8889);ShareAddress这个标志不加第二个实例启动时就会 bind 失败表现为“一台机器能收到广播两台机器不行”。4.3 QThread 与 QRunnable 两种并发写法耗时的活必须挪出主线程否则界面会进入“未响应”。范例里给了两种写法各有适用面。继承 QThread 重写 run适合长时间运行、需要中途取消的任务class Worker : public QThread { Q_OBJECT public: void run() override { for (int i 0; i 100; i) { if (isInterruptionRequested()) return; // 协作式退出别用 terminate emit progress(i); msleep(50); } } signals: void progress(int); };启动用worker-start()取消用worker-requestInterruption()槽里靠isInterruptionRequested()主动退出。terminate()能杀线程但不做清理锁和资源都可能留在半路属于不到万不得已不用的手段。QRunnable 配 QThreadPool 更适合“一堆短任务”的场景比如批量导出一百张图扔进池子里自动排队class Task : public QRunnable { public: void run() override { /* 干一小段活 */ } }; QThreadPool::globalInstance()-start(new Task());不管哪种写法一条铁律不变子线程里不能直接操作界面控件。要更新界面就发信号让槽函数在主线程执行Qt 默认的跨线程连接方式就是排队执行这一点省了很多事。4.4 数据安全AES、MD5 与随机数安全这块范例给了四种QByteArray 简单变换、AES、MD5、随机数。三者的定位完全不同混用是要出事的。MD5 是摘要算法单向不可逆正确用途是校验文件完整性或比对密码哈希不是“加密”QByteArray digest QCryptographicHash::hash(hello, QCryptographicHash::Md5).toHex(); // 结果固定 32 个十六进制字符同样输入永远同样输出AES 是对称加密能加密也能解密但 Qt 核心模块并没有直接暴露 AES 接口范例里的做法是引入现成的封装类或者调用系统加密接口。选型时要确认工作模式CBC / ECB和填充方式模式不匹配两边都拿同一个密钥也解不出来。随机数在安全场景里不能用rand()Qt 5.10 之后提供的是 QRandomGeneratorQByteArray salt(16, 0); for (int i 0; i salt.size(); i) salt[i] static_castchar(QRandomGenerator::global()-bounded(256));密钥和盐值不能写死在源码里编译进二进制的字符串用文本编辑器就能搜出来这一点在交付评审时经常被问到。5. 避坑与常见问题排查5 个最容易翻车的点范例代码在自己的工程里跑得好好的搬到别人的机器上就开始出问题这类情况见得太多了。下面五条按“现象 → 原因 → 解决”整理基本覆盖了从编译到运行的主要坑位。一、换台机器双击 exe 提示缺少 Qt5Core.dll现象是把 release 目录整个拷到另一台没装 Qt 的电脑上双击弹窗报找不到动态库。原因是 Qt 默认动态链接exe 只带了入口真正的模块在 Qt 安装目录的 bin 下。解决办法是在 release 目录里跑一次部署命令把依赖的 dll 和插件目录一起收进来重点是platforms/qwindows.dll这个平台插件——少了它前面 dll 全带齐也照样报“could not find or load the Qt platform plugin”。二、qrc 里换了图片程序里还是旧图标现象是明明替换了 resource 目录下的 ico重新编译后窗口图标纹丝不动。原因是 qrc 编译产物在构建目录里图片换了但资源表没有重新生成。解决办法是执行一次“执行 qmake”再构建如果还不行检查代码里的路径前缀和别名是否和 qrc 里写的完全一致:/img/ico和:/img/QT.ico是两个不同的资源。三、无边框窗体拖不动或者一按就跳到屏幕角落现象是鼠标按下后窗体瞬间位移之后才跟着走。原因是用了e-pos()计算偏移而这个坐标是相对父控件的窗体位置一变参考系就变了。解决办法是统一改成e-globalPos()减去frameGeometry().topLeft()并且在鼠标移动事件里补上e-buttons() Qt::LeftButton的判断防止没按键也触发移动。四、TCP 收到的消息总是缺一截现象是发一段长文本接收端解析时报格式错误短消息却正常。原因是把readyRead当成“一条消息到达”的信号实际上它表示“缓冲区里有字节可读”长消息会分多次到达。解决办法是定协议要么前四字节写长度要么用换行符分隔收不全就把数据追加到缓冲区凑够一条再解析。五、子线程里更新界面程序随机崩溃现象是跑一会儿就闪退日志里没有任何有效信息。原因是 QWidget 及其子类不是线程安全的从工作线程直接调setText属于未定义行为什么时候崩全看运气。解决办法是把结果通过信号发回主线程连接方式用默认的自动连接即可Qt 会识别跨线程并转成排队调用。这条属于没有后悔药的类型写完并发代码一定要回头把所有 UI 调用点检查一遍。6. 打包交付与答辩验收把例程跑成一份能交的成品代码能跑和作品能交是两件事。验收环节最常见的失分点不是功能缺失而是“在我电脑上没问题”。所以交付前一定要做一次干净环境测试找一台没装过开发环境的机器或者临时把 Qt 相关路径从环境变量里摘掉双击 release 目录里的 exe看它能不能独立启动。这一步过不了后面演示得再流畅也白搭。打包的实际动作分两段。第一段是收集依赖把 exe 同目录补齐运行库和platforms插件目录确认双击可启动第二段是做安装包把整个目录压成一个可执行的安装程序顺带写好开始菜单快捷方式和卸载入口。范例里给了两种打包工具的用法本质差别只是脚本语法选一个顺手的就行关键是把“安装后能直接运行”作为验收条件。答辩演示建议按固定顺序走别临场自由发挥。先开主窗口展示菜单、工具栏和托盘再走一遍文件选择与配置保存然后开第二个实例演示网络通信最后展示图表或图像效果。整个流程控制在三到五分钟每一步都有明确的可见反馈评委不用等你解释就知道在干什么。下面这张自检表可以对着过一遍验收项自检方法常见扣分点独立启动干净机器双击 exe缺平台插件报 plugin 错误界面完整缩放窗口、切页签写死坐标控件重叠或截断数据持久改配置后重启程序没调 sync本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑