使QGraphicsProxyWidget可移动和可选

dav*_*ave 4 c++ qt

我想把a 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);
Run Code Online (Sandbox Code Playgroud)

小部件显示正确,但它既不可移动也不可选择!

任何帮助将不胜感激 ;)

rba*_*dar 10

我问你自己和你在这里问的问题一样.在我继续解决这个问题之前,我想出了几个小时的努力解决这个问题后,我不得不提一下,如果想要在其中添加大量的小部件,通常在场景中添加小部件并不是一个好主意.您可以在此处阅读此处附带的问题.

现在回到正轨.以下是在尝试实现您想要的内容时出现的一些重大问题:

  • 如果乱用mouseMove()mouseHover()你的widget代理,你会搞砸了widget的UI组件的行为方式.
  • 同时,您希望它是可选择和可移动的,因此您必须以某种方式向窗口小部件代理添加此类功能

我们怎么做?好吧,直接这似乎是不可能的.然而,我提出了一个简单的解决方案,可以完成工作 - 结合QGraphicsProxyWidget一个QGraphicsItem!

为了在窗口小部件中保留UI组件的功能,您需要将代理作为子项添加到图形项.另一方面,图形项(代理的父项)将覆盖选择和移动部分.

这是一个小的演示(我不排除错误,因为这是我正在研究自己的东西,大多数时候我使用PyQt而不是C++ Qt):

main.cpp中

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

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

  return a.exec();
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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!
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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("---");
}
Run Code Online (Sandbox Code Playgroud)

以下是一些截图:

初步看法 在此输入图像描述

三分之二被选中 在此输入图像描述

移动 在此输入图像描述

旋转

在此输入图像描述

与widget交互 - 使用QComboBox 在此输入图像描述

与小部件交互 - 使用QCheckBox 在此输入图像描述

现在由于QGraphicsItem(以及QGraphicsProxyWidget)的性质,存在一些问题,其中最令人烦恼的是重叠

重叠 在此输入图像描述

然而,通过使用碰撞检测并且基本上根本不允许重叠,可以相对容易地避免重叠.既然QGraphicsProxyWidget也可以使用该QGraphicsItem功能,collisionWithItem(...)您可以实现处理这种情况的机制.由于我是新手QGraphicsScene以及所有这些(2天前在SO:D回答问题时开始)可能会有某种综合机制QGraphicsScene来处理这个问题.

失真 在旋转屏幕截图中,您可能已经注意到有一些视觉上的奇怪现象,以前看起来很完美的直线.这些就是所谓的锯齿.目前我不知道如何摆脱这些.我尝试过使用高质量的抗锯齿,但结果甚至比只使用抗锯齿(QPainter::Antialiasing)更糟糕.

进一步的研究 我实际上正在研究我的一个小项目,我想为图像处理创建一个基于复合节点的UI.在线查看此主题始终返回人们使用简单QGraphicsItems的解决方案,并且节点配置本身部分外包到QGraphicsViewer小部件的外部.您可以使用我上面描述的设计,并根据您以后要执行的操作添加更多内容.多个QGraphicItems也可以附加到窗口小部件代理.你可以对此发疯,但要记住这会对性能产生影响(请阅读我在回答的开头一遍又一遍地链接的博文).


use*_*927 -2

这种行为不是 QGraphicsProxyWidget 的意图,例如,假设您有一个 QTextEdit 小部件,并且您想单击该小部件的文本来选择某些内容。此时,您会遇到这个鼠标事件是否应该由 QTextEdit 处理以进行选择过程的问题,还是由 GraphicsWidget 来处理以移动 QTextEdit 的问题。

选项 1:从 QGraphicsProxyWidget 派生,在鼠标函数(mouseMove,...)中,您可以再次调用 QGraphicsItem::mouseMove(),这应该再次恢复移动 Widget 的功能。

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

现在您的 Widget 可以通过移动父矩形来移动。您还可以添加其他以 R 作为父级的 GraphicsRectIrems r,例如,以实现调整大小功能。您只需为 r 安装一个 EventFilter 并在过滤器函数中对 mouseMove 事件做出反应。