Qt入門: QGraphicsViewのラバーバンド選択を徹底解説
Qtプログラミングにおけるvoid QGraphicsView::rubberBandChanged()
は、QGraphicsView
クラスのシグナルです。
これは、ユーザーがビュー上で「ラバーバンド(ゴムひも)」を使って範囲選択を行っている際に、そのラバーバンドの形状や位置が変化するたびに発せられるシグナルです。
ラバーバンドとは?
グラフィック編集ソフトウェアなどでよく見られる、マウスをドラッグして矩形(四角形)の選択範囲を作成する機能のことです。QtのQGraphicsView
では、setDragMode(QGraphicsView::RubberBandDrag)
を設定することで、このラバーバンド選択を有効にできます。
rubberBandChanged()
シグナルの詳細
このシグナルは、以下の3つの引数を持ちます。
QPointF toScenePoint
: ラバーバンドの現在の終点(通常、マウスカーソルの現在の位置)をシーン座標系で表すQPointF
オブジェクトです。QPointF fromScenePoint
: ラバーバンドの開始点(通常、マウスドラッグの開始点)をシーン座標系で表すQPointF
オブジェクトです。シーン座標は、QGraphicsScene
のアイテムが配置されている仮想的な座標空間です。QRect viewportRect
: ラバーバンドがビューポート座標系で占める領域を表すQRect
オブジェクトです。ビューポート座標は、QGraphicsView
ウィジェット自体のピクセル座標です。
rubberBandChanged()
が発せられるタイミング
- ユーザーがマウスボタンを離してラバーバンド選択を終了する時(この場合、
viewportRect
が空の矩形となることがあります)。 - ラバーバンドのサイズや位置がマウスの動きによって変化する時。
- ユーザーがマウスの左ボタンを押してドラッグを開始し、ラバーバンド選択が始まる時。
このシグナルの利用例
このシグナルは、ラバーバンド選択の進捗に合わせて何らかの処理を行いたい場合に非常に便利です。例えば、以下のような用途が考えられます。
- カスタム選択ロジック: デフォルトの選択動作とは異なる、独自の選択ロジックを実装する(例:選択された領域内の特定の種類のアイテムのみを選択する)。
- ズームイン/ズームアウト: ラバーバンドで指定された領域にビューをズームインさせる機能。ユーザーがドラッグを終了したときに、その領域に合わせてビューを調整する。
- 選択範囲の動的な表示/更新: ラバーバンドの領域に合わせて、シーン内のアイテムの選択状態をリアルタイムで更新する。
通常、このシグナルをカスタムスロットに接続して使用します。
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QDebug> // デバッグ出力用
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT // シグナルとスロットを使用するために必要
public:
MyGraphicsView(QGraphicsScene* scene, QWidget* parent = nullptr)
: QGraphicsView(scene, parent)
{
// ラバーバンド選択を有効にする
setDragMode(QGraphicsView::RubberBandDrag);
// rubberBandChanged シグナルをカスタムスロットに接続
connect(this, &QGraphicsView::rubberBandChanged,
this, &MyGraphicsView::handleRubberBandChanged);
}
private slots:
void handleRubberBandChanged(const QRect& viewportRect,
const QPointF& fromScenePoint,
const QPointF& toScenePoint)
{
qDebug() << "ラバーバンドが変更されました:";
qDebug() << " ビューポート座標 (QRect):" << viewportRect;
qDebug() << " 開始シーン座標 (QPointF):" << fromScenePoint;
qDebug() << " 終了シーン座標 (QPointF):" << toScenePoint;
// ここでラバーバンドの変更に応じたカスタム処理を記述
// 例: ラバーバンドが終了したときに処理を行う
if (viewportRect.isNull()) {
qDebug() << "ラバーバンド選択が終了しました。";
// 最後のラバーバンド領域を使って何かをする
// (例: ズームなど)
}
}
};
// main 関数 (簡略化)
/*
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 800, 600); // シーンのサイズを設定
// アイテムを追加 (例: 四角形)
QGraphicsRectItem* rect1 = new QGraphicsRectItem(0, 0, 100, 50);
rect1->setPos(50, 50);
rect1->setBrush(Qt::blue);
scene.addItem(rect1);
QGraphicsRectItem* rect2 = new QGraphicsRectItem(0, 0, 80, 80);
rect2->setPos(200, 150);
rect2->setBrush(Qt::red);
scene.addItem(rect2);
MyGraphicsView view(&scene);
view.setWindowTitle("QGraphicsView RubberBand Example");
view.resize(600, 400);
view.show();
return a.exec();
}
*/
シグナルが全く発せられない (または期待通りに発せられない)
考えられる原因
-
QChartView を使用している場合
QChartView
はQGraphicsView
を継承していますが、独自のQRubberBand
実装を持っているため、標準のQGraphicsView::rubberBandChanged
シグナルが発せられないことがあります。 トラブルシューティング:QChartView
のドキュメントを確認するか、QChartView
が提供する独自のラバーバンド関連のシグナルやメソッドを使用する必要があります。場合によっては、QChartView
の内部にあるQRubberBand
オブジェクトにイベントフィルターをインストールし、手動でシグナルを発行するような複雑な対応が必要になることもあります (Stack Overflowの例に見られるように)。 -
他のマウスイベントハンドリングとの競合
QGraphicsView
またはQGraphicsScene
で独自のマウスイベントハンドリング (mousePressEvent
,mouseMoveEvent
,mouseReleaseEvent
) を実装している場合、それがデフォルトのラバーバンド選択動作を妨害している可能性があります。 トラブルシューティング: カスタムのマウスイベント処理を一時的に無効にして、rubberBandChanged
シグナルが発せられるか確認してください。もし競合している場合は、カスタム処理とラバーバンド選択を適切に連携させるロジックを検討する必要があります。例えば、特定のキーが押されている場合のみカスタム処理を有効にする、など。 -
シグナルとスロットの接続が正しくない
connect
ステートメントの構文エラーや、スロットの引数がシグナルの引数と一致しない場合など。 トラブルシューティング:- Qt 5以降の新しい
connect
構文 (connect(sender, &Sender::signal, receiver, &Receiver::slot);
) を使用しているか確認してください。この構文はコンパイル時に型チェックが行われるため、エラーを発見しやすいです。 - スロットの引数リストが、シグナルの引数リスト (
const QRect& viewportRect, const QPointF& fromScenePoint, const QPointF& toScenePoint
) と完全に一致していることを確認してください。引数の型、const
、参照 (&
) の有無も重要です。 Q_OBJECT
マクロがQGraphicsView
を継承したカスタムクラスのヘッダーファイルで定義されていることを確認してください。定義されていないと、シグナルやスロットが機能しません。
- Qt 5以降の新しい
-
setDragMode(QGraphicsView::RubberBandDrag) を設定していない
QGraphicsView
でラバーバンド選択を機能させるには、このドラッグモードを明示的に設定する必要があります。 トラブルシューティング:QGraphicsView
のコンストラクタ、または初期化を行う場所で、setDragMode(QGraphicsView::RubberBandDrag);
を呼び出していることを確認してください。
ラバーバンドの見た目がおかしい、または表示されない
考えられる原因
-
ビューポートの更新が不十分
QGraphicsView
の描画更新が追いついていない可能性があります。 トラブルシューティング:QGraphicsView::viewport()->update()
やQGraphicsView::update()
を呼び出すことで、強制的にビューポートを再描画させてみてください。ただし、rubberBandChanged
は通常、頻繁に発せられるため、過度な更新はパフォーマンスに影響を与える可能性があります。 -
スタイルシートや描画の問題
システムのテーマや、アプリケーションに適用されているカスタムスタイルシートが、QRubberBand
のデフォルトの描画を上書きしている可能性があります。 トラブルシューティング: アプリケーションからカスタムスタイルシートを一時的に削除して、問題が解決するか確認してください。
シグナルの引数 (QRect, QPointF) の解釈が誤っている
考えられる原因
- ビューポート座標とシーン座標の混同
viewportRect
はビューポート(QGraphicsView
ウィジェット自体)のピクセル座標であり、fromScenePoint
とtoScenePoint
はQGraphicsScene
の仮想的な座標空間における点です。これらを混同すると、期待通りの動作になりません。 トラブルシューティング:viewportRect
を使用して、QGraphicsView
の描画領域(ピクセル単位)で何かを行う場合は、QGraphicsView::mapToScene()
やQGraphicsView::mapFromScene()
を使って座標変換を行う必要があります。fromScenePoint
とtoScenePoint
は直接シーン内のアイテムとのインタラクションに使用できます。QGraphicsView
とQGraphicsScene
の座標変換について、Qt のドキュメントをよく確認してください。
パフォーマンスの問題
考えられる原因
- rubberBandChanged シグナル内部での重い処理
このシグナルはマウスドラッグ中に頻繁に発せられるため、接続されたスロット内部で複雑な計算や多くのアイテムに対する処理を行うと、アプリケーションの応答性が低下する可能性があります。 トラブルシューティング:- スロット内部の処理を可能な限り軽量化してください。
- リアルタイムで全てのアイテムを更新する必要があるか再検討してください。最終的なラバーバンドの範囲が確定した時(マウスリリース時)のみ、重い処理を実行するなどの最適化が考えられます。
rubberBandChanged
シグナルのviewportRect
がisNull()
を返すときに処理を実行するなど。 - 非同期処理やスレッドを使用することも検討できますが、GUI スレッドの制約に注意が必要です。
ラバーバンド選択後にアイテムが選択されない
考えられる原因
-
QGraphicsScene::items() の誤用
ラバーバンドの範囲内でカスタムの選択ロジックを実装する場合、QGraphicsScene::items(const QRectF &rect, Qt::ItemSelectionMode mode = Qt::IntersectsItemShape)
などのメソッドを使用することが多いですが、引数のrect
が正しいシーン座標であることと、mode
が期待する選択モードと一致していることを確認してください。 -
アイテムが選択可能に設定されていない
QGraphicsItem
がQGraphicsItem::ItemIsSelectable
フラグを持っている必要があります。 トラブルシューティング: シーンに追加されたアイテムがsetFlag(QGraphicsItem::ItemIsSelectable, true);
で選択可能になっていることを確認してください。 -
setRubberBandSelectionMode() の設定ミス
QGraphicsView
は、ラバーバンド選択時にどのアイテムを選択するかを制御するsetRubberBandSelectionMode()
メソッドを持っています。デフォルトはQt::IntersectsItemShape
ですが、例えばQt::ContainsItemShape
に設定すると、ラバーバンドに完全に含まれるアイテムのみが選択されます。 トラブルシューティング:QGraphicsView::setRubberBandSelectionMode()
の設定を確認し、アプリケーションの要件に合致しているか確認してください。
例1:ラバーバンドの情報をデバッグ出力するシンプルな例
これは、rubberBandChanged()
シグナルが発せられるたびに、その引数(ラバーバンドの領域と開始/終了点)をコンソールに出力する基本的な例です。
MyGraphicsView.h
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H
#include <QGraphicsView>
#include <QGraphicsScene> // QGraphicsScene を含める
#include <QDebug> // qDebug() を使用するため
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT // シグナルとスロットを使用するために必要
public:
explicit MyGraphicsView(QGraphicsScene* scene, QWidget* parent = nullptr);
private slots:
// rubberBandChanged シグナルに対応するスロット
void handleRubberBandChanged(const QRect& viewportRect,
const QPointF& fromScenePoint,
const QPointF& toScenePoint);
};
#endif // MYGRAPHICSVIEW_H
MyGraphicsView.cpp
#include "MyGraphicsView.h"
MyGraphicsView::MyGraphicsView(QGraphicsScene* scene, QWidget* parent)
: QGraphicsView(scene, parent)
{
// ラバーバンドドラッグモードを有効にする
setDragMode(QGraphicsView::RubberBandDrag);
// rubberBandChanged シグナルをカスタムスロットに接続
connect(this, &QGraphicsView::rubberBandChanged,
this, &MyGraphicsView::handleRubberBandChanged);
// アイテムの選択モードを設定(オプション)
// デフォルトは Qt::IntersectsItemShape (ラバーバンドと交差するアイテムを選択)
setRubberBandSelectionMode(Qt::IntersectsItemShape);
}
void MyGraphicsView::handleRubberBandChanged(const QRect& viewportRect,
const QPointF& fromScenePoint,
const QPointF& toScenePoint)
{
qDebug() << "--- ラバーバンド変更イベント ---";
qDebug() << "ビューポート座標 (QRect):" << viewportRect;
qDebug() << "シーン開始点 (QPointF):" << fromScenePoint;
qDebug() << "シーン終了点 (QPointF):" << toScenePoint;
// ラバーバンドが解除されたかどうかのチェック (通常、viewportRect が無効になる)
if (viewportRect.isNull()) {
qDebug() << "ラバーバンド選択が終了しました。";
}
}
main.cpp (アプリケーションのセットアップ)
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>
#include "MyGraphicsView.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// シーンを作成
QGraphicsScene scene;
scene.setSceneRect(0, 0, 800, 600); // シーンの論理的なサイズ
// シーンにいくつかのアイテムを追加
QGraphicsRectItem* rect1 = new QGraphicsRectItem(0, 0, 100, 50);
rect1->setPos(50, 50);
rect1->setBrush(Qt::blue);
rect1->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); // 移動・選択可能に設定
scene.addItem(rect1);
QGraphicsEllipseItem* ellipse1 = new QGraphicsEllipseItem(0, 0, 80, 80);
ellipse1->setPos(200, 100);
ellipse1->setBrush(Qt::red);
ellipse1->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
scene.addItem(ellipse1);
QGraphicsRectItem* rect2 = new QGraphicsRectItem(0, 0, 150, 70);
rect2->setPos(350, 250);
rect2->setBrush(Qt::green);
rect2->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
scene.addItem(rect2);
// カスタムの QGraphicsView を作成
MyGraphicsView view(&scene);
view.setWindowTitle("QGraphicsView::rubberBandChanged() Example 1");
view.resize(800, 600); // ウィンドウのサイズを設定
view.show();
return a.exec();
}
この例を実行し、ビュー上でマウスをドラッグすると、ラバーバンドの移動に合わせて handleRubberBandChanged
スロットが頻繁に呼び出され、その情報がデバッグ出力されます。マウスボタンを離すと、viewportRect
が isNull()
を返す状態で再度シグナルが発せられ、選択終了が示されます。
例2:ラバーバンドでズームインする機能
ラバーバンド選択の終了時に、その領域に合わせてビューをズームインさせる機能です。
MyZoomGraphicsView.h (例1の MyGraphicsView を変更)
#ifndef MYZOOMGRAPHICSVIEW_H
#define MYZOOMGRAPHICSVIEW_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QDebug>
#include <QGraphicsItem> // アイテム選択のため
class MyZoomGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
explicit MyZoomGraphicsView(QGraphicsScene* scene, QWidget* parent = nullptr);
private slots:
void handleRubberBandChanged(const QRect& viewportRect,
const QPointF& fromScenePoint,
const QPointF& toScenePoint);
private:
QRectF m_lastRubberBandSceneRect; // 最後に選択されたラバーバンドのシーン座標
};
#endif // MYZOOMGRAPHICSVIEW_H
MyZoomGraphicsView.cpp
#include "MyZoomGraphicsView.h"
MyZoomGraphicsView::MyZoomGraphicsView(QGraphicsScene* scene, QWidget* parent)
: QGraphicsView(scene, parent)
{
setDragMode(QGraphicsView::RubberBandDrag);
connect(this, &QGraphicsView::rubberBandChanged,
this, &MyZoomGraphicsView::handleRubberBandChanged);
// ズームイン時のアイテム選択は不要なので、NoMode に設定することも可能
// setRubberBandSelectionMode(Qt::NoItemInclusionMode); // アイテム選択を無効にする場合
}
void MyZoomGraphicsView::handleRubberBandChanged(const QRect& viewportRect,
const QPointF& fromScenePoint,
const QPointF& toScenePoint)
{
if (viewportRect.isNull()) {
// ラバーバンド選択が終了したとき
qDebug() << "ラバーバンド選択終了。ズームインを適用します。";
if (!m_lastRubberBandSceneRect.isNull() && !m_lastRubberBandSceneRect.isEmpty()) {
// fitInView を使用して、選択されたシーン領域にビューを合わせる
fitInView(m_lastRubberBandSceneRect, Qt::KeepAspectRatio);
}
m_lastRubberBandSceneRect = QRectF(); // 次の選択のためにリセット
} else {
// ラバーバンドがアクティブな間は、その領域を更新
// viewportRect をシーン座標にマッピングして保存
m_lastRubberBandSceneRect = mapToScene(viewportRect).boundingRect();
qDebug() << "現在のラバーバンド シーン座標:" << m_lastRubberBandSceneRect;
}
}
main.cpp (MyZoomGraphicsView を使用するように変更)
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>
#include "MyZoomGraphicsView.h" // 変更されたビューをインクルード
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 1000, 800); // シーンサイズを大きめに設定
// アイテムを追加(移動・選択可能にしない方がズームの動きがわかりやすい)
QGraphicsRectItem* rect1 = new QGraphicsRectItem(0, 0, 200, 100);
rect1->setPos(100, 100);
rect1->setBrush(Qt::blue);
scene.addItem(rect1);
QGraphicsEllipseItem* ellipse1 = new QGraphicsEllipseItem(0, 0, 150, 150);
ellipse1->setPos(400, 200);
ellipse1->setBrush(Qt::red);
scene.addItem(ellipse1);
QGraphicsRectItem* rect2 = new QGraphicsRectItem(0, 0, 300, 150);
rect2->setPos(200, 500);
rect2->setBrush(Qt::green);
scene.addItem(rect2);
MyZoomGraphicsView view(&scene);
view.setWindowTitle("QGraphicsView::rubberBandChanged() Zoom Example");
view.resize(800, 600);
view.show();
return a.exec();
}
この例では、ユーザーがビュー上でラバーバンドをドラッグし、マウスボタンを離すと、そのラバーバンドが囲んでいた領域にビューがズームインします。m_lastRubberBandSceneRect
を使って、ラバーバンドがアクティブな間にその領域をシーン座標で追跡しているのがポイントです。
ラバーバンドの変更に合わせて、現在選択されているアイテムの数をビューのステータスバーやラベルに表示する例です。
MySelectionView.h
#ifndef MYSELECTIONVIEW_H
#define MYSELECTIONVIEW_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QLabel> // ステータス表示用
#include <QVBoxLayout> // レイアウト用
#include <QDebug>
class MySelectionView : public QGraphicsView
{
Q_OBJECT
public:
explicit MySelectionView(QGraphicsScene* scene, QWidget* parent = nullptr);
private slots:
void handleRubberBandChanged(const QRect& viewportRect,
const QPointF& fromScenePoint,
const QPointF& toScenePoint);
private:
QLabel* m_statusLabel;
};
#endif // MYSELECTIONVIEW_H
MySelectionView.cpp
#include "MySelectionView.h"
MySelectionView::MySelectionView(QGraphicsScene* scene, QWidget* parent)
: QGraphicsView(scene, parent)
{
// ラバーバンドドラッグモードを有効にする
setDragMode(QGraphicsView::RubberBandDrag);
// アイテム選択モードを設定(デフォルトでも可)
setRubberBandSelectionMode(Qt::IntersectsItemShape);
// レイアウトとステータスラベルの設定
QVBoxLayout* layout = new QVBoxLayout(this);
m_statusLabel = new QLabel("選択アイテム数: 0", this);
layout->addWidget(m_statusLabel, 0, Qt::AlignTop | Qt::AlignLeft);
layout->addStretch(); // ラベルを上部に寄せる
// 背景を透明にしてラベルが見えるようにする
setAttribute(Qt::WA_TranslucentBackground, true);
setWindowFlags(Qt::FramelessWindowHint); // ウィンドウのフレームをなくす(オプション)
// ViewportWidget を取得し、その上にラベルを配置
// これは簡易的な方法で、より複雑なUIでは別のウィジェットにビューとラベルを配置するのが一般的
QWidget* viewportWidget = viewport();
QVBoxLayout* viewportLayout = new QVBoxLayout(viewportWidget);
viewportLayout->addWidget(m_statusLabel, 0, Qt::AlignTop | Qt::AlignLeft);
viewportLayout->addStretch();
viewportWidget->setLayout(viewportLayout);
// シグナルとスロットの接続
connect(this, &QGraphicsView::rubberBandChanged,
this, &MySelectionView::handleRubberBandChanged);
}
void MySelectionView::handleRubberBandChanged(const QRect& viewportRect,
const QPointF& fromScenePoint,
const QPointF& toScenePoint)
{
// ラバーバンド選択が終了した場合は、選択アイテムの最終数を表示
if (viewportRect.isNull()) {
int selectedCount = scene()->selectedItems().count();
m_statusLabel->setText(QString("選択アイテム数: %1 (最終)").arg(selectedCount));
} else {
// ラバーバンドがアクティブな間、現在ラバーバンドに含まれているアイテムの数を数える
// 注意: QGraphicsView のデフォルトのラバーバンド選択動作は、
// このシグナルが発せられる前にアイテムの選択状態を更新するとは限りません。
// 正確なリアルタイム更新には、手動でアイテムをチェックする必要があります。
// ここでは簡易的に、現在のビューポート矩形内のアイテム数を数えます。
QList<QGraphicsItem*> itemsInRubberBand = scene()->items(mapToScene(viewportRect));
m_statusLabel->setText(QString("選択アイテム数: %1 (現在)").arg(itemsInRubberBand.count()));
}
}
main.cpp (MySelectionView を使用するように変更)
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>
#include "MySelectionView.h" // 変更されたビューをインクルード
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 800, 600);
// アイテムを追加し、選択可能に設定
for (int i = 0; i < 5; ++i) {
QGraphicsRectItem* rect = new QGraphicsRectItem(0, 0, 50, 50);
rect->setPos(i * 100 + 50, i * 80 + 50);
rect->setBrush(QColor(qrand() % 256, qrand() % 256, qrand() % 256));
rect->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
scene.addItem(rect);
QGraphicsEllipseItem* ellipse = new QGraphicsEllipseItem(0, 0, 60, 60);
ellipse->setPos(i * 80 + 20, i * 120 + 200);
ellipse->setBrush(QColor(qrand() % 256, qrand() % 256, qrand() % 256));
ellipse->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
scene.addItem(ellipse);
}
MySelectionView view(&scene);
view.setWindowTitle("QGraphicsView::rubberBandChanged() Selection Count Example");
view.resize(800, 600);
view.show();
return a.exec();
}
この例では、ラバーバンドのドラッグ中に、ラバーバンドが現在カバーしているアイテムの数をリアルタイムで表示します。マウスボタンを離すと、実際に選択されたアイテムの最終数が表示されます。
- レイアウトの単純化
例3のレイアウトは簡略化されており、QGraphicsView
のビューポート内に直接QLabel
を配置しています。実際のアプリケーションでは、通常、QGraphicsView
を別のウィジェット(例:QMainWindow
の中央ウィジェットや、QVBoxLayout
など)に配置し、その外側にステータスバーやツールバーを追加する方が良い設計になります。 - リアルタイム選択とパフォーマンス
handleRubberBandChanged
の中でscene()->items(mapToScene(viewportRect))
を呼び出すことは、大量のアイテムがある場合にパフォーマンスに影響を与える可能性があります。アイテムの数が非常に多い場合は、より効率的なアルゴリズムを検討するか、更新頻度を落とす必要があります。
マウスイベントを直接処理する
最も柔軟な方法は、QGraphicsView
をサブクラス化し、マウスイベントハンドラをオーバーライドすることです。これにより、マウスのプレス、ムーブ、リリースの各イベントを細かく制御し、独自のラバーバンドロジックを実装できます。
利点
- パフォーマンス最適化
大量のアイテムがある場合など、独自の描画ロジックを最適化できます。 - 複雑な動作の実装
標準のラバーバンド選択では実現できないような、特殊な選択モード(例:多角形選択、フリーハンド選択)を実装できます。 - 完全な制御
ラバーバンドの描画、開始点、終了点、サイズ変更時の動作、アイテムの選択ロジックなど、すべてを自由にカスタマイズできます。
欠点
- デフォルト機能の再実装
標準のラバーバンド選択が提供する便利な機能(例:スクロールバーの自動調整)を、自分で再実装する必要がある場合があります。 - 実装の複雑さ
ゼロからラバーバンドの描画とロジックを記述する必要があるため、コード量が増え、デバッグが難しくなる可能性があります。
実装のポイント
- シーン座標とビューポート座標の変換(
mapToScene()
,mapFromScene()
)を適切に使用することが重要です。 QRubberBand
クラスを内部で作成し、その幾何学的形状を更新することで、視覚的なラバーバンドを描画できます。mouseReleaseEvent(QMouseEvent *event)
: マウスボタンが離されたときに、ラバーバンドの描画を終了し、最終的な選択(またはズームなど)の処理を実行します。mouseMoveEvent(QMouseEvent *event)
: マウスが移動するたびに、ラバーバンドの現在の形状と位置を更新し、再描画します。mousePressEvent(QMouseEvent *event)
: マウスボタンが押されたときに、ラバーバンドの開始点を記録します。必要に応じて、カスタムのラバーバンド描画を開始します。
// 概念的なコード例(簡略化)
class CustomRubberBandView : public QGraphicsView
{
Q_OBJECT
public:
CustomRubberBandView(QGraphicsScene* scene, QWidget* parent = nullptr)
: QGraphicsView(scene, parent), m_rubberBand(nullptr) {}
protected:
void mousePressEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
m_origin = event->pos(); // ビューポート座標の開始点
if (!m_rubberBand) {
m_rubberBand = new QRubberBand(QRubberBand::Rectangle, viewport());
m_rubberBand->setGeometry(QRect(m_origin, QSize()));
m_rubberBand->show();
}
event->accept(); // イベントを処理済みとしてマーク
} else {
QGraphicsView::mousePressEvent(event); // デフォルト処理
}
}
void mouseMoveEvent(QMouseEvent *event) override {
if (event->buttons() & Qt::LeftButton && m_rubberBand) {
m_rubberBand->setGeometry(QRect(m_origin, event->pos()).normalized());
// ここでラバーバンドの領域内のアイテムをリアルタイムでハイライトするなどのカスタムロジック
// QRectF selectionRect = mapToScene(m_rubberBand->geometry()).boundingRect();
// QList<QGraphicsItem*> items = scene()->items(selectionRect);
event->accept();
} else {
QGraphicsView::mouseMoveEvent(event);
}
}
void mouseReleaseEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton && m_rubberBand) {
m_rubberBand->hide();
// 最終的なラバーバンド領域を取得し、選択処理などを実行
QRect finalViewportRect = m_rubberBand->geometry();
QRectF finalSceneRect = mapToScene(finalViewportRect).boundingRect();
// 例: シーン内のアイテムを選択
scene()->setSelectionArea(finalSceneRect);
delete m_rubberBand;
m_rubberBand = nullptr;
event->accept();
} else {
QGraphicsView::mouseReleaseEvent(event);
}
}
private:
QPoint m_origin;
QRubberBand* m_rubberBand;
};
イベントフィルターを使用する
QGraphicsView
のマウスイベントを直接オーバーライドする代わりに、QGraphicsView
またはその viewport()
にイベントフィルターをインストールする方法もあります。これにより、QGraphicsView
のデフォルトの動作を変更することなく、イベントを傍受して処理できます。
利点
- 複数の機能の追加
1つのビューに複数のイベントフィルターをインストールして、異なる動作を組み合わせることができます。 - 疎結合
QGraphicsView
をサブクラス化する必要がないため、既存のQGraphicsView
インスタンスにカスタムのラバーバンド動作を追加できます。
欠点
- デバッグの複雑さ
イベントフィルターチェーンのどこでイベントが処理されているかを追跡するのが難しくなる場合があります。 - イベントの伝播管理
イベントを処理した後、true
を返すかfalse
を返すかによって、イベントがさらに伝播するかどうかを慎重に制御する必要があります。
実装のポイント
- 処理したイベントについては
true
を返し、それ以外はfalse
を返してイベントをさらに伝播させます。 eventFilter(QObject *watched, QEvent *event)
メソッドをオーバーライドし、QEvent::MouseButtonPress
、QEvent::MouseMove
、QEvent::MouseButtonRelease
を処理します。QObject::installEventFilter(this)
を呼び出して、ビューまたはそのビューポートにフィルターをインストールします。
// 概念的なコード例(簡略化)
class RubberBandEventFilter : public QObject
{
Q_OBJECT
public:
RubberBandEventFilter(QGraphicsView* view, QObject* parent = nullptr)
: QObject(parent), m_view(view), m_rubberBand(nullptr) {
m_view->viewport()->installEventFilter(this); // ビューポートにフィルターをインストール
}
protected:
bool eventFilter(QObject *watched, QEvent *event) override {
if (watched == m_view->viewport()) {
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
m_origin = mouseEvent->pos();
if (!m_rubberBand) {
m_rubberBand = new QRubberBand(QRubberBand::Rectangle, m_view->viewport());
m_rubberBand->setGeometry(QRect(m_origin, QSize()));
m_rubberBand->show();
}
return true; // イベントを消費
}
} else if (event->type() == QEvent::MouseMove) {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->buttons() & Qt::LeftButton && m_rubberBand) {
m_rubberBand->setGeometry(QRect(m_origin, mouseEvent->pos()).normalized());
return true;
}
} else if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton && m_rubberBand) {
m_rubberBand->hide();
QRect finalViewportRect = m_rubberBand->geometry();
QRectF finalSceneRect = m_view->mapToScene(finalViewportRect).boundingRect();
m_view->scene()->setSelectionArea(finalSceneRect);
delete m_rubberBand;
m_rubberBand = nullptr;
return true;
}
}
}
return QObject::eventFilter(watched, event); // 他のイベントは親クラスに渡す
}
private:
QGraphicsView* m_view;
QPoint m_origin;
QRubberBand* m_rubberBand;
};
QGraphicsScene::selectionChanged() シグナルを利用する
これは直接 rubberBandChanged()
の代替ではありませんが、ラバーバンド選択の結果に基づいて何かを行いたい場合に非常に有用です。QGraphicsView::RubberBandDrag
モードを使用すると、ラバーバンド選択の完了後に QGraphicsScene
がアイテムの選択状態を更新し、selectionChanged()
シグナルを発します。
利点
- すべての選択方法に対応
マウスによるクリック選択、Ctrl+クリックによる複数選択、そしてラバーバンド選択のいずれでも発せられます。 - シンプルさ
選択されたアイテムのリスト (QGraphicsScene::selectedItems()
) を取得するだけで済みます。 - 高レベルな抽象化
個々のマウスイベントを気にする必要がなく、選択状態の変化にのみ反応します。
欠点
- リアルタイム性がない
ラバーバンドがドラッグされている間ではなく、選択が完了した時点でのみ発せられます。ドラッグ中にリアルタイムで何かを更新したい場合には使えません。
- スロット内で
scene->selectedItems()
を呼び出して、現在選択されているアイテムのリストを取得し、必要な処理を行います。 QGraphicsScene
のselectionChanged()
シグナルをカスタムスロットに接続します。
// 概念的なコード例
class MyScene : public QGraphicsScene
{
Q_OBJECT
public:
MyScene(QObject* parent = nullptr) : QGraphicsScene(parent) {
connect(this, &QGraphicsScene::selectionChanged,
this, &MyScene::handleSelectionChanged);
}
private slots:
void handleSelectionChanged() {
QList<QGraphicsItem*> selectedItems = this->selectedItems();
qDebug() << "選択アイテムが変更されました。現在選択されているアイテム数:" << selectedItems.count();
// 選択されたアイテムに対して処理を行う(例:色を変更、プロパティパネルを更新)
for (QGraphicsItem* item : selectedItems) {
// 例: アイテムが QGraphicsRectItem なら、境界線の色を変更する
if (QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item)) {
QPen pen = rectItem->pen();
pen.setColor(Qt::red);
rectItem->setPen(pen);
}
}
// 非選択アイテムの元の状態に戻す処理も必要になる場合がある
}
};
// main.cpp などで MyScene を使用
/*
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyScene scene;
scene.setSceneRect(0, 0, 800, 600);
QGraphicsView view(&scene);
view.setDragMode(QGraphicsView::RubberBandDrag); // ラバーバンド選択を有効にする
// アイテムを追加(選択可能にするのを忘れずに)
QGraphicsRectItem* rect = new QGraphicsRectItem(50, 50, 100, 100);
rect->setFlag(QGraphicsItem::ItemIsSelectable);
scene.addItem(rect);
view.show();
return a.exec();
}
*/
-
選択状態の変化にのみ反応すればよく、ラバーバンドの描画中の詳細な情報は不要な場合
QGraphicsScene::selectionChanged()
シグナルが最もシンプルで、高レベルなアプローチです。
-
QGraphicsView のサブクラス化を避けたい、または複数のカスタム動作を組み合わせたい場合
- イベントフィルターの使用を検討します。ただし、イベント伝播の管理に注意が必要です。
-
ラバーバンドの見た目や動作を完全にカスタマイズしたい、または標準機能では不十分な場合
QGraphicsView
のマウスイベント (mousePressEvent
,mouseMoveEvent
,mouseReleaseEvent
) を直接オーバーライドするのが最も柔軟です。QRubberBand
を使って描画します。
-
デフォルトのラバーバンド選択のリアルタイムな情報が必要なだけの場合
QGraphicsView::rubberBandChanged()
シグナルが最も簡単で適切です。