Qt開発者必見: QGraphicsView::focusOutEvent()の一般的な落とし穴と解決策
Qtプログラミングにおけるvoid QGraphicsView::focusOutEvent(QFocusEvent *event)
は、QGraphicsView
ウィジェットがキーボード入力フォーカスを失ったときに呼び出されるイベントハンドラです。
詳しく説明すると、以下のようになります。
QGraphicsView
とは
QGraphicsView
は、QtのGraphics View Frameworkの一部であり、QGraphicsScene
の内容を表示するためのウィジェットです。QGraphicsScene
には、線、図形、テキスト、カスタムアイテムなど、様々な2Dグラフィカルアイテムが配置されます。QGraphicsView
は、このシーンをスクロール可能なビューポートに表示します。
フォーカスイベントとは
Qtにおける「フォーカス」とは、キーボード入力が現在どのウィジェットに送られているかを指します。ユーザーがTabキーを押したり、マウスで別のウィジェットをクリックしたりすると、フォーカスが移動します。このフォーカスの変化を処理するために、Qtは「フォーカスイベント」を発生させます。
フォーカスイベントには主に以下の2種類があります。
QEvent::FocusOut
: ウィジェットがフォーカスを失ったとき。QEvent::FocusIn
: ウィジェットがフォーカスを得たとき。
これらのイベントはQFocusEvent
クラスによって表現され、イベントの種類だけでなく、なぜフォーカスが変更されたのか(例えば、マウス操作、Tabキー、ウィンドウシステムの変更など)を示すreason()
も提供されます。
focusOutEvent()
の役割
QGraphicsView::focusOutEvent(QFocusEvent *event)
は、QGraphicsView
オブジェクトがキーボード入力フォーカスを失ったときに、Qtによって自動的に呼び出される仮想関数です。
この関数をサブクラスで**再実装(オーバーライド)**することで、QGraphicsView
がフォーカスを失ったときに、特定のカスタム動作を実行できます。例えば:
- 他のコンポーネントへの通知: ビューがフォーカスを失ったことを、アプリケーション内の他の部分に通知する。
- データの確定: ビュー内で編集中のデータがある場合、フォーカスが外れる前にその変更を確定させる。
- 状態の保存: フォーカスが外れる前に、ビューの現在の状態(例えば、選択中のアイテムやズームレベルなど)を保存する。
- 視覚的なフィードバックの変更: フォーカスを失ったことを示すために、ボーダーの色を変えたり、特定のアイテムのハイライトを解除したりする。
#include <QGraphicsView>
#include <QFocusEvent>
#include <QDebug> // デバッグ出力用
class MyGraphicsView : public QGraphicsView
{
public:
MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
: QGraphicsView(scene, parent)
{
// ビューがフォーカスを受け取れるように設定する(通常はデフォルトでtrue)
// setFocusPolicy(Qt::StrongFocus); // 必要に応じて
}
protected:
// focusOutEventをオーバーライドする
void focusOutEvent(QFocusEvent *event) override
{
qDebug() << "MyGraphicsView::focusOutEvent() が呼び出されました。";
qDebug() << "フォーカス喪失の理由: " << event->reason();
// ここにフォーカスが失われたときに実行したいカスタムロジックを記述します。
// 例: 選択中のアイテムの選択状態を解除する
if (scene()) {
scene()->clearSelection();
qDebug() << "シーンの選択を解除しました。";
}
// 基底クラスのfocusOutEventを呼び出すことを忘れないでください。
// これにより、デフォルトのフォーカス喪失処理が実行されます。
QGraphicsView::focusOutEvent(event);
}
};
// メイン関数での使用例 (簡略化)
/*
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.addText("Hello, World!");
MyGraphicsView view(&scene);
view.show();
return a.exec();
}
*/
setFocusPolicy()
:QGraphicsView
がフォーカスイベントを受け取るためには、適切なフォーカスポリシーが設定されている必要があります(デフォルトでQt::StrongFocus
が設定されていることがほとんどですが、明示的に設定することも可能です)。- 基底クラスの呼び出し:
focusOutEvent
をオーバーライドする際は、必ず基底クラスのQGraphicsView::focusOutEvent(event)
を呼び出すようにしてください。これを怠ると、Qtの標準的なフォーカス処理が正しく行われず、予期せぬ動作につながる可能性があります。 QFocusEvent *event
: このポインタを通じて、フォーカスイベントに関する情報(フォーカスを失った理由など)を取得できます。event->reason()
でQt::FocusReason
を取得できます。
基底クラスのfocusOutEvent()を呼び忘れる
一般的なエラー
focusOutEvent()
をオーバーライドする際に、オーバーライドしたメソッドの最後に基底クラスのQGraphicsView::focusOutEvent(event);
を呼び出すのを忘れてしまうことです。
// 誤った例
void MyGraphicsView::focusOutEvent(QFocusEvent *event)
{
// カスタムロジックのみ
qDebug() << "フォーカスが失われました";
// QGraphicsView::focusOutEvent(event); の呼び出しがない
}
問題点
基底クラスのメソッドを呼び出さないと、QtがQGraphicsView
のフォーカス喪失に関する標準的な内部処理(例えば、内部状態の更新、シーンへのイベント伝播など)を実行できません。これにより、予期せぬ動作、表示の不整合、または他のイベントが正しく機能しないなどの問題が発生する可能性があります。例えば、ビュー内のQGraphicsItem
が持つフォーカスが正しく解除されないなどが考えられます。
トラブルシューティング
常にオーバーライドしたメソッドの最後で、基底クラスのfocusOutEvent()
を呼び出すようにしてください。
// 正しい例
void MyGraphicsView::focusOutEvent(QFocusEvent *event)
{
// カスタムロジック
qDebug() << "フォーカスが失われました";
// **重要**: 基底クラスのメソッドを呼び出す
QGraphicsView::focusOutEvent(event);
}
フォーカス喪失の理由を考慮しない
一般的なエラー
focusOutEvent()
内で、フォーカスが失われた「理由」(QFocusEvent::reason()
)を考慮せずに、常に同じ処理を実行してしまうことです。
void MyGraphicsView::focusOutEvent(QFocusEvent *event)
{
// 常にこの処理を実行
scene()->clearSelection(); // ウィンドウが閉じられた場合でも選択解除
QGraphicsView::focusOutEvent(event);
}
問題点
ユーザーがTabキーで別のウィジェットに移動したのか、マウスでアプリケーション外をクリックしたのか、あるいはアプリケーションのウィンドウ自体が閉じられようとしているのかによって、実行すべき処理が異なる場合があります。例えば、ウィンドウが閉じられる際にシーンの選択を解除するのは適切かもしれませんが、単に別の入力フィールドにフォーカスが移っただけの場合に同じ処理を行うと、ユーザー体験が悪化する可能性があります。
トラブルシューティング
QFocusEvent::reason()
を使用して、フォーカス喪失の理由に基づいて異なる処理を実行するように分岐させます。
void MyGraphicsView::focusOutEvent(QFocusEvent *event)
{
switch (event->reason()) {
case Qt::TabFocusReason:
qDebug() << "Tabキーでフォーカスが失われました。";
// Tabキーでの移動時に実行する処理
break;
case Qt::MouseFocusReason:
qDebug() << "マウスクリックでフォーカスが失われました。";
// マウス操作による移動時に実行する処理
break;
case Qt::ActiveWindowFocusReason:
qDebug() << "アクティブウィンドウが変更されたためフォーカスが失われました。";
// 例: アプリケーションのウィンドウが非アクティブになった場合
break;
case Qt::CloseCausedFocusReason:
qDebug() << "ウィンドウが閉じられたためフォーカスが失われました。";
// 例: ウィンドウが閉じられる直前のクリーンアップ処理
break;
default:
qDebug() << "その他の理由でフォーカスが失われました。理由: " << event->reason();
break;
}
// 必要に応じて共通の処理
QGraphicsView::focusOutEvent(event);
}
QGraphicsItemのフォーカスと混同する
一般的なエラー
QGraphicsView
のfocusOutEvent()
と、QGraphicsItem
のfocusOutEvent()
を混同してしまうことです。
問題点
QGraphicsView
はビューポート全体のフォーカスを扱いますが、QGraphicsItem
はシーン内の個々のアイテムのフォーカスを扱います。例えば、ビューの中にQGraphicsTextItem
があり、それが現在編集中でフォーカスを持っている場合、ユーザーがそのQGraphicsTextItem
から別のQGraphicsItem
にフォーカスを移しても、QGraphicsView
自体はフォーカスを失いません。QGraphicsTextItem
のfocusOutEvent()
が呼ばれるだけです。
トラブルシューティング
どのレベルでフォーカス喪失イベントを処理したいのかを明確にします。
- 特定のグラフィックアイテムがフォーカスを失った場合: その
QGraphicsItem
のサブクラスでQGraphicsItem::focusOutEvent()
をオーバーライドします。 - ビュー全体がフォーカスを失った場合:
QGraphicsView::focusOutEvent()
をオーバーライドします。
フォーカス設定(focusPolicy)の問題
一般的なエラー
QGraphicsView
がそもそもキーボードフォーカスを受け取れるように設定されていない場合、focusOutEvent()
は期待通りに呼び出されません。
問題点
デフォルトでは、QGraphicsView
は通常、フォーカスを受け取れるように設定されています(Qt::StrongFocus
など)。しかし、明示的にsetFocusPolicy(Qt::NoFocus)
などに設定してしまっている場合、フォーカスイベントは発生しません。
トラブルシューティング
QGraphicsView
のfocusPolicy
が適切に設定されていることを確認します。
MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
: QGraphicsView(scene, parent)
{
// フォーカスを受け取れるように設定
setFocusPolicy(Qt::StrongFocus); // キーボード、Tab、クリックでフォーカスを受け取る
// または Qt::ClickFocus, Qt::TabFocus, Qt::WheelFocus など、要件に応じて
}
一般的なエラー
focusOutEvent()
内で時間のかかる処理(ファイルI/O、ネットワーク通信、重い計算など)を実行してしまうことです。
問題点
focusOutEvent()
はQtのイベントループの一部として実行されるため、この中で重い処理を行うとUIがフリーズしたり、応答性が悪くなったりする可能性があります。
- シグナル/スロットメカニズムを使用して、
focusOutEvent()
から別のスロットに処理をキューイング(Qt::QueuedConnection
)し、非同期で実行します。 - 時間のかかる処理は、
QtConcurrent
や個別のスレッド(QThread
)にオフロードします。
例1:フォーカス喪失時にビューの視覚的な状態を変更する
この例では、QGraphicsView
がフォーカスを失ったときに、そのビューポートのボーダー色を変更して、ユーザーに視覚的なフィードバックを提供します。
カスタムQGraphicsViewクラス (MyGraphicsView.h
)
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H
#include <QGraphicsView>
#include <QFocusEvent>
#include <QBrush> // QBrush を含める
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT // Q_OBJECT マクロはシグナル/スロットを可能にするために必要
public:
explicit MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
protected:
// focusOutEventをオーバーライド
void focusOutEvent(QFocusEvent *event) override;
// focusInEventも一緒にオーバーライドすると、フォーカス取得時の動作も定義できる
void focusInEvent(QFocusEvent *event) override;
private:
void updateBorderStyle(); // ボーダーを更新するヘルパー関数
};
#endif // MYGRAPHICSVIEW_H
カスタムQGraphicsViewクラス (MyGraphicsView.cpp
)
#include "MyGraphicsView.h"
#include <QDebug>
#include <QPainter> // QPainter を含める (スタイルシートでボーダーを直接描画しない場合)
MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
: QGraphicsView(scene, parent)
{
// ビューがキーボードフォーカスを受け取れるように設定
setFocusPolicy(Qt::StrongFocus);
// 初期スタイルを設定 (フォーカスがない状態)
updateBorderStyle();
}
void MyGraphicsView::focusOutEvent(QFocusEvent *event)
{
qDebug() << "MyGraphicsView: フォーカスが失われました。理由: " << event->reason();
updateBorderStyle(); // スタイルを更新してフォーカスが失われたことを示す
// **重要**: 基底クラスのfocusOutEventを呼び出す
QGraphicsView::focusOutEvent(event);
}
void MyGraphicsView::focusInEvent(QFocusEvent *event)
{
qDebug() << "MyGraphicsView: フォーカスを取得しました。理由: " << event->reason();
updateBorderStyle(); // スタイルを更新してフォーカスがあることを示す
// **重要**: 基底クラスのfocusInEventを呼び出す
QGraphicsView::focusInEvent(event);
}
void MyGraphicsView::updateBorderStyle()
{
if (hasFocus()) {
// フォーカスがある場合、青い太いボーダーにする
setStyleSheet("QGraphicsView { border: 2px solid blue; }");
} else {
// フォーカスがない場合、灰色の細いボーダーにする
setStyleSheet("QGraphicsView { border: 1px solid gray; }");
}
}
メインアプリケーション (main.cpp
)
#include <QApplication>
#include <QGraphicsScene>
#include <QLineEdit> // フォーカスを移動させるためのウィジェット
#include <QVBoxLayout>
#include <QWidget>
#include "MyGraphicsView.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// シーンの作成
QGraphicsScene *scene = new QGraphicsScene();
scene->addText("QGraphicsViewの例");
scene->addRect(0, 0, 100, 100);
// カスタムビューの作成
MyGraphicsView *view = new MyGraphicsView(scene);
view->setFixedSize(300, 200); // サイズを固定
// フォーカスを移すための他のウィジェット
QLineEdit *lineEdit1 = new QLineEdit("LineEdit 1");
QLineEdit *lineEdit2 = new QLineEdit("LineEdit 2");
// レイアウトの作成
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(lineEdit1);
layout->addWidget(view);
layout->addWidget(lineEdit2);
QWidget *window = new QWidget();
window->setLayout(layout);
window->setWindowTitle("QGraphicsView Focus Demo");
window->show();
return a.exec();
}
この例の説明
MyGraphicsView
クラスはQGraphicsView
を継承し、focusOutEvent()
とfocusInEvent()
をオーバーライドしています。updateBorderStyle()
ヘルパー関数を使って、ビューがフォーカスを持っているかどうか(hasFocus()
)に基づいてスタイルシートを適用し、ボーダーの色と太さを変更しています。main.cpp
では、MyGraphicsView
インスタンスと2つのQLineEdit
を作成し、垂直レイアウトに配置しています。- アプリケーションを実行し、
MyGraphicsView
をクリックしてフォーカスを与えたり、TabキーやマウスでQLineEdit
にフォーカスを移動させたりすると、MyGraphicsView
のボーダーが変化するのを確認できます。デバッグ出力も同時に表示されます。
この例では、QGraphicsView
内にテキストアイテムがあり、それが編集中の場合に、ビューがフォーカスを失ったらそのテキストアイテムの編集状態を終了させます。
カスタムQGraphicsViewクラス (MyEditingView.h
)
#ifndef MYEDITINGVIEW_H
#define MYEDITINGVIEW_H
#include <QGraphicsView>
#include <QGraphicsTextItem>
#include <QFocusEvent>
class MyEditingView : public QGraphicsView
{
Q_OBJECT
public:
explicit MyEditingView(QGraphicsScene *scene, QWidget *parent = nullptr);
protected:
void focusOutEvent(QFocusEvent *event) override;
private:
QGraphicsTextItem *editableTextItem; // 編集可能なテキストアイテム
};
#endif // MYEDITINGVIEW_H
カスタムQGraphicsViewクラス (MyEditingView.cpp
)
#include "MyEditingView.h"
#include <QDebug>
#include <QGraphicsScene>
MyEditingView::MyEditingView(QGraphicsScene *scene, QWidget *parent)
: QGraphicsView(scene, parent), editableTextItem(nullptr)
{
setFocusPolicy(Qt::StrongFocus); // フォーカスを受け取れるようにする
// シーンに編集可能なテキストアイテムを追加
editableTextItem = new QGraphicsTextItem("ここをダブルクリックして編集");
editableTextItem->setPos(50, 50);
editableTextItem->setTextInteractionFlags(Qt::TextEditorInteraction); // 編集可能にする
scene->addItem(editableTextItem);
}
void MyEditingView::focusOutEvent(QFocusEvent *event)
{
qDebug() << "MyEditingView: フォーカスが失われました。理由: " << event->reason();
// フォーカス喪失時に、もしテキストアイテムが編集中であれば編集を終了させる
if (editableTextItem && editableTextItem->textInteractionFlags().testFlag(Qt::TextEditorInteraction))
{
// QGraphicsTextItemの編集モードを終了させる一般的な方法
// 通常はQt::TextEditorInteractionを解除し、Qt::LinksAccessibleByMouseなどに戻す
editableTextItem->setTextInteractionFlags(Qt::NoTextInteraction); // 編集不可にする (または表示のみ)
// ユーザーがクリックで再度編集できるようにするには、
// ダブルクリックイベントなどで再度Qt::TextEditorInteractionを設定する必要があります。
qDebug() << "テキストアイテムの編集状態を終了しました。";
}
// **重要**: 基底クラスのfocusOutEventを呼び出す
QGraphicsView::focusOutEvent(event);
}
メインアプリケーション (main.cpp
)
#include <QApplication>
#include <QGraphicsScene>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QWidget>
#include "MyEditingView.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
MyEditingView *view = new MyEditingView(scene);
view->setFixedSize(400, 300);
QLineEdit *dummyLineEdit = new QLineEdit("ダミーの入力フィールド (フォーカス移動用)");
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(view);
layout->addWidget(dummyLineEdit);
QWidget *window = new QWidget();
window->setLayout(layout);
window->setWindowTitle("QGraphicsView Focus Out Event (Editing)");
window->show();
return a.exec();
}
MyEditingView
クラスはQGraphicsView
を継承し、focusOutEvent()
をオーバーライドしています。- コンストラクタで、編集可能な
QGraphicsTextItem
を作成し、シーンに追加しています。このアイテムはQt::TextEditorInteraction
フラグを持っているため、ダブルクリックで編集モードに入ることができます。 focusOutEvent()
内で、もしeditableTextItem
が編集可能な状態であれば、setTextInteractionFlags(Qt::NoTextInteraction)
を設定することで編集モードを終了させています。これにより、ビューからフォーカスが外れたときに、テキストアイテムの変更が確定されます。- アプリケーションを実行し、
QGraphicsView
内のテキストをダブルクリックして編集モードに入り、その後QLineEdit
をクリックしてフォーカスを移動させると、テキストの編集が自動的に終了するのを確認できます。
QWidget::eventFilter() を使用する
これは非常に強力なメカニズムで、特定のオブジェクト(この場合はQGraphicsView
のインスタンス)に送られるすべてのイベントを監視し、フィルタリングすることができます。
利点
- イベントの阻止
イベントフィルタ内でevent->ignore()
を呼び出すことで、イベントの通常の処理を阻止できます。 - 柔軟性
複数のウィジェットのイベントを単一のイベントフィルタで処理できます。 - サブクラス化不要
QGraphicsView
をサブクラス化する必要がありません。既存のQGraphicsView
インスタンスに対して動的にイベントフィルタをインストールできます。
欠点
- イベントの順序
オーバーライドされたイベントハンドラよりも先にイベントフィルタが呼び出されます。これは、イベントを阻止したい場合に便利ですが、他の処理との兼ね合いを考慮する必要があります。 - 複雑さ
イベントの種類をチェックし、適切なキャストを行う必要があるため、コードが少し複雑になる可能性があります。
コード例
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QEvent>
#include <QFocusEvent>
#include <QDebug>
// イベントフィルタクラス
class FocusEventFilter : public QObject
{
Q_OBJECT
public:
explicit FocusEventFilter(QObject *parent = nullptr) : QObject(parent) {}
protected:
bool eventFilter(QObject *watched, QEvent *event) override
{
if (watched->inherits("QGraphicsView")) { // QGraphicsViewのインスタンスであるか確認
if (event->type() == QEvent::FocusOut) {
QFocusEvent *focusEvent = static_cast<QFocusEvent*>(event);
qDebug() << "イベントフィルタ: QGraphicsViewがフォーカスを失いました。理由: " << focusEvent->reason();
// ここにカスタムロジックを記述
// 例: 視覚的なフィードバックの変更
if (QGraphicsView *view = qobject_cast<QGraphicsView*>(watched)) {
view->setStyleSheet("QGraphicsView { border: 1px solid red; }");
}
} else if (event->type() == QEvent::FocusIn) {
QFocusEvent *focusEvent = static_cast<QFocusEvent*>(event);
qDebug() << "イベントフィルタ: QGraphicsViewがフォーカスを取得しました。理由: " << focusEvent->reason();
// ここにカスタムロジックを記述
if (QGraphicsView *view = qobject_cast<QGraphicsView*>(watched)) {
view->setStyleSheet("QGraphicsView { border: 2px solid green; }");
}
}
}
// イベントを他のハンドラに渡すためにtrueを返す
// イベントを消費したい場合はfalseを返す
return QObject::eventFilter(watched, event);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
scene->addText("イベントフィルタの例");
QGraphicsView *view = new QGraphicsView(scene);
view->setFixedSize(300, 200);
view->setFocusPolicy(Qt::StrongFocus); // フォーカスを受け取れるように設定
// イベントフィルタをビューにインストール
FocusEventFilter *filter = new FocusEventFilter(view); // viewを親にすることでviewが破棄されたときにfilterも破棄される
view->installEventFilter(filter);
QLineEdit *lineEdit = new QLineEdit("LineEdit");
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(view);
layout->addWidget(lineEdit);
QWidget *window = new QWidget();
window->setLayout(layout);
window->setWindowTitle("Event Filter Focus Demo");
window->show();
return a.exec();
}
QApplicationレベルのイベントフィルタ
アプリケーション全体のすべてのウィジェットのイベントを監視したい場合、QApplication
インスタンスにイベントフィルタをインストールできます。
利点
- デバッグ
特定のイベントがどのように伝播しているかを追跡するのに役立ちます。 - グローバル監視
アプリケーション内の任意のウィジェットのフォーカス変更を監視できます。
欠点
- 責任の分散
イベントハンドラが分散しすぎると、コードの管理が難しくなる可能性があります。 - イベントの種類を区別
どのウィジェットのイベントかを判断するために、watched
オブジェクトの型をチェックする必要があります。 - パフォーマンスへの影響
すべてのイベントがフィルタを通過するため、パフォーマンスにわずかな影響を与える可能性があります。
コード例
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QEvent>
#include <QFocusEvent>
#include <QDebug>
// グローバルイベントフィルタクラス
class GlobalFocusEventFilter : public QObject
{
Q_OBJECT
public:
explicit GlobalFocusEventFilter(QObject *parent = nullptr) : QObject(parent) {}
protected:
bool eventFilter(QObject *watched, QEvent *event) override
{
if (event->type() == QEvent::FocusOut) {
QFocusEvent *focusEvent = static_cast<QFocusEvent*>(event);
qDebug() << "グローバルフィルタ: " << watched->objectName() << "がフォーカスを失いました。理由: " << focusEvent->reason();
// 特定のQGraphicsViewのインスタンスを識別して処理する場合
if (QGraphicsView *view = qobject_cast<QGraphicsView*>(watched)) {
qDebug() << "グローバルフィルタ: QGraphicsViewのフォーカス喪失を検出。";
view->setStyleSheet("QGraphicsView { border: 1px dashed orange; }");
}
} else if (event->type() == QEvent::FocusIn) {
QFocusEvent *focusEvent = static_cast<QFocusEvent*>(event);
qDebug() << "グローバルフィルタ: " << watched->objectName() << "がフォーカスを取得しました。理由: " << focusEvent->reason();
if (QGraphicsView *view = qobject_cast<QGraphicsView*>(watched)) {
view->setStyleSheet("QGraphicsView { border: 2px dashed purple; }");
}
}
return QObject::eventFilter(watched, event);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// グローバルイベントフィルタをアプリケーションにインストール
GlobalFocusEventFilter *globalFilter = new GlobalFocusEventFilter();
a.installEventFilter(globalFilter);
QGraphicsScene *scene = new QGraphicsScene();
scene->addText("グローバルイベントフィルタの例");
QGraphicsView *view = new QGraphicsView(scene);
view->setFixedSize(300, 200);
view->setObjectName("MyGraphicsViewInstance"); // オブジェクト名を付けて識別しやすくする
view->setFocusPolicy(Qt::StrongFocus);
QLineEdit *lineEdit1 = new QLineEdit("LineEdit 1");
lineEdit1->setObjectName("LineEdit1Instance");
QLineEdit *lineEdit2 = new QLineEdit("LineEdit 2");
lineEdit2->setObjectName("LineEdit2Instance");
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(view);
layout->addWidget(lineEdit1);
layout->addWidget(lineEdit2);
QWidget *window = new QWidget();
window->setLayout(layout);
window->setWindowTitle("Global Event Filter Focus Demo");
window->show();
return a.exec();
}
QApplication::focusChanged シグナルを使用する
Qt 5以降では、QApplication
がfocusChanged(QWidget *old, QWidget *now)
というシグナルを提供しています。これは、フォーカスが古いウィジェットから新しいウィジェットに移動したときに発せられます。
利点
- 変更前後
フォーカスを持つウィジェットが変更される直前と変更後のウィジェットの両方を知ることができます。 - グローバル監視
アプリケーション全体のフォーカス変更を監視できます。 - クリーンなAPI
シグナル/スロットメカニズムを使用するため、イベントフィルタよりもコードが読みやすくなる場合があります。
欠点
- オーバーライドの代替ではない
これは、focusOutEvent()
の直接の代替というよりは、アプリケーション全体のフォーカス管理に使用されることが多いです。 - QEvent情報へのアクセスが限定的
QFocusEvent::reason()
のような詳細なフォーカスイベントの理由には直接アクセスできません。
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
scene->addText("focusChangedシグナルの例");
QGraphicsView *view = new QGraphicsView(scene);
view->setFixedSize(300, 200);
view->setFocusPolicy(Qt::StrongFocus);
view->setObjectName("MyGraphicsViewInstance"); // シグナル/スロットで識別しやすくするため
QLineEdit *lineEdit = new QLineEdit("LineEdit");
lineEdit->setObjectName("LineEditInstance");
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(view);
layout->addWidget(lineEdit);
QWidget *window = new QWidget();
window->setLayout(layout);
window->setWindowTitle("QApplication::focusChanged Demo");
window->show();
// QApplication::focusChangedシグナルを接続
QObject::connect(&a, &QApplication::focusChanged,
[&](QWidget *oldWidget, QWidget *newWidget) {
if (oldWidget && oldWidget->objectName() == "MyGraphicsViewInstance") {
qDebug() << "QApplication::focusChanged: MyGraphicsViewInstanceがフォーカスを失いました。";
// 例: スタイルシートを更新
oldWidget->setStyleSheet("QGraphicsView { border: 1px solid red; }");
}
if (newWidget && newWidget->objectName() == "MyGraphicsViewInstance") {
qDebug() << "QApplication::focusChanged: MyGraphicsViewInstanceがフォーカスを取得しました。";
// 例: スタイルシートを更新
newWidget->setStyleSheet("QGraphicsView { border: 2px solid blue; }");
}
qDebug() << "フォーカス変更: " << (oldWidget ? oldWidget->objectName() : "なし")
<< " -> " << (newWidget ? newWidget->objectName() : "なし");
});
return a.exec();
}
- アプリケーション全体のフォーカス監視 (QApplicationレベルのイベントフィルタまたは focusChangedシグナル)
アプリケーション全体でフォーカス変更を監視し、例えばグローバルなUI状態(ツールバーの有効/無効など)を更新する場合に有用です。focusChanged
シグナルはより高レベルでクリーンなAPIですが、イベントの理由にはアクセスできません。詳細な理由が必要な場合は、QApplication
レベルのイベントフィルタが優れています。 - 特定のインスタンスに適用される柔軟性 (QWidget::eventFilter())
QGraphicsView
のサブクラス化を避けたい場合や、実行時に動的にフォーカス処理を追加・削除したい場合に適しています。 - 最も直接的な方法 (focusOutEvent()のオーバーライド)
特定のQGraphicsView
インスタンスのフォーカス喪失時のみに処理を行いたい場合、これが最もクリーンで推奨される方法です。コードが自己完結型になり、管理しやすいです。