QGraphicsScene stickyFocus の落とし穴!回避策とベストプラクティス
2025-04-26
基本的な概念
- スティッキーフォーカス (Sticky Focus)
スティッキーフォーカスが有効な場合、あるアイテムがフォーカスを得ると、そのアイテムが明示的にフォーカスを失うまで、または別のアイテムが強制的にフォーカスを得るまで、フォーカスを保持し続けます。つまり、マウスで別のアイテムをクリックしても、フォーカスを持っていたアイテムがフォーカスを失いません。 - フォーカス (Focus)
GUI アプリケーションにおいて、キーボード入力などを受け付ける状態を指します。グラフィックスシーンでは、特定のQGraphicsItem
がフォーカスを持つことができます。
詳細な説明
QGraphicsScene::stickyFocus
プロパティは、ブール値 (true または false) を持ちます。
- stickyFocus が false の場合 (デフォルト)
- あるアイテムがフォーカスを得ると、ユーザーが別のアイテムをクリックしたり、シーン外をクリックしたりすると、そのアイテムはフォーカスを失います。
- 標準的なGUIアプリケーションのフォーカス動作に近いです。
- stickyFocus が true の場合
- あるアイテムがフォーカスを得ると、そのアイテムは、ユーザーが別のアイテムをクリックしたり、シーン外をクリックしたりしても、フォーカスを保持します。
- フォーカスを失うためには、
QGraphicsItem::clearFocus()
を明示的に呼び出すか、別のアイテムがQGraphicsItem::setFocus()
を呼び出して強制的にフォーカスを得る必要があります。 - これにより、ユーザーが特定のアイテムに集中して作業を続ける必要がある場合に便利です。
使用例
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 400, 300);
QGraphicsRectItem *rect1 = scene.addRect(50, 50, 100, 50);
rect1->setFlag(QGraphicsItem::ItemIsFocusable);
rect1->setFocus();
QGraphicsRectItem *rect2 = scene.addRect(200, 100, 100, 50);
rect2->setFlag(QGraphicsItem::ItemIsFocusable);
QGraphicsView view(&scene);
view.show();
// スティッキーフォーカスを有効にする
scene.setStickyFocus(true);
return app.exec();
}
上記の例では、scene.setStickyFocus(true);
を呼び出すことでスティッキーフォーカスを有効にしています。この状態で、rect1
が初期状態でフォーカスを持っています。rect2
をクリックしても、rect1
はフォーカスを保持します。
一般的なエラーとトラブルシューティング
- 意図しないフォーカス保持
- 原因
stickyFocus
がtrue
に設定されているにもかかわらず、アイテムが意図せずフォーカスを保持し続ける。 - トラブルシューティング
QGraphicsItem::clearFocus()
が必要な場所で適切に呼び出されているか確認します。- 他のアイテムが誤って
QGraphicsItem::setFocus()
を呼び出していないか確認します。 - イベントハンドラー (
mousePressEvent
など) でフォーカスが予期せず設定されていないか確認します。 QGraphicsScene::focusItem()
を用いて、現在どのアイテムがフォーカスを持っているか確認します。
- 原因
- フォーカスが移動しない
- 原因
stickyFocus
がtrue
に設定されており、意図的にフォーカスを移動させようとしても移動しない。 - トラブルシューティング
- フォーカスを移動させたいアイテムで
QGraphicsItem::setFocus()
が呼び出されているか確認します。 - 他のアイテムがフォーカスをクリアする前に、再度フォーカスを設定していないか確認します。
- フォーカスを受け取るアイテムの
QGraphicsItem::ItemIsFocusable
フラグが設定されていることを確認します。
- フォーカスを移動させたいアイテムで
- 原因
- フォーカス関連のイベントが期待通りに発生しない
- 原因
フォーカスが変更された際に発生するfocusInEvent
やfocusOutEvent
が期待通りに発生しない。 - トラブルシューティング
QGraphicsItem::setFlag(QGraphicsItem::ItemIsFocusable)
が設定されているか確認します。- イベントハンドラーが正しく実装されているか確認します。
- シーンまたはビューのイベントフィルターがフォーカスイベントを妨害していないか確認します。
- 原因
- 複数のアイテムが同時にフォーカスを持つ
- 原因
通常、QGraphicsScene
では同時に複数のアイテムがフォーカスを持つことはありませんが、誤った実装により発生する可能性があります。 - トラブルシューティング
- フォーカスの設定とクリアを管理するコードを確認し、競合がないか確認します。
- カスタムのフォーカス管理ロジックが意図しない動作を引き起こしていないか確認します。
- 原因
- ビューのフォーカスとの競合
- 原因
QGraphicsView
自体もフォーカスを持つことがあり、シーンのフォーカスと競合する可能性があります。 - トラブルシューティング
- ビューのフォーカス動作 (
setFocusPolicy
など) を確認し、シーンのフォーカスと適切に連携するように調整します。 - ビューのフォーカスが不要な場合は、
Qt::NoFocus
などを設定します。
- ビューのフォーカス動作 (
- 原因
- デバッグのヒント
- QGraphicsScene::focusItem()
現在フォーカスを持っているアイテムを特定するために使用します。 - qDebug()
フォーカス関連のイベントや変数の値をログ出力して、動作を追跡します。 - イベントフィルター
シーンまたはビューにイベントフィルターを設定して、フォーカス関連のイベントを監視し、デバッグします。
- QGraphicsScene::focusItem()
// 例:意図しないフォーカス保持のデバッグ
void MyItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsItem::mousePressEvent(event);
setFocus(); // ここで予期せずフォーカスを設定している可能性がある
// デバッグ用ログ
qDebug() << "Item clicked, focus set.";
// ...
}
// 例:フォーカスが移動しない場合のデバッグ
void MyScene::mousePressEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsScene::mousePressEvent(event);
QGraphicsItem *item = itemAt(event->scenePos(), QTransform());
if (item && item->isFocusable()) {
item->setFocus();
qDebug() << "Focus set to item:" << item;
}
qDebug() << "current focus item:" << focusItem();
}
例1: スティッキーフォーカスを有効にする基本的な例
この例では、QGraphicsScene
上に2つの矩形アイテムを作成し、stickyFocus
を有効にして、最初の矩形がフォーカスを保持する様子を示します。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 400, 300);
QGraphicsRectItem *rect1 = scene.addRect(50, 50, 100, 50);
rect1->setFlag(QGraphicsItem::ItemIsFocusable);
rect1->setFocus(); // 最初の矩形に初期フォーカスを設定
QGraphicsRectItem *rect2 = scene.addRect(200, 100, 100, 50);
rect2->setFlag(QGraphicsItem::ItemIsFocusable);
QGraphicsView view(&scene);
view.show();
// スティッキーフォーカスを有効にする
scene.setStickyFocus(true);
// デバッグ用:現在のフォーカスアイテムを表示
qDebug() << "Initial focus item:" << scene.focusItem();
return app.exec();
}
説明
- 2つの
QGraphicsRectItem
を作成し、ItemIsFocusable
フラグを設定します。 rect1
にsetFocus()
を呼び出して初期フォーカスを設定します。scene.setStickyFocus(true)
を呼び出して、スティッキーフォーカスを有効にします。qDebug()
を使用して、初期フォーカスアイテムを表示します。- この状態でアプリケーションを実行すると、
rect1
がフォーカスを持ち続け、rect2
をクリックしてもフォーカスは移動しません。
例2: スティッキーフォーカスを無効にし、フォーカスを明示的に切り替える例
この例では、スティッキーフォーカスを無効にし、マウスをクリックしたアイテムにフォーカスを切り替えるようにします。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>
class MyScene : public QGraphicsScene {
public:
MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
QGraphicsScene::mousePressEvent(event);
QGraphicsItem *item = itemAt(event->scenePos(), QTransform());
if (item && item->isFocusable()) {
item->setFocus();
qDebug() << "Focus set to item:" << item;
}
qDebug() << "current focus item:" << focusItem();
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MyScene scene; // カスタムシーンを使用
scene.setSceneRect(0, 0, 400, 300);
QGraphicsRectItem *rect1 = scene.addRect(50, 50, 100, 50);
rect1->setFlag(QGraphicsItem::ItemIsFocusable);
rect1->setFocus();
QGraphicsRectItem *rect2 = scene.addRect(200, 100, 100, 50);
rect2->setFlag(QGraphicsItem::ItemIsFocusable);
QGraphicsView view(&scene);
view.show();
// スティッキーフォーカスを無効にする
scene.setStickyFocus(false);
return app.exec();
}
説明
QGraphicsScene
を継承したMyScene
クラスを作成し、mousePressEvent
をオーバーライドします。mousePressEvent
内で、クリックされたアイテムがフォーカス可能であれば、そのアイテムにsetFocus()
を呼び出します。scene.setStickyFocus(false)
を呼び出して、スティッキーフォーカスを無効にします。- この状態でアプリケーションを実行すると、クリックした矩形にフォーカスが移動します。
例3: clearFocus()
を使用してフォーカスを明示的にクリアする例
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QPushButton>
#include <QGraphicsProxyWidget>
#include <QVBoxLayout>
#include <QWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 400, 300);
QGraphicsRectItem *rect = scene.addRect(50, 50, 100, 50);
rect->setFlag(QGraphicsItem::ItemIsFocusable);
rect->setFocus();
QWidget *widget = new QWidget();
QVBoxLayout *layout = new QVBoxLayout(widget);
QPushButton *clearButton = new QPushButton("Clear Focus");
layout->addWidget(clearButton);
QGraphicsProxyWidget *proxy = scene.addWidget(widget);
proxy->setPos(200, 100);
QGraphicsView view(&scene);
view.show();
scene.setStickyFocus(true);
QObject::connect(clearButton, &QPushButton::clicked, [rect]() {
rect->clearFocus();
});
return app.exec();
}
QGraphicsRectItem
とQPushButton
を作成し、シーンに追加します。stickyFocus
をtrue
に設定します。QPushButton
のclicked
シグナルをラムダ式に接続し、rect->clearFocus()
を呼び出して矩形のフォーカスをクリアします。- ボタンをクリックすると、矩形のフォーカスがクリアされます。
カスタムフォーカス管理
stickyFocus
を使用せず、独自のフォーカス管理ロジックを実装する方法です。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>
class MyScene : public QGraphicsScene {
public:
MyScene(QObject *parent = nullptr) : QGraphicsScene(parent), currentFocusItem(nullptr) {}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
QGraphicsScene::mousePressEvent(event);
QGraphicsItem *item = itemAt(event->scenePos(), QTransform());
if (item && item->isFocusable()) {
setCustomFocus(item);
}
}
void setCustomFocus(QGraphicsItem *item) {
if (currentFocusItem != item) {
if (currentFocusItem) {
// 前のアイテムのフォーカスをクリア(必要に応じて)
// currentFocusItem->clearFocus();
qDebug() << "Focus lost from item:" << currentFocusItem;
}
currentFocusItem = item;
item->setFocus();
qDebug() << "Focus set to item:" << item;
}
}
private:
QGraphicsItem *currentFocusItem;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MyScene scene;
scene.setSceneRect(0, 0, 400, 300);
QGraphicsRectItem *rect1 = scene.addRect(50, 50, 100, 50);
rect1->setFlag(QGraphicsItem::ItemIsFocusable);
scene.setCustomFocus(rect1); // 初期フォーカスを設定
QGraphicsRectItem *rect2 = scene.addRect(200, 100, 100, 50);
rect2->setFlag(QGraphicsItem::ItemIsFocusable);
QGraphicsView view(&scene);
view.show();
return app.exec();
}
説明
MyScene
クラスでcurrentFocusItem
を保持し、現在のフォーカスアイテムを追跡します。setCustomFocus()
メソッドで、フォーカスを切り替えるロジックを実装します。mousePressEvent()
で、クリックされたアイテムにsetCustomFocus()
を呼び出します。- これにより、
stickyFocus
を使用せずに、独自のフォーカス制御を実現できます。
イベントフィルターの使用
イベントフィルターを使用して、フォーカス関連のイベントを監視し、特定の条件でフォーカスを制御する方法です。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QEvent>
#include <QDebug>
class FocusFilter : public QObject {
public:
FocusFilter(QGraphicsScene *scene) : scene(scene) {}
protected:
bool eventFilter(QObject *obj, QEvent *event) override {
if (event->type() == QEvent::GraphicsSceneMousePress) {
QGraphicsSceneMouseEvent *mouseEvent = static_cast<QGraphicsSceneMouseEvent *>(event);
QGraphicsItem *item = scene->itemAt(mouseEvent->scenePos(), QTransform());
if (item && item->isFocusable()) {
item->setFocus();
qDebug() << "Focus set to item:" << item;
return true; // イベントを処理済みとして扱う
}
}
return QObject::eventFilter(obj, event);
}
private:
QGraphicsScene *scene;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 400, 300);
QGraphicsRectItem *rect1 = scene.addRect(50, 50, 100, 50);
rect1->setFlag(QGraphicsItem::ItemIsFocusable);
rect1->setFocus();
QGraphicsRectItem *rect2 = scene.addRect(200, 100, 100, 50);
rect2->setFlag(QGraphicsItem::ItemIsFocusable);
QGraphicsView view(&scene);
view.show();
FocusFilter *filter = new FocusFilter(&scene);
scene.installEventFilter(filter);
return app.exec();
}
説明
FocusFilter
クラスを作成し、eventFilter()
をオーバーライドします。eventFilter()
で、QEvent::GraphicsSceneMousePress
イベントを監視し、クリックされたアイテムにフォーカスを設定します。scene.installEventFilter(filter)
で、シーンにイベントフィルターをインストールします。- これにより、マウスイベントを監視してフォーカスを制御できます。
ステートマシンを使用
複雑なフォーカス制御が必要な場合、ステートマシンを使用してフォーカスの状態を管理する方法があります。
// (ステートマシンライブラリを使用するなど、複雑なためコードは省略)
- フォーカスの状態を定義します(例:
NO_FOCUS
,ITEM1_FOCUSED
,ITEM2_FOCUSED
)。 - 状態遷移を定義します(例:アイテムクリックで状態遷移)。
- 状態に応じてフォーカスを設定・クリアします。
- 複雑なフォーカス制御に適していますが、実装が複雑になる可能性があります。