应用QGraphicsItem :: ItemIgnoresTransformations后保持相对子项位置

8

我有一个QGraphicsTextItem,其父项为QGraphicsItem。我希望QGraphicsTextItem始终位于QGraphicsItem的正上方,但我也希望当缩放因子小于1时,文本保持相同的大小,即当父图形项被缩小时,文本仍保持缩放因子为1时的大小。我发现在缩放因子小于1时将QGraphicsItem :: ItemIgnoresTransformations标志设置为true可以保留大小。

但是我似乎找不到一种方法来使文本的位置始终保持在QGraphicsItem上方。有没有办法做到这一点?我尝试使用deviceTransform()函数,但是随着滚动条的滚动,文本仍然会移出QGraphicsItem。更糟糕的是,一些文本项目开始“抖动”,即它们开始微微地连续改变位置,看起来像是在抖动。如果这是我需要使用的函数,那么我想我不知道如何正确使用它。

在我的QGraphicsItem构造函数中,我添加了一个QGraphicsTextItem:

fTextItem = new QGraphicsTextItem(getName(), this);
fTextItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);

这是QGraphicsItem的绘制函数代码片段:
qreal lod = painter->worldTransform().m22();
if(lod <= 1.0) {
     fTextItem-setFlag(QGraphicsItem::ItemIgnoresTransformations);
     fTextItem->setPos(fTextItem->deviceTransform(view-viewportTransform()).inverted().map(view->mapFromScene(mapToScene(0,0))));
} else {
     fTextItem->setFlag(QGraphicsItem::ItemIgnoresTransformations, false);
     fTextItem->setPos(0, 0);
}

你应该将QGraphicsTextItem对象的标志设置为忽略父变换,而不是QGraphicsItem吗? - ArmenB
6个回答

6

我的建议是以以下方式子类化QGraphicsSimpleTextItem:

class TextItem
    : public QGraphicsSimpleTextItem
{
public:
    TextItem(const QString &text)
        : QGraphicsSimpleTextItem(text)
    {

    }
    void paint(QPainter *painter, 
        const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        painter->translate(boundingRect().topLeft());
        QGraphicsSimpleTextItem::paint(painter, option, widget);
        painter->translate(-boundingRect().topLeft());
    }
    QRectF boundingRect() const
    {
        QRectF b = QGraphicsSimpleTextItem::boundingRect();
        return QRectF(b.x()-b.width()/2.0, b.y()-b.height()/2.0, 
            b.width(), b.height());
    }
};
QGraphicsSimpleTextItem *mText = new TextItem("Item");
scene()->addItem(mText);
mText->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
mText->setPos(itemToFollow->pos());

请注意,KD07 询问了关于 QGraphicsTextItem 的问题(它支持 HTML/富文本格式)。在 Qt Centre 上可以找到另一种对 QGraphicsSimpleTextItem 的处理方式。 - handle

4

免责声明:这可能超出了您尝试完成的任务范畴。我们在项目中有一些额外的限制,使得这个解决方案对我们来说最容易。

我们在一个项目中需要做类似的事情,结果我们最简单的方法是不使用 ItemIgnoresTransformations,而是自己编写转换代码。以下是我们用于创建仅翻译(无缩放)转换以便在特定位置绘制物品的主要函数。您可以修改它以适合您的使用。

static QTransform GenerateTranslationOnlyTransform(
    const QTransform &original_transform,
    const QPointF &target_point) {
  // To draw the unscaled icons, we desire a transform with scaling factors
  // of 1 and shearing factors of 0 and the appropriate translation such that
  // our icon center ends up at the same point. According to the
  // documentation, QTransform transforms a point in the plane to another
  // point using the following formulas:
  // x' = m11*x + m21*y + dx
  // y' = m22*y + m12*x + dy
  //
  // For our new transform, m11 and m22 (scaling) are 1, and m21 and m12
  // (shearing) are 0. Since we want x' and y' to be the same, we have the
  // following equations:
  // m11*x + m21*y + dx = x + dx[new]
  // m22*y + m12*x + dy = y + dy[new]
  //
  // Thus,
  // dx[new] = m11*x - x + m21*y + dx
  // dy[new] = m22*y - y + m12*x + dy
  qreal dx = original_transform.m11() * target_point.x()
             - target_point.x()
             + original_transform.m21() * target_point.y()
             + original_transform.m31();
  qreal dy = original_transform.m22() * target_point.y()
             - target_point.y()
             + original_transform.m12() * target_point.x()
             + original_transform.m32();

  return QTransform::fromTranslate(dx, dy);
}

使用时,获取传递给绘制方法的QPainter变换对象并执行以下操作:

painter->save();
painter->setTransform(GenerateTranslationOnlyTransform(painter->transform(),
                                                       some_point));
// Draw your item.
painter->restore();

目前看起来对于我的问题来说有点过度了 :) 但如果没有简单的方法,从基础开始是一个可靠的方法。非常感谢! - KD07
我在我的应用程序中尝试使用此自定义转换。它在转换方面工作得很好,但是我的项目位置仍然有偏差。我希望我的子项(具有自定义转换)保持在父项的(0,0)点。我将某些点作为 (0,0) 传递。 - KD07

3

我找到了另一个解决方案,它不涉及任何转换或手动缩放/定位。在QGraphicsItem :: ItemIgnoresTransformations 标志说明中有一个提示:

QGraphicsItem :: ItemIgnoresTransformations

该项忽略继承的变换(即,其位置仍然锚定在其父级上,但父级或视图旋转、缩放或 剪切变换被忽略)。[...]

那就是关键!我们需要两个项目:一个父项目将保持相对位置(没有设置任何标志),并且一个子项目将在父项目的(0,0)点进行绘制(使用 QGraphicsItem :: ItemIgnoresTransformations 标志集)。 就这么简单!

