【初心者向け】Qt Widgetsにおけるグラフィックアイテムの形状定義:QGraphicsItem::shape() の使い方


Qt WidgetsのQGraphicsItem::shape()関数は、グラフィックアイテムの形状を定義するQPainterPathオブジェクトを返します。この形状は、衝突検出、ヒットテスト、マウスイベントの処理など、さまざまな目的に使用されます。

デフォルトの動作

デフォルトでは、QGraphicsItem::shape()はアイテムの境界矩形に基づいた単純な形状を返します。これは、アイテムが矩形である場合に適切ですが、より複雑な形状を持つアイテムの場合は不十分です。

カスタム形状の定義

より複雑な形状を持つアイテムの場合は、QGraphicsItem::shape()をオーバーライドして、アイテムの形状を定義するカスタムQPainterPathオブジェクトを返す必要があります。

例:円形アイテム

円形アイテムの形状を定義するには、次のコードのようにQGraphicsItem::shape()をオーバーライドできます。

class MyCircularItem : public QGraphicsItem
{
public:
    MyCircularItem(const QPointF &center, qreal radius);

    virtual QPainterPath shape() const override;

private:
    QPointF m_center;
    qreal m_radius;
};

MyCircularItem::MyCircularItem(const QPointF &center, qreal radius)
    : m_center(center), m_radius(radius)
{
}

QPainterPath MyCircularItem::shape() const
{
    QPainterPath path;
    path.addEllipse(m_center - QPointF(m_radius, m_radius), 2 * m_radius, 2 * m_radius);
    return path;
}

このコードは、QPainterPathを使用して、アイテムの中心点と半径に基づいて円形パスを作成します。

利点

QGraphicsItem::shape()をオーバーライドすることで、次のような利点が得られます。

  • アイテムの外観が向上します。
  • マウスイベントの処理がより正確になります。
  • ヒットテストの精度が向上します。
  • 衝突検出の精度が向上します。

注意事項

QGraphicsItem::shape()をオーバーライドする場合は、次の点に注意する必要があります。

  • アイテムの形状が変更された場合、アイテムの境界矩形も更新する必要があります。
  • アイテムの形状が複雑な場合は、パフォーマンスに影響を与える可能性があります。
  • アイテムの形状が変更された場合は、itemChanged()シグナルをemitする必要があります。


例:円形アイテム

この例では、円形アイテムの形状を定義するMyCircularItemクラスを作成します。

class MyCircularItem : public QGraphicsItem
{
public:
    MyCircularItem(const QPointF &center, qreal radius);

    virtual QPainterPath shape() const override;

private:
    QPointF m_center;
    qreal m_radius;
};

MyCircularItem::MyCircularItem(const QPointF &center, qreal radius)
    : m_center(center), m_radius(radius)
{
}

QPainterPath MyCircularItem::shape() const
{
    QPainterPath path;
    path.addEllipse(m_center - QPointF(m_radius, m_radius), 2 * m_radius, 2 * m_radius);
    return path;
}

このコードをメインプログラムで実行するには、次のコードを使用します。

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

    QGraphicsScene scene;
    QGraphicsView view(&scene);

    MyCircularItem *item = new MyCircularItem(QPointF(100, 100), 50);
    scene.addItem(item);

    view.show();

    return app.exec();
}

このコードを実行すると、次の図のような円形アイテムが表示されます。

例:星形アイテム

この例では、星形アイテムの形状を定義するMyStarItemクラスを作成します。

class MyStarItem : public QGraphicsItem
{
public:
    MyStarItem(const QPointF &center, qreal radius, int numPoints);

    virtual QPainterPath shape() const override;

private:
    QPointF m_center;
    qreal m_radius;
    int m_numPoints;
};

MyStarItem::MyStarItem(const QPointF &center, qreal radius, int numPoints)
    : m_center(center), m_radius(radius), m_numPoints(numPoints)
{
}

QPainterPath MyStarItem::shape() const
{
    QPainterPath path;

    qreal angleStep = M_PI * 2 / m_numPoints;
    qreal innerRadius = m_radius * 0.5;

    for (int i = 0; i < m_numPoints; ++i) {
        QPointF point1 = m_center + QPointF(m_radius * cos(angleStep * i), m_radius * sin(angleStep * i));
        QPointF point2 = m_center + QPointF(innerRadius * cos(angleStep * i + angleStep / 2), innerRadius * sin(angleStep * i + angleStep / 2));

        if (i == 0) {
            path.moveTo(point1);
        } else {
            path.lineTo(point1);
        }

        path.lineTo(point2);
    }

    path.close();

    return path;
}
int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QGraphicsScene scene;
    QGraphicsView view(&scene);

    MyStarItem *item = new MyStarItem(QPointF(100, 100), 50, 5);
    scene.addItem(item);

    view.show();

    return app.exec();
}

これらの例は、QGraphicsItem::shape()関数の使用方法を示すほんの一例に過ぎません。この関数は、さまざまな形状を定義するために使用できます。

  • 画像アイテム
  • テキストアイテム
  • カスタムポリゴンアイテム
  • 長方形アイテム
  • 三角形アイテム


代替方法

  1. boundingRect() を使用する

    boundingRect() 関数は、アイテムの最小矩形を返します。これは、単純な矩形アイテムの場合は適切ですが、より複雑な形状を持つアイテムの場合は不十分です。

  2. QPainterPath を直接使用する

    paint() 関数内で QPainterPath オブジェクトを作成し、アイテムを描画することができます。この方法は、より複雑な形状を持つアイテムを定義する場合に役立ちます。

  3. QGraphicsProxyItem を使用する

    QGraphicsProxyItem を使用すると、別のアイテムの形状を代理することができます。これは、複雑な形状を持つアイテムを再利用したい場合に役立ちます。

各方法の詳細

boundingRect() を使用する

class MyItem : public QGraphicsItem
{
public:
    QRectF boundingRect() const override {
        // アイテムの境界矩形を計算する
        return QRectF(...);
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override {
        // アイテムを描画する
        painter->drawRect(boundingRect());
    }
};

QPainterPath を直接使用する

class MyItem : public QGraphicsItem
{
public:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override {
        // アイテムの形状を定義する QPainterPath を作成する
        QPainterPath path;
        path.addEllipse(QPointF(50, 50), 40, 30);

        // アイテムを描画する
        painter->fillPath(path, Qt::red);
    }
};

QGraphicsProxyItem を使用する

class MyItem : public QGraphicsItem
{
public:
    MyItem(QGraphicsItem *sourceItem)
        : QGraphicsProxyItem(sourceItem)
    {
    }
};
方法利点欠点
boundingRect()シンプルで使いやすい複雑な形状には不向き
QPainterPath柔軟性が高いコードが複雑になる可能性がある
QGraphicsProxyItemコードの再利用が可能パフォーマンスが低下する可能性がある