dragcontroller.h
#ifndef DRAGCONTROLLER_H
#define DRAGCONTROLLER_H
#include <QObject>
#include <QPointer>
#include <QWidget>
class DragController : public QObject
{
Q_OBJECT
public:
explicit DragController(QObject *parent = nullptr);
void setSourceWidget(QWidget *w);
// QML 里直接调用:DragController.startDrag({type:"rect"})
Q_INVOKABLE void startDrag(const QVariantMap &payload);
private:
QPointer<QWidget> source_;
};
#endif // DRAGCONTROLLER_H
dragcontroller.cpp
#include "dragcontroller.h"
#include <QDrag>
#include <QMimeData>
#include <QJsonDocument>
#include <QJsonObject>
#include <QVariantMap>
DragController::DragController(QObject *parent)
{
}
void DragController::setSourceWidget(QWidget *w) {
source_ = w;
}
void DragController::startDrag(const QVariantMap &payload) {
if (!source_) return;
// 把 payload 转成 JSON bytes,放进自定义 mime
QJsonObject obj = QJsonObject::fromVariantMap(payload);
QByteArray bytes = QJsonDocument(obj).toJson(QJsonDocument::Compact);
auto *mime = new QMimeData;
mime->setData("application/x-palette-item", bytes);
auto *drag = new QDrag(source_);
drag->setMimeData(mime);
// 可选:给个拖拽图标(不设置也行)
// drag->setPixmap(QPixmap(":/icons/rect.png"));
drag->exec(Qt::CopyAction);
}
dropscene.h
#ifndef DROPSCENE_H
#define DROPSCENE_H
#include <QGraphicsScene>
#include <QGraphicsSceneDragDropEvent>
#include <QtQuickWidgets/QQuickWidget>
#include <QMimeData>
#include <QJsonDocument>
#include <QJsonObject>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>
#include <QGraphicsTextItem>
#include <QPen>
#include <QBrush>
#include <QGraphicsItem>
#include <QVector>
class PlaceholderItem;
class SnapRectItem;
class DropScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit DropScene(QObject* parent = nullptr);
PlaceholderItem* placeholderAt(const QPointF& scenePos) const;
void snapItemToPlaceholder(SnapRectItem* item, PlaceholderItem* ph);
protected:
void dragEnterEvent(QGraphicsSceneDragDropEvent* event) override;
void dragMoveEvent(QGraphicsSceneDragDropEvent* event) override ;
void dropEvent(QGraphicsSceneDragDropEvent* event) override;
private:
QVector<PlaceholderItem*> placeholders_;
};
#endif // DROPSCENE_H
dropscene.cpp
#include "dropscene.h"
#include "placeholderitem.h"
#include "snaprectitem.h"
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <QJsonDocument>
#include <QJsonObject>
#include <QPen>
#include <QBrush>
DropScene::DropScene(QObject *parent)
: QGraphicsScene(parent)
{
const qreal w = 160, h = 500;
const qreal gap = 20;
const qreal startX = 80;
const qreal y = 80;
for (int i = 0; i < 5; ++i) {
QRectF
r(startX + i*(w+gap), y, w, h);
auto* ph = new PlaceholderItem(r);
addItem(ph);
placeholders_.push_back(ph);
}
// 让 sceneRect 自动包住占位框(加点边距)
setSceneRect(itemsBoundingRect().adjusted(-50, -50, 50, 50));
qDebug() << "items count(after):" << items().size()
<< "sceneRect:" << sceneRect();
}
PlaceholderItem* DropScene::placeholderAt(const QPointF& scenePos) const {
for (auto* ph : placeholders_) {
if (ph->rect().contains(scenePos))
return ph;
}
return nullptr;
}
void DropScene::snapItemToPlaceholder(SnapRectItem* item, PlaceholderItem* ph) {
if (!item || !ph || ph->occupied) return;
// 让矩形填满占位框
QRectF r = ph->rect();
item->setRect(0, 0, r.width(), r.height());
item->setPos(r.topLeft());
ph->occupied = true;
}
void DropScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
if (event->mimeData()->hasFormat("application/x-palette-item")) {
event->acceptProposedAction();
return;
}
QGraphicsScene::dragEnterEvent(event);
}
void DropScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
{
if (event->mimeData()->hasFormat("application/x-palette-item")) {
event->acceptProposedAction();
return;
}
QGraphicsScene::dragMoveEvent(event);
}
void DropScene::dropEvent(QGraphicsSceneDragDropEvent *event)
{
if (!event->mimeData()->hasFormat("application/x-palette-item")) {
QGraphicsScene::dropEvent(event);
return;
}
const QByteArray bytes = event->mimeData()->data("application/x-palette-item");
const QJsonDocument doc = QJsonDocument::fromJson(bytes);
const QJsonObject obj = doc.object();
const QString type = obj.value("type").toString();
const QPointF pos = event->scenePos();
if (type == "rect") {
// 1) 看看是不是落在某个占位框里
auto* ph = placeholderAt(pos);
// 2) 创建可吸附矩形
auto* item = new SnapRectItem(this);
item->setBrush(QBrush(Qt::lightGray));
item->setPen(QPen(Qt::black));
item->setFlag(QGraphicsItem::ItemIsMovable, true);
item->setFlag(QGraphicsItem::ItemIsSelectable, true);
addItem(item);
if (ph && !ph->occupied) {
snapItemToPlaceholder(item, ph); // 自动填充
} else {
// 不在框里:自由放置一个默认大小
item->setRect(0, 0, 120, 60);
item->setPos(pos);
}
}
// ellipse/text 你可继续保持原逻辑...
event->acceptProposedAction();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTabWidget>
#include <QGraphicsView>
#include <QDockWidget>
#include <QQuickWidget>
#include <QQmlContext>
#include <QQuickView>
#include <QWidget>
#include "dropscene.h"
#include "dragcontroller.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
void addNewTab(const QString& name);
QTabWidget* tabs_ = nullptr;
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
{
setWindowTitle("Widgets + QML Palette Drag&Drop");
resize(1200, 800);
tabs_ = new QTabWidget(this);
setCentralWidget(tabs_);
addNewTab("Scene 1");
addNewTab("Scene 2");
auto *dock = new QDockWidget("Tools", this);
addDockWidget(Qt::LeftDockWidgetArea, dock);
auto *quickView = new QQuickView;
quickView->setResizeMode(QQuickView::SizeRootObjectToView);
// 1) 创建 controller
auto *dragCtrl = new DragController(this);
// 2) 暴露给 QML:DragController.startDrag(...)
quickView->rootContext()->setContextProperty("DragController", dragCtrl);
quickView->setSource(QUrl::fromLocalFile("D:/work/tmp/Qt/TestSceneItem/image/controls.qml"));
auto *container = QWidget::createWindowContainer(quickView, dock);
dock->setWidget(container);
// 3) source widget 用 container(QDrag 需要 QWidget 作为源)
dragCtrl->setSourceWidget(container);
}
void MainWindow::addNewTab(const QString &name)
{
auto* scene = new DropScene(this);
auto* view = new QGraphicsView(scene);
view
->setAcceptDrops(true);
view
->viewport()->setAcceptDrops(true);
view
->setDragMode(QGraphicsView::RubberBandDrag);
// 关键:强制视口对准占位框区域
view
->centerOn(0, 0); // 或者 centerOn(80, 80);
// 也可以更精确:保证 (80,80,...) 这块一定可见
view
->ensureVisible(QRectF(60, 60, 5*160 + 4*20 + 40, 90 + 40));
tabs_
->addTab(view, name);
}
placeholderitem.h
#ifndef PLACEHOLDERITEM_H
#define PLACEHOLDERITEM_H
#include <QGraphicsRectItem>
#include <QPen>
class PlaceholderItem : public QGraphicsRectItem
{
public:
explicit PlaceholderItem(const QRectF& r);
signals:
public:
bool occupied = false;
};
#endif // PLACEHOLDERITEM_H
placeholderitem.cpp
#include "placeholderitem.h"
PlaceholderItem::PlaceholderItem(const QRectF &r)
: QGraphicsRectItem(r)
{
setPen(QPen(Qt::darkGray, 2, Qt::DashLine));
setBrush(Qt::NoBrush);
setZValue(-10); // 放底层
setFlag(QGraphicsItem::ItemIsSelectable, false);
setAcceptedMouseButtons(Qt::NoButton);
}
snaprectItem.h
#ifndef SNAPRECTITEM_H
#define SNAPRECTITEM_H
#include <QGraphicsRectItem>
class DropScene;
class SnapRectItem : public QGraphicsRectItem {
public:
explicit SnapRectItem(DropScene* sceneOwner, QGraphicsItem* parent=nullptr);
protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
private:
DropScene* owner_ = nullptr;
};
#endif // SNAPRECTITEM_H
snaprectItem.cpp
#include "snaprectItem.h"
#include "dropscene.h"
#include "placeholderitem.h"
#include <QGraphicsSceneMouseEvent>
SnapRectItem::SnapRectItem(DropScene* sceneOwner, QGraphicsItem* parent)
: QGraphicsRectItem(parent), owner_(sceneOwner) {}
void SnapRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
QGraphicsRectItem::mouseReleaseEvent(event);
if (!owner_) return;
// 用图元中心点判断落在哪个框里
QPointF center= sceneBoundingRect().center();
auto* ph = owner_->placeholderAt(center);
if (ph && !ph->occupied) {
owner_->snapItemToPlaceholder(this, ph);
}
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
运行截图:
