【Qt】QGraphicsViewでのキー操作:基本から応用までのコード例

2025-05-27

Qtプログラミングにおけるvoid QGraphicsView::keyPressEvent(QKeyEvent *event)は、QGraphicsViewクラスの保護された仮想関数であり、キーボードのキーが押されたときに発生するイベントを処理するためのメソッドです。

QGraphicsViewは、QGraphicsSceneの内容を表示するためのウィジェットです。ユーザーがキーボードを操作すると、その入力はイベントとしてシステムからアプリケーションに通知されます。keyPressEventは、QGraphicsViewがこのキー押下イベントを受け取ったときに自動的に呼び出される関数です。

この関数をオーバーライド(再実装)することで、特定のキーが押されたときにQGraphicsViewがどのように振る舞うかをカスタマイマズできます。例えば、矢印キーでビューをスクロールさせたり、特定のキーでシーン内のアイテムを操作したりすることができます。

QKeyEvent *event 引数について

keyPressEvent関数は、QKeyEvent型のポインタを引数として受け取ります。このQKeyEventオブジェクトには、押されたキーに関する詳細な情報が含まれています。

  • event->text(): 押されたキーに対応するテキストを返します。例えば、「A」キーが押されれば「A」という文字列が返されます。
  • event->modifiers(): 押されたキーに加えて、Ctrl、Shift、Altなどの修飾キーが同時に押されていたかを示すQt::KeyboardModifiers列挙型の値を返します。
  • event->key(): どのキーが押されたかを示すQt::Key列挙型の値を返します。例えば、Qt::Key_Upは上矢印キー、Qt::Key_Spaceはスペースキーを表します。

keyPressEvent の再実装方法

keyPressEventを再実装するには、QGraphicsViewを継承したカスタムクラスを作成し、その中でこのメソッドをオーバーライドします。

#include <QGraphicsView>
#include <QKeyEvent>
#include <QDebug> // デバッグ出力用

class MyGraphicsView : public QGraphicsView
{
public:
    MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr)
        : QGraphicsView(scene, parent)
    {
        // キーボードイベントを受け取るためにフォーカスを設定することが重要です。
        // デフォルトでは一部のウィジェットはフォーカスを受け取らない場合があります。
        setFocusPolicy(Qt::StrongFocus);
    }

protected:
    void keyPressEvent(QKeyEvent *event) override
    {
        // 押されたキーを判定
        switch (event->key()) {
        case Qt::Key_Up:
            qDebug() << "上矢印キーが押されました!";
            // ビューを上にスクロールするなどの処理
            break;
        case Qt::Key_Down:
            qDebug() << "下矢印キーが押されました!";
            // ビューを下にスクロールするなどの処理
            break;
        case Qt::Key_Space:
            qDebug() << "スペースキーが押されました!";
            // 何らかのゲーム内のアクションを実行するなどの処理
            break;
        default:
            // 処理しないキーイベントは親クラスのハンドラに渡すことが重要です。
            // そうしないと、デフォルトのスクロール動作などが失われる可能性があります。
            QGraphicsView::keyPressEvent(event);
            break;
        }

        // イベントが処理されたことをQtに通知します。
        // もしイベントをこれ以上伝播させたくない場合は、
        // event->accept() を呼び出すか、この関数内で処理を完結させ、
        // QGraphicsView::keyPressEvent(event); を呼ばないようにします。
        // デフォルトではキーイベントは accepted() == true で開始します。
        // 特定のキーイベントを無視して親ウィジェットやフォーカスされたアイテムに
        // 処理させたい場合は、event->ignore() を呼び出すこともできます。
    }
};
  1. フォーカス (Focus): QGraphicsViewがキーイベントを受け取るためには、ビューにキーボードフォーカスが当たっている必要があります。setFocusPolicy(Qt::StrongFocus)などを呼び出して、ビューがフォーカスを受け取れるように設定することが一般的です。
  2. イベントの伝播 (Event Propagation): keyPressEventをオーバーライドする場合、処理しなかったキーイベントは必ず親クラスのkeyPressEventを呼び出すようにしてください (QGraphicsView::keyPressEvent(event);)。そうしないと、QGraphicsViewのデフォルトの動作(例えば、スクロールバーの操作など)が失われてしまう可能性があります。もし、特定のキーイベントを完全に自分で処理し、それ以上他のオブジェクトに伝播させたくない場合は、親クラスの呼び出しを省略し、必要に応じてevent->accept()を明示的に呼び出すことができます。ただし、通常はデフォルトでQKeyEventacceptedの状態です。
  3. QGraphicsItem のキーイベント: QGraphicsView自体ではなく、シーン内の個々のQGraphicsItemにキーイベントを処理させたい場合もあります。その場合、QGraphicsItemkeyPressEventをオーバーライドし、そのアイテムがItemIsFocusableフラグを持っていること、そしてキーボードフォーカスが当たっていることを確認する必要があります。QGraphicsViewは、デフォルトでキーイベントをフォーカスを持つアイテムに転送します。


