Qt QGraphicsViewのfocusInEvent:エラー解決とデバッグのコツ

2025-05-27

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::StrongFocusQt::ClickFocus)。デフォルトでは、多くのウィジェットはキーボードフォーカスを受け取りません。


focusInEvent() は、QGraphicsView ウィジェットがフォーカスを受け取ったときに呼び出される重要なイベントハンドラですが、いくつかの一般的な落とし穴があります。

focusInEvent() が呼び出されない

問題
focusInEvent() をオーバーライドしたにもかかわらず、ビューがフォーカスを受け取ったときにカスタムコードが実行されない。

原因とトラブルシューティング

  • イベントフィルタリング
    • 原因
      アプリケーションまたは親ウィジェットにイベントフィルターが設定されており、focusInEventQGraphicsView に到達する前に処理または破棄されている可能性があります。
    • 解決策
      イベントフィルターのコードを確認し、focusInEvent が正しく伝播されているかを確認します。
  • 他のウィジェットがフォーカスを奪っている
    • 原因
      同じウィンドウ内に他のウィジェット(例: QLineEditQPushButton など)があり、それらが先にフォーカスを受け取ってしまっている可能性があります。
    • 解決策
      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();
}

解説

  1. MyGraphicsView クラスの定義
    QGraphicsView を継承し、focusInEventfocusOutEventprotected メンバとしてオーバーライドします。
  2. setFocusPolicy(Qt::StrongFocus);
    これが最も重要です。この設定がないと、QGraphicsView はデフォルトでフォーカスを受け取らないため、focusInEvent が呼び出されません。
  3. focusInEvent の実装
    • qDebug() でデバッグメッセージを出力し、いつイベントが発生したか、その原因(event->reason())が何であったかを確認できます。
    • setPalette() を使ってビューのパレットを変更し、枠線の色を青に設定します。
    • setFrameShape(), setFrameShadow(), setLineWidth() を使って、枠線の見た目を変更します。
    • QGraphicsView::focusInEvent(event);必ず親クラスの関数を呼び出すようにしてください。 これにより、QGraphicsView 内部のフォーカス処理(例: スクロールバーの更新など)が正しく行われます。これを忘れると、予期しない問題が発生する可能性があります。
  4. focusOutEvent の実装
    フォーカスが離れたときに、元の枠線の見た目に戻す処理を記述します。これも同様に親クラスのメソッドを呼び出します。
  5. 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();
}

解説

  1. アイテムのハイライト
    focusInEvent の中で scene()->items() を使ってシーン内の全てのアイテムを取得し、qgraphicsitem_cast<QGraphicsTextItem *>(item)QGraphicsTextItem にキャストできるかどうかを確認します。
  2. テキスト色の変更
    キャストに成功したら、setDefaultTextColor(Qt::red) を呼び出してテキストの色を赤に変更し、ハイライト効果を与えます。
  3. ハイライトの解除
    focusOutEvent では、テキストの色を元の黒に戻します。

これらの例から、QGraphicsView::focusInEvent() の基本的な使い方と、それを使ってビューまたはシーン内のアイテムの見た目を動的に変更する方法を理解できたかと思います。

重要なポイントは以下の通りです。

  • デバッグ
    qDebug() を積極的に使用し、イベントがいつ、どのように発生しているかを確認する。
  • QFocusEvent *event
    イベントオブジェクトから event->reason() を取得することで、フォーカスが移った理由を調べ、よりきめ細やかな処理を実装できる。
  • 親クラスの呼び出し
    オーバーライドしたイベントハンドラ内で、必ず親クラスの対応するメソッドを呼び出す。
  • setFocusPolicy()
    QGraphicsView がフォーカスを受け取れるように必ず設定する。


QGraphicsView::focusInEvent() は、QGraphicsView が直接フォーカスを受け取った際の処理を記述するのに便利ですが、Qt のイベントシステムには、同じ目的を達成するための他の強力なメカニズムがいくつか存在します。これらの代替手段は、より柔軟性を提供したり、特定のシナリオにおいてより適切な場合があります。

主な代替手段は以下の通りです。

  1. イベントフィルター (QObject::installEventFilter())
  2. QApplication::focusChanged シグナル
  3. QGraphicsItem::setFlag(QGraphicsItem::ItemIsFocusable)QGraphicsItem::focusInEvent() (アイテムレベルのフォーカス)
  4. ウィジェットのプロパティとスタイルシート (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ライクな構文で変更する強力な機能です。QGraphicsViewQWidget を継承しているため、スタイルシートを適用できます。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();
}
  1. MyGraphicsViewfocusInEventfocusOutEvent で、カスタムプロパティ "hasFocus"true または false に設定します。
  2. style()->polish(this); を呼び出すことで、スタイルシートがプロパティの変更を検知し、見た目を更新するように促します。
  3. アプリケーション全体のスタイルシートで、MyGraphicsView[hasFocus="true"] のようなセレクタを使って、このカスタムプロパティに基づいて異なるスタイルを適用します。