2. 自定义图元基类设计
任务目标
在Qt工业组态中,不同设备图元(电机、阀门、传感器等)需要共享缩放、选中、拖拽、属性编辑等交互能力。本案例设计一个CustomGraphicsItem基类,继承自QGraphicsObject,封装通用交互逻辑,并实现一个派生图元验证基类可用性。
核心设计
- 缩放锚点:以图元中心为基准进行缩放,保证视觉位置稳定
- 选中态绘制:选中时绘制虚线边框,未选中时仅绘制图元本体
- 属性接口:提供
itemId与itemName两个属性,供外部组态配置使用 - 信号通知:位置、缩放变化时发出
geometryChanged信号,便于属性面板同步
关键完整代码
// customgraphicsitem.h
#pragma once
#include <QGraphicsObject>
#include <QPen>
class CustomGraphicsItem : public QGraphicsObject
{
Q_OBJECT
Q_PROPERTY(QString itemId READ itemId WRITE setItemId)
Q_PROPERTY(QString itemName READ itemName WRITE setItemName)
public:
explicit CustomGraphicsItem(QGraphicsItem *parent = nullptr);
// 子类必须实现
QRectF boundingRect() const override = 0;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget) override = 0;
QString itemId() const { return m_id; }
void setItemId(const QString &id) { m_id = id; }
QString itemName() const { return m_name; }
void setItemName(const QString &name) { m_name = name; }
// 以中心为锚点缩放
void scaleBy(qreal factor);
signals:
void geometryChanged();
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
void drawSelectionFrame(QPainter *painter);
private:
QString m_id;
QString m_name;
bool m_dragging = false;
bool m_hovered = false;
QPointF m_dragOffset;
};
// customgraphicsitem.cpp
#include "customgraphicsitem.h"
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneHoverEvent>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
CustomGraphicsItem::CustomGraphicsItem(QGraphicsItem *parent)
: QGraphicsObject(parent)
{
setFlags(QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemSendsGeometryChanges);
setAcceptHoverEvents(true);
}
void CustomGraphicsItem::scaleBy(qreal factor)
{
prepareGeometryChange();
QTransform t;
t.translate(boundingRect().center().x(), boundingRect().center().y());
t.scale(factor, factor);
t.translate(-boundingRect().center().x(), -boundingRect().center().y());
setTransform(t, true);
emit geometryChanged();
}
void CustomGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
m_dragging = true;
m_dragOffset = event->scenePos() - pos();
}
QGraphicsObject::mousePressEvent(event);
}
void CustomGraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (m_dragging) {
setPos(event->scenePos() - m_dragOffset);
emit geometryChanged();
}
QGraphicsObject::mouseMoveEvent(event);
}
void CustomGraphicsItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
m_dragging = false;
QGraphicsObject::mouseReleaseEvent(event);
}
void CustomGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
m_hovered = true;
update();
QGraphicsObject::hoverEnterEvent(event);
}
void CustomGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
m_hovered = false;
update();
QGraphicsObject::hoverLeaveEvent(event);
}
QVariant CustomGraphicsItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionHasChanged || change == ItemTransformHasChanged)
emit geometryChanged();
return QGraphicsObject::itemChange(change, value);
}
void CustomGraphicsItem::drawSelectionFrame(QPainter *painter)
{
if (!isSelected() && !m_hovered)
return;
QPen pen(isSelected() ? Qt::blue : Qt::gray);
pen.setStyle(Qt::DashLine);
pen.setWidth(1);
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->drawRect(boundingRect().adjusted(1, 1, -1, -1));
}
// 派生示例:矩形设备图元
// motoritem.h
#pragma once
#include "customgraphicsitem.h"
class MotorItem : public CustomGraphicsItem
{
public:
MotorItem() { setItemName("Motor"); }
QRectF boundingRect() const override { return QRectF(-30, -20, 60, 40); }
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
};
// motoritem.cpp
#include "motoritem.h"
#include <QPainter>
void MotorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
Q_UNUSED(option); Q_UNUSED(widget);
painter->setRenderHint(QPainter::Antialiasing);
painter->setBrush(QColor(100, 180, 220));
painter->setPen(QPen(Qt::black, 1.5));
painter->drawRoundedRect(boundingRect(), 5, 5);
painter->drawText(boundingRect(), Qt::AlignCenter, itemName());
drawSelectionFrame(painter);
}
// main.cpp 最小验证入口
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include "motoritem.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene scene;
MotorItem *motor = new MotorItem();
motor->setPos(50, 50);
scene.addItem(motor);
QGraphicsView view(&scene);
view.setRenderHint(QPainter::Antialiasing);
view.resize(400, 300);
view.show();
return app.exec();
}
工程配置
在.pro文件中添加:
QT += widgets
SOURCES += main.cpp motoritem.cpp customgraphicsitem.cpp
HEADERS += motoritem.h customgraphicsitem.h
运行与验证
- 使用Qt Creator打开项目,构建并运行
- 窗口中出现一个蓝色圆角矩形图元,中心文字显示"Motor"
- 拖拽验证:按住左键拖动图元,图元跟随移动,属性面板(若接入)可收到
geometryChanged信号 - 选中验证:单击图元,出现蓝色虚线边框;点击空白处取消选中,虚线消失
- 缩放验证:在代码中调用
motor->scaleBy(1.5)后重新运行,图元以自身中心放大1.5倍,且中心点位置不变 - 属性验证:在
main()中添加motor->setItemId("MTR-001"),通过qDebug() << motor->itemId()输出确认属性读写正常
通过条件:上述交互行为全部符合预期,且派生类只需实现boundingRect()和paint()即可获得完整的选中、拖拽、缩放与信号通知能力。