@@ -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); | |||
@@ -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 \ | |||
@@ -1,6 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE QtCreatorProject> | |||
<!-- Written by QtCreator 4.11.1, 2025-08-07T09:52:05. --> | |||
<!-- Written by QtCreator 4.11.1, 2025-08-07T18:51:27. --> | |||
<qtcreator> | |||
<data> | |||
<variable>EnvironmentId</variable> | |||
@@ -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<Item*> 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<Item::AnchorType>(c.fromType), | |||
// itemObjs[c.to], static_cast<Item::AnchorType>(c.toType)); | |||
// hmi_scene_->addItem(conn); | |||
// } | |||
// } | |||
} | |||
void HMI::extractSceneToProject(Project& proj) | |||
{ | |||
proj.clear(); | |||
QList<Item*> items; | |||
for (QGraphicsItem* gi : hmi_scene_->items()) { | |||
if (Item* it = dynamic_cast<Item*>(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<Connection*>(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<int>(conn->fromType_); | |||
c.toType = static_cast<int>(conn->toType_); | |||
proj.connections_.append(c); | |||
} | |||
} | |||
} | |||
} | |||
void HMI::onListwidgetCurrenttextchanged(const QString &text) | |||
{ | |||
selectedComponentType = text; | |||
@@ -3,6 +3,7 @@ | |||
#include <QGraphicsScene> | |||
#include <QWidget> | |||
#include <project.h> | |||
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; | |||
}; | |||
@@ -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; | |||
// 创建菜单 | |||
@@ -21,19 +21,24 @@ public: | |||
void removeConnection(Connection* conn); | |||
QList<Connection*> 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<Connection*> connections_; | |||
QString registerId_; | |||
}; | |||
#endif // ITEM_H |
@@ -11,6 +11,7 @@ | |||
#include <QGraphicsView> | |||
#include <QMessageBox> | |||
#include <QAction> | |||
#include <QFileDialog> | |||
#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, "错误", "保存项目失败"); | |||
} | |||
} | |||
} |
@@ -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 |
@@ -4,6 +4,7 @@ | |||
#include <QGraphicsScene> | |||
#include <QDebug> | |||
#include <QMenu> | |||
#include <QInputDialog> | |||
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) { | |||
@@ -44,6 +44,7 @@ private: | |||
public slots: | |||
void onItemRequestCopy(Item*); | |||
void onItemRequestDelete(Item*); | |||
void onItemRequestBindRegister(Item*); | |||
}; | |||
#endif // MYGRAPHICSVIEW_H |
@@ -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<Item*> 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<Item::AnchorType>(c.fromType), | |||
itemObjs[c.to], static_cast<Item::AnchorType>(c.toType)); | |||
plc_scene_->addItem(conn); | |||
} | |||
} | |||
} | |||
void PLC::extractSceneToProject(Project& proj) | |||
{ | |||
proj.clear(); | |||
QList<Item*> items; | |||
// (1) 先收集所有Item | |||
for (QGraphicsItem* gi : plc_scene_->items()) { | |||
if (Item* it = dynamic_cast<Item*>(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<Connection*>(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<int>(conn->fromType_); | |||
c.toType = static_cast<int>(conn->toType_); | |||
proj.connections_.append(c); | |||
} | |||
} | |||
} | |||
} | |||
void PLC::onListwidgetCurrenttextchanged(const QString &text) | |||
{ | |||
selectedComponentType = text; | |||
@@ -3,6 +3,7 @@ | |||
#include <QGraphicsScene> | |||
#include <QWidget> | |||
#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; | |||
}; | |||
@@ -0,0 +1,68 @@ | |||
#include "project.h" | |||
#include <QJsonDocument> | |||
#include <QJsonObject> | |||
#include <QJsonArray> | |||
#include <QFile> | |||
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; | |||
} |
@@ -0,0 +1,28 @@ | |||
#ifndef PROJECT_H | |||
#define PROJECT_H | |||
#include <QString> | |||
#include <QList> | |||
// 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<ItemData> items_; | |||
QList<ConnectionData> connections_; | |||
}; | |||
#endif // PROJECT_H |