キーイベントが発生しない (イベントがビューに届かない)

これは最も一般的な問題です。

考えられる原因と解決策

  • オーバーライドが正しくない (Incorrect Override)
    C++ のオーバーライド規則に従っていない場合、カスタムの keyPressEvent が呼び出されません。
    • 解決策
      • 基底クラスの keyPressEventvirtual であること (QGraphicsView の場合はそうなっています)。
      • カスタムクラスの宣言で override キーワードを使用し、コンパイラがオーバーライドエラーを検出できるようにします。
        class MyGraphicsView : public QGraphicsView
        {
        protected:
            void keyPressEvent(QKeyEvent *event) override; // ここに override をつける
        };
        
      • 関数シグネチャ (void keyPressEvent(QKeyEvent *event)) が基底クラスと完全に一致していることを確認します。
  • ウィンドウがアクティブでない (Window is Not Active)
    アプリケーションのウィンドウ全体がオペレーティングシステムによってアクティブなウィンドウとして認識されていない場合、キーイベントはアプリケーション自体に送られません。
    • 解決策
      アプリケーションのウィンドウが前面にあることを確認します。これはユーザーの操作によるもので、コードで直接制御するものではありません。
  • 他のウィジェットがフォーカスを持っている (Another Widget has Focus)
    QGraphicsView 以外に、入力フィールド (QLineEdit) やボタン (QPushButton) など、フォーカスを受け取れる別のウィジェットがアクティブになっている場合、そちらがキーイベントを消費してしまいます。
    • 解決策
      フォーカスを明示的に QGraphicsView に移す必要があります。ユーザーがビューをクリックしたときにフォーカスが移るように setFocusPolicy を設定するのが一般的です。あるいは、特定の操作後にプログラムで myView->setFocus(); を呼び出します。
  • ビューにフォーカスがない (No Focus)
    QGraphicsView は、キーボードフォーカスを持っているときにのみキーイベントを受け取ります。
    • 解決策
      QGraphicsView のインスタンスに対して setFocusPolicy(Qt::StrongFocus); または setFocusPolicy(Qt::ClickFocus); を設定します。
      • Qt::StrongFocus: タブキー、マウスクリック、またはプログラミングでフォーカスを受け取ります。
      • Qt::ClickFocus: マウスクリックでのみフォーカスを受け取ります。
      • テスト目的で、アプリケーションの起動時にmyView->setFocus(); を呼び出して強制的にフォーカスを設定することもできます。

特定のキーが反応しない、または期待通りの動作をしない

キーイベントがビューに届いているが、特定のキーに対する処理がうまくいかない場合。

