Browse Source

实现HMI页面能够拖动按钮和指示灯,并能够复制粘贴删除

master
付春阳 2 days ago
commit
e3a4c9be7d
8 changed files with 654 additions and 0 deletions
  1. +117
    -0
      customgraphics.cpp
  2. +72
    -0
      customgraphics.h
  3. +148
    -0
      customgraphicsscene.cpp
  4. +46
    -0
      customgraphicsscene.h
  5. +154
    -0
      hmimodule.cpp
  6. +49
    -0
      hmimodule.h
  7. +36
    -0
      mainwindow.cpp
  8. +32
    -0
      mainwindow.h

+ 117
- 0
customgraphics.cpp View File

@@ -0,0 +1,117 @@
#include "customgraphics.h"
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
#include <QMenu>
#include <QGraphicsScene>

// HmiComponent 基类实现

HmiComponent::HmiComponent(QGraphicsItem *parent) : QGraphicsObject(parent)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setAcceptHoverEvents(true);
}

void HmiComponent::setColor(const QColor& color) {
if (m_color != color) {
m_color = color;
update();
}
}

QColor HmiComponent::color() const {
return m_color;
}

void HmiComponent::setComponentName(const QString& name) {
m_componentName = name;
}

QString HmiComponent::componentName() const {
return m_componentName;
}

void HmiComponent::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setRenderHint(QPainter::Antialiasing);

paintShape(painter);

if (isSelected()) {
painter->setBrush(Qt::NoBrush);
painter->setPen(QPen(Qt::darkRed, 2, Qt::DashLine));
painter->drawRect(boundingRect());
}
}

void HmiComponent::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
// 右键不触发选中信号,留给 contextMenuEvent 处理
if (event->button() == Qt::LeftButton) {
emit selected(this);
}
QGraphicsObject::mousePressEvent(event);
}

// 实现 contextMenuEvent
void HmiComponent::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
// 右键点击时,先清空场景中的其他选中项,然后选中当前项
scene()->clearSelection();
setSelected(true);
emit selected(this); // 同样可以发出选中信号,以便更新属性面板等

QMenu menu;
QAction *copyAction = menu.addAction("复制");
QAction *deleteAction = menu.addAction("删除");

// 使用 connect 将菜单动作的触发连接到信号的发射,“复制”和“删除”
connect(copyAction, &QAction::triggered, this, [this]() {
emit copyRequested(this);
});
connect(deleteAction, &QAction::triggered, this, [this]() {
emit deleteRequested(this);
});

// 在鼠标光标位置显示菜单
menu.exec(event->screenPos());
}


// HmiButton 类实现

HmiButton::HmiButton(QGraphicsItem *parent) : HmiComponent(parent) {
m_color = Qt::gray;
m_componentName = "Button";
}

QRectF HmiButton::boundingRect() const {
return QRectF(0, 0, 80, 30);
}

void HmiButton::paintShape(QPainter *painter) {
painter->setPen(Qt::NoPen);
painter->setBrush(m_color);
painter->drawRect(boundingRect());
}


// HmiIndicator 类实现

HmiIndicator::HmiIndicator(QGraphicsItem *parent) : HmiComponent(parent) {
m_color = Qt::green;
m_componentName = "Indicator";
}

QRectF HmiIndicator::boundingRect() const {
return QRectF(0, 0, 30, 30);
}

void HmiIndicator::paintShape(QPainter *painter) {
painter->setPen(Qt::NoPen);
painter->setBrush(m_color);
painter->drawEllipse(boundingRect());
}

+ 72
- 0
customgraphics.h View File

@@ -0,0 +1,72 @@
#ifndef CUSTOMGRAPHICS_H
#define CUSTOMGRAPHICS_H

#include <QGraphicsObject>
// 只需要前向声明,因为头文件中只用到了指针
class QPainter;
class QGraphicsSceneMouseEvent;
class QGraphicsSceneContextMenuEvent;

enum class ComponentType {
Button,
Indicator
};

// HMI组件基类
class HmiComponent : public QGraphicsObject
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor)
Q_PROPERTY(QString componentName READ componentName WRITE setComponentName)

signals:
void selected(HmiComponent* item);
// 为复制和删除请求添加信号
void copyRequested(HmiComponent* item);
void deleteRequested(HmiComponent* item);

public:
explicit HmiComponent(QGraphicsItem *parent = nullptr);

void setColor(const QColor& color);
QColor color() const;
void setComponentName(const QString& name);
QString componentName() const;

void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
// 重写上下文菜单事件
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
virtual void paintShape(QPainter* painter) = 0;

protected:
QColor m_color;
QString m_componentName;
};


// 按钮类
class HmiButton : public HmiComponent
{
public:
HmiButton(QGraphicsItem *parent = nullptr);
QRectF boundingRect() const override;

protected:
void paintShape(QPainter *painter) override;
};