我已经将此功能封装到一个单独的类中 - 这是一些代码:

#include <QGraphicsItem>
#include <QPainter>

class SampleShape : public QGraphicsItem
{
private:
    /* This class implements shape drawing */
    class SampleShapeImpl : public QGraphicsItem
    {
    public:
        SampleShapeImpl (qreal len, QGraphicsItem *parent = nullptr)
            : QGraphicsItem(parent), m_len(len)
        {
            /* ignore transformations (!) */
            setFlag(QGraphicsItem::ItemIgnoresTransformations);
        }

        QRectF boundingRect (void) const override
        {
            /* sample bounding rectangle */
            return QRectF(-m_len, -m_len, m_len*2, m_len*2);
        }

        void paint (QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
        {
            /* draw a shape, (0,0) is an anchor */
            painter->drawLine(0, -m_len, 0, m_len);
            painter->drawLine(-m_len, 0, m_len, 0);
            // ...
        }

    private:
        qreal m_len;  // sample shape parameter
    };

public:
    /* This is actually almost an empty class, you only need to set
     * a position and pass any parameters to a SampleShapeImpl class.
     */
    SampleShape (qreal x, qreal y, qreal len, QGraphicsItem *parent = nullptr)
        : QGraphicsItem(parent), m_impl(len, this) // <-- IMPORTANT!!!
    {
        /* set position at (x, y), view transformations will apply */
        setPos(x, y);
    }

    QRectF boundingRect (void) const override
    {
        return QRectF(); // it's just a point, no size
    }

    void paint (QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        // empty, drawing is done in SampleShapeImpl
    }

private:
    SampleShapeImpl m_impl;
};

感谢解决方案,选择了这个,效果非常好。 - undefined

1

Dave Mateer的回答非常好!我有一个问题,我想在不同的缩放级别下定义不同的比例因子。这是我做的:

void MyGraphicsItem::paint(QPainter * painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
    //save painter for later operations
    painter->save();
    QTransform originalTransform = painter->transform();
    QPointF originalCenter = rect().center();
    qreal dx = originalTransform.m11() * originalCenter.x() + originalTransform.m21() * originalCenter.y() + originalTransform.m31();
    qreal dy = originalTransform.m22() * originalCenter.y() + originalTransform.m12() * originalCenter.x() + originalTransform.m32();
    //normally our target scale factor is 1, meaning the item has keeps its size, regardless of zoom
    //we adjust the scale factor though when the item is smaller than one pixel in comparison to the background image
    qreal factor = 1.0;
    //check if scale factor if bigger that the item size, and thus it occupies less that a pixel in comparision to the background image
    if (rect().width() < originalTransform.m11()) {
        //calculate adjusted scale factor
        factor = originalTransform.m11() / rect().width();
    }
    //adjust position according to scale factor
    dx -= factor * originalCenter.x();
    dy -= factor * originalCenter.y();
    //set the new transform for painting
    painter->setTransform(QTransform::fromScale(factor, factor) * QTransform::fromTranslate(dx, dy));
    //now paint...
    QGraphicsXYZItem::paint(painter, option, widget);
    //restore original painter
    painter->restore();
}

在这种情况下,您确实需要调整边界矩形:
QRectF MyGraphicsItem::boundingRect() const
{
    QRectF rect = QGraphicsEllipseItem::boundingRect();
    //this is a bit hackish, let me know if you know another way...
    if (scene() != NULL && scene()->views().at(0) != NULL)
    {
        //get viewport transform
        QTransform itemTransform = scene()->views().at(0)->transform();
        QPointF originalCenter = rect.center();
        //calculate back-projected original size of item
        qreal realSizeX = rect.width() / itemTransform.m11();
        qreal realSizeY = rect.height() / itemTransform.m11();
        //check if scale factor is bigger that the item size, and thus it occupies less that a pixel in comparison 
        //to the background image and adjust size back to equivalent of 1 pixel
        realSizeX = realSizeX < 1.0 ? 1.0 : realSizeX;
        realSizeY = realSizeY < 1.0 ? 1.0 : realSizeY;
        //set adjusted position and size according to scale factor
        rect = QRectF(rect.center().x() - realSizeX / 2.0, rect.center().y() - realSizeY / 2.0, realSizeX, realSizeY);
    }
    return rect;
}

使用这个解决方案,我的情况下物品运作得非常好。


0

这里是我设计的一个非常简单的解决方案:

1)获取父级的boundingRect()并将其映射到场景中 2)取此点列表中X和Y的最小值,这是您的项目在场景坐标中的真实原点 3)设置子项的位置

在Pyside中:

    br = parent.mapToScene(parent.boundingRect())
    realX = min([item.x() for item in br])
    realY = min([item.y() for item in br])
    child.setPos(parent.mapFromScene(realX, realY)) #modify according to need

0

补充Dave Mateer的答案,我认为在某些情况下,您还应该维护对象的适当边界矩形(以及形状)。对于我来说,我需要稍微修改boundingRect()以实现正确的对象选择行为。请记住,如果我们不使用ItemIgnoresTransformations标志,则对象的边界矩形将像往常一样进行缩放和变换。因此,我们还需要重新调整边界矩形以保持视图独立效果。

保持视图无关的边界矩形非常容易:只需从deviceTransform(m_view->viewportTransform()).inverted().m11()中获取缩放因子,并将此常量乘以您的本地坐标边界矩形即可。例如:

qreal m = this->deviceTransform(m_view->viewportTransform()).inverted().m11();
return QRectF(m*(m_shapeX), m*(m_shapeY),
              m*(m_shapeR), m*(m_shapeR)); 

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