考えられる原因と解決策

  • 大文字・小文字の区別 (Case Sensitivity)
    Qt::Key_A などは通常、Aキーが押された場合に反応しますが、小文字の 'a' を入力した場合にも反応します。しかし、event->text() を使用して文字列を比較する場合は、大文字・小文字の区別を考慮する必要があります。
    • 解決策
      event->text().toLower() などを使用して比較するか、Qt::Key 列挙型を使用します。
  • キーコードの誤り (Incorrect Key Code)
    event->key() の値が期待するものと異なる場合があります。
    • 解決策
      Qt のドキュメントで Qt::Key 列挙型を確認し、正しいキーコードを使用していることを確認します。また、qDebug() << event->key(); で実際に押されたキーの値をデバッグ出力して確認することも有効です。
  • 修飾キーの考慮漏れ (Missing Modifier Key Consideration)
    Ctrl, Shift, Alt などの修飾キーが同時に押されている場合、event->key() だけでは不十分です。
    • 解決策
      event->modifiers() を使用して修飾キーの状態を確認します。
      if (event->key() == Qt::Key_S && event->modifiers() == Qt::ControlModifier) {
          qDebug() << "Ctrl+S が押されました!";
          // 保存処理
      } else if (event->key() == Qt::Key_Up) {
          // 上矢印キー
      }
      
  • イベントの伝播を停止していない (Not Stopping Event Propagation)
    keyPressEvent() をオーバーライドし、イベントを処理した場合、通常はそのイベントをこれ以上親ウィジェットやデフォルトのハンドラに渡したくないでしょう。もし QGraphicsView::keyPressEvent(event); を呼び出してしまうと、Qt のデフォルトの処理(例: 矢印キーでのスクロールなど)が実行され、カスタムの処理と競合する可能性があります。
    • 解決策
      カスタムで処理したキーイベントについては、QGraphicsView::keyPressEvent(event); を呼び出さないでください。処理が終わった後にevent->accept()を明示的に呼び出すこともできますが、キーイベントは通常デフォルトでaccepted状態です。
    • 注意
      処理しないキーイベント(例えば、ユーザーが自分で実装しない Tab キーや Escape キーなど)については、必ず QGraphicsView::keyPressEvent(event); を呼び出して、基底クラスのデフォルトの動作を維持するようにしてください。そうしないと、ビューの基本的な機能が失われます。

キーイベントの処理順序の誤解

複数のオブジェクトがキーイベントを処理できる場合、その順序を理解することが重要です。

  • 親ウィジェットへの伝播 (Propagation to Parent Widgets)
    keyPressEvent() の中で QGraphicsView::keyPressEvent(event); を呼び出すと、イベントは親クラスのデフォルト実装に渡されます。もし親クラスがイベントを処理しない場合、さらにその親へとイベントが伝播していきます。
    • 解決策
      前述のとおり、カスタムで処理したイベントは親に伝播させないように QGraphicsView::keyPressEvent(event); を呼ばないことで制御します。
  • QGraphicsItem と QGraphicsView の競合
    QGraphicsScene 内の QGraphicsItem もキーイベントを受け取ることができます(ItemIsFocusable フラグが設定され、アイテムにフォーカスがある場合)。QGraphicsView は、通常、最初にフォーカスを持つ QGraphicsItem にキーイベントを転送しようとします。
    • 解決策
      • アイテムで処理したい場合
        QGraphicsItem::keyPressEvent() をオーバーライドし、setFlag(QGraphicsItem::ItemIsFocusable) を設定し、QGraphicsScene の選択モデルや setFocusItem() でアイテムにフォーカスを与えます。
      • ビューで処理したい場合
        • アイテムでイベントを処理させたくない場合は、そのアイテムの keyPressEventevent->ignore() を呼び出すことで、イベントをビューに伝播させます。
        • あるいは、QGraphicsItem にフォーカスがない状態を維持します。
  • イベントフィルタを使う
    QApplicationQGraphicsView にイベントフィルタをインストールして、イベントがどこで消費されているかを追跡することも可能です。
    // MyEventFilter クラスを QObject を継承して作成し、eventFilter() をオーバーライド
    bool MyEventFilter::eventFilter(QObject *obj, QEvent *event) {
        if (event->type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
            qDebug() << "Event Filter: Key pressed on " << obj->objectName() << ":" << keyEvent->key();
        }
        return QObject::eventFilter(obj, event);
    }
    
    // メインウィンドウやビューにインストール
    myView->installEventFilter(new MyEventFilter(this));
    qApp->installEventFilter(new MyEventFilter(this)); // アプリケーション全体
    
  • qDebug() を使う
    keyPressEvent() の先頭に qDebug() << "Key pressed: " << event->key() << event->text() << event->modifiers(); を追加し、期待通りにイベントが呼び出されているか、そしてその中の情報が正しいかを確認します。


