QGraphicsViewのラバーバンド選択を自在に操る!Qtの代替プログラミング手法
rubberBandSelectionMode
とは
QGraphicsView
は、QGraphicsScene
上に配置されたグラフィックアイテムを表示するためのウィジェットです。ユーザーがマウスをドラッグして矩形領域を描画することで、その領域内にあるアイテムを選択する機能を提供します。この矩形領域が「ラバーバンド」と呼ばれ、その選択方法を rubberBandSelectionMode
で設定します。
設定できるモード (Qt::ItemSelectionMode)
rubberBandSelectionMode
プロパティは、Qt::ItemSelectionMode
型の値を取ります。主なモードは以下の通りです。
-
Qt::IntersectsItemBoundingRect
:- このモードが設定されている場合、ラバーバンドの矩形領域と交差するアイテムの境界矩形(
boundingRect()
)を持つアイテムが選択されます。 - アイテムの形状ではなく、そのアイテム全体を囲む最小の矩形領域(
boundingRect()
)がラバーバンドに少しでも重なっていれば選択されます。
- このモードが設定されている場合、ラバーバンドの矩形領域と交差するアイテムの境界矩形(
-
Qt::ContainsItemBoundingRect
:- このモードが設定されている場合、ラバーバンドの矩形領域に完全に含まれるアイテムの境界矩形(
boundingRect()
)を持つアイテムが選択されます。 - アイテムの形状ではなく、そのアイテム全体を囲む最小の矩形領域(
boundingRect()
)がラバーバンドに完全に含まれている場合に選択されます。
- このモードが設定されている場合、ラバーバンドの矩形領域に完全に含まれるアイテムの境界矩形(
-
Qt::IntersectsItemShape
:- このモードが設定されている場合、ラバーバンドの矩形領域と交差するアイテムが選択されます。
- アイテムの形状(
QGraphicsItem::shape()
で定義される領域)がラバーバンドと少しでも重なっていれば選択されます。これは、QGraphicsView
のデフォルトのラバーバンド選択モードです。
-
Qt::ContainsItemShape
:- このモードが設定されている場合、ラバーバンドの矩形領域に完全に含まれるアイテムのみが選択されます。
- アイテムの形状(
QGraphicsItem::shape()
で定義される領域)がすべてラバーバンド内に収まっている必要があります。アイテムの境界矩形(boundingRect()
)がラバーバンドに交差していても、形状が完全に含まれていなければ選択されません。
なぜ重要なのか
このプロパティを設定することで、アプリケーションのユーザーインターフェース(UI)における選択の挙動を細かく制御できます。例えば、
- 大まかな選択で良い場合:
Qt::IntersectsItemShape
(デフォルト) やQt::IntersectsItemBoundingRect
を使用して、多少の重なりでもアイテムが選択されるようにできます。 - 厳密な選択が必要な場合:
Qt::ContainsItemShape
やQt::ContainsItemBoundingRect
を使用して、ユーザーが意図したアイテムのみが選択されるようにできます。
#include <QtWidgets/QApplication>
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsView>
#include <QtWidgets/QGraphicsRectItem>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 800, 600);
// アイテムの追加
QGraphicsRectItem *rect1 = new QGraphicsRectItem(50, 50, 100, 100);
rect1->setBrush(Qt::blue);
rect1->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
scene.addItem(rect1);
QGraphicsEllipseItem *ellipse1 = new QGraphicsEllipseItem(180, 50, 100, 100);
ellipse1->setBrush(Qt::red);
ellipse1->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
scene.addItem(ellipse1);
QGraphicsRectItem *rect2 = new QGraphicsRectItem(100, 150, 150, 80);
rect2->setBrush(Qt::green);
rect2->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
scene.addItem(rect2);
QGraphicsView view(&scene);
view.setWindowTitle("QGraphicsView RubberBand Selection Example");
view.setDragMode(QGraphicsView::RubberBandDrag); // ラバーバンドドラッグを有効にする
// ここでラバーバンド選択モードを設定
view.setRubberBandSelectionMode(Qt::ContainsItemShape); // 例: 形状が完全に含まれる場合に選択
view.resize(800, 600);
view.show();
return a.exec();
}
この例では、view.setRubberBandSelectionMode(Qt::ContainsItemShape);
の行で、ラバーバンド選択のモードを Qt::ContainsItemShape
に設定しています。これにより、ユーザーがドラッグして作成したラバーバンド矩形に、アイテムの形状が完全に収まっている場合にのみ、そのアイテムが選択されるようになります。
よくあるエラーと問題点
-
- 原因
アイテムのboundingRect()
やshape()
の実装が不正確である可能性が高いです。boundingRect()
は、アイテムの描画領域全体を囲む最小の矩形を返す必要があります。これには、線の太さやシャドウなども含める必要があります。もしboundingRect()
が実際の描画領域よりも小さい場合、Qt::ContainsItemBoundingRect
モードでは、ラバーバンドがアイテム全体を囲んでいるように見えても選択されないことがあります。shape()
は、アイテムの正確な形状をQPainterPath
で返す必要があります。Qt::ContainsItemShape
モードでは、このshape()
がラバーバンドに完全に含まれていないと選択されません。特に複雑な形状の場合、正確なshape()
の実装が難しいことがあります。
- トラブルシューティング
- カスタムアイテムの
boundingRect()
とshape()
の実装を慎重に確認してください。 - デバッグ用に、
boundingRect()
やshape()
が返す領域を一時的に描画して、実際にその領域がどのように認識されているかを確認します(例:QPainter::drawRect(boundingRect())
やQPainter::drawPath(shape())
)。 - 特に、ペン(線)の太さが
boundingRect()
に考慮されているかを確認してください。太いペンで描画しているのにboundingRect()
が線の太さを含んでいない場合、選択の問題が発生します。
- カスタムアイテムの
- 原因
-
ラバーバンド選択自体が機能しない
- 原因
QGraphicsView::setDragMode(QGraphicsView::RubberBandDrag);
を呼び出していない。- シーン上のアイテムが選択可能に設定されていない(
QGraphicsItem::ItemIsSelectable
フラグが設定されていない)。 - アイテムの
mousePressEvent
などでイベントがaccept()
されてしまい、QGraphicsView
にイベントが伝播していない。
- トラブルシューティング
setDragMode(QGraphicsView::RubberBandDrag);
が適切に設定されているかを確認します。- 選択したいすべての
QGraphicsItem
のsetFlags(QGraphicsItem::ItemIsSelectable);
またはsetFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
のような形でItemIsSelectable
フラグが設定されていることを確認します。 - カスタムアイテムの
mousePressEvent
やmouseMoveEvent
でevent->ignore();
を呼び出すことで、イベントがQGraphicsView
に伝播するようにします。
- 原因
-
パフォーマンスの問題(特に多数のアイテムがある場合)
- 原因
Qt::ContainsItemShape
やQt::IntersectsItemShape
モードは、shape()
の計算を頻繁に行うため、多くの複雑なアイテムがある場合にパフォーマンスに影響を与える可能性があります。shape()
の計算はboundingRect()
よりもコストがかかります。 - トラブルシューティング
- どうしても
shape()
ベースの選択が必要でない限り、より高速なQt::IntersectsItemBoundingRect
またはQt::ContainsItemBoundingRect
を検討してください。これらはboundingRect()
のみに基づくため、計算コストが低いです。 - アイテムの
shape()
の実装を最適化し、不必要な計算を避けるようにします。 - ビューポート更新モードを調整する(例:
view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
)。ただし、これは一般的に選択モードとは直接関連しませんが、全体のパフォーマンスに影響します。
- どうしても
- 原因
-
スクロール中のラバーバンドの挙動
- 原因
ユーザーがラバーバンドをドラッグ中にビューが自動的にスクロールしない、またはスクロールしたときにラバーバンドの選択範囲が正しく更新されない場合があります。これはQGraphicsView
のデフォルトの挙動ではサポートされていないことがほとんどです。 - トラブルシューティング
QGraphicsView
をサブクラス化し、mouseMoveEvent
をオーバーライドして、ラバーバンドドラッグ中にマウスカーソルがビューの端に近づいたときにプログラムでスクロールするロジックを実装する必要があります。これは少し複雑な実装になります。- スクロールとラバーバンドの連携は、Qtの公式ドキュメントやフォーラムで詳細なカスタム実装例を探すことが有効です。
- 原因
-
Ctrlキーなどモディファイアキーとの組み合わせ
- 原因
Ctrlキーを押しながらクリックするとアイテムの追加選択、Shiftキーで範囲選択といった挙動は、QGraphicsView
が自動的に処理してくれますが、カスタムのmousePressEvent
やmouseMoveEvent
をオーバーライドしている場合、これらのデフォルト挙動が上書きされてしまうことがあります。 - トラブルシューティング
- カスタムイベントハンドラ内で
event->modifiers()
を確認し、必要なモディファイアキーが押されている場合にのみカスタムロジックを実行し、それ以外の場合はevent->ignore();
を呼び出してQtのデフォルト処理に任せるようにします。
- カスタムイベントハンドラ内で
- 原因
- scene()->selectedItems() の確認
ラバーバンドドラッグ後に、view->scene()->selectedItems()
を呼び出して、実際にどのアイテムが選択されているかをプログラム的に確認します。 - イベントの監視
QGraphicsView
とQGraphicsItem
のmousePressEvent
,mouseMoveEvent
,mouseReleaseEvent
をオーバーライドし、デバッグ出力(qDebug()
)を追加して、イベントがどのように伝播しているか、どのアイテムがイベントを受け取っているかを確認します。 - 簡単なケースで試す
複雑なアイテムや大量のアイテムを使用する前に、単純なQGraphicsRectItem
やQGraphicsEllipseItem
でrubberBandSelectionMode
の各モードをテストし、基本的な動作を理解してください。
ここでは、それぞれの Qt::ItemSelectionMode
を使った具体的なプログラミング例を解説します。
基本となる共通設定
まず、すべての例に共通する基本的なセットアップを示します。
#include <QtWidgets/QApplication>
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsView>
#include <QtWidgets/QGraphicsRectItem>
#include <QtWidgets/QGraphicsEllipseItem>
#include <QtWidgets/QGraphicsPolygonItem>
#include <QtGui/QPainterPath> // QGraphicsPolygonItem の shape() 用
// カスタムアイテムクラス (ItemIsSelectable フラグを有効にする)
class MyGraphicsRectItem : public QGraphicsRectItem
{
public:
MyGraphicsRectItem(const QRectF& rect, QGraphicsItem* parent = nullptr)
: QGraphicsRectItem(rect, parent)
{
setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable); // 選択と移動を可能にする
}
};
class MyGraphicsEllipseItem : public QGraphicsEllipseItem
{
public:
MyGraphicsEllipseItem(const QRectF& rect, QGraphicsItem* parent = nullptr)
: QGraphicsEllipseItem(rect, parent)
{
setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
}
};
class MyGraphicsPolygonItem : public QGraphicsPolygonItem
{
public:
MyGraphicsPolygonItem(const QPolygonF& polygon, QGraphicsItem* parent = nullptr)
: QGraphicsPolygonItem(polygon, parent)
{
setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
}
// デフォルトの QGraphicsPolygonItem::shape() は bounding rect なので、正確な形状を返すようにオーバーライド
QPainterPath shape() const override
{
QPainterPath path;
path.addPolygon(polygon());
return path;
}
};
void setupScene(QGraphicsScene& scene) {
scene.setSceneRect(-300, -200, 600, 400); // シーンの範囲設定
// 四角形のアイテム
MyGraphicsRectItem* rect1 = new MyGraphicsRectItem(QRectF(-200, -100, 80, 80));
rect1->setBrush(QBrush(Qt::blue));
scene.addItem(rect1);
MyGraphicsRectItem* rect2 = new MyGraphicsRectItem(QRectF(-100, -50, 120, 60));
rect2->setBrush(QBrush(Qt::cyan));
scene.addItem(rect2);
// 楕円のアイテム
MyGraphicsEllipseItem* ellipse1 = new MyGraphicsEllipseItem(QRectF(50, -100, 90, 90));
ellipse1->setBrush(QBrush(Qt::red));
scene.addItem(ellipse1);
MyGraphicsEllipseItem* ellipse2 = new MyGraphicsEllipseItem(QRectF(120, -40, 70, 70));
ellipse2->setBrush(QBrush(Qt::magenta));
scene.addItem(ellipse2);
// 多角形のアイテム
QPolygonF triangle;
triangle << QPointF(-150, 50) << QPointF(-100, 150) << QPointF(-200, 150);
MyGraphicsPolygonItem* poly1 = new MyGraphicsPolygonItem(triangle);
poly1->setBrush(QBrush(Qt::green));
scene.addItem(poly1);
QPolygonF star; // 星形
star << QPointF(50, 50) << QPointF(60, 80) << QPointF(90, 80) << QPointF(70, 100)
<< QPointF(80, 130) << QPointF(50, 110) << QPointF(20, 130) << QPointF(30, 100)
<< QPointF(10, 80) << QPointF(40, 80);
MyGraphicsPolygonItem* poly2 = new MyGraphicsPolygonItem(star);
poly2->setBrush(QBrush(Qt::yellow));
scene.addItem(poly2);
}
Qt::IntersectsItemShape (デフォルト)
ラバーバンドがアイテムの形状と少しでも交差すると選択されます。ほとんどの場合、このモードで十分です。
// main.cpp
#include "common_setup.h" // 上記の共通設定コードを別ファイルに保存した場合
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
setupScene(scene); // シーンのセットアップ
QGraphicsView view(&scene);
view.setWindowTitle("RubberBand Selection: IntersectsItemShape (Default)");
view.setDragMode(QGraphicsView::RubberBandDrag); // ラバーバンドドラッグを有効にする
// **設定ポイント:** デフォルトなので明示的に設定しなくても同じ
view.setRubberBandSelectionMode(Qt::IntersectsItemShape);
view.resize(800, 600);
view.show();
return a.exec();
}
挙動
マウスでドラッグしてラバーバンドを描画すると、ラバーバンドの矩形がアイテムの実際の形状(QGraphicsItem::shape()
で定義される領域)と少しでも重なっていれば、そのアイテムが選択されます。これにより、ユーザーはアイテムの境界線に触れるだけで選択できます。
Qt::ContainsItemShape
ラバーバンドがアイテムの形状を完全に含んでいる場合にのみ選択されます。より厳密な選択が必要な場合に適しています。
// main.cpp
#include "common_setup.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
setupScene(scene);
QGraphicsView view(&scene);
view.setWindowTitle("RubberBand Selection: ContainsItemShape");
view.setDragMode(QGraphicsView::RubberBandDrag);
// **設定ポイント:** 形状が完全に含まれる場合のみ選択
view.setRubberBandSelectionMode(Qt::ContainsItemShape);
view.resize(800, 600);
view.show();
return a.exec();
}
挙動
ラバーバンドの矩形が、アイテムの shape()
によって定義されるすべてのピクセルを完全に覆っている場合にのみ、そのアイテムが選択されます。アイテムの端が少しでもラバーバンドの外にはみ出している場合は選択されません。これは、複雑な形状のアイテムを正確に選択したい場合に有用です。
Qt::IntersectsItemBoundingRect
ラバーバンドがアイテムの**境界矩形(boundingRect()
)**と少しでも交差すると選択されます。IntersectsItemShape
よりも高速ですが、アイテムの実際の形状が考慮されないため、意図しない選択が発生する可能性があります。
// main.cpp
#include "common_setup.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
setupScene(scene);
QGraphicsView view(&scene);
view.setWindowTitle("RubberBand Selection: IntersectsItemBoundingRect");
view.setDragMode(QGraphicsView::RubberBandDrag);
// **設定ポイント:** 境界矩形が交差する場合に選択
view.setRubberBandSelectionMode(Qt::IntersectsItemBoundingRect);
view.resize(800, 600);
view.show();
return a.exec();
}
挙動
ラバーバンドの矩形が、アイテムの boundingRect()
によって定義される矩形と少しでも重なっていれば、そのアイテムが選択されます。例えば、細い線状のアイテムでも、その boundingRect()
がラバーバンドに少しでも触れれば選択されます。shape()
の計算よりも高速なため、非常に多くのアイテムがあるシーンでパフォーマンスが重要となる場合に検討されます。ただし、形状の透明な部分でも選択される可能性があるため、ユーザー体験に影響を与えることがあります。
Qt::ContainsItemBoundingRect
ラバーバンドがアイテムの**境界矩形(boundingRect()
)**を完全に含んでいる場合にのみ選択されます。ContainsItemShape
よりも高速で、より厳密な選択を boundingRect()
ベースで行いたい場合に適しています。
// main.cpp
#include "common_setup.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
setupScene(scene);
QGraphicsView view(&scene);
view.setWindowTitle("RubberBand Selection: ContainsItemBoundingRect");
view.setDragMode(QGraphicsView::RubberBandDrag);
// **設定ポイント:** 境界矩形が完全に含まれる場合のみ選択
view.setRubberBandSelectionMode(Qt::ContainsItemBoundingRect);
view.resize(800, 600);
view.show();
return a.exec();
}
挙動
ラバーバンドの矩形が、アイテムの boundingRect()
によって定義される矩形を完全に覆っている場合にのみ、そのアイテムが選択されます。アイテムの形状が複雑でも、その外接矩形がラバーバンドに完全に収まっていれば選択されます。ContainsItemShape
と同様に厳密な選択ですが、shape()
の計算コストを避けることができます。
これらのコードを実行するには、以下の手順が必要です。
- 上記共通設定コードを
common_setup.h
というファイル名で保存します。 - 各モードの
main.cpp
コードをそれぞれmain_intersects_shape.cpp
,main_contains_shape.cpp
などの名前で保存します。 .pro
ファイルを作成し、Qt Creator または QMake でプロジェクトをビルドします。
QT += widgets
SOURCES += main_intersects_shape.cpp # 実行したい main.cpp ファイルに合わせて変更
HEADERS += common_setup.h
# または、すべてのメインファイルをまとめてビルドする(実行時に選択)
# SOURCES += main_intersects_shape.cpp \
# main_contains_shape.cpp \
# main_intersects_bounding_rect.cpp \
# main_contains_bounding_rect.cpp
ここでは、QGraphicsView::rubberBandSelectionMode
の代替となるプログラミング方法をいくつか解説します。
QGraphicsView のマウスイベントをオーバーライドする
これが最も一般的で柔軟なアプローチです。QGraphicsView
をサブクラス化し、マウスイベントハンドラをオーバーライドすることで、ラバーバンドの描画とアイテム選択のロジックを完全に制御できます。
概念
mouseReleaseEvent(QMouseEvent *event)
: ドラッグ終了。ラバーバンドの描画を停止し、最終的な選択を確定します。mouseMoveEvent(QMouseEvent *event)
: ドラッグ中にラバーバンドの矩形を更新し、描画します。この矩形に基づいて、選択ロジックを実行します。mousePressEvent(QMouseEvent *event)
: ドラッグ開始位置を記録し、ラバーバンド描画フラグを立てます。
実装のポイント
- 選択ロジック:
QGraphicsScene::items(const QRectF &rect, Qt::ItemSceneOrder order = Qt::SceneLayer) const
またはQGraphicsScene::items(const QPainterPath &path, Qt::ItemSceneOrder order = Qt::SceneLayer) const
を使用して、指定した領域内のアイテムを取得します。- 取得したアイテムに対して、
setSelected(true)
やsetSelected(false)
を呼び出し、選択状態を更新します。 - 既存の選択を維持するか(Ctrlキーでの追加選択など)、新しい選択に置き換えるかを
event->modifiers()
を確認して判断します。
- ラバーバンドの描画:
QRubberBand
クラスを使用するのが最も簡単です。QRubberBand
は、矩形を描画するウィジェットであり、親ウィジェット(この場合はQGraphicsView
のビューポート)上に表示できます。- または、
QGraphicsScene
に一時的なQGraphicsRectItem
を追加し、これをラバーバンドとして使用することもできます。しかし、これはビューポート上の描画とは少し異なる概念になります。通常はQRubberBand
が推奨されます。
コード例(抜粋)
#include <QtWidgets/QApplication>
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsView>
#include <QtWidgets/QGraphicsRectItem>
#include <QtWidgets/QRubberBand> // QRubberBand を使用
#include <QtGui/QMouseEvent>
#include <QtCore/QDebug>
// QGraphicsView をサブクラス化
class CustomRubberBandView : public QGraphicsView
{
Q_OBJECT
public:
CustomRubberBandView(QGraphicsScene* scene, QWidget* parent = nullptr)
: QGraphicsView(scene, parent), rubberBand(nullptr)
{
// View のドラッグモードは None に設定(自分で制御するため)
setDragMode(NoDrag);
}
protected:
void mousePressEvent(QMouseEvent* event) override
{
if (event->button() == Qt::LeftButton) {
origin = event->pos(); // ドラッグ開始位置を記録
if (!rubberBand) {
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
}
rubberBand->setGeometry(QRect(origin, QSize()));
rubberBand->show();
// 既存の選択をクリアするかどうかを判断 (Ctrlキーが押されていなければクリア)
if (!(event->modifiers() & Qt::ControlModifier)) {
scene()->clearSelection();
}
}
QGraphicsView::mousePressEvent(event); // デフォルトのイベント処理も呼び出す
}
void mouseMoveEvent(QMouseEvent* event) override
{
if (rubberBand && event->buttons() & Qt::LeftButton) {
rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
// 選択ロジックをここに実装
QRectF selectionRect = mapToScene(rubberBand->geometry()).boundingRect();
// シーン内のアイテムをラバーバンド領域で取得
QList<QGraphicsItem*> itemsInRect = scene()->items(selectionRect, Qt::IntersectsItemShape);
// あるいは、Qt::ContainsItemShape, Qt::IntersectsItemBoundingRect など、ここでモードを切り替えられる
// 選択状態を更新
for (QGraphicsItem* item : scene()->items()) { // シーン内の全てのアイテムをイテレート
if (itemsInRect.contains(item)) {
item->setSelected(true); // ラバーバンド内にあるアイテムを選択
} else {
// Ctrlキーが押されていない場合は、ラバーバンド外のアイテムの選択を解除
// Ctrlキーで追加選択している場合は、選択解除しない
if (!(event->modifiers() & Qt::ControlModifier) || !item->isSelected()) {
item->setSelected(false);
}
}
}
}
QGraphicsView::mouseMoveEvent(event);
}
void mouseReleaseEvent(QMouseEvent* event) override
{
if (rubberBand && event->button() == Qt::LeftButton) {
rubberBand->hide();
// 最終的な選択は mouseMoveEvent で行われているため、ここでは主にクリーンアップ
}
QGraphicsView::mouseReleaseEvent(event);
}
private:
QPoint origin;
QRubberBand* rubberBand;
};
// ... (main 関数とシーンのセットアップは共通の例と同様)
// main.cpp:
// QApplication a(argc, argv);
// QGraphicsScene scene;
// setupScene(scene); // setupScene関数は共通の例から持ってくる
// CustomRubberBandView view(&scene);
// view.setWindowTitle("Custom RubberBand Selection");
// view.resize(800, 600);
// view.show();
// return a.exec();
利点
- 視覚的なフィードバック: ラバーバンドの描画中に、選択されたアイテムをハイライト表示するなど、よりリッチな視覚的フィードバックを提供できます。
- 複雑な条件: 特定の種類のアイテムのみを選択する、特定のプロパティを持つアイテムのみを選択するといった、より複雑な選択条件を実装できます。
- 完全な制御: ラバーバンドの描画方法、色、透過度、選択ロジック(
Contains
vsIntersects
、Shape
vsBoundingRect
)、モディファイアキーの挙動など、すべてを自由にカスタマイズできます。
欠点
- バグの可能性: 自分でイベント処理を実装するため、バグを導入するリスクが高まります。
- 実装の手間: Qt が提供する組み込み機能に比べて、コード量が増え、実装が複雑になります。
QGraphicsScene の選択変更シグナルを利用する
QGraphicsView::rubberBandSelectionMode
を使用しつつ、選択が変更されたタイミングで追加の処理を行いたい場合に有用です。
概念
QGraphicsScene::selectionChanged()
シグナルにスロットを接続し、選択されたアイテムのリストを取得して、それらに基づいて何か処理を行います。
コード例
// main.cpp (QGraphicsView::setRubberBandSelectionMode は使用)
#include <QtWidgets/QApplication>
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsView>
#include <QtWidgets/QGraphicsRectItem>
#include <QtCore/QDebug>
// 共通設定のアイテムクラス (ItemIsSelectable フラグは必須)
class MyGraphicsRectItem : public QGraphicsRectItem
{
public:
MyGraphicsRectItem(const QRectF& rect, QGraphicsItem* parent = nullptr)
: QGraphicsRectItem(rect, parent)
{
setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
}
};
void handleSelectionChanged() {
QGraphicsScene* scene = qobject_cast<QGraphicsScene*>(QObject::sender());
if (scene) {
QList<QGraphicsItem*> selectedItems = scene->selectedItems();
qDebug() << "選択されたアイテム数:" << selectedItems.count();
for (QGraphicsItem* item : selectedItems) {
qDebug() << " 選択されたアイテムの型:" << item->type();
}
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 400, 300);
MyGraphicsRectItem* rect1 = new MyGraphicsRectItem(QRectF(50, 50, 100, 100));
rect1->setBrush(Qt::blue);
scene.addItem(rect1);
MyGraphicsRectItem* rect2 = new MyGraphicsRectItem(QRectF(150, 100, 80, 80));
rect2->setBrush(Qt::green);
scene.addItem(rect2);
QGraphicsView view(&scene);
view.setWindowTitle("Selection Changed Signal Example");
view.setDragMode(QGraphicsView::RubberBandDrag);
view.setRubberBandSelectionMode(Qt::IntersectsItemShape); // 組み込みモードを使用
// シーンの selectionChanged シグナルを接続
QObject::connect(&scene, &QGraphicsScene::selectionChanged, handleSelectionChanged);
view.resize(600, 400);
view.show();
return a.exec();
}
利点
- 選択ロジック自体を再実装する必要がない。
QGraphicsView::rubberBandSelectionMode
の恩恵を受けつつ、選択後の処理を追加できる。
欠点
- ラバーバンド選択の「方法」自体をカスタマイズすることはできない。
これは、QGraphicsView
ではなく QGraphicsScene
のレベルでマウスイベントを処理する方法です。アイテムのない場所でのクリックやドラッグに反応させたい場合に有効です。
概念
QGraphicsScene
をサブクラス化し、mousePressEvent
や mouseMoveEvent
をオーバーライドします。
コード例(概念的)
// QGraphicsScene をサブクラス化
class CustomSelectionScene : public QGraphicsScene
{
Q_OBJECT
public:
CustomSelectionScene(QObject* parent = nullptr) : QGraphicsScene(parent) {}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent* event) override
{
// アイテムのない場所でのクリックやドラッグを検出
if (selectedItems().isEmpty() && event->button() == Qt::LeftButton) {
// ここでカスタムラバーバンド選択の開始ロジック
// 例: ドラッグ開始点を記録、QRubberBand を表示する
}
QGraphicsScene::mousePressEvent(event); // デフォルトのイベント処理も呼び出す
}
void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override
{
// ドラッグ中のラバーバンド更新とアイテム選択ロジック
// 例: QRubberBand のサイズを更新、QGraphicsScene::items(rect) でアイテムを取得して選択
QGraphicsScene::mouseMoveEvent(event);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override
{
// ラバーバンド非表示、最終的な選択確定
QGraphicsScene::mouseReleaseEvent(event);
}
};
// main.cpp:
// ...
// CustomSelectionScene scene;
// setupScene(scene);
// QGraphicsView view(&scene);
// view.setDragMode(QGraphicsView::NoDrag); // View のドラッグモードは無効にする
// ...
利点
- 複数のビューで同じ選択ロジックを共有できる。
- シーン全体にわたるカスタムなインタラクションを実装できる。
QGraphicsView
のマウスイベントオーバーライドと同様に、実装の手間がかかる。QGraphicsView
のイベント処理とは異なる座標系(シーン座標)で作業する必要がある。
- シーンレベルでマウスインタラクションを制御したい場合:
QGraphicsScene
をサブクラス化し、マウスイベントをオーバーライドします。 - 選択後に何らかの処理を行いたいだけなら:
QGraphicsScene::selectionChanged()
シグナルを利用します。 - 選択の挙動を細かく制御したい、あるいは視覚的なカスタマイズが必要な場合:
QGraphicsView
をサブクラス化し、マウスイベントをオーバーライドしてQRubberBand
を使ったカスタム選択を実装します。これが最も一般的なカスタマイズ方法です。 - 簡単なラバーバンド選択で十分なら:
QGraphicsView::setRubberBandSelectionMode()
を使用するのが最も簡単で推奨されます。