Qt QGraphicsViewのfocusInEvent:エラー解決とデバッグのコツ
focusInEvent
とは?
Qtのイベントシステムにおいて、ウィジェットがユーザーからのキーボード入力を受け取る準備ができた状態を「フォーカスがある」と言います。focusInEvent
は、このフォーカスが QGraphicsView
に移ってきたときに、Qtフレームワークによって自動的に呼び出される仮想関数です。
役割
この関数をオーバーライド(再実装)することで、QGraphicsView
がフォーカスを得た際に、特定の処理を実行できます。例えば、以下のようなシナリオで利用されます。
- キーボード操作の有効化
フォーカスが移った際に、特定のキーボードショートカットやアクションを有効にする。 - 初期状態の設定
フォーカスされたときに、特定のグラフィックスアイテムに初期フォーカスを設定する。 - 視覚的なフィードバックの提供
フォーカスが移ったことをユーザーに示すために、ビューの枠線の色を変えたり、特定のアイテムをハイライト表示したりする。
引数
QFocusEvent *event
: このイベントはフォーカスイベントに関する情報を含んでいます。特に重要なのはevent->reason()
で、フォーカスが移った理由(マウスによるクリック、Tabキーによる移動、プログラムによる設定など)を調べることができます。
使用例(C++)
#include <QGraphicsView>
#include <QFocusEvent>
#include <QDebug> // デバッグ出力用
class MyGraphicsView : public QGraphicsView
{
public:
MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
: QGraphicsView(scene, parent)
{
// ビューがフォーカスを受け取れるように設定
setFocusPolicy(Qt::StrongFocus);
}
protected:
// focusInEvent をオーバーライド
void focusInEvent(QFocusEvent *event) override
{
qDebug() << "QGraphicsView がフォーカスを受け取りました。理由: " << event->reason();
// ここにフォーカス取得時のカスタム処理を記述します
// 例: ビューの背景色を変更する
// setBackgroundBrush(Qt::lightGray);
// 親クラスの focusInEvent を呼び出すことを忘れないでください
// これにより、デフォルトのフォーカス処理が実行されます
QGraphicsView::focusInEvent(event);
}
// focusOutEvent も併せてオーバーライドすると、フォーカスが離れた時の処理も記述できます
void focusOutEvent(QFocusEvent *event) override
{
qDebug() << "QGraphicsView からフォーカスが離れました。理由: " << event->reason();
// 例: 背景色を元に戻す
// setBackgroundBrush(Qt::white);
QGraphicsView::focusOutEvent(event);
}
};
// メイン関数での使用例
// int main(int argc, char *argv[])
// {
// QApplication app(argc, argv);
//
// QGraphicsScene scene;
// scene.addText("Hello, QGraphicsView!");
//
// MyGraphicsView view(&scene);
// view.setWindowTitle("My Custom Graphics View");
// view.show();
//
// return app.exec();
// }
- オーバーライドしたイベントハンドラ内で、必ず親クラスの対応するイベントハンドラ(例:
QGraphicsView::focusInEvent(event)
)を呼び出すようにしてください。そうしないと、Qtのデフォルトのフォーカス処理が適切に機能しなくなる可能性があります。 QGraphicsView
(または他のウィジェット)がフォーカスイベントを受け取るためには、setFocusPolicy()
メソッドで適切なフォーカスポリシーを設定する必要があります(例:Qt::StrongFocus
やQt::ClickFocus
)。デフォルトでは、多くのウィジェットはキーボードフォーカスを受け取りません。
focusInEvent()
は、QGraphicsView
ウィジェットがフォーカスを受け取ったときに呼び出される重要なイベントハンドラですが、いくつかの一般的な落とし穴があります。
focusInEvent() が呼び出されない
問題
focusInEvent()
をオーバーライドしたにもかかわらず、ビューがフォーカスを受け取ったときにカスタムコードが実行されない。
原因とトラブルシューティング
- イベントフィルタリング
- 原因
アプリケーションまたは親ウィジェットにイベントフィルターが設定されており、focusInEvent
がQGraphicsView
に到達する前に処理または破棄されている可能性があります。 - 解決策
イベントフィルターのコードを確認し、focusInEvent
が正しく伝播されているかを確認します。
- 原因
- 他のウィジェットがフォーカスを奪っている
- 原因
同じウィンドウ内に他のウィジェット(例:QLineEdit
、QPushButton
など)があり、それらが先にフォーカスを受け取ってしまっている可能性があります。 - 解決策
QGraphicsView
に手動でフォーカスを設定するためにview->setFocus()
を呼び出したり、Tabオーダー(Qt Designerで設定可能)を調整したりして、QGraphicsView
に確実にフォーカスが来るようにします。デバッグ出力 (qDebug()
) を使って、どのウィジェットがフォーカスを受け取っているかを確認することも有効です。
- 原因
- オーバーライドの誤り
- 原因
関数シグネチャが正確に一致していない、またはoverride
キーワードを忘れているために、関数が正しくオーバーライドされていない可能性があります。 - 解決策
void focusInEvent(QFocusEvent *event) override
のように、シグネチャが完全に一致し、override
キーワードが使用されていることを確認してください。
- 原因
親クラスの focusInEvent() を呼び出していない
問題
focusInEvent()
をオーバーライドした後、ビューの通常のフォーカス動作(例えば、スクロールバーの表示や内部的な状態更新)が適切に機能しない。
原因とトラブルシューティング
- 解決策
カスタム処理の後に、必ず親クラスの関数を呼び出すようにしてください。void MyGraphicsView::focusInEvent(QFocusEvent *event) { // カスタム処理 qDebug() << "My custom focus in handling."; // 非常に重要: 親クラスのメソッドを呼び出す QGraphicsView::focusInEvent(event); }
- 原因
カスタムのfocusInEvent()
の中で、親クラスのQGraphicsView::focusInEvent(event)
を呼び出していないため、Qtのデフォルトのフォーカス処理が実行されていません。
フォーカスイベントの無限ループまたは不適切な挙動
問題
focusInEvent()
内で setFocus()
を呼び出すなど、フォーカスを再設定するような処理を行っている場合、無限ループに陥ったり、予期しない挙動を引き起こすことがあります。
原因とトラブルシューティング
- 解決策
focusInEvent()
内でフォーカスを再設定する必要がある場合は、QTimer::singleShot()
を使用して遅延させるか、フラグを使って再入を防ぐなど、慎重に設計する必要があります。ほとんどの場合、focusInEvent()
内でsetFocus()
を呼び出す必要はありません。 - 原因
focusInEvent()
の中で直接的または間接的に自分自身にフォーカスを戻そうとする処理が、イベントの再トリガーを引き起こします。
QFocusEvent の情報の不適切な利用
問題
QFocusEvent
オブジェクトから取得できる情報を適切に利用していない。
原因とトラブルシューティング
- 解決策
event->reason()
を使用して、フォーカスの原因(例:Qt::MouseFocusReason
,Qt::TabFocusReason
,Qt::ActiveWindowFocusReason
など)に応じて異なる処理を行うことを検討してください。これにより、よりスマートで直感的なUIを提供できます。 - 原因
event->reason()
を確認せずに、フォーカスが移った理由に関わらず一律の処理を行っている場合、ユーザーエクスペリエンスが低下する可能性があります。
デバッグのヒント
focusInEvent()
のトラブルシューティングには、qDebug()
を使用してイベントの発生状況やフォーカスを持っているウィジェットを確認するのが非常に有効です。
// MyGraphicsView.h
class MyGraphicsView : public QGraphicsView
{
// ...
protected:
void focusInEvent(QFocusEvent *event) override;
void focusOutEvent(QFocusEvent *event) override; // フォーカスが離れるイベントも確認
};
// MyGraphicsView.cpp
#include <QDebug>
#include <QApplication> // QApplication::focusWidget() のために必要
void MyGraphicsView::focusInEvent(QFocusEvent *event)
{
qDebug() << "MyGraphicsView: Focus In! Reason:" << event->reason();
qDebug() << "Currently focused widget:" << QApplication::focusWidget();
QGraphicsView::focusInEvent(event);
}
void MyGraphicsView::focusOutEvent(QFocusEvent *event)
{
qDebug() << "MyGraphicsView: Focus Out! Reason:" << event->reason();
qDebug() << "Currently focused widget:" << QApplication::focusWidget();
QGraphicsView::focusOutEvent(event);
}
QGraphicsView::focusInEvent()
は、QGraphicsView
ウィジェットがキーボードフォーカスを受け取ったときに呼び出される仮想関数です。これをオーバーライドすることで、フォーカス取得時のカスタムな動作を実装できます。
ここでは、いくつかの具体的な例を通してその使い方を解説します。
例1: フォーカス時にビューの枠線を変更する
この例では、QGraphicsView
がフォーカスを得たときに、その枠線(ボーダー)の色とスタイルを変更し、フォーカスが離れたら元に戻します。
mygraphicsview.h
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H
#include <QGraphicsView>
#include <QFocusEvent> // QFocusEvent を使用するためにインクルード
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT // シグナル&スロットを使用する場合に必要
public:
explicit MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
protected:
// focusInEvent をオーバーライドします
void focusInEvent(QFocusEvent *event) override;
// focusOutEvent もオーバーライドして、フォーカスが離れた時の処理を記述します
void focusOutEvent(QFocusEvent *event) override;
private:
// 元の枠線情報を保存しておくための変数 (オプション)
QPalette originalPalette;
};
#endif // MYGRAPHICSVIEW_H
mygraphicsview.cpp
#include "mygraphicsview.h"
#include <QGraphicsScene> // QGraphicsScene が必要なのでインクルード
#include <QDebug> // デバッグ出力用
#include <QPainter> // ボーダー描画のヒントとして (直接は使わないが概念として)
MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
: QGraphicsView(scene, parent)
{
// ***重要: フォーカスを受け取れるように設定する***
// Qt::StrongFocus はマウスクリック、Tabキー、プログラムによる設定でフォーカスを受け取ります。
setFocusPolicy(Qt::StrongFocus);
// オリジナルのパレットを保存しておきます
originalPalette = palette();
}
void MyGraphicsView::focusInEvent(QFocusEvent *event)
{
qDebug() << "MyGraphicsView: フォーカスを受け取りました (原因: " << event->reason() << ")";
// フォーカス取得時のカスタム処理
// ここでは、ビューのパレット(スタイル)を変更して、枠線を目立たせます。
QPalette p = palette();
p.setColor(QPalette::WindowFrame, Qt::blue); // 枠線の色を青に
p.setColor(QPalette::Highlight, Qt::darkBlue); // 選択時の色 (関連する場合)
setPalette(p);
// 枠線のスタイルを設定 (QFrame::StyledPanel, QFrame::Sunken は QGraphicsView が QFrame を継承しているため利用可能)
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Raised);
setLineWidth(2); // 枠線の太さ
// ***重要: 親クラスの focusInEvent を呼び出す***
// これにより、QGraphicsView のデフォルトのフォーカス処理(内部状態の更新など)が実行されます。
QGraphicsView::focusInEvent(event);
}
void MyGraphicsView::focusOutEvent(QFocusEvent *event)
{
qDebug() << "MyGraphicsView: フォーカスを失いました (原因: " << event->reason() << ")";
// フォーカスを失った時のカスタム処理
// ここでは、元のパレットに戻して、枠線を元の状態に戻します。
setPalette(originalPalette);
setFrameShape(QFrame::NoFrame); // または QFrame::Panel などデフォルトに戻す
setFrameShadow(QFrame::Plain);
setLineWidth(1); // 元の太さに戻す
// ***重要: 親クラスの focusOutEvent を呼び出す***
QGraphicsView::focusOutEvent(event);
}
main.cpp
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsTextItem> // シーンにアイテムを追加するため
#include "mygraphicsview.h" // カスタムビューのヘッダ
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// シーンを作成
QGraphicsScene scene;
scene.setSceneRect(0, 0, 400, 300); // シーンのサイズを設定
QGraphicsTextItem *textItem = new QGraphicsTextItem("このビューをクリックするか、Tabキーでフォーカスを当ててみてください。");
textItem->setPos(50, 100);
scene.addItem(textItem);
// カスタムビューを作成し、シーンを設定
MyGraphicsView view(&scene);
view.setWindowTitle("FocusInEvent Example");
view.resize(500, 400); // ウィンドウサイズ
// テスト用の別のウィジェット(フォーカス移動のため)
QLineEdit *lineEdit = new QLineEdit("別のウィジェット");
lineEdit->setFixedSize(200, 30); // サイズ固定
// ウィンドウを作成し、レイアウトを設定
QWidget window;
QVBoxLayout *layout = new QVBoxLayout(&window);
layout->addWidget(&view);
layout->addWidget(lineEdit); // lineEdit を追加
window.setLayout(layout);
window.show();
return a.exec();
}
解説
- MyGraphicsView クラスの定義
QGraphicsView
を継承し、focusInEvent
とfocusOutEvent
をprotected
メンバとしてオーバーライドします。 - setFocusPolicy(Qt::StrongFocus);
これが最も重要です。この設定がないと、QGraphicsView
はデフォルトでフォーカスを受け取らないため、focusInEvent
が呼び出されません。 - focusInEvent の実装
qDebug()
でデバッグメッセージを出力し、いつイベントが発生したか、その原因(event->reason()
)が何であったかを確認できます。setPalette()
を使ってビューのパレットを変更し、枠線の色を青に設定します。setFrameShape()
,setFrameShadow()
,setLineWidth()
を使って、枠線の見た目を変更します。QGraphicsView::focusInEvent(event);
:必ず親クラスの関数を呼び出すようにしてください。 これにより、QGraphicsView
内部のフォーカス処理(例: スクロールバーの更新など)が正しく行われます。これを忘れると、予期しない問題が発生する可能性があります。
- focusOutEvent の実装
フォーカスが離れたときに、元の枠線の見た目に戻す処理を記述します。これも同様に親クラスのメソッドを呼び出します。 - main.cpp
QGraphicsScene
を作成し、ビューに設定します。MyGraphicsView
のインスタンスを作成します。QLineEdit
など、別のフォーカス可能なウィジェットを配置することで、Tabキーでのフォーカス移動を試すことができます。
例2: フォーカス時にシーン内の特定アイテムをハイライト表示する
この例では、QGraphicsView
がフォーカスを得たときに、シーン内の特定のアイテム(ここではテキストアイテム)をハイライト表示し、フォーカスが離れたら元に戻します。
mygraphicsview.h
(変更なし、例1と同じ)
mygraphicsview.cpp
#include "mygraphicsview.h"
#include <QGraphicsScene>
#include <QGraphicsTextItem> // テキストアイテムを操作するため
#include <QDebug>
MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
: QGraphicsView(scene, parent)
{
setFocusPolicy(Qt::StrongFocus);
originalPalette = palette();
}
void MyGraphicsView::focusInEvent(QFocusEvent *event)
{
qDebug() << "MyGraphicsView: フォーカスを受け取りました (原因: " << event->reason() << ")";
// シーン内の特定のアイテムをハイライト
// ここでは、QGraphicsScene::items() を使って全てのアイテムを探索し、
// QGraphicsTextItem である最初のアイテムを対象とします。
for (QGraphicsItem *item : scene()->items()) {
if (QGraphicsTextItem *textItem = qgraphicsitem_cast<QGraphicsTextItem *>(item)) {
// テキストアイテムの背景色を変更してハイライト
textItem->setDefaultTextColor(Qt::red); // テキストの色を赤に
break; // 最初のテキストアイテムのみをハイライト
}
}
// ビュー自身の視覚的フィードバック(例1と同じ)
QPalette p = palette();
p.setColor(QPalette::WindowFrame, Qt::blue);
setPalette(p);
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Raised);
setLineWidth(2);
QGraphicsView::focusInEvent(event);
}
void MyGraphicsView::focusOutEvent(QFocusEvent *event)
{
qDebug() << "MyGraphicsView: フォーカスを失いました (原因: " << event->reason() << ")";
// シーン内のアイテムのハイライトを解除
for (QGraphicsItem *item : scene()->items()) {
if (QGraphicsTextItem *textItem = qgraphicsitem_cast<QGraphicsTextItem *>(item)) {
textItem->setDefaultTextColor(Qt::black); // テキストの色を黒に戻す
break;
}
}
// ビュー自身の視覚的フィードバックを元に戻す(例1と同じ)
setPalette(originalPalette);
setFrameShape(QFrame::NoFrame);
setFrameShadow(QFrame::Plain);
setLineWidth(1);
QGraphicsView::focusOutEvent(event);
}
main.cpp
(例1とほぼ同じだが、シーンに複数のアイテムを追加してみる)
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsTextItem>
#include <QGraphicsRectItem> // 四角形アイテムも追加
#include "mygraphicsview.h"
#include <QLineEdit>
#include <QVBoxLayout>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 400, 300);
QGraphicsTextItem *textItem1 = new QGraphicsTextItem("フォーカスされると赤くなるテキスト");
textItem1->setPos(50, 50);
scene.addItem(textItem1);
QGraphicsTextItem *textItem2 = new QGraphicsTextItem("別のテキストアイテム");
textItem2->setPos(50, 100);
scene.addItem(textItem2);
QGraphicsRectItem *rectItem = new QGraphicsRectItem(150, 150, 100, 50);
rectItem->setBrush(Qt::green);
scene.addItem(rectItem);
MyGraphicsView view(&scene);
view.setWindowTitle("FocusInEvent Item Highlight Example");
view.resize(500, 400);
QLineEdit *lineEdit = new QLineEdit("Tabキーでフォーカスを切り替えてみてください");
QWidget window;
QVBoxLayout *layout = new QVBoxLayout(&window);
layout->addWidget(&view);
layout->addWidget(lineEdit);
window.setLayout(layout);
window.show();
return a.exec();
}
解説
- アイテムのハイライト
focusInEvent
の中でscene()->items()
を使ってシーン内の全てのアイテムを取得し、qgraphicsitem_cast<QGraphicsTextItem *>(item)
でQGraphicsTextItem
にキャストできるかどうかを確認します。 - テキスト色の変更
キャストに成功したら、setDefaultTextColor(Qt::red)
を呼び出してテキストの色を赤に変更し、ハイライト効果を与えます。 - ハイライトの解除
focusOutEvent
では、テキストの色を元の黒に戻します。
これらの例から、QGraphicsView::focusInEvent()
の基本的な使い方と、それを使ってビューまたはシーン内のアイテムの見た目を動的に変更する方法を理解できたかと思います。
重要なポイントは以下の通りです。
- デバッグ
qDebug()
を積極的に使用し、イベントがいつ、どのように発生しているかを確認する。 - QFocusEvent *event
イベントオブジェクトからevent->reason()
を取得することで、フォーカスが移った理由を調べ、よりきめ細やかな処理を実装できる。 - 親クラスの呼び出し
オーバーライドしたイベントハンドラ内で、必ず親クラスの対応するメソッドを呼び出す。 - setFocusPolicy()
QGraphicsView
がフォーカスを受け取れるように必ず設定する。
QGraphicsView::focusInEvent()
は、QGraphicsView
が直接フォーカスを受け取った際の処理を記述するのに便利ですが、Qt のイベントシステムには、同じ目的を達成するための他の強力なメカニズムがいくつか存在します。これらの代替手段は、より柔軟性を提供したり、特定のシナリオにおいてより適切な場合があります。
主な代替手段は以下の通りです。
- イベントフィルター (
QObject::installEventFilter()
) QApplication::focusChanged
シグナルQGraphicsItem::setFlag(QGraphicsItem::ItemIsFocusable)
とQGraphicsItem::focusInEvent()
(アイテムレベルのフォーカス)- ウィジェットのプロパティとスタイルシート (
QWidget::setProperty()
,QGraphicsView::setStyleSheet()
)
イベントフィルター (QObject::installEventFilter())
説明
イベントフィルターは、任意の QObject
が他の QObject
のイベントを処理するための非常に強力なメカニズムです。QGraphicsView
の親ウィジェットや、アプリケーション全体にイベントフィルターをインストールすることで、QGraphicsView
に送られる QEvent::FocusIn
イベントを捕捉して処理できます。
利点
- イベントを破棄したり、変更したりする柔軟性がある。
- 複数のウィジェットのイベントを単一のイベントフィルターで監視できる。
QGraphicsView
サブクラスを作成する必要がない。
欠点
- イベントの順序に注意が必要になる場合がある。
- イベントのタイプを明示的にチェックする必要がある。
使用例
// mainwindow.h (例として QMainWindow にイベントフィルターをインストール)
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsView> // QGraphicsView を扱うため
#include <QEvent> // イベントタイプを扱うため
class MyGraphicsView; // 前方宣言
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
// イベントフィルターをオーバーライドします
bool eventFilter(QObject *obj, QEvent *event) override;
private:
MyGraphicsView *graphicsView; // カスタムビューのポインタ
// ... 他のウィジェット
};
#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include "mygraphicsview.h" // MyGraphicsView の定義が必要 (setFocusPolicyなど)
#include <QDebug>
#include <QVBoxLayout>
#include <QLineEdit> // フォーカス移動のため
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// MyGraphicsView を作成 (ここでは QGraphicsView を直接使うことも可能)
QGraphicsScene *scene = new QGraphicsScene(this);
scene->setSceneRect(0, 0, 400, 300);
scene->addText("イベントフィルターでフォーカスを監視");
graphicsView = new MyGraphicsView(scene, this); // MyGraphicsView は setFocusPolicy(Qt::StrongFocus) を持つと仮定
graphicsView->setWindowTitle("Graphics View (Event Filter)");
graphicsView->resize(500, 400);
QLineEdit *lineEdit = new QLineEdit("別のウィジェット");
QWidget *centralWidget = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(centralWidget);
layout->addWidget(graphicsView);
layout->addWidget(lineEdit);
setCentralWidget(centralWidget);
// ***QGraphicsView にイベントフィルターをインストールする***
graphicsView->installEventFilter(this); // this (MainWindow) が graphicsView のイベントを監視する
}
MainWindow::~MainWindow()
{
}
// イベントフィルターの実装
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
// graphicsView のイベントかどうかを確認
if (obj == graphicsView) {
if (event->type() == QEvent::FocusIn) {
QFocusEvent *focusEvent = static_cast<QFocusEvent*>(event);
qDebug() << "イベントフィルター: QGraphicsView がフォーカスを受け取りました (原因: " << focusEvent->reason() << ")";
// ここで QGraphicsView がフォーカスを受け取った時の処理を記述
// 例: ビューのボーダーを変更
graphicsView->setStyleSheet("QGraphicsView { border: 2px solid green; }");
// イベントをさらに伝播させる場合は true を返す
// イベントを消費して、他のオブジェクトに伝播させない場合は false を返す
return false; // または true, シナリオによる
} else if (event->type() == QEvent::FocusOut) {
QFocusEvent *focusEvent = static_cast<QFocusEvent*>(event);
qDebug() << "イベントフィルター: QGraphicsView からフォーカスが離れました (原因: " << focusEvent->reason() << ")";
// フォーカスが離れた時の処理
graphicsView->setStyleSheet(""); // スタイルシートをクリア
return false;
}
}
// それ以外のイベントは親クラスに処理を任せる
return QMainWindow::eventFilter(obj, event);
}
QApplication::focusChanged シグナル
説明
QApplication
クラスは、アプリケーション全体でフォーカスを持つウィジェットが変更されたときに focusChanged(QWidget *old, QWidget *now)
シグナルを発します。このシグナルにスロットを接続することで、フォーカスが特定の QGraphicsView
に移ったことを検出できます。
利点
- スロットとシグナルの接続により、コードの分離性が高まる。
QGraphicsView
をサブクラス化する必要がない。- アプリケーション全体でフォーカス変更を監視できる。
欠点
- フォーカスが他のウィジェットから
QGraphicsView
へ、またはその逆、という場合にのみ反応する。 - フォーカスが移った「理由」(
QFocusEvent::reason()
) を直接取得できない(必要であれば、QFocusEvent
を受け取る他の方法と組み合わせる)。
使用例
// mainwindow.h (または任意のコントローラークラス)
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsView>
#include <QApplication> // QApplication が必要
class MyGraphicsView; // 前方宣言
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
// focusChanged シグナルを受け取るスロット
void handleFocusChanged(QWidget *oldWidget, QWidget *newWidget);
private:
MyGraphicsView *graphicsView;
// ...
};
#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include "mygraphicsview.h" // MyGraphicsView の定義
#include <QDebug>
#include <QVBoxLayout>
#include <QLineEdit>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QGraphicsScene *scene = new QGraphicsScene(this);
scene->addText("QApplication::focusChanged シグナルでフォーカスを監視");
graphicsView = new MyGraphicsView(scene, this); // MyGraphicsView は setFocusPolicy(Qt::StrongFocus) を持つと仮定
QLineEdit *lineEdit = new QLineEdit("別のウィジェット");
QWidget *centralWidget = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(centralWidget);
layout->addWidget(graphicsView);
layout->addWidget(lineEdit);
setCentralWidget(centralWidget);
// ***QApplication::focusChanged シグナルを接続する***
connect(QApplication::instance(), &QApplication::focusChanged,
this, &MainWindow::handleFocusChanged);
}
MainWindow::~MainWindow()
{
}
void MainWindow::handleFocusChanged(QWidget *oldWidget, QWidget *newWidget)
{
// フォーカスが MyGraphicsView に移った場合
if (newWidget == graphicsView) {
qDebug() << "QApplication::focusChanged: QGraphicsView にフォーカスが来ました!";
graphicsView->setStyleSheet("QGraphicsView { border: 2px solid purple; }");
}
// フォーカスが MyGraphicsView から離れた場合
else if (oldWidget == graphicsView) {
qDebug() << "QApplication::focusChanged: QGraphicsView からフォーカスが離れました!";
graphicsView->setStyleSheet(""); // スタイルシートをクリア
}
}
QGraphicsItem::setFlag(QGraphicsItem::ItemIsFocusable) と QGraphicsItem::focusInEvent() (アイテムレベルのフォーカス)
説明
もし、QGraphicsView
自体ではなく、シーン内の特定の QGraphicsItem
がフォーカスを受け取ったときに処理を行いたい場合は、アイテム自身の focusInEvent()
をオーバーライドできます。アイテムがフォーカスを受け取るには、QGraphicsItem::ItemIsFocusable
フラグを設定する必要があります。
利点
QGraphicsView
のサブクラス化は不要。- アイテム固有のフォーカス処理を直接記述できる。
欠点
- アイテムにフォーカスを当てるためのロジック(例: マウスクリック時にアイテムにフォーカスを設定する)が必要になる場合がある。
QGraphicsView
全体のフォーカス状態ではなく、特定のアイテムのフォーカス状態にのみ反応する。
使用例
// myfocusableitem.h
#ifndef MYFOCUSABLEITEM_H
#define MYFOCUSABLEITEM_H
#include <QGraphicsRectItem> // 例として QGraphicsRectItem を継承
#include <QFocusEvent>
class MyFocusableItem : public QGraphicsRectItem
{
public:
explicit MyFocusableItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent = nullptr);
protected:
void focusInEvent(QFocusEvent *event) override;
void focusOutEvent(QFocusEvent *event) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override; // クリックでフォーカスを設定
};
#endif // MYFOCUSABLEITEM_H
// myfocusableitem.cpp
#include "myfocusableitem.h"
#include <QDebug>
#include <QBrush>
#include <QGraphicsSceneMouseEvent> // マウスイベント用
MyFocusableItem::MyFocusableItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent)
: QGraphicsRectItem(x, y, width, height, parent)
{
// ***重要: アイテムをフォーカス可能にする***
setFlag(QGraphicsItem::ItemIsFocusable);
setBrush(Qt::lightGray); // デフォルトのブラシ
}
void MyFocusableItem::focusInEvent(QFocusEvent *event)
{
qDebug() << "MyFocusableItem: フォーカスを受け取りました (原因: " << event->reason() << ")";
setBrush(Qt::green); // フォーカス時に色を変更
// 親クラスのイベントハンドラを呼び出す
QGraphicsRectItem::focusInEvent(event);
}
void MyFocusableItem::focusOutEvent(QFocusEvent *event)
{
qDebug() << "MyFocusableItem: フォーカスを失いました (原因: " << event->reason() << ")";
setBrush(Qt::lightGray); // フォーカスが離れたら色を元に戻す
// 親クラスのイベントハンドラを呼び出す
QGraphicsRectItem::focusOutEvent(event);
}
void MyFocusableItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
// アイテムがクリックされたら、そのアイテムにフォーカスを設定
setFocus();
QGraphicsRectItem::mousePressEvent(event);
}
// main.cpp (シーンに MyFocusableItem を追加)
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView> // 今回はQGraphicsViewを直接使う
#include <QLineEdit>
#include <QVBoxLayout>
#include "myfocusableitem.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 600, 400);
MyFocusableItem *item1 = new MyFocusableItem(50, 50, 100, 100);
scene.addItem(item1);
MyFocusableItem *item2 = new MyFocusableItem(200, 50, 100, 100);
scene.addItem(item2);
QGraphicsView view(&scene);
view.setWindowTitle("QGraphicsItem Focus Example");
view.resize(700, 500);
// ビュー自体もフォーカス可能にしておく(Tabキーでビューにフォーカスが来るように)
view.setFocusPolicy(Qt::StrongFocus);
QLineEdit *lineEdit = new QLineEdit("Tabキーでアイテム間を移動できます");
QWidget window;
QVBoxLayout *layout = new QVBoxLayout(&window);
layout->addWidget(&view);
layout->addWidget(lineEdit);
window.setLayout(layout);
window.show();
return a.exec();
}
ウィジェットのプロパティとスタイルシート (QWidget::setProperty(), QGraphicsView::setStyleSheet())
説明
Qt のスタイルシートは、ウィジェットの見た目をCSSライクな構文で変更する強力な機能です。QGraphicsView
は QWidget
を継承しているため、スタイルシートを適用できます。focusInEvent
の中で setStyleSheet()
を呼び出す代わりに、QWidget::setProperty()
を使ってカスタムプロパティを設定し、そのプロパティに基づいてスタイルシートを適用する方法もあります。これにより、UIの見た目のロジックとフォーカスイベントの処理を分離できます。
利点
- 複雑な見た目の変更をCSSライクな構文で記述できる。
- 見た目の変更ロジックがイベント処理ロジックから分離される。
欠点
- 実行時に動的にスタイルシートを生成・適用する必要がある場合がある。
- スタイルの変更のみに限定される。
使用例
// mainwindow.cpp (MyGraphicsView クラスは例1の定義を使用)
#include "mainwindow.h"
#include "mygraphicsview.h"
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsTextItem>
#include <QVBoxLayout>
#include <QLineEdit>
// MyGraphicsView の focusInEvent と focusOutEvent を以下のように変更:
// (mygraphicsview.cpp 内)
/*
void MyGraphicsView::focusInEvent(QFocusEvent *event)
{
// カスタムプロパティを設定
setProperty("hasFocus", true);
// スタイルシートを再適用させるために update() を呼び出す(QSSはプロパティ変更に即座に反応しない場合があるため)
style()->polish(this); // QStyle::polish() を使用してスタイルを再適用
QGraphicsView::focusInEvent(event);
}
void MyGraphicsView::focusOutEvent(QFocusEvent *event)
{
setProperty("hasFocus", false);
style()->polish(this);
QGraphicsView::focusOutEvent(event);
}
*/
// main.cpp または UI の設定部分でスタイルシートを定義
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// QGraphicsView にスタイルシートを設定
// QGraphicsView が focusHas="true" プロパティを持つときにスタイルを適用
a.setStyleSheet(R"(
MyGraphicsView[hasFocus="true"] {
border: 3px solid #FF5733; /* フォーカスがあるときは太いオレンジの枠線 */
background-color: #F0F8FF; /* 背景色も変更 */
}
MyGraphicsView[hasFocus="false"] {
border: 1px solid gray; /* フォーカスがないときは細い灰色の枠線 */
background-color: white;
}
)");
QGraphicsScene scene;
scene.setSceneRect(0, 0, 400, 300);
scene.addText("スタイルシートでフォーカスを表現");
MyGraphicsView view(&scene);
view.setWindowTitle("QSS Focus Example");
view.resize(500, 400);
view.setFocusPolicy(Qt::StrongFocus);
view.setProperty("hasFocus", false); // 初期状態
QLineEdit *lineEdit = new QLineEdit("Tabキーでフォーカスを切り替えてみてください");
QWidget window;
QVBoxLayout *layout = new QVBoxLayout(&window);
layout->addWidget(&view);
layout->addWidget(lineEdit);
window.setLayout(layout);
window.show();
return a.exec();
}
MyGraphicsView
のfocusInEvent
とfocusOutEvent
で、カスタムプロパティ"hasFocus"
をtrue
またはfalse
に設定します。style()->polish(this);
を呼び出すことで、スタイルシートがプロパティの変更を検知し、見た目を更新するように促します。- アプリケーション全体のスタイルシートで、
MyGraphicsView[hasFocus="true"]
のようなセレクタを使って、このカスタムプロパティに基づいて異なるスタイルを適用します。