使QGraphicsProxyWidget可移动和可选择

7
我希望将一个QWidget放入QGraphicsView中,并使用QGraphicsProxyWidget使小部件可选择和移动。(对于QGraphicsRectItem,QGraphicItem等,这完全有效。)
这是我目前正在使用的代码:
// Create new QGraphicsScene and assign to graphicsView
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);

// Create widget and add to scene
MyWidget *widget = new MyWidget;
QGraphicsProxyWidget *proxy = scene->addWidget(widget);

// Make selectable
proxy->setFlag(QGraphicsItem::ItemIsSelectable, true);
// Make movable
proxy->setFlag(QGraphicsItem::ItemIsMovable, true);

小部件可以正确显示,但既不可移动也不可选择!
任何帮助将不胜感激;)

你确定它没有被选中吗?尝试使用QGraphicsScene::selectedItems().count()。它可能只是看起来没有被选中。 - adam.baker
我的回答解决了你的问题吗? - rbaleksandar
2个回答

17
我和你一样也在思考这个问题。在我解决这个问题的几个小时里,我必须提到一个普遍的问题:如果您想在场景中放置大量小部件,则将小部件添加到场景中并不是一个好主意。您可以在此处阅读有关此问题的信息。
现在回到正题。在尝试实现您想要的内容时,会出现以下一些重要问题:
  • 如果更改小部件代理的mouseMove()mouseHover(),将会破坏小部件UI组件的行为。
  • 同时,您希望它可选择和可移动,因此必须以某种方式向小部件代理添加此类功能。
我们该怎么做呢?嗯,直接来看似乎是不可能的。但是,我想出了一个简单的解决方案 - 将QGraphicsProxyWidgetQGraphicsItem结合起来!
为了保留小部件的UI组件功能,您需要将代理作为子项添加到图形项中。另一方面,图形项(代理的父项)将覆盖选择和移动部分。
这是一个小演示(我不排除错误,因为这是我目前正在研究的内容,大多数时间我使用PyQt而不是C++ Qt):

scenewithmovableproxies.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>SceneWithMovableProxies</class>
 <widget class="QWidget" name="SceneWithMovableProxies">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>SceneWithMovableProxies</string>
  </property>
  <layout class="QGridLayout" name="gridLayout">
   <item row="0" column="0">
    <widget class="QGraphicsView" name="graphicsView"/>
   </item>
  </layout>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

main.cpp

#include "scenewithmovableproxies.hpp"
#include <QApplication>

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  SceneWithMovableProxies w;
  w.show();

  return a.exec();
}

scenewithmovableproxies.hpp

#ifndef SCENEWITHMOVABLEPROXIES_HPP
#define SCENEWITHMOVABLEPROXIES_HPP

#include <QWidget>
#include <QPoint>

namespace Ui {
  class SceneWithMovableProxies;
}

class SceneWithMovableProxies : public QWidget
{
    Q_OBJECT

  public:
    explicit SceneWithMovableProxies(QWidget *parent = 0);
    ~SceneWithMovableProxies();

  private:
    Ui::SceneWithMovableProxies *ui;
    void addWidgetToScene(QPoint initPos);
};

#endif // SCENEWITHMOVABLEPROXIES_HPP

scenewithmovableproxies.cpp

#include "scenewithmovableproxies.hpp"
#include "ui_scenewithmovableproxies.h"
#include "scenewidgetitem.hpp"
#include <QGraphicsProxyWidget>

SceneWithMovableProxies::SceneWithMovableProxies(QWidget *parent) :
  QWidget(parent),
  ui(new Ui::SceneWithMovableProxies)
{
  ui->setupUi(this);
  ui->graphicsView->setRenderHint(QPainter::Antialiasing);
  ui->graphicsView->setScene(new QGraphicsScene(this));

  // Add widget proxies + their parenting graphics items to scene
  addWidgetToScene(QPoint(10, 10));
  addWidgetToScene(QPoint(300, 100));
  addWidgetToScene(QPoint(200, 200));
}

SceneWithMovableProxies::~SceneWithMovableProxies()
{
  delete ui;
}

void SceneWithMovableProxies::addWidgetToScene(QPoint initPos)
{
  // Create a widget
  SceneWidgetItem *widget = new SceneWidgetItem();
  // Create the graphics item that will be used to move the widget around the screen as well as be selectable (for example in case we want to delete a widget that is in the scene)
  // Depending on the position of the graphics item relative to its widget proxy you can adjust the size and location of both
  QGraphicsRectItem *proxyControl = ui->graphicsView->scene()->addRect(initPos.x(), initPos.y(), widget->width(), 20, QPen(Qt::black), QBrush(Qt::darkGreen)); // widget->width() works properly here because of the resize(layout->sizeHint()) that we have used inside it
  proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true);
  proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true);
  // Create the proxy by adding the widget to the scene
  QGraphicsProxyWidget * const proxy = ui->graphicsView->scene()->addWidget(widget);
  // In my case the rectangular graphics item is supposed to be above my widget so the position of the widget is shifted along the Y axis based on the height of the rectangle of that graphics item
  proxy->setPos(initPos.x(), initPos.y()+proxyControl->rect().height());
  proxy->setParentItem(proxyControl);

  // proxyControl->setRotation(45); // Because the widget is a child of the graphics item if we do some sort of transformation to it, the change will be propagated to the widget too!
}

scenewidgetitem.hpp

#ifndef SCENEWIDGETITEM_HPP
#define SCENEWIDGETITEM_HPP

#include <QWidget>
#include <QVBoxLayout>
#include <QCheckBox>
#include <QComboBox>
#include <QLabel>
#include <QPushButton>