// 指示灯类
class HmiIndicator : public HmiComponent
{
public:
HmiIndicator(QGraphicsItem *parent = nullptr);
QRectF boundingRect() const override;

protected:
void paintShape(QPainter *painter) override;
};

#endif // CUSTOMGRAPHICS_H

+ 148
- 0
customgraphicsscene.cpp View File

@@ -0,0 +1,148 @@
#include "customgraphicsscene.h"
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QKeyEvent>
#include <QGuiApplication>
#include <QMenu>

// 实现新的构造函数
CustomGraphicsScene::CustomGraphicsScene(QGraphicsView* view, QObject *parent)
: QGraphicsScene(parent), m_mode(Normal), m_view(view)
{
// 断言确保传入的 view 是有效的
Q_ASSERT(m_view);
}

void CustomGraphicsScene::setMode(Mode mode)
{
if (m_mode == mode) return;

m_mode = mode;

// 根据模式设置光标
if (m_mode == CreateItem) {
m_view->setCursor(Qt::CrossCursor);
} else {
m_view->setCursor(Qt::ArrowCursor); // 恢复为标准箭头光标
}
}

// setComponentTypeToCreate 函数无需改动
void CustomGraphicsScene::setComponentTypeToCreate(ComponentType type)
{
m_componentTypeToCreate = type;
}

// mousePressEvent 函数无需改动
void CustomGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
if (mouseEvent->button() != Qt::LeftButton) {
QGraphicsScene::mousePressEvent(mouseEvent);
return;
}

if (m_mode == CreateItem) {
HmiComponent* item = nullptr;

switch (m_componentTypeToCreate) {
case ComponentType::Button:
item = new HmiButton();
break;
case ComponentType::Indicator:
item = new HmiIndicator();
break;
}

if (item)
{
item->setPos(mouseEvent->scenePos());
if (item->scene() == nullptr)
{
addItem(item);
emit componentCreated(item);
}
else
{
// 如果 item 已经被添加到场景中,则删除它,避免内存泄漏
delete item;
}
}

setMode(Normal);
} else {
QGraphicsScene::mousePressEvent(mouseEvent);
}
}

// mouseMoveEvent 函数无需改动
void CustomGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
QGraphicsScene::mouseMoveEvent(mouseEvent);
}


// 修改或新增 keyPressEvent
void CustomGraphicsScene::keyPressEvent(QKeyEvent *keyEvent)
{
// 退出创建模式
if (keyEvent->key() == Qt::Key_Escape && m_mode == CreateItem) {
setMode(Normal);
keyEvent->accept();
return;
}

// 处理删除、复制、粘贴
if (selectedItems().isEmpty()) {
QGraphicsScene::keyPressEvent(keyEvent);
return;
}

switch (keyEvent->key()) {
case Qt::Key_Delete:
emit deleteRequestFromScene();
keyEvent->accept();
break;
case Qt::Key_C:
if (keyEvent->modifiers() & Qt::ControlModifier) {
emit copyRequestFromScene();
keyEvent->accept();
}
break;
case Qt::Key_V:
if (keyEvent->modifiers() & Qt::ControlModifier) {
// 获取最后一个鼠标事件的位置来确定粘贴位置
emit pasteRequestFromScene(views().first()->mapToScene(views().first()->mapFromGlobal(QCursor::pos())));
keyEvent->accept();
}
break;
default:
break;
}

QGraphicsScene::keyPressEvent(keyEvent);
}

void CustomGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *contextMenuEvent)
{
// 检查鼠标下方是否有图形项
QGraphicsItem *item = itemAt(contextMenuEvent->scenePos(), views().first()->transform());

if (!item) {
// 如果点击在空白处,则显示我们的粘贴菜单
contextMenuEvent->accept();

QMenu menu;
QAction *pasteAction = menu.addAction("粘贴");

// 将菜单动作连接到场景的粘贴请求信号
connect(pasteAction, &QAction::triggered, this, [this, contextMenuEvent]() {
emit pasteRequestFromScene(contextMenuEvent->scenePos());
});

menu.exec(contextMenuEvent->screenPos());
} else {
// 如果点击在图形项上,则将事件传递给该图形项处理
// (这会调用 HmiComponent::contextMenuEvent)
QGraphicsScene::contextMenuEvent(contextMenuEvent);
}
}

+ 46
- 0
customgraphicsscene.h View File

@@ -0,0 +1,46 @@
#ifndef CUSTOMGRAPHICSSCENE_H
#define CUSTOMGRAPHICSSCENE_H

#include <QGraphicsScene>
#include "customgraphics.h"

class QGraphicsSceneMouseEvent;
class QGraphicsSceneContextMenuEvent;
class QGraphicsView;
class QKeyEvent;

