在两个QGraphicsScene/QGraphicsView之间拖放QGraphicsItem

4

我有一个应用程序,其中包含一个主窗口,在该窗口中我创建了一个 QGraphicsScene,代码如下:

DiagramWindow::DiagramWindow()
{
    scene = new QGraphicsScene(0, 0, 600, 500);

然后在同一程序中,我使用另一个(不同的)QGraphicsScene调用另一个窗口。这两个场景都有各自的QGraphicsViews,并且我在每个场景/窗口中使用相同的自定义类来绘制QGraphicsItem。
现在我正在尝试使用此处和this实现两个场景/窗口之间的拖放,并且我得到了一个效果,我认为与此SO问题中的类似/相同。基本上,当我从第二个窗口/场景拖动QGraphicsItem到主窗口时,它不会触发场景中的事件,但是它确实会在主窗口的工具栏/边框中触发。
我的事件处理函数如下:
void DiagramWindow::dragEnterEvent(QDragEnterEvent *event)
{
    qDebug() << "I'm on the main window!";

    event->acceptProposedAction();
}

并且

void DiagramWindow::dropEvent(QDropEvent *event)
{
    event->acceptProposedAction();
    qDebug() << "got a drop!";
}

根据那里的答案,我需要在QGraphicsScene中设置setAcceptDrops()(这是不可能的),所以诀窍似乎是重载QGraphicsScene::dragMoveEvent()。由于我没有针对我的QGraphicsScene的特定类(只有它的父类DiagramWindow),我不知道如何编写一个函数来定位场景的特定dragMoveEvent()。
问题1:我希望我可以做一些像:
DiagramWindow->scene::dragMoveEvent()
{
    ...
}

当然这是不可能的。我对C++/Qt非常新,并且整个工作流程/语法动态仍然困扰着我。我如何针对MainWindow中的QGraphicsScene编写事件处理函数?
问题2:此外,我注意到通过重写这些事件处理函数,我(显然)失去了大部分在主窗口中的功能-选择和移动QGraphicsItems不再起作用。是否有任何方法可以使这些事件仅在第二个窗口中产生时触发?我已经查看了QDrag->source(),但我也不知道它是如何工作的-类似于“如果事件源于第二个窗口,则执行此操作,否则,请继续执行之前的操作”-我实际上不知道那是什么... :)
1个回答

5

问题1

如果事件已经被 diagramWindow 接收,但是想要让当前由 view 显示的 scene 也接收到该事件,那么你需要将该事件传递给视图,它将把它转换为 QGraphicsSceneDragDropEvent 并将其重定向到场景:

void DiagramWindow::dragMoveEvent(event)
{
    view->dragMoveEvent(event);
}

问题2

对于拖动事件我不是很了解,无法提供帮助,但如果想要根据if语句来获得先前的行为,应该这样做:

void MyDerivedClass::myEvent(event)
{
    if(...)
        // do specific behaviour
    else
        QBaseClass::myEvent(event); // default behaviour
}

假设你的类 MyDerivedClass(在你的情况下,是 DiagramWindow)继承自 Qt 类 QBaseClass(在你的情况下,是 QMainWindow),并且你想要重写的事件方法是 myEvent()(在你的情况下,是 dragMoveEvent)。
以下是一个可行的示例,可以为你提供所有必要的思路以使其在你的情况下工作。我建议你从这个可行的示例开始,并进行修改以满足你的需求。
main.cpp:
#include <QApplication>

#include "Scene.h"
#include "View.h"

int main(int argc, char * argv[])
{
    QApplication app(argc,argv);

    Scene * scene1 = new Scene(1);
    View * view1 = new View(scene1, 1);

    Scene * scene2 = new Scene(2);
    View * view2 = new View(scene2,2);

    view1->show();
    view2->show();
    return app.exec();
}

Scene.h:

#ifndef SCENE_H
#define SCENE_H

#include <QGraphicsScene>
#include <QGraphicsEllipseItem>

class Item;

class Item: public QGraphicsEllipseItem
{
public:
    Item(int x,int y);

protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
};


class Scene: public QGraphicsScene
{
public:
    Scene(int i);

protected:
    virtual void dragEnterEvent ( QGraphicsSceneDragDropEvent * event );
    virtual void dragLeaveEvent ( QGraphicsSceneDragDropEvent * event );
    virtual void dragMoveEvent ( QGraphicsSceneDragDropEvent * event );
    virtual void dropEvent ( QGraphicsSceneDragDropEvent * event );

    int i;
};

#endif

Scene.cpp:

#include "Scene.h"

#include <QtDebug>
#include <QGraphicsSceneMouseEvent>
#include <QDrag>
#include <QMimeData>

Item::Item(int x, int y) : QGraphicsEllipseItem(x,y,50,50) {}