基本的なキー入力の処理

この例では、カスタムの QGraphicsView を作成し、特定のキーが押されたときにデバッグメッセージを出力します。

MyGraphicsView.h

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QKeyEvent> // QKeyEvent を使用するために必要
#include <QDebug>    // qDebug() を使用するために必要

class MyGraphicsView : public QGraphicsView
{
public:
    // コンストラクタ
    MyGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);

protected:
    // キーが押されたときに呼び出されるイベントハンドラをオーバーライド
    void keyPressEvent(QKeyEvent *event) override;
};

#endif // MYGRAPHICSVIEW_H

MyGraphicsView.cpp

#include "MyGraphicsView.h"

MyGraphicsView::MyGraphicsView(QGraphicsScene *scene, QWidget *parent)
    : QGraphicsView(scene, parent)
{
    // このビューがキーボードフォーカスを受け取るように設定します。
    // これがないと keyPressEvent は呼び出されません。
    setFocusPolicy(Qt::StrongFocus);
}

void MyGraphicsView::keyPressEvent(QKeyEvent *event)
{
    // 押されたキーを判定
    switch (event->key()) {
    case Qt::Key_W:
        qDebug() << "Wキーが押されました!";
        // 何らかのカスタム処理(例:ビューを上に移動、アイテムを移動)
        break;
    case Qt::Key_S:
        qDebug() << "Sキーが押されました!";
        break;
    case Qt::Key_A:
        qDebug() << "Aキーが押されました!";
        break;
    case Qt::Key_D:
        qDebug() << "Dキーが押されました!";
        break;
    case Qt::Key_Space:
        qDebug() << "スペースキーが押されました!";
        break;
    case Qt::Key_Escape:
        qDebug() << "Escapeキーが押されました!";
        // アプリケーションを終了するなどの処理
        // QWidget::close()などを呼び出す
        break;
    default:
        // 処理しないキーイベントは、親クラスのデフォルトのキーハンドラに渡します。
        // これにより、矢印キーによるスクロールなど、QGraphicsViewのデフォルト機能が維持されます。
        QGraphicsView::keyPressEvent(event);
        break;
    }

    // イベントが処理されたことをQtに伝えることで、これ以上イベントが伝播するのを防ぎます。
    // 多くのキーイベントはデフォルトでaccepted状態ですが、明示的に記述することもできます。
    // もしイベントを伝播させたい場合は、event->ignore(); を呼び出します。
    // この例では、switch-caseブロック内で処理されたキーについては、
    // QGraphicsView::keyPressEvent(event); を呼ばないことで自動的にイベントが消費されます。
}

main.cpp

#include <QApplication>
#include <QGraphicsScene>
#include "MyGraphicsView.h" // カスタムビューのヘッダーをインクルード

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // シーンを作成
    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 800, 600); // シーンの範囲を設定

    // シーンに簡単なアイテムを追加(例:四角形)
    scene.addRect(100, 100, 50, 50, QPen(Qt::blue), QBrush(Qt::cyan));
    scene.addText("Press W, A, S, D, Space or Escape keys!");

    // カスタムビューを作成し、シーンを関連付ける
    MyGraphicsView view(&scene);
    view.setWindowTitle("Key Press Event Example");
    view.resize(800, 600); // ビューのサイズを設定
    view.show();

    return a.exec();
}

プロジェクトファイル (.pro) の設定

QT       += widgets

SOURCES += \
    main.cpp \
    MyGraphicsView.cpp

HEADERS += \
    MyGraphicsView.h

ビューのスクロールとズーム

この例では、矢印キーでビューをスクロールし、+- キーでズームイン/ズームアウトします。

MyInteractiveView.h

#ifndef MYINTERACTIVEVIEW_H
#define MYINTERACTIVEVIEW_H

#include <QGraphicsView>
#include <QKeyEvent>
#include <QWheelEvent> // ホイールイベントも処理するために必要

