详细讲解如何在 Visual Studio 2022 中结合 Qt 进行 C++ 项目开发,涵盖环境配置、项目创建、调试和部署,重点展示 模板 和 多线程,并提供一个复杂的Demo

详细讲解如何在 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

  1. 下载安装:

    • 访问 Visual Studio 官网,下载 Visual Studio 2022 Community(免费版)。

    • 选择工作负载:“使用 C++ 的桌面开发”。

    • 可选组件:勾选 English language pack(确保一致性)。

  2. 验证:

    • 安装后,打开 Visual Studio,创建一个简单的 C++ 控制台项目,确认编译器(MSVC)正常工作。

1.2 安装 Qt

  1. 下载 Qt:

    • 访问 Qt 官网,下载 Qt Online Installer。

    • 选择版本:推荐 Qt 5.15.2(稳定,广泛支持)或 Qt 6.x(需 Visual Studio 2022 支持)。

  2. 安装组件:

    • 选择 Qt 5.15.2(或 Qt 6.x)下的 MSVC 2019 64-bit(与 Visual Studio 2022 兼容)。

    • 勾选 Qt Charts(用于图表)、Qt Creator(可选,辅助设计 UI)。

    • 安装路径:例如 C:\Qt。

  3. 环境变量:

    • 添加 Qt 的 bin 目录到系统 PATH,例如 C:\Qt\5.15.2\msvc2019_64\bin。

    • 验证:命令行运行 qmake -version,确认版本正确。

1.3 安装 Visual Studio Qt 插件

  1. 安装 Qt Visual Studio Tools:

    • 在 Visual Studio 2022 中,打开 扩展 > 管理扩展。

    • 搜索 Qt Visual Studio Tools,安装最新版本(例如 3.x)。

    • 重启 Visual Studio。

  2. 配置 Qt 插件:

    • 打开 扩展 > Qt VS Tools > Qt Options。

    • 点击 Add,添加 Qt 安装路径(例如 C:\Qt\5.15.2\msvc2019_64)。

    • 选择该版本,点击 OK。

  3. 验证:

    • 创建一个 Qt 项目(见下文),确认 .ui 文件可打开。

1.4 其他工具

  • Git:用于版本控制,可通过 Visual Studio 集成。

  • CMake(可选):若使用 CMake 替代 qmake。

  • Modbus 模拟器:如 ModSim(Windows)或 QModMaster,测试 Modbus TCP。

2. 在 Visual Studio 2022 中创建 Qt 项目

2.1 创建项目

  1. 新建项目:

    • 打开 Visual Studio 2022,选择 创建新项目。

    • 搜索 Qt,选择 Qt Widgets Application,点击 下一步。

    • 配置:

      • 项目名称:DebugTool。

      • 位置:任意目录(如 D:\Projects)。

      • 解决方案名称:DebugToolSolution。

  2. 项目设置:

    • Qt 版本:选择已配置的 Qt 5.15.2 MSVC 2019 64-bit。

    • 模块:默认包含 core、gui、widgets,稍后手动添加 network、charts。

    • 点击 创建。

  3. 项目结构:

    • 生成的文件: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

  1. 打开 UI 文件:

    • 在 Visual Studio 的解决方案资源管理器中,双击 mainwindow.ui。

    • 打开 Qt Designer(由 Qt VS Tools 集成)。

  2. 布局设计(见 Demo 的 UI 部分)。

  3. 保存并编译:

    • Qt VS Tools 自动将 .ui 编译为 ui_mainwindow.h。

2.4 编译与调试

  1. 设置启动项目:

    • 右键 DebugTool 项目,选择 设为启动项目。

  2. 选择配置:

    • Debug:用于调试,包含符号信息。

    • Release:优化性能,适合部署。

  3. 编译:

    • 按 F5(调试)或 Ctrl+F5(运行)。

    • Qt VS Tools 会调用 qmake 生成 Makefile,再用 MSVC 编译。

  4. 调试:

    • 设置断点:点击代码行左侧。

    • 使用 调试 > 开始调试,观察变量、调用堆栈。

    • 支持 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 部署

  1. Windows 部署:

    • 编译 Release 模式,生成 DebugTool.exe(如 bin\Release)。

    • 使用 Qt 的 windeployqt:

      cmd

      windeployqt bin\Release\DebugTool.exe
    • 复制 MSVC 运行时库(vcruntime140.dll 等)或安装 Microsoft Visual C++ Redistributable

  2. 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 的部署细节,请提供要求,我可以进一步定制!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhxup606

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值