void Item::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    qDebug() << "item mouse press";

    // Create the mime  data that will be transfered  from one scene
    // to another
    QMimeData * mimeData = new QMimeData;

    // In our case, the data will be the address of the item.
    //
    // Note: This is  UNSAFE, and just for the  sake of example. The
    // good way to do it is to create your own mime type, containing
    // all the information necessary to recreate an identical Item.
    // 
    // This  is because  drag  and  drop is  meant  to work  between
    // applications, and the address  of your item is not accessible
    // by  other  applications   (deferencing  it  would  produce  a
    // segfault). It  works fine  in this case  since you  perform a
    // drag  and   drop  between  different  windows   of  the  same
    // application.
    Item * item = this;
    QByteArray byteArray(reinterpret_cast<char*>(&item),sizeof(Item*));
    mimeData->setData("Item",byteArray);

    // start the event
    QDrag * drag = new QDrag(event->widget());
    drag->setMimeData(mimeData);
    drag->start();
}

Scene::Scene(int i) : i(i)
{
    Item * item = new Item(100+100*i,100);
    addItem(item);
}           

void Scene::dragEnterEvent ( QGraphicsSceneDragDropEvent * event )
{
    qDebug() << "scene" << i << "drag enter"; 
}

void Scene::dragLeaveEvent ( QGraphicsSceneDragDropEvent * event )
{
    qDebug() << "scene" << i << "drag leave"; 
}

void Scene::dragMoveEvent ( QGraphicsSceneDragDropEvent * event )
{
    qDebug() << "scene" << i << "drag move";
}


void Scene::dropEvent ( QGraphicsSceneDragDropEvent * event )
{
    qDebug() << "scene" << i << "drop";

    // retrieve the address of the item from the mime data
    QByteArray byteArray = event->mimeData()->data("Item");
    Item * item = *reinterpret_cast<Item**>(byteArray.data());

    // add the item  to the scene (automatically remove  it from the
    // other scene)
    addItem(item);
}

View.h:

#ifndef VIEW_H
#define VIEW_H

#include <QGraphicsView>
#include "Scene.h"

class View: public QGraphicsView
{
public:
    View(Scene * scene, int i);

protected:
    virtual void dragEnterEvent ( QDragEnterEvent * event );
    virtual void dragLeaveEvent ( QDragLeaveEvent * event );
    virtual void dragMoveEvent ( QDragMoveEvent * event );
    virtual void dropEvent ( QDropEvent * event );

private:
    Scene * scene_;
    int i;
};

#endif

View.cpp:

#include "View.h"
#include <QtDebug>

View::View(Scene * scene, int i) :
    QGraphicsView(scene),
    scene_(scene),
    i(i)
{
}

void View::dragEnterEvent ( QDragEnterEvent * event )
{
    qDebug() << "view" << i << "drag enter";
    QGraphicsView::dragEnterEvent(event);
}

void View::dragLeaveEvent ( QDragLeaveEvent * event )
{
    qDebug() << "view" << i <<"drag leave";
    QGraphicsView::dragLeaveEvent(event);
}

void View::dragMoveEvent ( QDragMoveEvent * event )
{
    qDebug() << "view" << i << "drag move";
    QGraphicsView::dragMoveEvent(event);
}

void View::dropEvent ( QDropEvent * event )
{
    qDebug() << "view" << i << "drop";
    QGraphicsView::dropEvent(event);
}

在您的情况下,如果您需要从DiagramWindow中显式调用view->someDragDropEvent(event),那么您只需要将protected:更改为public:。但我认为这并不是必要的,只需在DiagramWindow不重新实现拖放事件,它应该会自动调用您视图中的拖放事件。

你对第一个问题的回答不起作用,因为QGraphicsScene的dragMoveEvent需要一个QGraphicsSceneDragDropEvent类型的事件,而DiagramWindow的dragMoveEventQDragEnterEvent类型的。因此,似乎需要进行某种类型的_转换_。有什么想法吗? - Joum
不行...它在上下文中受到保护,我也试过了。 - Joum
1
好的,没问题。只需要将它公开;-) 如果您使用直接的QGraphicsView,则意味着使用派生类,其中您需要重新实现事件方法,但将其声明为public而不是protected。在重新实现中,按照我的答案第二部分所述,只需调用基本方法,而无需使用“if”。或者,您可以保持该方法受保护,但将DiagramWindow声明为继承QGraphicsView的类的友元类。 - Boris Dalstein
1
@Joum:不,你绝对不需要自己重新实现这些东西,这并不难。我有一个可行的例子可以给你一个实现的思路。希望能帮到你 :) - Boris Dalstein
这太棒了,非常感谢!我将不得不花费一些时间重构我的程序以集成它,希望它不会破坏我已经拥有的大部分功能,但是这真的很有帮助,谢谢! - Joum
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接