class MyInteractiveView : public QGraphicsView
{
public:
    MyInteractiveView(QGraphicsScene *scene, QWidget *parent = nullptr);

protected:
    void keyPressEvent(QKeyEvent *event) override;
    void wheelEvent(QWheelEvent *event) override; // マウスホイールによるズームも実装

private:
    void zoom(double factor);
    void scrollBy(int dx, int dy);
};

#endif // MYINTERACTIVEVIEW_H

MyInteractiveView.cpp

#include "MyInteractiveView.h"
#include <QScrollBar> // スクロールバー操作に必要

MyInteractiveView::MyInteractiveView(QGraphicsScene *scene, QWidget *parent)
    : QGraphicsView(scene, parent)
{
    setFocusPolicy(Qt::StrongFocus);
    setRenderHint(QPainter::Antialiasing); // アンチエイリアスを有効にして描画を滑らかに
    setDragMode(QGraphicsView::ScrollHandDrag); // マウスドラッグでスクロールできるように設定
}

void MyInteractiveView::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_Up:
        scrollBy(0, -10); // 上にスクロール
        break;
    case Qt::Key_Down:
        scrollBy(0, 10);  // 下にスクロール
        break;
    case Qt::Key_Left:
        scrollBy(-10, 0); // 左にスクロール
        break;
    case Qt::Key_Right:
        scrollBy(10, 0);  // 右にスクロール
        break;
    case Qt::Key_Plus:
    case Qt::Key_Equal: // 一部のキーボードでは '+' が 'Equal' と同じ
        zoom(1.1); // ズームイン
        break;
    case Qt::Key_Minus:
        zoom(0.9); // ズームアウト
        break;
    default:
        // その他のキーは親クラスに処理を任せる
        QGraphicsView::keyPressEvent(event);
        break;
    }
}

void MyInteractiveView::wheelEvent(QWheelEvent *event)
{
    // マウスホイールの回転量を取得
    qreal numDegrees = event->angleDelta().y() / 8.0;
    qreal numSteps = numDegrees / 15.0; // 1ステップあたりの回転数

    qreal factor = 1.0 + numSteps * 0.05; // ズームファクターの計算 (0.05は調整可能)

    zoom(factor);
    event->accept(); // イベントを消費
}

void MyInteractiveView::zoom(double factor)
{
    // 変換の中心をビューの中心に設定 (ズーム時に中心がずれないように)
    setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // マウスカーソルを中心にズーム
    // setTransformationAnchor(QGraphicsView::AnchorViewCenter); // ビューの中心にズーム

    scale(factor, factor); // ビューをスケーリング
}

void MyInteractiveView::scrollBy(int dx, int dy)
{
    horizontalScrollBar()->setValue(horizontalScrollBar()->value() + dx);
    verticalScrollBar()->setValue(verticalScrollBar()->value() + dy);
}

main.cpp (上記例のMyGraphicsViewをMyInteractiveViewに置き換えるだけ)

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsRectItem> // アイテムを追加するために必要
#include "MyInteractiveView.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(-500, -500, 1000, 1000); // 広いシーンを作成

    // シーンにいくつかのアイテムを追加
    scene.addRect(-100, -100, 200, 200, QPen(Qt::red), QBrush(Qt::yellow));
    scene.addEllipse(50, 50, 80, 80, QPen(Qt::green), QBrush(Qt::blue));
    scene.addText("Use arrow keys to scroll, +/- to zoom, or drag with middle mouse button!");

    MyInteractiveView view(&scene);
    view.setWindowTitle("Interactive Graphics View");
    view.resize(800, 600);
    view.show();

    return a.exec();
}

プロジェクトファイル (.pro) の設定

QT       += widgets

SOURCES += \
    main.cpp \
    MyInteractiveView.cpp

HEADERS += \
    MyInteractiveView.h

QGraphicsView ではなく、特定の QGraphicsItem がキーイベントを処理する例です。この場合、QGraphicsView はフォーカスを持つアイテムにキーイベントを転送します。

MyMovableRect.h

#ifndef MYMOVABLERECT_H
#define MYMOVABLERECT_H

#include <QGraphicsRectItem>
#include <QKeyEvent>
#include <QDebug>