class CustomGraphicsScene : public QGraphicsScene
{
Q_OBJECT

public:
enum Mode { Normal, CreateItem };

explicit CustomGraphicsScene(QGraphicsView* view, QObject *parent = nullptr);

void setMode(Mode mode);
void setComponentTypeToCreate(ComponentType type);

signals:
void componentCreated(HmiComponent* item);
// 转发键盘操作请求
void copyRequestFromScene();
void pasteRequestFromScene(const QPointF& scenePos); // 传递粘贴位置
void deleteRequestFromScene();

protected:
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) override;

// 在这里添加下面这行代码
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override;

void keyPressEvent(QKeyEvent *keyEvent) override;
void contextMenuEvent(QGraphicsSceneContextMenuEvent *contextMenuEvent) override;

private:
Mode m_mode;
ComponentType m_componentTypeToCreate;
QGraphicsView* m_view;
};

#endif // CUSTOMGRAPHICSSCENE_H

+ 154
- 0
hmimodule.cpp View File

@@ -0,0 +1,154 @@
#include "hmimodule.h"
#include <QGraphicsScene>
#include <QGraphicsView> // --- 确保包含 QGraphicsView 头文件 ---
#include <QDateTime>

// HMIModule 构造函数无需改动
HMIModule::HMIModule(Ui::MainWindow* ui, QObject *parent)
: QObject{parent}, ui_(ui), m_scene(nullptr)
{}

// 封装设置图标和大小的通用函数
void HMIModule::setButtonIcon(QAbstractButton *button, const QString &iconPath)
{
button->setIcon(QIcon(iconPath));
button->setIconSize(QSize(30, 30));
}

void HMIModule::init() {
setButtonIcon(ui_->button, ":/resource/image/button.png");
setButtonIcon(ui_->lamp, ":/resource/image/lamp.png");
setButtonIcon(ui_->addPage, ":/resource/image/add.png");
setButtonIcon(ui_->deletePage, ":/resource/image/delete.png");
setButtonIcon(ui_->prePage, ":/resource/image/pre.png");
setButtonIcon(ui_->nextPage, ":/resource/image/next.png");

if (!ui_->hmiGraphicsView->scene()) {
// 创建场景时,将 view 指针传入
m_scene = new CustomGraphicsScene(ui_->hmiGraphicsView, this);
m_scene->setSceneRect(0, 0, 800, 600);
ui_->hmiGraphicsView->setScene(m_scene);
} else {
m_scene = qobject_cast<CustomGraphicsScene*>(ui_->hmiGraphicsView->scene());
Q_ASSERT(m_scene);
}

connect(ui_->button, &QPushButton::clicked, this, &HMIModule::prepareToCreateButton);
connect(ui_->lamp, &QPushButton::clicked, this, &HMIModule::prepareToCreateIndicator);
connect(m_scene, &CustomGraphicsScene::componentCreated, this, &HMIModule::onComponentCreated);
// 连接来自场景的键盘事件信号
connect(m_scene, &CustomGraphicsScene::copyRequestFromScene, this, &HMIModule::onCopyRequested);
connect(m_scene, &CustomGraphicsScene::pasteRequestFromScene, this, &HMIModule::onPasteRequested);
connect(m_scene, &CustomGraphicsScene::deleteRequestFromScene, this, &HMIModule::onDeleteRequested);
}
// 辅助函数,用于设置新创建的组件
void HMIModule::setupNewComponent(HmiComponent* item)
{
if (!item) return;

// 连接所有需要的信号
connect(item, &HmiComponent::selected, this, &HMIModule::onComponentSelected);
connect(item, &HmiComponent::copyRequested, this, &HMIModule::onCopyRequested);
connect(item, &HmiComponent::deleteRequested, this, &HMIModule::onDeleteRequested);

// 生成日志
QString currentTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
QString log = QString("[%1] 创建 %2 组件")
.arg(currentTime)
.arg(item->componentName());
emit logMessageGenerated(log);
}

// 准备创建按钮的槽函数
void HMIModule::prepareToCreateButton()
{
m_scene->setComponentTypeToCreate(ComponentType::Button);
m_scene->setMode(CustomGraphicsScene::Mode::CreateItem);
}

// 准备创建指示灯的槽函数
void HMIModule::prepareToCreateIndicator()
{
m_scene->setComponentTypeToCreate(ComponentType::Indicator);
m_scene->setMode(CustomGraphicsScene::Mode::CreateItem);
}

// 处理新创建组件的槽函数
void HMIModule::onComponentCreated(HmiComponent* item)
{
setupNewComponent(item);
}

