问题描述
我有一个QListWidget
和QGraphicsView
都被子类化以覆盖其某些成员。我准备了一个最小的可验证示例,以显示我遇到的问题here
我可以从QListWidget
拖放特定字段(由QTableWidget表示)并将其拖放到QGraphicsView
中,为此,我使用QGraphicsProxyWidget
方法如下所示。
问题
现在,如何将qradiobutton
的一个单元格内的2个QTableWidget
与另一个QTableWidget
的另一个单元格相连?
重要的是要提到绿色的QGraphicsRectItem
用于在QTableWidget
周围移动并调整其尺寸。
到目前为止,我可以得到以下结果:
在我一直试图达到的预期结果以下:
在代码最重要的部分下面:
scene.h
#ifndef SCENE_H
#define SCENE_H
#include <QGraphicsScene>
class Scene : public QGraphicsScene
{
public:
Scene(QObject *parent = nullptr);
protected:
void dragenterEvent(QGraphicsSceneDragDropEvent *event);
void dragMoveEvent(QGraphicsSceneDragDropEvent *event);
void dropEvent(QGraphicsSceneDragDropEvent *event);
};
#endif // SCENE_H
scene.cpp
#include "arrow.h"
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <QTableWidget>
#include <QGraphicsProxyWidget>
#include <QVBoxLayout>
#include <QMetaEnum>
#include <QEvent>
#include <QSizegrip>
#include <qradiobutton>
Scene::Scene(QObject *parent)
{
setBackgroundBrush(Qt::lightGray);
}
void Scene::dragenterEvent(QGraphicsSceneDragDropEvent *event) {
if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
event->setAccepted(true);
}
void Scene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) {
if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
event->setAccepted(true);
}
void Scene::dropEvent(QGraphicsSceneDragDropEvent *event) {
QByteArray encoded =
event->mimeData()->data("application/x-qabstractitemmodeldatalist");
QDataStream stream(&encoded,qiodevice::ReadOnly);
QStringList rosTables;
QString newString;
while (!stream.atEnd()) {
int row,col;
QMap<int,QVariant> roleDataMap;
stream >> row >> col >> roleDataMap;
rosTables << roleDataMap[Qt::displayRole].toString();
}
for (const QString &tableType : rosTables) {
if (tableType == "Images") {
QPoint initPos(0,0);
auto *wgt = new CustomTableWidget;
auto *proxyControl = addRect(0,QPen(Qt::black),QBrush(Qt::darkGreen));
auto *sizegrip = new QSizegrip(wgt);
auto *layout = new QHBoxLayout(wgt);
layout->setContentsMargins(0,0);
layout->addWidget(sizegrip,Qt::AlignRight | Qt::AlignBottom);
connect(wgt,&CustomTableWidget::sizeChanged,[wgt,proxyControl](){
proxyControl->setRect(wgt->geometry().adjusted(-10,-10,10,10));
});
wgt->setColumnCount(4);
wgt->setRowCount(4);
for (int ridx = 0; ridx < wgt->rowCount(); ridx++) {
for (int cidx = 0; cidx < wgt->columnCount(); cidx++) {
qradiobutton *radio1,*radio2;
auto* item = new QTableWidgetItem();
item->setText(QString("%1").arg(ridx));
wgt->setItem(ridx,cidx,item);
radio1 = new qradiobutton;
radio2 = new qradiobutton;
wgt->setCellWidget(cidx,radio1);
wgt->setCellWidget(cidx,3,radio2);
Arrow *arrow = new Arrow;
}
}
auto *const proxy = addWidget(wgt);
proxy->setPos(initPos.x(),initPos.y()
+ proxyControl->rect().height());
proxy->setParentItem(proxyControl);
proxyControl->setPos(initPos.x(),initPos.y());
proxyControl->setFlag(QGraphicsItem::ItemIsMovable,true);
proxyControl->setFlag(QGraphicsItem::ItemIsSelectable,true);
proxyControl->setRect(wgt->geometry().adjusted(-10,10));
}
}
}
diagramitem.h
#ifndef DIAGRAMITEM_H
#define DIAGRAMITEM_H
#include <QGraphicspolygonItem>
class Arrow;
class DiagramItem : public QGraphicspolygonItem
{
public:
DiagramItem(QMenu *contextMenu,QGraphicsItem *parent = Q_NULLPTR);
void removeArrow(Arrow *arrow);
void removeArrows();
void addArrow(Arrow *arrow);
Qpixmap image() const;
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
QVariant itemChange(GraphicsItemChange change,const QVariant &value) override;
private:
QpolygonF mypolygon;
QList<Arrow*> arrows;
QMenu *myContextMenu;
};
#endif // DIAGRAMITEM_H
diagramitem.cpp
#include "diagramitem.h"
#include "arrow.h"
#include <QPainter>
#include <QGraphicsScene>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
DiagramItem::DiagramItem(QMenu *contextMenu,QGraphicsItem *parent) : QGraphicspolygonItem(parent)
{
myContextMenu = contextMenu;
setpolygon(mypolygon);
setFlag(QGraphicsItem::ItemIsMovable,true);
setFlag(QGraphicsItem::ItemIsSelectable,true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges,true);
}
void DiagramItem::removeArrow(Arrow *arrow)
{
int index = arrows.indexOf(arrow);
if (index != -1)
arrows.removeAt(index);
}
void DiagramItem::removeArrows()
{
foreach (Arrow *arrow,arrows) {
arrow->startItem()->removeArrow(arrow);
arrow->endItem()->removeArrow(arrow);
scene()->removeItem(arrow);
delete arrow;
}
}
void DiagramItem::addArrow(Arrow *arrow)
{
arrows.append(arrow);
}
void DiagramItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
scene()->clearSelection();
setSelected(true);
myContextMenu->exec(event->screenPos());
}
QVariant DiagramItem::itemChange(QGraphicsItem::GraphicsItemChange change,const QVariant &value)
{
if (change == QGraphicsItem::ItemPositionChange) {
foreach (Arrow *arrow,arrows) {
arrow->updatePosition();
}
}
return value;
}
arrow.h
#ifndef ARROW_H
#define ARROW_H
#include <QGraphicslineItem>
#include "diagramitem.h"
class Arrow : public QGraphicslineItem
{
public:
enum { Type = UserType + 4 };
Arrow(DiagramItem *startItem,DiagramItem *endItem,QGraphicsItem *parent = nullptr);
DiagramItem *startItem() const { return myStartItem; }
DiagramItem *endItem() const { return myEndItem; }
QPainterPath shape() const override;
void setColor(const QColor &color) {
myColor = color;
}
int type() const override { return Type; }
void updatePosition();
protected:
void paint(QPainter *painter,const qstyleOptionGraphicsItem *option,QWidget *widget = nullptr) override;
private:
QColor myColor;
DiagramItem *myStartItem;
DiagramItem *myEndItem;
QpolygonF arrowHead;
};
#endif // ARROW_H
arrow.cpp
#include "arrow.h"
#include <QPen>
#include <QPainter>
#include "qmath.h"
Arrow::Arrow(DiagramItem *startItem,QGraphicsItem *parent) : QGraphicslineItem(parent)
{
myStartItem = startItem;
myEndItem = endItem;
myColor = Qt::GlobalColor::black;
setPen(QPen(myColor,2,Qt::SolidLine,Qt::RoundCap,Qt::RoundJoin));
setFlag(QGraphicsItem::ItemIsSelectable,true);
}
QPainterPath Arrow::shape() const
{
QPainterPath path = QGraphicslineItem::shape();
path.addpolygon(arrowHead);
return path;
}
void Arrow::updatePosition()
{
QLineF line(mapFromItem(myStartItem,0),mapFromItem(myEndItem,0));
setLine(line);
}
void Arrow::paint(QPainter *painter,QWidget *widget)
{
Q_UNUSED(option)
Q_UNUSED(widget)
if (myStartItem->collidesWithItem(myEndItem))
return;
QPen myPen = pen();
myPen.setColor(myColor);
qreal arrowSize = 20;
painter->setPen(myPen);
painter->setBrush(myColor);
QLineF centerLine(myStartItem->pos(),myEndItem->pos());
QpolygonF endpolygon = myEndItem->polygon();
QPointF p1 = endpolygon.first() + myEndItem->pos();
QPointF p2;
QPointF intersectPoint;
QLineF polyLine;
for (int i = 1; i < endpolygon.count(); ++i) {
p2 = endpolygon.at(i) + myEndItem->pos();
polyLine = QLineF(p1,p2);
QLineF::IntersectType intersectType =
polyLine.intersect(centerLine,&intersectPoint);
if (intersectType == QLineF::BoundedIntersection)
break;
p1 = p2;
}
setLine(QLineF(intersectPoint,myStartItem->pos()));
double angle = std::atan2(-line().dy(),line().dx());
QPointF arrowP1 = line().p1() + QPointF(sin(angle + M_PI / 3) * arrowSize,cos(angle + M_PI / 3) * arrowSize);
QPointF arrowP2 = line().p1() + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize,cos(angle + M_PI - M_PI / 3) * arrowSize);
arrowHead.clear();
arrowHead << line().p1() << arrowP1 << arrowP2;
painter->drawLine(line());
painter->drawpolygon(arrowHead);
if (isSelected()) {
painter->setPen(QPen(myColor,1,Qt::DashLine));
QLineF myLine = line();
myLine.translate(0,4.0);
painter->drawLine(myLine);
myLine.translate(0,-8.0);
painter->drawLine(myLine);
}
}
到目前为止,我为解决该问题所做的事情:
1)我碰到了this post,它对于理解有关如何执行操作的最初想法很有用,但它并没有真正提供一种方法或有关如何实现的实现想法最好地进行
2)我研究了官方文档,在问这个问题之前,我仔细阅读了提供的整个Diagram Scene示例,并了解了如何创建Arrow
对象。关于该文档的文档非常好,让我了解了必须如何形成图形订单项。
但是,我无法(返回示例)如何使qradiobutton
“意识到”我试图将其中心用作箭头广告的起点,因此,如何使“ {wareness}”目的地qradiobutton
在另一个必须连接在那里的单元格中?
以下是我的意思:
因此,qradiobutton
的起点基本上会更改颜色(或样式),到达点也会更改颜色。
3)我认为必须在子类Arrow
内创建QGraphicsScene
对象,因为它已经处理了mouse events
。
4)尽管我到目前为止已尝试过,但找不到其他有用的帮助。尽管我仍在研究如何做到这一点。
如果有人遇到过同样的情况,请提供有关如何更好地解决该问题并找到解决方案的指导。
解决方法
解决方案
选中开始和结束单选按钮后,您需要使用这些按钮作为开始和结束节点来创建箭头,例如:
void Backend::onInputRadioButton(bool checked)
{
m_endNode = checked ? static_cast<QRadioButton *>(sender()) : nullptr;
if (m_startNode && m_endNode)
m_scene->addItem(new ArrowItem(m_startNode,m_endNode));
}
然后,您需要将保存表格的最上面的图形项目的信号与updatePosition
的{{1}}插槽相连,例如:
ArrowItem
注意::我正在使用一个属性来保存对容器项的引用。
最后,您需要更新箭头线,例如:
connect(m_startItem->property("item").value<MovableItem *>(),&MovableItem::itemMoved,this,&ArrowItem::updatePosition);
connect(m_endItem->property("item").value<MovableItem *>(),&ArrowItem::updatePosition);
示例
我敢建议对您的代码进行改进。您可以在GitHub上找到我为您写的完整示例。
结果
提供的示例将产生以下结果:
注意:缺少箭头。再次检查Diagram Scene Example,以了解如何绘制它们。