class MyMovableRect : public QGraphicsRectItem
{
public:
    // コンストラクタ
    MyMovableRect(const QRectF &rect, QGraphicsItem *parent = nullptr);

protected:
    // キーが押されたときに呼び出されるイベントハンドラをオーバーライド
    void keyPressEvent(QKeyEvent *event) override;
};

#endif // MYMOVABLERECT_H

MyMovableRect.cpp

#include "MyMovableRect.h"

MyMovableRect::MyMovableRect(const QRectF &rect, QGraphicsItem *parent)
    : QGraphicsRectItem(rect, parent)
{
    // このアイテムがキーボードフォーカスを受け取るように設定
    setFlag(QGraphicsItem::ItemIsFocusable);
    // アイテムを選択可能にする (選択するとフォーカスが当たりやすい)
    setFlag(QGraphicsItem::ItemIsSelectable);
    // アイテムを移動可能にする (マウスでドラッグ可能に)
    setFlag(QGraphicsItem::ItemIsMovable);

    // デフォルトの色を設定
    setBrush(Qt::red);
    setPen(QPen(Qt::darkRed, 2));
}

void MyMovableRect::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_W:
        moveBy(0, -10); // 上に移動
        qDebug() << "MovableRect: Wキーで上に移動";
        break;
    case Qt::Key_S:
        moveBy(0, 10);  // 下に移動
        qDebug() << "MovableRect: Sキーで下に移動";
        break;
    case Qt::Key_A:
        moveBy(-10, 0); // 左に移動
        qDebug() << "MovableRect: Aキーで左に移動";
        break;
    case Qt::Key_D:
        moveBy(10, 0);  // 右に移動
        qDebug() << "MovableRect: Dキーで右に移動";
        break;
    default:
        // 処理しないキーイベントは、親クラス(QGraphicsItem)に渡します。
        // これにより、アイテムのデフォルトの動作(例:選択時のEnterキー)が維持されます。
        QGraphicsRectItem::keyPressEvent(event);
        break;
    }
}

main.cpp

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include "MyMovableRect.h" // カスタムアイテムのヘッダーをインクルード

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 800, 600);

    // カスタムの動かせる四角形アイテムを作成し、シーンに追加
    MyMovableRect *movableRect = new MyMovableRect(QRectF(100, 100, 80, 80));
    scene.addItem(movableRect);

    // テキストアイテムも追加
    QGraphicsTextItem *textItem = new QGraphicsTextItem("Click the red square to select it, then use WASD keys to move!");
    textItem->setPos(50, 50);
    scene.addItem(textItem);

    QGraphicsView view(&scene);
    view.setWindowTitle("QGraphicsItem Key Press Example");
    view.resize(800, 600);
    view.show();

    // 最初からアイテムにフォーカスを設定する (任意)
    // movableRect->setFocus();
    // movableRect->setSelected(true); // 選択状態にする

    return a.exec();
}

プロジェクトファイル (.pro) の設定

QT       += widgets

SOURCES += \
    main.cpp \
    MyMovableRect.cpp

HEADERS += \
    MyMovableRect.h
  1. 上記プログラムを実行します。
  2. 「QGraphicsItem Key Press Example」というウィンドウが表示されます。
  3. ウィンドウ内の赤い四角形をクリックして選択します。すると、四角形の周りに点線が表示され、キーボードフォーカスが当たったことを示します。
  4. キーボードの W, A, S, D キーを押すと、赤い四角形が対応する方向に移動します。


QGraphicsItem の keyPressEvent() を使用する

これは、QGraphicsView 自体ではなく、シーン内の特定のアイテムがキーボード入力に応答する必要がある場合に推奨される方法です。

  • 使用例
    • ゲームでプレイヤーキャラクターをキーで動かす。
    • グラフィックエディタで選択された図形をキーで移動・変形する。
    • カスタムのインタラクティブなウィジェット (QGraphicsWidget から継承) でテキスト入力を受け付ける。
  • 利点
    • 責務の分離: ビューとアイテム間のイベント処理の責務が明確になります。
    • モジュール性: アイテムは自己完結型になり、再利用しやすくなります。
    • 簡素化: ビューのイベントハンドラがシンプルになり、ビュー全体の操作(スクロール、ズームなど)に集中できます。
  • 説明
    • QGraphicsItem にキーボードフォーカスを割り当てるには、そのアイテムのフラグに QGraphicsItem::ItemIsFocusable を設定する必要があります。
    • アイテムにフォーカスが当たっている場合、QGraphicsView は受信したキーイベントをそのアイテムの keyPressEvent() メソッドに自動的に転送します。
    • これにより、キーイベントの処理を、影響を受ける特定のアイテムにカプセル化できます。

