详细讲解如何在 Visual Studio 2022 中结合 Qt 进行 C++ 项目开发,涵盖环境配置、项目创建、调试和部署,重点展示 模板 和 多线程(延续前文的深入知识点),并提供一个复杂的 Demo 项目,扩展前文的 串口与 Modbus TCP 调试工具。该 Demo 将是一个 Qt GUI 应用程序,支持串口和 Modbus TCP 数据读取,集成时间戳记录,展示模板、多线程、跨平台(Windows 和 Linux)、中文支持(UTF-8),使用 C++17、右值引用、智能指针和线程安全设计。以下是详细介绍和完整 Demo 代码。
C++ 在 Visual Studio 2022 和 Qt 联合开发详解
1. 环境配置
1.1 安装 Visual Studio 2022
-
下载安装:
-
访问 Visual Studio 官网,下载 Visual Studio 2022 Community(免费版)。
-
选择工作负载:“使用 C++ 的桌面开发”。
-
可选组件:勾选 English language pack(确保一致性)。
-
-
验证:
-
安装后,打开 Visual Studio,创建一个简单的 C++ 控制台项目,确认编译器(MSVC)正常工作。
-
1.2 安装 Qt
-
下载 Qt:
-
访问 Qt 官网,下载 Qt Online Installer。
-
选择版本:推荐 Qt 5.15.2(稳定,广泛支持)或 Qt 6.x(需 Visual Studio 2022 支持)。
-
-
安装组件:
-
选择 Qt 5.15.2(或 Qt 6.x)下的 MSVC 2019 64-bit(与 Visual Studio 2022 兼容)。
-
勾选 Qt Charts(用于图表)、Qt Creator(可选,辅助设计 UI)。
-
安装路径:例如 C:\Qt。
-
-
环境变量:
-
添加 Qt 的 bin 目录到系统 PATH,例如 C:\Qt\5.15.2\msvc2019_64\bin。
-
验证:命令行运行 qmake -version,确认版本正确。
-
1.3 安装 Visual Studio Qt 插件
-
安装 Qt Visual Studio Tools:
-
在 Visual Studio 2022 中,打开 扩展 > 管理扩展。
-
搜索 Qt Visual Studio Tools,安装最新版本(例如 3.x)。
-
重启 Visual Studio。
-
-
配置 Qt 插件:
-
打开 扩展 > Qt VS Tools > Qt Options。
-
点击 Add,添加 Qt 安装路径(例如 C:\Qt\5.15.2\msvc2019_64)。
-
选择该版本,点击 OK。
-
-
验证:
-
创建一个 Qt 项目(见下文),确认 .ui 文件可打开。
-
1.4 其他工具
-
Git:用于版本控制,可通过 Visual Studio 集成。
-
CMake(可选):若使用 CMake 替代 qmake。
-
Modbus 模拟器:如 ModSim(Windows)或 QModMaster,测试 Modbus TCP。
2. 在 Visual Studio 2022 中创建 Qt 项目
2.1 创建项目
-
新建项目:
-
打开 Visual Studio 2022,选择 创建新项目。
-
搜索 Qt,选择 Qt Widgets Application,点击 下一步。
-
配置:
-
项目名称:DebugTool。
-
位置:任意目录(如 D:\Projects)。
-
解决方案名称:DebugToolSolution。
-
-
-
项目设置:
-
Qt 版本:选择已配置的 Qt 5.15.2 MSVC 2019 64-bit。
-
模块:默认包含 core、gui、widgets,稍后手动添加 network、charts。
-
点击 创建。
-
-
项目结构:
-
生成的文件:DebugTool.pro(qmake 项目文件)、main.cpp、mainwindow.h/cpp/ui。
-
Visual Studio 会自动加载 .pro 文件并生成 MSVC 项目。
-
2.2 配置 .pro 文件
编辑 DebugTool.pro:
pro
QT += core gui widgets network charts
CONFIG += c++17
TARGET = DebugTool
TEMPLATE = app
SOURCES += main.cpp mainwindow.cpp serialport.cpp modbusclient.cpp datamanager.cpp timeutils.cpp threadpool.cpp
HEADERS += mainwindow.h serialport.h modbusclient.h datamanager.h timeutils.h threadpool.h
FORMS += mainwindow.ui
INCLUDEPATH += $$PWD
win32 {
LIBS += -lsetupapi -lws2_32
QMAKE_CXXFLAGS += -D_WIN32_WINNT=0x0600
}
unix {
LIBS += -lgthread-2.0
QMAKE_CXXFLAGS += -pthread
}
CODECFORTR = UTF-8
CODECFORSRC = UTF-8
QMAKE_CXXFLAGS += -Wall -Wextra -O2
2.3 设计 UI
-
打开 UI 文件:
-
在 Visual Studio 的解决方案资源管理器中,双击 mainwindow.ui。
-
打开 Qt Designer(由 Qt VS Tools 集成)。
-
-
布局设计(见 Demo 的 UI 部分)。
-
保存并编译:
-
Qt VS Tools 自动将 .ui 编译为 ui_mainwindow.h。
-
2.4 编译与调试
-
设置启动项目:
-
右键 DebugTool 项目,选择 设为启动项目。
-
-
选择配置:
-
Debug:用于调试,包含符号信息。
-
Release:优化性能,适合部署。
-
-
编译:
-
按 F5(调试)或 Ctrl+F5(运行)。
-
Qt VS Tools 会调用 qmake 生成 Makefile,再用 MSVC 编译。
-
-
调试:
-
设置断点:点击代码行左侧。
-
使用 调试 > 开始调试,观察变量、调用堆栈。
-
支持 Qt 类型的调试(如 QString)。
-
3. Demo 项目:增强版串口与 Modbus TCP 调试工具
3.1 项目概述
扩展前文的调试工具,展示 Visual Studio 2022 和 Qt 的联合开发,集成 模板 和 多线程:
-
功能:
-
添加/删除串口和 Modbus TCP 设备。
-
异步读取数据(串口原始数据,Modbus 功能码 0x03)。
-
周期性读取(每秒)。
-
显示数据、时间戳、响应时间(表格、日志)。
-
图表展示 Modbus 寄存器值。
-
-
C++ 特性:
-
模板:泛型数据存储、变参序列化。
-
多线程:线程池处理异步 I/O,std::mutex 确保线程安全。
-
其他:C++17、右值引用、智能指针、<chrono>、异常处理。
-
-
Qt 特性:
-
QMainWindow:GUI。
-
QTableWidget:数据表格。
-
QChart:寄存器图表。
-
QTimer:定时读取。
-
-
环境:
-
Visual Studio 2022,Qt 5.15.2 MSVC 2019 64-bit。
-
跨平台(Windows 优先,Linux 通过 qmake 支持)。
-
中文支持(UTF-8)。
-
3.2 项目文件结构
DebugTool/
├── main.cpp
├── mainwindow.h
├── mainwindow.cpp
├── mainwindow.ui
├── serialport.h
├── serialport.cpp
├── modbusclient.h
├── modbusclient.cpp
├── datamanager.h
├── datamanager.cpp
├── timeutils.h
├── timeutils.cpp
├── threadpool.h
├── threadpool.cpp
├── DebugTool.pro
3.3 代码实现
3.3.1 项目配置文件
已在上文提供(DebugTool.pro)。
3.3.2 时间工具类(timeutils.h/cpp)
复用前文:
cpp
// timeutils.h
#ifndef TIMEUTILS_H
#define TIMEUTILS_H
#include <chrono>
#include <string>
#include <QString>
class TimeUtils {
public:
static QString getCurrentTimeQString(const QString& format = "yyyy-MM-dd hh:mm:ss");
static std::string getCurrentTimeChrono(const std::string& format = "%Y-%m-%d %H:%M:%S");
static int64_t getDurationMs(const std::chrono::system_clock::time_point& start,
const std::chrono::system_clock::time_point& end);
};
#endif
cpp
// timeutils.cpp
#include "timeutils.h"
#include <QDateTime>
#include <iomanip>
#include <sstream>
QString TimeUtils::getCurrentTimeQString(const QString& format) {
return QDateTime::currentDateTime().toString(format);
}
std::string TimeUtils::getCurrentTimeChrono(const std::string& format) {
auto now = std::chrono::system_clock::now();
time_t rawtime = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&rawtime), format.c_str());
return ss.str();
}
int64_t TimeUtils::getDurationMs(const std::chrono::system_clock::time_point& start,
const std::chrono::system_clock::time_point& end) {
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
}
3.3.3 线程池(threadpool.h/cpp)
复用前文:
cpp
// threadpool.h
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
class ThreadPool {
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queueMutex;
std::condition_variable cv;
bool stop = false;
public:
ThreadPool(size_t threads);
template<typename F, typename... Args>
void enqueue(F&& f, Args&&... args);
~ThreadPool();
};
template<typename F, typename... Args>
void ThreadPool::enqueue(F&& f, Args&&... args) {
auto task = std::make_shared<std::packaged_task<void()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
{
std::unique_lock<std::mutex> lock(queueMutex);
if (stop) throw std::runtime_error("线程池已停止");
tasks.emplace([task]() { (*task)(); });
}
cv.notify_one();
}
#endif
cpp
// threadpool.cpp
#include "threadpool.h"
ThreadPool::ThreadPool(size_t threads) {
for (size_t i = 0; i < threads; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
cv.wait(lock, [this] { return stop || !tasks.empty(); });
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
stop = true;
}
cv.notify_all();
for (auto& worker : workers) {
if (worker.joinable()) worker.join();
}
}
3.3.4 串口类(serialport.h/cpp)
复用前文,略。
3.3.5 Modbus 客户端(modbusclient.h/cpp)
复用前文,略。
3.3.6 数据管理类(datamanager.h/cpp)
扩展支持图表数据:
cpp
// datamanager.h
#ifndef DATAMANAGER_H
#define DATAMANAGER_H
#include <QObject>
#include <QTimer>
#include <mutex>
#include <vector>
#include <memory>
#include <type_traits>
#include "serialport.h"
#include "modbusclient.h"
#include "timeutils.h"
#include "threadpool.h"
// 数据基类
struct DeviceData {
std::string timestamp;
std::string deviceId;
int64_t responseTimeMs;
DeviceData(std::string ts, std::string id, int64_t rt)
: timestamp(std::move(ts)), deviceId(std::move(id)), responseTimeMs(rt) {}
virtual ~DeviceData() = default;
virtual std::string toString() const = 0;
};
// 串口数据
struct SerialDeviceData : DeviceData {
std::vector<uint8_t> rawData;
SerialDeviceData(std::string ts, std::string id, std::vector<uint8_t> d, int64_t rt)
: DeviceData(std::move(ts), std::move(id), rt), rawData(std::move(d)) {}
std::string toString() const override {
std::stringstream ss;
ss << "时间: " << timestamp << ", 设备: " << deviceId << ", 数据: ";
for (auto v : rawData) ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(v) << " ";
ss << ", 响应时间: " << std::dec << responseTimeMs << "ms";
return ss.str();
}
};
// Modbus 数据
struct ModbusDeviceData : DeviceData {
std::vector<uint16_t> registers;
ModbusDeviceData(std::string ts, std::string id, std::vector<uint16_t> r, int64_t rt)
: DeviceData(std::move(ts), std::move(id), rt), registers(std::move(r)) {}
std::string toString() const override {
std::stringstream ss;
ss << "时间: " << timestamp << ", 设备: " << deviceId << ", 寄存器: ";
for (auto r : registers) ss << r << " ";
ss << ", 响应时间: " << std::dec << responseTimeMs << "ms";
return ss.str();
}
};
// 模板记录容器
template<typename T>
class DataRecord {
std::vector<std::unique_ptr<T>> records;
public:
void addRecord(std::unique_ptr<T>&& record) { records.push_back(std::move(record)); }
const std::vector<std::unique_ptr<T>>& getRecords() const { return records; }
void clear() { records.clear(); }
};
// 变参模板序列化
template<typename... Args>
std::string serialize(Args&&... args) {
std::stringstream ss;
((ss << std::forward<Args>(args) << " "), ...);
return ss.str();
}
class DataManager : public QObject {
Q_OBJECT
private:
std::map<std::string, std::unique_ptr<SerialPort>> serialDevices;
std::map<std::string, std::unique_ptr<ModbusClient>> modbusDevices;
std::map<std::string, QTimer*> timers;
DataRecord<SerialDeviceData> serialRecords;
DataRecord<ModbusDeviceData> modbusRecords;
std::mutex recordsMutex;
ThreadPool threadPool;
std::map<std::string, std::chrono::system_clock::time_point> requestTimes;
public:
DataManager(QObject* parent = nullptr);
~DataManager();
template<typename T, typename = std::enable_if_t<
std::is_same_v<std::decay_t<T>, std::pair<std::string, int>>>>
bool addDevice(const std::string& deviceId, T&& config);
bool removeDevice(const std::string& deviceId);
void startRead(const std::string& deviceId, bool periodic = false, int intervalMs = 1000);
const std::vector<std::unique_ptr<SerialDeviceData>>& getSerialRecords() const;
const std::vector<std::unique_ptr<ModbusDeviceData>>& getModbusRecords() const;
std::vector<std::string> getDeviceIds() const;
void clearRecords();
signals:
void dataUpdated();
void errorOccurred(const QString& error);
void deviceStateChanged(const std::string& deviceId, bool isConnected);
private slots:
void onReadTimeout(const std::string& deviceId);
void onDeviceConnected(const std::string& deviceId);
void onDeviceDisconnected(const std::string& deviceId);
};
template<typename T, typename>
bool DataManager::addDevice(const std::string& deviceId, T&& config) {
std::lock_guard<std::mutex> lock(recordsMutex);
if (serialDevices.find(deviceId) != serialDevices.end() || modbusDevices.find(deviceId) != modbusDevices.end()) {
emit errorOccurred("设备 ID 已存在: " + QString::fromStdString(deviceId));
return false;
}
if (config.first.find("COM") == 0 || config.first.find("/dev/tty") == 0) {
try {
auto serial = std::make_unique<SerialPort>(config.first, config.second);
serialDevices[deviceId] = std::move(serial);
timers[deviceId] = new QTimer(this);
connect(timers[deviceId], &QTimer::timeout, this, [this, deviceId]() {
onReadTimeout(deviceId);
});
emit deviceStateChanged(deviceId, true);
return true;
} catch (const std::exception& e) {
emit errorOccurred(QString::fromStdString("添加串口设备失败: " + std::string(e.what())));
return false;
}
} else {
try {
auto modbus = std::make_unique<ModbusClient>(config.first, config.second, this);
connect(modbus.get(), &ModbusClient::connected, this, [this, deviceId]() {
onDeviceConnected(deviceId);
});
connect(modbus.get(), &ModbusClient::disconnected, this, [this, deviceId]() {
onDeviceDisconnected(deviceId);
});
connect(modbus.get(), &ModbusClient::errorOccurred, this, &DataManager::errorOccurred);
modbusDevices[deviceId] = std::move(modbus);
timers[deviceId] = new QTimer(this);
connect(timers[deviceId], &QTimer::timeout, this, [this, deviceId]() {
onReadTimeout(deviceId);
});
modbusDevices[deviceId]->connectToServer();
return true;
} catch (const std::exception& e) {
emit errorOccurred(QString::fromStdString("添加 Modbus 设备失败: " + std::string(e.what())));
return false;
}
}
}
#endif
cpp
// datamanager.cpp
#include "datamanager.h"
DataManager::DataManager(QObject* parent) : QObject(parent), threadPool(std::thread::hardware_concurrency()) {}
DataManager::~DataManager() {
for (auto& [id, timer] : timers) {
timer->stop();
timer->deleteLater();
}
}
bool DataManager::removeDevice(const std::string& deviceId) {
std::lock_guard<std::mutex> lock(recordsMutex);
if (timers.find(deviceId) != timers.end()) {
timers[deviceId]->stop();
timers[deviceId]->deleteLater();
timers.erase(deviceId);
}
if (serialDevices.erase(deviceId) || modbusDevices.erase(deviceId)) {
requestTimes.erase(deviceId);
emit deviceStateChanged(deviceId, false);
return true;
}
return false;
}
void DataManager::startRead(const std::string& deviceId, bool periodic, int intervalMs) {
std::lock_guard<std::mutex> lock(recordsMutex);
requestTimes[deviceId] = std::chrono::system_clock::now();
if (auto it = serialDevices.find(deviceId); it != serialDevices.end()) {
threadPool.enqueue([this, deviceId, start = requestTimes[deviceId]]() {
it->second->asyncRead([this, deviceId, start](std::vector<uint8_t> data, std::chrono::system_clock::time_point) {
auto end = std::chrono::system_clock::now();
int64_t responseTimeMs = TimeUtils::getDurationMs(start, end);
auto record = std::make_unique<SerialDeviceData>(
TimeUtils::getCurrentTimeChrono(), deviceId, std::move(data), responseTimeMs);
{
std::lock_guard<std::mutex> lock(recordsMutex);
serialRecords.addRecord(std::move(record));
}
emit dataUpdated();
});
});
} else if (auto it = modbusDevices.find(deviceId); it != modbusDevices.end()) {
threadPool.enqueue([this, deviceId, start = requestTimes[deviceId]]() {
it->second->readRegisters(0, 2, [this, deviceId, start](std::vector<uint16_t> registers, std::chrono::system_clock::time_point) {
auto end = std::chrono::system_clock::now();
int64_t responseTimeMs = TimeUtils::getDurationMs(start, end);
auto record = std::make_unique<ModbusDeviceData>(
TimeUtils::getCurrentTimeChrono(), deviceId, std::move(registers), responseTimeMs);
{
std::lock_guard<std::mutex> lock(recordsMutex);
modbusRecords.addRecord(std::move(record));
}
emit dataUpdated();
});
});
} else {
emit errorOccurred("设备不存在: " + QString::fromStdString(deviceId));
return;
}
if (periodic && timers.find(deviceId) != timers.end()) {
timers[deviceId]->start(intervalMs);
}
}
void DataManager::onReadTimeout(const std::string& deviceId) {
startRead(deviceId, false);
}
void DataManager::onDeviceConnected(const std::string& deviceId) {
emit deviceStateChanged(deviceId, true);
}
void DataManager::onDeviceDisconnected(const std::string& deviceId) {
emit deviceStateChanged(deviceId, false);
}
const std::vector<std::unique_ptr<SerialDeviceData>>& DataManager::getSerialRecords() const {
return serialRecords.getRecords();
}
const std::vector<std::unique_ptr<ModbusDeviceData>>& DataManager::getModbusRecords() const {
return modbusRecords.getRecords();
}
std::vector<std::string> DataManager::getDeviceIds() const {
std::lock_guard<std::mutex> lock(recordsMutex);
std::vector<std::string> ids;
for (const auto& [id, _] : serialDevices) ids.push_back(id);
for (const auto& [id, _] : modbusDevices) ids.push_back(id);
return ids;
}
void DataManager::clearRecords() {
std::lock_guard<std::mutex> lock(recordsMutex);
serialRecords.clear();
modbusRecords.clear();
}
3.3.7 主窗口(mainwindow.h/cpp)
cpp
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTableWidget>
#include <QtCharts/QChart>
#include <QtCharts/QLineSeries>
#include "datamanager.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget* parent = nullptr);
~MainWindow();
private slots:
void addDevice();
void removeDevice();
void startRead();
void clearData();
void updateTableAndChart();
void togglePeriodicRead();
void showError(const QString& error);
void updateDeviceStatus(const std::string& deviceId, bool isConnected);
void updateDeviceSelection();
private:
Ui::MainWindow* ui;
DataManager* manager;
QtCharts::QChart* chart;
QtCharts::QLineSeries* regSeries;
};
#endif
cpp
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QInputDialog>
#include <QCheckBox>
#include <QtCharts/QChartView>
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
setWindowTitle("串口与 Modbus 调试工具");
manager = new DataManager(this);
ui->dataTable->setColumnCount(4);
ui->dataTable->setHorizontalHeaderLabels({"时间", "设备ID", "数据", "响应时间(ms)"});
ui->dataTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
chart = new QtCharts::QChart;
regSeries = new QtCharts::QLineSeries;
chart->addSeries(regSeries);
chart->createDefaultAxes();
chart->setTitle("Modbus 寄存器值");
ui->chartView->setChart(chart);
ui->chartView->setRenderHint(QPainter::Antialiasing);
connect(ui->addButton, &QPushButton::clicked, this, &MainWindow::addDevice);
connect(ui->removeButton, &QPushButton::clicked, this, &MainWindow::removeDevice);
connect(ui->readButton, &QPushButton::clicked, this, &MainWindow::startRead);
connect(ui->clearButton, &QPushButton::clicked, this, &MainWindow::clearData);
connect(ui->periodicCheck, &QCheckBox::toggled, this, &MainWindow::togglePeriodicRead);
connect(manager, &DataManager::dataUpdated, this, &MainWindow::updateTableAndChart);
connect(manager, &DataManager::errorOccurred, this, &MainWindow::showError);
connect(manager, &DataManager::deviceStateChanged, this, &MainWindow::updateDeviceStatus);
updateDeviceSelection();
}
MainWindow::~MainWindow() {
delete ui;
}
void MainWindow::addDevice() {
bool ok;
QString type = QInputDialog::getItem(this, "添加设备", "设备类型:", {"串口", "Modbus TCP"}, 0, false, &ok);
if (!ok) return;
QString deviceId = QInputDialog::getText(this, "添加设备", "设备 ID:", QLineEdit::Normal, "", &ok);
if (!ok || deviceId.isEmpty()) return;
if (type == "串口") {
QString port = QInputDialog::getText(this, "添加串口设备", "端口 (如 COM3 或 /dev/ttyUSB0):",
QLineEdit::Normal, "COM3", &ok);
if (ok && manager->addDevice(deviceId.toStdString(), std::make_pair(port.toStdString(), CBR_9600))) {
ui->logText->append("已添加串口设备: " + deviceId);
}
} else {
QString host = QInputDialog::getText(this, "添加 Modbus 设备", "主机地址:", QLineEdit::Normal, "127.0.0.1", &ok);
int port = QInputDialog::getInt(this, "添加 Modbus 设备", "端口:", 502, 1, 65535, 1, &ok);
if (ok && manager->addDevice(deviceId.toStdString(), std::make_pair(host.toStdString(), port))) {
ui->logText->append("已添加 Modbus 设备: " + deviceId);
}
}
updateDeviceSelection();
}
void MainWindow::removeDevice() {
QString deviceId = ui->deviceCombo->currentText();
if (!deviceId.isEmpty() && manager->removeDevice(deviceId.toStdString())) {
ui->logText->append("已删除设备: " + deviceId);
updateDeviceSelection();
}
}
void MainWindow::startRead() {
QString deviceId = ui->deviceCombo->currentText();
if (!deviceId.isEmpty()) {
manager->startRead(deviceId.toStdString(), ui->periodicCheck->isChecked(), 1000);
ui->logText->append("开始读取: " + deviceId);
}
}
void MainWindow::clearData() {
manager->clearRecords();
ui->dataTable->setRowCount(0);
regSeries->clear();
ui->logText->append("数据已清空");
}
void MainWindow::togglePeriodicRead() {
QString deviceId = ui->deviceCombo->currentText();
if (!deviceId.isEmpty()) {
manager->startRead(deviceId.toStdString(), ui->periodicCheck->isChecked(), 1000);
}
}
void MainWindow::updateDeviceSelection() {
ui->deviceCombo->clear();
for (const auto& id : manager->getDeviceIds()) {
ui->deviceCombo->addItem(QString::fromStdString(id));
}
}
void MainWindow::updateTableAndChart() {
const auto& serialRecords = manager->getSerialRecords();
const auto& modbusRecords = manager->getModbusRecords();
ui->dataTable->setRowCount(serialRecords.size() + modbusRecords.size());
size_t row = 0;
for (const auto& record : serialRecords) {
ui->dataTable->setItem(row, 0, new QTableWidgetItem(QString::fromStdString(record->timestamp)));
ui->dataTable->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(record->deviceId)));
ui->dataTable->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(record->toString())));
ui->dataTable->setItem(row, 3, new QTableWidgetItem(QString::number(record->responseTimeMs)));
++row;
}
for (const auto& record : modbusRecords) {
ui->dataTable->setItem(row, 0, new QTableWidgetItem(QString::fromStdString(record->timestamp)));
ui->dataTable->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(record->deviceId)));
ui->dataTable->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(record->toString())));
ui->dataTable->setItem(row, 3, new QTableWidgetItem(QString::number(record->responseTimeMs)));
++row;
}
// 更新图表
regSeries->clear();
QString selectedDevice = ui->deviceCombo->currentText();
int index = 0;
for (const auto& record : modbusRecords) {
if (record->deviceId == selectedDevice.toStdString()) {
for (auto value : record->registers) {
regSeries->append(index++, value);
}
}
}
chart->axes(Qt::Horizontal)->first()->setRange(0, index);
chart->axes(Qt::Vertical)->first()->setRange(0, index > 0 ? 60000 : 100);
}
void MainWindow::showError(const QString& error) {
ui->logText->append("错误: " + error);
}
void MainWindow::updateDeviceStatus(const std::string& deviceId, bool isConnected) {
ui->statusLabel->setText(QString::fromStdString(deviceId) + (isConnected ? ": 已连接" : ": 未连接"));
ui->statusLabel->setStyleSheet(isConnected ? "color: green" : "color: red");
}
3.3.9 UI 文件(mainwindow.ui)
在 Visual Studio 的 Qt Designer 中:
-
布局:QVBoxLayout:
-
QHBoxLayout:
-
QLabel(statusLabel):设备状态。
-
QComboBox(deviceCombo):设备选择。
-
QPushButton(addButton):文本“添加设备”。
-
QPushButton(removeButton):文本“删除设备”。
-
-
QHBoxLayout:
-
QPushButton(readButton):文本“读取数据”。
-
QPushButton(clearButton):文本“清空数据”。
-
QCheckBox(periodicCheck):文本“周期性读取”。
-
-
QTableWidget(dataTable):4 列(时间、设备ID、数据、响应时间)。
-
QChartView(chartView):显示 Modbus 寄存器折线图。
-
QTextEdit(logText):日志。
-
3.3.10 主程序
cpp
// main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
4. Visual Studio 2022 开发流程
4.1 项目管理
-
添加文件:
-
右键项目 > 添加 > 新项,选择 Qt Class、Qt UI File 或 C++ File。
-
自动更新 .pro 文件。
-
-
依赖管理:
-
编辑 .pro 添加 Qt 模块(如 charts)或库(如 ws2_32)。
-
运行 Qt VS Tools > Run qmake。
-
4.2 调试技巧
-
断点:
-
在 mainwindow.cpp 或 datamanager.cpp 设置断点,观察 records 或 socket 状态。
-
-
监视变量:
-
添加 serialRecords 或 modbusRecords 到监视窗口,检查数据。
-
-
Qt 调试:
-
Qt VS Tools 支持 QString、QList 等类型的可视化。
-
-
日志:
-
使用 ui->logText->append 输出调试信息。
-
4.3 部署
-
Windows 部署:
-
编译 Release 模式,生成 DebugTool.exe(如 bin\Release)。
-
使用 Qt 的 windeployqt:
cmd
windeployqt bin\Release\DebugTool.exe
-
复制 MSVC 运行时库(vcruntime140.dll 等)或安装 Microsoft Visual C++ Redistributable。
-
-
Linux 部署:
-
在 Linux 环境中用 qmake 和 make 编译。
-
确保 Qt 库和依赖(如 libQt5Widgets.so)已安装。
-
5. 输出示例
-
添加设备:串口 Serial1(COM3)、Modbus TCP1(127.0.0.1:502)。
-
串口返回:01 02 03,响应时间 10ms。
-
Modbus:功能码 0x03,返回 {4660, 123},响应时间 5ms。
-
日志:
已添加串口设备: Serial1 已添加 Modbus 设备: TCP1 开始读取: Serial1 串口数据: Serial1, 时间: 2025-06-05 17:30:00, 数据: 01 02 03, 响应时间: 10ms 开始读取: TCP1 Modbus 数据: TCP1, 时间: 2025-06-05 17:30:01, 寄存器: 4660 123, 响应时间: 5ms 数据已清空
-
表格:
时间 | 设备ID | 数据 | 响应时间(ms) 2025-06-05 17:30:00 | Serial1 | 时间: ..., 数据: 01 02 03, 响应时间: 10ms | 10 2025-06-05 17:30:01 | TCP1 | 时间: ..., 寄存器: 4660 123, 响应时间: 5ms | 5
-
图表:
-
折线图显示 TCP1 的寄存器值 [4660, 123]。
-
6. 模板与多线程应用
-
模板:
-
DataRecord<T>:存储 SerialDeviceData 和 ModbusDeviceData。
-
serialize<Args...>:序列化数据。
-
addDevice<T>:使用 SFINAE 限制配置类型。
-
-
多线程:
-
ThreadPool:异步处理串口和 Modbus 读取。
-
std::mutex:保护 recordsMutex。
-
QTimer:与线程池结合实现定时读取。
-
7. 常见问题与解决
-
Qt 插件未加载:
-
问题:无法打开 .ui 文件。
-
解决:检查 Qt Options,确保 Qt 路径正确。
-
-
链接错误:
-
问题:ws2_32 或 Qt 库未找到。
-
解决:确认 .pro 中的 LIBS 和 QT 配置。
-
-
中文乱码:
-
问题:日志显示乱码。
-
解决:确保 .pro 设置 CODECFORTR = UTF-8,代码中使用 QString::fromStdString。
-
-
Modbus 未响应:
-
问题:服务器未启动。
-
解决:运行 ModSim 或 QModMaster,监听 127.0.0.1:502。
-
8. Visual Studio 2022 优势
-
集成调试:强大的 MSVC 调试器,支持 Qt 类型。
-
项目管理:解决方案资源管理器便于文件组织。
-
Qt 插件:无缝集成 Qt Designer 和 qmake。
-
性能:MSVC 编译优化适合高性能应用。
9. 扩展建议
-
模板:添加 CRTP 实现设备类型扩展。
-
多线程:使用 std::future 跟踪异步任务。
-
功能:支持 Modbus 功能码 0x06、0x10。
-
数据库:集成 SQLite 存储历史数据。
10. 总结
本教程详细讲解了在 Visual Studio 2022 中结合 Qt 开发 C++ 项目,提供了环境配置、开发流程和部署方法。通过 串口与 Modbus TCP 调试工具 Demo,展示了:
-
模板:泛型数据、SFINAE。
-
多线程:线程池、线程安全。
-
Qt 集成:GUI、图表、定时器。
-
Visual Studio:调试、项目管理。
如果需要更深入的功能(如数据库集成、特定 Modbus 功能码)或针对 Linux 的部署细节,请提供要求,我可以进一步定制!