Qt拖动工具栏控件到图页中均匀展示

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();
}

运行截图:

相关推荐
二狗哈6 小时前
Cesium快速入门22:fabric自定义着色器
运维·开发语言·前端·webgl·fabric·cesium·着色器
白狐_7986 小时前
计算机网络复习全书(详细整理)
开发语言·计算机网络·php
黑岚樱梦6 小时前
Linux系统编程
java·开发语言·前端
乌萨奇也要立志学C++6 小时前
【洛谷】贪心专题之推公式 用 “相邻元素交换” 推导最优规则
c++·算法
Kristen_YXQDN6 小时前
PyCharm 中 pytest 运行 python 测试文件报错:D:\Python_file\.venv\Scripts\python.exe: No module named pytest
运维·开发语言·python·pycharm·pytest
IMPYLH6 小时前
Lua 的 Debug(调试) 模块
开发语言·笔记·python·单元测试·lua·fastapi
王老师青少年编程6 小时前
csp信奥赛C++标准模板库STL(3):list的使用详解
c++·容器·stl·list·标准模板库·csp·信奥赛
ULTRA??6 小时前
STL deque 的详细特征
c++·算法
九死九歌6 小时前
【Sympydantic】使用sympydantic,利用pydantic告别numpy与pytorch编程中,tensor形状带来的烦人痛点!
开发语言·pytorch·python·机器学习·numpy·pydantic