class SceneWidgetItem : public QWidget
{
    Q_OBJECT
    QVBoxLayout *layout;
    QLabel *label;
    QCheckBox *checkbox;
    QComboBox *combobox;
    QPushButton *resetButton;
  public:
    explicit SceneWidgetItem(QWidget *parent = 0);
    ~SceneWidgetItem();

  signals:

  public slots:
    void reset();
};

#endif // SCENEWIDGETITEM_HPP

scenewidgetitem.cpp

#include "scenewidgetitem.hpp"

// Create a widget with whichever UI components you like
SceneWidgetItem::SceneWidgetItem(QWidget *parent) : QWidget(parent)
{
  layout = new QVBoxLayout(this);
  checkbox = new QCheckBox("Enable proxy", this);
  checkbox->setChecked(true);
  combobox = new QComboBox(this);
  combobox->addItem("---");
  combobox->addItem("Item 1");
  combobox->addItem("Item 2");
  combobox->addItem("Item 3");
  label = new QLabel(this);
  label->setText(combobox->itemText(0));
  resetButton = new QPushButton("Reset", this);

  // Maybe add some signals :P
  connect(checkbox, SIGNAL(toggled(bool)), combobox, SLOT(setEnabled(bool)));
  connect(checkbox, SIGNAL(toggled(bool)), resetButton, SLOT(setEnabled(bool)));
  connect(resetButton, SIGNAL(clicked(bool)), this, SLOT(reset()));
  connect(combobox, SIGNAL(currentIndexChanged(QString)), label, SLOT(setText(QString)));

  layout->addWidget(checkbox);
  layout->addWidget(label);
  layout->addWidget(resetButton);
  layout->addWidget(combobox);

  // Resizing the widget to its layout's content is very important. If 
  // you don't do that the parenting graphics item will not visually fit 
  // to its child widget and you will get a mess
  resize(layout->sizeHint());
  setLayout(layout);
}

SceneWidgetItem::~SceneWidgetItem()
{

}

void SceneWidgetItem::reset()
{
  combobox->setCurrentIndex(0);
  label->setText("---");
}

以下是一些屏幕截图:

初始视图 enter image description here

三个中选择两个 enter image description here

移动 enter image description here

旋转

enter image description here

使用QComboBox与小部件交互 在此输入图片描述 使用QCheckBox与小部件交互 在此输入图片描述 由于QGraphicsItem(以及QGraphicsProxyWidget)的特性,存在一些问题,其中最烦人的是重叠。 重叠问题 在此输入图片描述 然而,通过使用碰撞检测并基本上不允许重叠,可以相对容易地避免重叠。由于QGraphicsProxyWidget也可以使用QGraphicsItem函数collisionWithItem(...),因此您可以实现处理此情况的机制。由于我刚开始接触QGraphicsScene和所有这些(在SO上回答问题时2天前开始:D),所以QGraphicsScene本身可能有某种集成机制来处理这个问题。

扭曲 在旋转截图中,您可能已经注意到先前完美的直线出现了一些视觉异常。这些是所谓的jaggies。目前我不知道如何摆脱它们。我尝试使用高质量抗锯齿,但结果甚至比仅使用抗锯齿(QPainter::Antialiasing)更糟。

进一步研究 我目前正在开展一个小项目,想要创建一个基于复合节点的图像处理用户界面。在网上查找此主题时,总是返回人们使用简单的QGraphicsItem并且节点配置部分外包给QGraphicsViewer小部件的解决方案。您可以使用我上面描述的设计,并根据以后想要做什么添加更多内容。多个QGraphicItem也可以附加到小部件代理上。您可以尽情发挥,但请记住这会产生性能影响(再次阅读我在答案开头提供的博客帖子)。


嗨@rbaleksandar,我想知道自从你发布这个答案以来,你是否测试过其他解决方案?你认为这个解决方案是你所知道的最好的吗? - The_Average_Engineer
1
@The_Average_Engineer 这取决于1)场景中要放置多少小部件和2)这些小部件有多复杂。对于高复杂度和/或大数量,我建议创建配对 - 图形项(一些足够简单的形状或一组形状)和一个单独的视图,在该视图中,一旦选择了图形项,就会加载相应的小部件及其状态。对于简单的小部件,您可以自己绘制这些小部件(请参见[Qt Node Editor](https://github.com/paceholder/nodeeditor)作为示例。您可以在[此处]找到更全面的(非Qt)示例(https://nodes.io/story) - rbaleksandar
谢谢。我肯定会选择第一个解决方案。 - The_Average_Engineer

-2

QGraphicsProxyWidget并不支持这种行为,例如您有一个QTextEdit小部件,想要单击该小部件的文字来选择某些内容。此时您会遇到问题,即鼠标事件应由QTextEdit处理选择过程还是由GraphicsWidget移动QTextEdit。

选项1: 派生自QGraphicsProxyWidget,在鼠标函数(mouseMove,...)中,您可以再次调用QGraphicsItem :: mouseMove(),这应该会再次带回移动小部件的功能。

选项2: 这更像是一种解决方法,但在大多数情况下是更好的解决方案,因为它可以避免您管理事件,而这是选项1所必需的。 只需创建一个较大的GraphicsRectItem R,它比您的小部件W大。设置R可移动等。将W添加到场景中,您将收到一个QGraphicsProxyWidget P。调用P->setParentItem(R),最后scene->AddItem(R);

现在您的小部件可以通过移动父矩形来移动。您还可以添加额外的GraphicsRectIrems r,它们的父级是R,例如实现缩放功能。您只需要为r安装EventFilter,并在filter函数中对mouseMove事件做出反应即可。


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