void HMIModule::onCopyRequested()
{
QList<QGraphicsItem*> selected = m_scene->selectedItems();
if (selected.isEmpty()) return;

// 只复制第一个选中的项目
HmiComponent* itemToCopy = qgraphicsitem_cast<HmiComponent*>(selected.first());
if (!itemToCopy) return;

// 存储组件信息到 "剪贴板"
if (dynamic_cast<HmiButton*>(itemToCopy)) {
m_copiedType = ComponentType::Button;
} else if (dynamic_cast<HmiIndicator*>(itemToCopy)) {
m_copiedType = ComponentType::Indicator;
}
m_copiedColor = itemToCopy->color();
m_hasCopiedItem = true;

// 日志
emit logMessageGenerated(QString("[%1] 复制组件: %2")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
.arg(itemToCopy->componentName()));
}

void HMIModule::onPasteRequested(const QPointF& scenePos)
{
if (!m_hasCopiedItem) return;

HmiComponent* newItem = nullptr;
if (m_copiedType == ComponentType::Button) {
newItem = new HmiButton();
} else {
newItem = new HmiIndicator();
}

if (newItem) {
newItem->setColor(m_copiedColor);
// 为了避免完全重叠,给一个小的偏移量
newItem->setPos(scenePos + QPointF(10, 10));

// 使用辅助函数完成添加和信号连接
// 注意:setupNewComponent内部会再次生成“创建”日志,这里是合理的
setupNewComponent(newItem);
}
}

void HMIModule::onDeleteRequested()
{
QList<QGraphicsItem*> selected = m_scene->selectedItems();
if (selected.isEmpty()) return;

// 日志
emit logMessageGenerated(QString("[%1] 删除 %2 个组件")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
.arg(selected.count()));

// 从后往前删,避免迭代器失效问题
for (int i = selected.count() - 1; i >= 0; --i) {
QGraphicsItem* item = selected.at(i);
m_scene->removeItem(item);
delete item; // 释放内存
}
}

// 统一处理选中事件的槽函数,无需修改
void HMIModule::onComponentSelected(HmiComponent* item)
{
// if (item) {
// qDebug() << "Component selected:" << item->componentName()
// << "Color:" << item->color();
// // 未来可以在这里将item对象传递给属性编辑器
// }
}

+ 49
- 0
hmimodule.h View File

@@ -0,0 +1,49 @@
#ifndef HMIMODULE_H
#define HMIMODULE_H

#include <QObject>
#include "ui_MainWindow.h"
// #include "customgraphics.h" // 已被 customgraphicsscene.h 包含
#include "customgraphicsscene.h" // 引入新的场景头文件

class HMIModule : public QObject
{
Q_OBJECT
public:
explicit HMIModule(Ui::MainWindow* ui, QObject *parent = nullptr);
void setButtonIcon(QAbstractButton* button, const QString& iconPath);
void init();

private slots:
// 准备创建组件,而不是直接创建
void prepareToCreateButton();
void prepareToCreateIndicator();

// 当场景创建了一个组件后,进行后续处理
void onComponentCreated(HmiComponent* item);

void onComponentSelected(HmiComponent* item);

// 处理复制、粘贴、删除的槽函数
void onCopyRequested();
void onPasteRequested(const QPointF& scenePos);
void onDeleteRequested();

// 一个用于发送日志消息的信号
signals:
void logMessageGenerated(const QString& message);

private:
void setupNewComponent(HmiComponent* item);

private:
Ui::MainWindow* ui_;
CustomGraphicsScene* m_scene;

// 用于实现剪贴板功能的成员变量
ComponentType m_copiedType;
QColor m_copiedColor;
bool m_hasCopiedItem = false;
};

#endif // HMIMODULE_H

+ 36
- 0
mainwindow.cpp View File

@@ -0,0 +1,36 @@
#include "mainwindow.h"
#include <QToolBar>
#include <QToolButton>
#include <QVBoxLayout>
#include <QButtonGroup>


MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui_(new Ui::MainWindow)
{
ui_->setupUi(this);
hmi_ = new HMIModule(ui_, this);
hmi_->init();

initMainWindow();
// 连接 HMIModule 的信号到 MainWindow 的槽
connect(hmi_, &HMIModule::logMessageGenerated, this, &MainWindow::appendLog);
}

MainWindow::~MainWindow()
{
delete ui_;
}

void MainWindow::initMainWindow()
{
setWindowTitle("综合平台编辑器");
setWindowIcon(QIcon(":/resource/image/editor.png"));
}

// 实现槽函数
void MainWindow::appendLog(const QString& message)
{
ui_->textEdit->append(message);
}

+ 32
- 0
mainwindow.h View File

@@ -0,0 +1,32 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "hmimodule.h"

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

private:
void initMainWindow();

// 一个用于接收日志并更新UI的槽函数
private slots:
void appendLog(const QString& message);

private:
Ui::MainWindow *ui_;
HMIModule* hmi_;
};
#endif // MAINWINDOW_H

Loading…
Cancel
Save