From 0b76463cae1e0129cb8199d88c7e0db41286781c Mon Sep 17 00:00:00 2001 From: lipengpeng Date: Thu, 7 Aug 2025 19:09:09 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E5=AD=98=E8=AF=BB=E5=8F=96=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- connection.cpp | 2 +- editor.pro | 6 ++-- editor.pro.user | 2 +- hmi.cpp | 64 +++++++++++++++++++++++++++++++-- hmi.h | 7 +++- item.cpp | 16 +++------ item.h | 11 ++++-- mainwindow.cpp | 89 ++++++++++++++++++++++++++++++++++++++++++++++ mainwindow.h | 8 +++++ mygraphicsview.cpp | 19 ++++++++++ mygraphicsview.h | 1 + plc.cpp | 68 +++++++++++++++++++++++++++++++++-- plc.h | 7 +++- project.cpp | 68 +++++++++++++++++++++++++++++++++++ project.h | 28 +++++++++++++++ 15 files changed, 372 insertions(+), 24 deletions(-) create mode 100644 project.cpp create mode 100644 project.h diff --git a/connection.cpp b/connection.cpp index 7be132f..bd4c950 100644 --- a/connection.cpp +++ b/connection.cpp @@ -8,7 +8,7 @@ Connection::Connection(Item* from, Item::AnchorType fromType, : QGraphicsLineItem(parent), from_(from), to_(to), fromType_(fromType), toType_(toType) { - setZValue(-1); // 在图元之下 + setZValue(-1); setPen(QPen(Qt::black, 2)); setFlags(QGraphicsItem::ItemIsSelectable); from_->addConnection(this); diff --git a/editor.pro b/editor.pro index dcadb17..0a766e2 100644 --- a/editor.pro +++ b/editor.pro @@ -28,7 +28,8 @@ SOURCES += \ main.cpp \ mainwindow.cpp \ mygraphicsview.cpp \ - plc.cpp + plc.cpp \ + project.cpp HEADERS += \ button.h \ @@ -42,7 +43,8 @@ HEADERS += \ light.h \ mainwindow.h \ mygraphicsview.h \ - plc.h + plc.h \ + project.h FORMS += \ hmi.ui \ diff --git a/editor.pro.user b/editor.pro.user index 6b8b30b..8d07fb3 100644 --- a/editor.pro.user +++ b/editor.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/hmi.cpp b/hmi.cpp index b48b907..ed7d7ff 100644 --- a/hmi.cpp +++ b/hmi.cpp @@ -16,8 +16,8 @@ HMI::HMI(QWidget *parent) : { ui->setupUi(this); /* 1. 场景 */ - m_scene = new QGraphicsScene(this); - ui->graphicsView->setScene(m_scene); + hmi_scene_ = new QGraphicsScene(this); + ui->graphicsView->setScene(hmi_scene_); ui->graphicsView->setSceneRect(0, 0, 800, 600); ui->graphicsView->setDragMode(QGraphicsView::RubberBandDrag); @@ -100,6 +100,66 @@ void HMI::createComponents() } } +void HMI::clearScene() +{ + hmi_scene_->clear(); +} + +void HMI::applyProjectToScene(const Project& proj) +{ + clearScene(); + QVector itemObjs; + for (const auto& d : proj.items_) { + Item* item = creatItem(d.type); + if (!item) continue; + item->setPos(d.x, d.y); + connect(item, &Item::requestCopy, ui->graphicsView, &MyGraphicsView::onItemRequestCopy); + connect(item, &Item::requestDelete, ui->graphicsView, &MyGraphicsView::onItemRequestDelete); + hmi_scene_->addItem(item); + itemObjs.append(item); + } +// for (const auto& c : proj.connections_) { +// if (c.from >= 0 && c.from < itemObjs.size() && c.to >= 0 && c.to < itemObjs.size()) { +// Connection* conn = new Connection( +// itemObjs[c.from], static_cast(c.fromType), +// itemObjs[c.to], static_cast(c.toType)); +// hmi_scene_->addItem(conn); +// } +// } +} + +void HMI::extractSceneToProject(Project& proj) +{ + proj.clear(); + QList items; + for (QGraphicsItem* gi : hmi_scene_->items()) { + if (Item* it = dynamic_cast(gi)) { + items.append(it); + } + } + for (Item* it : items) { + Project::ItemData d; + d.type = it->itemType(); + d.x = it->pos().x(); + d.y = it->pos().y(); + proj.items_.append(d); + } + for (QGraphicsItem* gi : hmi_scene_->items()) { + if (Connection* conn = dynamic_cast(gi)) { + int fromIdx = items.indexOf(conn->from_); + int toIdx = items.indexOf(conn->to_); + if (fromIdx >= 0 && toIdx >= 0) { + Project::ConnectionData c; + c.from = fromIdx; + c.to = toIdx; + c.fromType = static_cast(conn->fromType_); + c.toType = static_cast(conn->toType_); + proj.connections_.append(c); + } + } + } +} + void HMI::onListwidgetCurrenttextchanged(const QString &text) { selectedComponentType = text; diff --git a/hmi.h b/hmi.h index 9f831ee..d2eb0c4 100644 --- a/hmi.h +++ b/hmi.h @@ -3,6 +3,7 @@ #include #include +#include namespace Ui { class HMI; @@ -17,6 +18,10 @@ public: ~HMI(); void createComponents(); + void clearScene(); + void applyProjectToScene(const Project& proj); + void extractSceneToProject(Project& proj); + protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -25,7 +30,7 @@ private slots: private: Ui::HMI *ui; - QGraphicsScene *m_scene; + QGraphicsScene *hmi_scene_; QString selectedComponentType; }; diff --git a/item.cpp b/item.cpp index ffd4192..c09a137 100644 --- a/item.cpp +++ b/item.cpp @@ -62,6 +62,7 @@ void Item::MenuActions(QMenu *menu) { menu->addAction("复制"); menu->addAction("删除"); + menu->addAction("对象"); } void Item::addMenuActions(QMenu *menu) @@ -74,9 +75,12 @@ void Item::handleMenuAction(QAction *action) if (action->text() == "复制") { emit requestCopy(this); } - else if (action->text() == "删除") { + if (action->text() == "删除") { emit requestDelete(this); } + if (action->text() == "对象"){ + emit requestBindRegister(this); + } } QVariant Item::itemChange(GraphicsItemChange change, const QVariant &value) @@ -90,16 +94,6 @@ QVariant Item::itemChange(GraphicsItemChange change, const QVariant &value) void Item::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { -// QMenu menu; -// QAction* copyAct = menu.addAction("复制"); -// QAction* deleteAct = menu.addAction("删除"); -// QAction* selected = menu.exec(event->screenPos()); -// if (selected == copyAct) { -// emit requestCopy(this); -// } -// if (selected == deleteAct) { -// emit requestDelete(this); -// } QMenu menu; // 创建菜单 diff --git a/item.h b/item.h index 34fdce8..1f5d3bc 100644 --- a/item.h +++ b/item.h @@ -21,19 +21,24 @@ public: void removeConnection(Connection* conn); QList connections(); QString itemType(); - virtual void MenuActions(QMenu *menu); // 添加基本菜单项 - virtual void addMenuActions(QMenu *menu); // 添加额外菜单项 - virtual void handleMenuAction(QAction *action); // 处理菜单动作 + void setRegisterId(const QString& id) { registerId_ = id; } + QString registerId() const { return registerId_; } + signals: void requestCopy(Item*); void requestDelete(Item*); + void requestBindRegister(Item*); protected: + virtual void MenuActions(QMenu *menu); + virtual void addMenuActions(QMenu *menu); + virtual void handleMenuAction(QAction *action); QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; QString type_; QList connections_; + QString registerId_; }; #endif // ITEM_H diff --git a/mainwindow.cpp b/mainwindow.cpp index c102b1e..8123370 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "creatitem.h" MainWindow::MainWindow(QWidget *parent) @@ -24,6 +25,13 @@ MainWindow::MainWindow(QWidget *parent) hmi_->hide(); connect(ui->action_plc,&QAction::triggered,this,&MainWindow::plcChange); connect(ui->action_hmi,&QAction::triggered,this,&MainWindow::hmiChange); + + connect(ui->action_new, &QAction::triggered, this, &MainWindow::newProject); + connect(ui->action_save, &QAction::triggered, this, &MainWindow::saveProject); + connect(ui->action_open, &QAction::triggered, this, &MainWindow::openProject); + + plc_->applyProjectToScene(plcProject_); // 初始空 + hmi_->applyProjectToScene(hmiProject_); } MainWindow::~MainWindow() @@ -35,11 +43,92 @@ void MainWindow::plcChange() { plc_->show(); hmi_->hide(); + currentIsPLC_ = true; + setWindowTitle((plcFilePath_.isEmpty() ? "未命名项目" : QFileInfo(plcFilePath_).fileName()) + " - PLC编辑器"); } void MainWindow::hmiChange() { hmi_->show(); plc_->hide(); + currentIsPLC_ = false; + setWindowTitle((hmiFilePath_.isEmpty() ? "未命名项目" : QFileInfo(hmiFilePath_).fileName()) + " - HMI编辑器"); +} + +void MainWindow::newProject() +{ + if (currentIsPLC_) { + plcProject_.clear(); + plcFilePath_.clear(); + plc_->clearScene(); + setWindowTitle("未命名项目 - PLC编辑器"); + } else { + hmiProject_.clear(); + hmiFilePath_.clear(); + hmi_->clearScene(); + setWindowTitle("未命名项目 - HMI编辑器"); + } } +void MainWindow::openProject() +{ + if (currentIsPLC_) + { + QString filePath = QFileDialog::getOpenFileName(this, "打开项目", "", "项目文件 (*.plcproj)"); + if (filePath.isEmpty()) return; + if (plcProject_.loadFromFile(filePath)) + { + plcFilePath_ = filePath; + plc_->applyProjectToScene(plcProject_); + setWindowTitle(QFileInfo(plcFilePath_).fileName() + " - PLC编辑器"); + } + else + { + QMessageBox::critical(this, "错误", "无法打开PLC项目文件"); + } + } + else + { + QString filePath = QFileDialog::getOpenFileName(this, "打开项目", "", "项目文件 (*.hmiproj)"); + if (filePath.isEmpty()) return; + if (hmiProject_.loadFromFile(filePath)) + { + hmiFilePath_ = filePath; + hmi_->applyProjectToScene(hmiProject_); + setWindowTitle(QFileInfo(hmiFilePath_).fileName() + " - HMI编辑器"); + } + else + { + QMessageBox::critical(this, "错误", "无法打开HMI项目文件"); + } + } +} + +void MainWindow::saveProject() +{ + if (currentIsPLC_) { + if (plcFilePath_.isEmpty()) { + QString filePath = QFileDialog::getSaveFileName(this, "保存项目", "", "PLC项目文件 (*.plcproj)"); + if (filePath.isEmpty()) return; + if (!filePath.endsWith(".plcproj", Qt::CaseInsensitive)) + filePath += ".plcproj"; + plcFilePath_ = filePath; + } + plc_->extractSceneToProject(plcProject_); + if (!plcProject_.saveToFile(plcFilePath_)) { + QMessageBox::critical(this, "错误", "保存项目失败"); + } + } else { + if (hmiFilePath_.isEmpty()) { + QString filePath = QFileDialog::getSaveFileName(this, "保存项目", "", "HMI项目文件 (*.hmiproj)"); + if (filePath.isEmpty()) return; + if (!filePath.endsWith(".hmiproj", Qt::CaseInsensitive)) + filePath += ".hmiproj"; + hmiFilePath_ = filePath; + } + hmi_->extractSceneToProject(hmiProject_); + if (!hmiProject_.saveToFile(hmiFilePath_)) { + QMessageBox::critical(this, "错误", "保存项目失败"); + } + } +} diff --git a/mainwindow.h b/mainwindow.h index d357df0..ac8540a 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -21,11 +21,19 @@ public: private slots: void plcChange(); void hmiChange(); + void newProject(); + void openProject(); + void saveProject(); private: Ui::MainWindow *ui; PLC *plc_; HMI *hmi_; + Project plcProject_; + Project hmiProject_; + QString plcFilePath_; + QString hmiFilePath_; + bool currentIsPLC_ = true; }; #endif // MAINWINDOW_H diff --git a/mygraphicsview.cpp b/mygraphicsview.cpp index 8b70786..5d37642 100644 --- a/mygraphicsview.cpp +++ b/mygraphicsview.cpp @@ -4,6 +4,7 @@ #include #include #include +#include MyGraphicsView::ClipInfo MyGraphicsView::clipboard_ = {}; @@ -46,6 +47,7 @@ void MyGraphicsView::dropEvent(QDropEvent *event) item->setPos(scenePos); connect(item, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy); connect(item, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete); + connect(item, &Item::requestBindRegister, this, &MyGraphicsView::onItemRequestBindRegister); scene()->addItem(item); event->acceptProposedAction(); @@ -92,6 +94,7 @@ void MyGraphicsView::keyPressEvent(QKeyEvent *event) newItem->setPos(center); connect(newItem, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy); connect(newItem, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete); + connect(newItem, &Item::requestBindRegister, this, &MyGraphicsView::onItemRequestBindRegister); scene()->addItem(newItem); } } else { @@ -112,6 +115,7 @@ void MyGraphicsView::contextMenuEvent(QContextMenuEvent *event) newItem->setPos(scenePos); connect(newItem, &Item::requestCopy, this, &MyGraphicsView::onItemRequestCopy); connect(newItem, &Item::requestDelete, this, &MyGraphicsView::onItemRequestDelete); + connect(newItem, &Item::requestBindRegister, this, &MyGraphicsView::onItemRequestBindRegister); scene()->addItem(newItem); } } else { @@ -159,6 +163,21 @@ void MyGraphicsView::onItemRequestDelete(Item *item) delete item; } +void MyGraphicsView::onItemRequestBindRegister(Item *item) +{ + bool ok = false; + QString reg = QInputDialog::getText(this, + "寄存器", "编号:", + QLineEdit::Normal, + item->registerId(), + &ok); + if (ok && !reg.isEmpty()) { + item->setRegisterId(reg); + // 可选:在图元上显示寄存器编号 + item->update(); + } +} + void MyGraphicsView::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { diff --git a/mygraphicsview.h b/mygraphicsview.h index 614eef3..193fd61 100644 --- a/mygraphicsview.h +++ b/mygraphicsview.h @@ -44,6 +44,7 @@ private: public slots: void onItemRequestCopy(Item*); void onItemRequestDelete(Item*); + void onItemRequestBindRegister(Item*); }; #endif // MYGRAPHICSVIEW_H diff --git a/plc.cpp b/plc.cpp index 0be8e03..42d0ab8 100644 --- a/plc.cpp +++ b/plc.cpp @@ -18,8 +18,8 @@ PLC::PLC(QWidget *parent) : { ui->setupUi(this); /* 1. 场景 */ - m_scene = new QGraphicsScene(this); - ui->graphicsView->setScene(m_scene); + plc_scene_ = new QGraphicsScene(this); + ui->graphicsView->setScene(plc_scene_); ui->graphicsView->setSceneRect(0, 0, 800, 600); ui->graphicsView->setDragMode(QGraphicsView::RubberBandDrag); @@ -105,6 +105,70 @@ void PLC::createComponents() } } +void PLC::clearScene() +{ + plc_scene_->clear(); +} + +void PLC::applyProjectToScene(const Project& proj) +{ + clearScene(); + QVector itemObjs; + for (const auto& d : proj.items_) { + Item* item = creatItem(d.type); + if (!item) continue; + item->setPos(d.x, d.y); + connect(item, &Item::requestCopy, ui->graphicsView, &MyGraphicsView::onItemRequestCopy); + connect(item, &Item::requestDelete, ui->graphicsView, &MyGraphicsView::onItemRequestDelete); + connect(item, &Item::requestBindRegister, ui->graphicsView, &MyGraphicsView::onItemRequestBindRegister); + plc_scene_->addItem(item); + itemObjs.append(item); + } + for (const auto& c : proj.connections_) { + if (c.from >= 0 && c.from < itemObjs.size() && c.to >= 0 && c.to < itemObjs.size()) { + Connection* conn = new Connection( + itemObjs[c.from], static_cast(c.fromType), + itemObjs[c.to], static_cast(c.toType)); + plc_scene_->addItem(conn); + } + } +} + +void PLC::extractSceneToProject(Project& proj) +{ + proj.clear(); + QList items; + // (1) 先收集所有Item + for (QGraphicsItem* gi : plc_scene_->items()) { + if (Item* it = dynamic_cast(gi)) { + items.append(it); + } + } + // (2) 存Item数据 + for (Item* it : items) { + Project::ItemData d; + d.type = it->itemType(); + d.x = it->pos().x(); + d.y = it->pos().y(); + proj.items_.append(d); + } + // (3) 存连线数据 + for (QGraphicsItem* gi : plc_scene_->items()) { + if (Connection* conn = dynamic_cast(gi)) { + int fromIdx = items.indexOf(conn->from_); + int toIdx = items.indexOf(conn->to_); + if (fromIdx >= 0 && toIdx >= 0) { + Project::ConnectionData c; + c.from = fromIdx; + c.to = toIdx; + c.fromType = static_cast(conn->fromType_); + c.toType = static_cast(conn->toType_); + proj.connections_.append(c); + } + } + } +} + void PLC::onListwidgetCurrenttextchanged(const QString &text) { selectedComponentType = text; diff --git a/plc.h b/plc.h index c56b766..95fd8ef 100644 --- a/plc.h +++ b/plc.h @@ -3,6 +3,7 @@ #include #include +#include "project.h" namespace Ui { class PLC; @@ -17,6 +18,10 @@ public: ~PLC(); void createComponents(); + void clearScene(); + void applyProjectToScene(const Project& proj); // 加载工程到scene + void extractSceneToProject(Project& proj); // 从scene导出工程数据 + protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -26,7 +31,7 @@ private slots: private: Ui::PLC *ui; - QGraphicsScene *m_scene; + QGraphicsScene *plc_scene_; QString selectedComponentType; }; diff --git a/project.cpp b/project.cpp new file mode 100644 index 0000000..a6abe9c --- /dev/null +++ b/project.cpp @@ -0,0 +1,68 @@ +#include "project.h" +#include +#include +#include +#include + +bool Project::saveToFile(const QString& filePath) const { + QJsonObject root; + QJsonArray itemsArray; + for (const ItemData& item : items_) { + QJsonObject obj; + obj["type"] = item.type; + obj["x"] = item.x; + obj["y"] = item.y; + itemsArray.append(obj); + } + root["items"] = itemsArray; + + QJsonArray connArray; + for (const ConnectionData& c : connections_) { + QJsonObject obj; + obj["from"] = c.from; + obj["to"] = c.to; + obj["fromType"] = c.fromType; + obj["toType"] = c.toType; + connArray.append(obj); + } + root["connections"] = connArray; + + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly)) + return false; + file.write(QJsonDocument(root).toJson()); + file.close(); + return true; +} + +bool Project::loadFromFile(const QString& filePath) { + clear(); + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) + return false; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + file.close(); + if (doc.isNull()) return false; + + QJsonObject root = doc.object(); + QJsonArray itemsArray = root["items"].toArray(); + for (const QJsonValue& v : itemsArray) { + QJsonObject obj = v.toObject(); + ItemData d; + d.type = obj["type"].toString(); + d.x = obj["x"].toDouble(); + d.y = obj["y"].toDouble(); + items_.append(d); + } + QJsonArray connArray = root["connections"].toArray(); + for (const QJsonValue& v : connArray) { + QJsonObject obj = v.toObject(); + ConnectionData d; + d.from = obj["from"].toInt(); + d.to = obj["to"].toInt(); + d.fromType = obj["fromType"].toInt(); + d.toType = obj["toType"].toInt(); + connections_.append(d); + } + return true; +} diff --git a/project.h b/project.h new file mode 100644 index 0000000..0969369 --- /dev/null +++ b/project.h @@ -0,0 +1,28 @@ +#ifndef PROJECT_H +#define PROJECT_H + +#include +#include + +// PLC工程数据模型 +class Project +{ +public: + struct ItemData { + QString type; + double x, y; + }; + struct ConnectionData { + int from, to; + int fromType, toType; + }; + + void clear() { items_.clear(); connections_.clear(); } + bool saveToFile(const QString& filePath) const; + bool loadFromFile(const QString& filePath); + + QList items_; + QList connections_; +}; + +#endif // PROJECT_H