イベントフィルタ (QObject::eventFilter()) を使用する

イベントフィルタは、特定のオブジェクトにイベントが送信される前に、そのイベントを傍受して処理できる強力なメカニズムです。

  • 使用例
    • 特定のキー入力がアプリケーション全体に影響を与えるグローバルなショートカットキーを実装する。
    • デバッグ目的で、特定のウィジェットに到達するすべてのキーイベントをログに記録する。
    • サードパーティのウィジェットのデフォルトのキーボード動作を無効にする。
  • 注意点
    • QGraphicsView のイベントフィルタをインストールする場合、通常は QGraphicsView 自体ではなく、QGraphicsView::viewport() (ビューの描画領域である QWidget) にインストールする必要があります。これは、QGraphicsView のイベントのほとんどが実際にはビューポートで発生するためです。
    • イベントフィルタは強力ですが、乱用するとコードの可読性や保守性を低下させる可能性があります。
  • 利点
    • 既存のクラスを変更せずにイベントを処理できる
      QGraphicsView をサブクラス化する必要がないため、サードパーティのビューを使用している場合などに便利です。
    • 複数のオブジェクトのイベントを単一の場所で処理できる
      複数のビューやウィジェットのキーイベントを、一元化されたフィルタで管理できます。
    • 柔軟なイベントの傍受
      keyPressEvent() が提供するよりも低レベルでイベントを捕捉できます。
  • 説明
    • QObject を継承したカスタムクラスを作成し、eventFilter(QObject *watched, QEvent *event) メソッドをオーバーライドします。
    • このフィルタオブジェクトを QGraphicsView のインスタンス(またはその viewport())にインストールします。
    • eventFilter() メソッド内で、イベントのタイプ (event->type() == QEvent::KeyPress) をチェックし、キーイベントを処理します。
    • イベントを処理した場合は true を返し、それ以上伝播させないようにします。処理しない場合は false を返し、通常のイベント処理パスに進ませます。

イベントフィルタの簡単な例

// MyEventFilter.h
#include <QObject>
#include <QEvent>
#include <QKeyEvent>
#include <QDebug>

class MyEventFilter : public QObject
{
    Q_OBJECT
public:
    explicit MyEventFilter(QObject *parent = nullptr) : QObject(parent) {}

protected:
    bool eventFilter(QObject *watched, QEvent *event) override
    {
        if (event->type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
            qDebug() << "Event Filter caught key:" << keyEvent->key()
                     << " from object:" << watched->objectName();

            // 例えば、Escapeキーが押されたらイベントを消費する
            if (keyEvent->key() == Qt::Key_Escape) {
                qDebug() << "Escape key handled by filter.";
                return true; // イベントを消費し、それ以上伝播させない
            }
        }
        // その他のイベントや処理しないキーイベントは、通常のイベント処理パスに進ませる
        return QObject::eventFilter(watched, event);
    }
};

// main.cpp (または他の場所) での使用例
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include "MyEventFilter.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.addText("Press keys to see filter in action!");

    QGraphicsView view(&scene);
    view.setWindowTitle("Event Filter Example");
    view.resize(600, 400);

    // ビューポートにイベントフィルタをインストール
    MyEventFilter *filter = new MyEventFilter(&view); // ビューを親にすることでライフサイクルを管理
    view.viewport()->installEventFilter(filter); // 重要: QGraphicsView::viewport() にインストール

    view.setFocusPolicy(Qt::StrongFocus); // フォーカス設定
    view.show();

    return a.exec();
}

QShortcut を使用する

特定のキーシーケンス(ショートカット)に対してアクションをトリガーする場合に最適です。

  • 使用例
    • Ctrl+Z で元に戻す、Ctrl+Y でやり直す。
    • F5 で更新。
    • Delete キーで選択されたアイテムを削除する。
  • 注意点
    • 単一のキーイベントではなく、特定のキーシーケンスの検出に特化しています。すべてのキーイベントを処理する汎用的な方法ではありません。
    • ショートカットが機能するには、関連するウィジェットにフォーカスが当たっている必要があります。
  • 利点
    • 宣言的で読みやすい
      ショートカットの定義が明確です。
    • アクションと直接紐付けられる
      メニューアクションやツールバーボタンなどの UI 要素と簡単に連携できます。
    • 自動的な競合解決
      複数のショートカットが定義されている場合、Qt が適切なものを自動的に判断します。
  • 説明
    • QShortcut オブジェクトを作成し、関連するウィジェットとキーシーケンス(例: QKeySequence("Ctrl+S"))を指定します。
    • QShortcutactivated() シグナルをカスタムスロットに接続します。
    • これにより、指定されたキーシーケンスがウィジェットにフォーカスがある状態で押されたときに、スロットが自動的に呼び出されます。

QShortcut の簡単な例

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QShortcut>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.setWindowTitle("QShortcut Example");
    view.resize(600, 400);

    // Ctrl+S ショートカットを作成し、view を親とする
    QShortcut *saveShortcut = new QShortcut(QKeySequence(Qt::Control | Qt::Key_S), &view);

    // ショートカットがアクティブになったときにスロットを呼び出す
    QObject::connect(saveShortcut, &QShortcut::activated, [&]() {
        qDebug() << "Ctrl+S activated! (Save action)";
        // ここに保存処理を記述
    });

    // Delete キーのショートカット (ビューがフォーカスを持つときに有効)
    QShortcut *deleteShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), &view);
    QObject::connect(deleteShortcut, &QShortcut::activated, [&]() {
        qDebug() << "Delete key activated! (Delete selected item)";
        // 選択されたアイテムを削除する処理を記述
    });

    view.setFocusPolicy(Qt::StrongFocus); // フォーカス設定
    view.show();

    return a.exec();
}

これは、アプリケーション内のすべてのイベントを傍受できる最も強力な方法です。

  • 使用例
    • すべてのキー入力をキャプチャして分析する。
    • カスタムのグローバルホットキーシステムを実装する。
  • 注意点
    • 慎重に使用する
      強力であるため、誤って使用するとアプリケーションの通常のイベント処理を妨害し、予期しない動作を引き起こす可能性があります。
    • パフォーマンスへの影響: すべてのイベントを処理するため、複雑な処理を行うとパフォーマンスに影響を与える可能性があります。
  • 利点
    • 究極の制御
      アプリケーション内のすべてのイベントを監視・変更・破棄できます。
    • グローバルな振る舞い
      アプリケーション全体にわたるキーボードショートカットやデバッグロギングなどに適しています。
  • 説明
    • QCoreApplication::instance()->installEventFilter(filterObject) を呼び出すことで、アプリケーション全体にイベントフィルタをインストールします。
    • このフィルタは、どのウィジェットにもイベントが送られる前に、すべてのイベントを受け取ります。
    • これにより、例えば、アプリケーションのどの場所でキーが押されても特定の動作をトリガーするといったグローバルな処理を実装できます。
  • QShortcut
    特定のキーシーケンス(ショートカット)に対してアクションをトリガーする場合に、宣言的で読みやすい方法を提供します。
  • イベントフィルタ (QObject::eventFilter())
    • QGraphicsView::viewport() にインストールする場合: ビューのサブクラス化を避けたい場合や、複数のビューで共通のキーボード動作を実装したい場合に便利です。
    • QApplication::eventFilter() にインストールする場合: アプリケーション全体のグローバルなキーボードイベントを捕捉する必要がある場合にのみ使用します。
  • QGraphicsItem::keyPressEvent() (オーバーライド)
    シーン内の個々のアイテムがキーボード入力に応答する必要がある場合に最適です。
  • QGraphicsView::keyPressEvent() (オーバーライド)
    ビュー固有のキーボード操作(スクロール、ズームなど)を実装する場合の標準的で推奨される方法です。