Qt アプリ開発:QGraphicsScene でキー入力(離し)を検知する方法

2025-04-26

QGraphicsScene::keyReleaseEvent() とは

QGraphicsScene::keyReleaseEvent() は、Qtのグラフィックフレームワークである Qt Graphics View Framework において、QGraphicsScene クラスが提供する仮想関数の一つです。この関数は、シーン(QGraphicsScene オブジェクト)上でキーボードのキーが離されたときに呼び出されます。

役割と目的

このイベントハンドラを再実装(オーバーライド)することで、シーン上でキーが離された瞬間に特定の処理を実行することができます。例えば、以下のような目的で利用されます。

  • フォーカス管理
    シーン内のアイテムがキーイベントを受け取らない場合に、シーン自体がキー離しイベントを処理する。
  • 特殊なキー操作の処理
    特定のキーの組み合わせが離されたときに、ゲームの操作、エディタの機能、アニメーションの制御などを実装する。
  • キー入力の完了を検知
    キーが押されてから離されるまでの一連の操作を認識し、それに応じたアクションを実行する。

イベントの流れ

  1. ユーザーがキーボードのキーを押します。
  2. そのキーがまだ押されている間は、通常、フォーカスを持つ QGraphicsItem オブジェクト(またはその親)の keyPressEvent() 関数が繰り返し呼び出されます(オートリピート設定による)。
  3. ユーザーがキーを離すと、以下のいずれかのオブジェクトの keyReleaseEvent() 関数が呼び出されます。
    • フォーカスを持つ QGraphicsItem
      もしシーン内のアイテムがキーボードフォーカスを持っていれば、そのアイテムの keyReleaseEvent() が最初に呼び出されます。
    • QGraphicsScene
      もしフォーカスを持つアイテムが存在しない場合、またはフォーカスを持つアイテムがイベントを無視した場合(event->ignore() を呼び出した場合)、シーン自身の keyReleaseEvent() が呼び出されます。

再実装の方法

QGraphicsScene を継承した独自のクラスを作成し、その中で keyReleaseEvent() 関数を再実装します。再実装する際には、引数として渡される QKeyEvent オブジェクトから、離されたキーの種類(仮想キーコード)、修飾キー(Shift、Ctrl、Alt など)の状態などを取得できます。

#include <QGraphicsScene>
#include <QKeyEvent>
#include <QDebug>

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {}

protected:
    void keyReleaseEvent(QKeyEvent *event) override {
        int key = event->key();
        Qt::KeyboardModifiers modifiers = event->modifiers();

        qDebug() << "キーが離されました:";

        switch (key) {
        case Qt::Key_Space:
            qDebug() << "  スペースキー";
            // スペースキーが離された時の処理
            break;
        case Qt::Key_A:
            qDebug() << "  'A' キー";
            // 'A' キーが離された時の処理
            break;
        // その他のキーの処理
        default:
            qDebug() << "  仮想キーコード:" << key;
            break;
        }

        if (modifiers & Qt::ShiftModifier) {
            qDebug() << "  Shiftキーが押されています。";
        }
        if (modifiers & Qt::ControlModifier) {
            qDebug() << "  Ctrlキーが押されています。";
        }
        if (modifiers & Qt::AltModifier) {
            qDebug() << "  Altキーが押されています。";
        }

        // デフォルトのイベント処理を呼び出す(必要に応じて)
        QGraphicsScene::keyReleaseEvent(event);
    }
};
  • 仮想キーコード
    QKeyEvent::key() メソッドが返す値は、Qtで定義された仮想キーコードです(例: Qt::Key_SpaceQt::Key_A など)。
  • フォーカス
    keyReleaseEvent() が呼び出されるためには、シーンまたはシーン内のアイテムがキーボードフォーカスを持っている必要があります。フォーカスを設定するには、setFocus() メソッドを使用します。
  • イベントの伝播
    キーイベントは、通常、フォーカスを持つアイテムから順に親へと伝播していきます。event->accept() を呼び出すことでイベントの伝播を止めることができます。event->ignore() を呼び出すと、イベントは次のイベントハンドラ(通常は親オブジェクト)に渡されます。


よくあるエラーとトラブルシューティング

QGraphicsScene::keyReleaseEvent() を使用する際に遭遇しやすいエラーとその解決策を以下に示します。

keyReleaseEvent() が呼び出されない

    • フォーカスがない
      QGraphicsScene またはシーン内のアイテムがキーボードフォーカスを持っていない可能性があります。キーイベントは、フォーカスを持つオブジェクトに最初に送信されます。
    • アイテムがイベントを横取りしている
      シーン内のアイテムが keyPressEvent() でイベントを受け取り、event->accept() を呼び出してイベントの伝播を止めている可能性があります。キー離しイベントも同様に横取りされることがあります。
    • イベントフィルター
      シーンまたはビューにインストールされたイベントフィルターが、キー離しイベントを処理または破棄している可能性があります。
    • 親ウィジェットの問題
      QGraphicsView を含む親ウィジェットがキーイベントを適切に処理していない可能性があります。

意図しないキーコードが検出される

  • トラブルシューティング

    • 修飾キーの確認
      QKeyEvent::modifiers() を使用して修飾キーの状態を正しく取得し、それに応じて処理を行ってください。
    • 仮想キーコードの利用
      物理キーに依存せず、Qt::Key_... のような仮想キーコードを使用してキーを識別してください。
    • オートリピートの考慮
      キーが押されている間の連続的な処理が必要な場合は、keyPressEvent() を利用し、QKeyEvent::isAutoRepeat() でオートリピートかどうかを判別することもできます。
  • 原因

    • 修飾キーの状態
      QKeyEvent::key() は主にメインのキーコードを返します。Shift、Ctrl、Alt などの修飾キーの状態は QKeyEvent::modifiers() で別途確認する必要があります。
    • ロケールとキーボードレイアウト
      キーボードレイアウトやシステムのロケール設定によって、同じ物理キーを押しても異なる仮想キーコードが生成されることがあります。アプリケーションは、特定の物理キーではなく、意図する機能に対応する仮想キーコードに基づいて処理を行うべきです。
    • オートリピート
      キーが押し続けられている場合、keyPressEvent() が繰り返し呼び出されますが、keyReleaseEvent() はキーが離されたときに一度だけ呼び出されます。キーが押されている間の処理は keyPressEvent() で行うべきです。

修飾キーの状態が正しく取得できない

  • トラブルシューティング

    • keyReleaseEvent() 内で確認
      キーが離された瞬間の正確な修飾キーの状態は、keyReleaseEvent() の引数である QKeyEvent オブジェクトから取得してください。
    • 状態の保持
      複数のキーの状態を追跡する必要がある場合は、keyPressEvent() でキーの状態を記録し、keyReleaseEvent() で更新するなどの方法を検討してください。
  • 原因

    • イベントのタイミング
      キーが押された瞬間と離された瞬間で修飾キーの状態が変化している可能性があります。keyReleaseEvent() が呼び出された時点での修飾キーの状態を確認してください。
    • 他のキーとの同時押し
      複数のキーが同時に押されている場合、修飾キーの状態が期待通りにならないことがあります。

シーン全体でキーイベントを処理したい

  • トラブルシューティング

    • アイテムのフォーカス設定
      シーン全体でキーイベントを処理したい場合は、シーン内のどのアイテムもキーボードフォーカスを持たないようにするか、またはアイテムで処理されないキーイベントを event->ignore() でシーンに伝播させるようにします。
    • シーンへのフォーカス設定
      QGraphicsView::setFocus() を呼び出してビューにフォーカスを設定し、さらに QGraphicsView::setFocusPolicy(Qt::StrongFocus) などを設定して、ビューがキーイベントを受け取れるようにします。その後、シーンの keyReleaseEvent() が呼び出されるようにします。
  • 原因

    • アイテムがフォーカスを奪っている
      シーン内のアイテムがフォーカスを持っている場合、キーイベントは最初にそのアイテムに送信されます。

特定のアイテムのキー離しイベントを処理したい

  • トラブルシューティング

    • フォーカス可能フラグの設定
      処理したいアイテムに対して setFlag(QGraphicsItem::ItemIsFocusable) を呼び出し、フォーカスを受け取れるように設定してください。その後、setFocus() を呼び出してそのアイテムにフォーカスを与えます。アイテムがフォーカスを持っていれば、そのアイテムの keyReleaseEvent() が呼び出されます。
  • 原因

    • アイテムが setFlag(QGraphicsItem::ItemIsFocusable) されていない
      アイテムがフォーカスを受け取れるように設定されていない可能性があります。

デバッグのヒント

  • フォーカスの追跡
    どのオブジェクトが現在フォーカスを持っているかを確認するために、QWidget::focusWidget() などを利用してください。
  • イベントフィルターの調査
    イベントフィルターを使用している場合は、その処理内容を慎重に確認してください。
  • qDebug() の利用
    keyReleaseEvent() 内で qDebug() を使用して、発生したキーコード、修飾キーの状態などを出力し、イベントが正しく処理されているか確認してください。


例1: シーン全体でキーの離しを検知し、メッセージを表示する

この例では、QGraphicsScene を継承したカスタムシーンを作成し、キーが離されたときにどのキーが離されたかと、修飾キーの状態を表示します。

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

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {}

protected:
    void keyReleaseEvent(QKeyEvent *event) override {
        int key = event->key();
        Qt::KeyboardModifiers modifiers = event->modifiers();

        qDebug() << "キーが離されました:";

        switch (key) {
        case Qt::Key_Space:
            qDebug() << "  スペースキー";
            break;
        case Qt::Key_A:
            qDebug() << "  'A' キー";
            break;
        case Qt::Key_Up:
            qDebug() << "  ↑ キー";
            break;
        default:
            qDebug() << "  仮想キーコード:" << key;
            break;
        }

        if (modifiers & Qt::ShiftModifier) {
            qDebug() << "  Shiftキーが押されています。";
        }
        if (modifiers & Qt::ControlModifier) {
            qDebug() << "  Ctrlキーが押されています。";
        }
        if (modifiers & Qt::AltModifier) {
            qDebug() << "  Altキーが押されています。";
        }

        // デフォルトのイベント処理を呼び出す(必要に応じて)
        QGraphicsScene::keyReleaseEvent(event);
    }
};

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

    MyScene scene;
    scene.setSceneRect(-100, -100, 200, 200);

    QGraphicsView view(&scene);
    view.show();
    view.setFocus(); // ビューにフォーカスを設定

    return a.exec();
}

説明

  1. MyScene クラスは QGraphicsScene を継承しています。
  2. keyReleaseEvent() 関数をオーバーライドし、キーが離されたときに呼び出される処理を記述しています。
  3. event->key() で離されたキーの仮想キーコードを取得します。
  4. event->modifiers() で修飾キー(Shift, Ctrl, Alt など)の状態を取得します。
  5. qDebug() を使用して、離されたキーと修飾キーの状態をコンソールに出力します。
  6. main() 関数では、MyScene のインスタンスを作成し、QGraphicsView に表示しています。view.setFocus() を呼び出すことで、ビュー(ひいてはシーン)がキーイベントを受け取れるようにしています。

例2: 特定のアイテムのキーの離しを検知し、アイテムの状態を変更する

この例では、シーンに矩形のアイテムを追加し、そのアイテムがフォーカスを持っているときに特定のキーが離されると、アイテムの色を変更します。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QKeyEvent>
#include <QDebug>
#include <QBrush>

class MyRectItem : public QGraphicsRectItem {
public:
    MyRectItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent = nullptr)
        : QGraphicsRectItem(x, y, width, height, parent) {
        setFlag(ItemIsFocusable); // フォーカスを受け取れるように設定
        setBrush(Qt::red);
    }

protected:
    void keyReleaseEvent(QKeyEvent *event) override {
        if (event->key() == Qt::Key_Space) {
            setBrush(Qt::blue);
            qDebug() << "矩形の色が青に変わりました (スペースキー離し)。";
        } else {
            // 他のキーイベントは親クラスに渡す
            QGraphicsRectItem::keyReleaseEvent(event);
        }
    }

    void focusInEvent(QFocusEvent *event) override {
        qDebug() << "矩形がフォーカスを受け取りました。";
        QGraphicsRectItem::focusInEvent(event);
    }

    void focusOutEvent(QFocusEvent *event) override {
        qDebug() << "矩形からフォーカスが離れました。";
        QGraphicsRectItem::focusOutEvent(event);
    }
};

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

    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);

    MyRectItem *rect = new MyRectItem(-50, -50, 100, 100);
    scene.addItem(rect);
    rect->setFocus(); // 最初から矩形にフォーカスを設定

    QGraphicsView view(&scene);
    view.show();

    return a.exec();
}

説明

  1. MyRectItem クラスは QGraphicsRectItem を継承しています。
  2. setFlag(ItemIsFocusable) をコンストラクタで呼び出すことで、このアイテムがキーボードフォーカスを受け取れるように設定しています。
  3. keyReleaseEvent() をオーバーライドし、離されたキーがスペースキー (Qt::Key_Space) であれば、矩形の色を青に変更します。
  4. スペースキー以外のキーが離された場合は、親クラスの keyReleaseEvent() を呼び出して、デフォルトの処理を継続します。
  5. focusInEvent()focusOutEvent() をオーバーライドして、アイテムがフォーカスを受け取ったり失ったりしたときにメッセージを表示します。
  6. main() 関数では、MyRectItem のインスタンスを作成し、シーンに追加した後、rect->setFocus() を呼び出して初期フォーカスを矩形に設定しています。

例3: シーンで特定のキーの離しを検知し、シーン内のすべてのアイテムを移動させる

この例では、シーン上で特定のキー(例えば 'R' キー)が離されると、シーン内のすべてのアイテムを少し右に移動させます。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QKeyEvent>
#include <QDebug>

class MyScene : public QGraphicsScene {
public:
    MyScene(QObject *parent = nullptr) : QGraphicsScene(parent) {
        addItem(new QGraphicsRectItem(-40, -40, 80, 80));
        addItem(new QGraphicsEllipseItem(50, -30, 60, 60));
    }

protected:
    void keyReleaseEvent(QKeyEvent *event) override {
        if (event->key() == Qt::Key_R) {
            qDebug() << "'R' キーが離されました。すべてのアイテムを右に移動します。";
            for (QGraphicsItem *item : items()) {
                item->moveBy(10, 0);
            }
        } else {
            QGraphicsScene::keyReleaseEvent(event);
        }
    }
};

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

    MyScene scene;
    scene.setSceneRect(-150, -100, 300, 200);

    QGraphicsView view(&scene);
    view.show();
    view.setFocus(); // シーンがキーイベントを受け取るためにビューにフォーカスを設定

    return a.exec();
}
  1. MyScene クラスは QGraphicsScene を継承しています。
  2. コンストラクタで2つのグラフィックアイテム(矩形と楕円)をシーンに追加しています。
  3. keyReleaseEvent() をオーバーライドし、離されたキーが 'R' キー (Qt::Key_R) であれば、items() メソッドでシーン内のすべてのアイテムのリストを取得し、それぞれのアイテムの moveBy(10, 0) メソッドを呼び出して右に10ピクセル移動させます。
  4. 'R' キー以外のキーが離された場合は、親クラスの keyReleaseEvent() を呼び出して、デフォルトの処理を継続します。
  5. main() 関数では、MyScene のインスタンスを作成し、QGraphicsView に表示しています。view.setFocus() を呼び出すことで、ビューがキーイベントを受け取り、それがシーンに伝播されるようにしています(シーン内のアイテムがフォーカスを持っていない場合)。


QGraphicsItem::keyReleaseEvent() を使用する

シーン全体ではなく、特定のグラフィックアイテムのキー離しイベントを処理したい場合、QGraphicsItem クラスの keyReleaseEvent() をオーバーライドします。

  • 使用例
    前述の「例2: 特定のアイテムのキーの離しを検知し、アイテムの状態を変更する」がこれに該当します。アイテムをフォーカス可能にし (setFlag(ItemIsFocusable)), フォーカスを得たアイテムがキー離しイベントを処理します。

  • 欠点

    • シーン全体に関わるキー操作を処理するには、すべての関連アイテムで同様の処理を実装するか、イベントを親に伝播させる必要があります。
    • アイテムがフォーカスを持っていないと、そのアイテムの keyReleaseEvent() は呼び出されません。
    • 特定のアイテムの操作に直接関連するキーイベントを、そのアイテム内で処理できるため、コードの локализация(局所化)と可読性が向上します。
    • 複数のアイテムが異なるキー操作に応答する場合に、それぞれのアイテムで個別の処理を実装できます。

イベントフィルターを使用する

QObject::installEventFilter() を使用して、QGraphicsScene または QGraphicsView オブジェクトにイベントフィルターをインストールすることで、キー離しイベントを含むすべてのイベントを傍受し、処理することができます。

  • 使用例

  • 欠点

    • イベントの流れを理解し、適切にフィルタリングを設定する必要があります。誤ったフィルタリングは、他の部分のイベント処理に影響を与える可能性があります。
    • 複数のイベントフィルターがインストールされている場合、処理順序が重要になることがあります。
  • 利点

    • 既存のクラスを継承せずに、外部からイベント処理を追加できます。
    • シーン全体またはビュー全体で一元的にキーイベントを処理できます。
    • 特定の条件に基づいてイベントをフィルタリングし、処理するかどうかを決定できます。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QKeyEvent>
#include <QDebug>

class MyEventFilter : public QObject {
protected:
    bool eventFilter(QObject *watched, QEvent *event) override {
        if (event->type() == QEvent::KeyRelease) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
            if (keyEvent->key() == Qt::Key_Escape) {
                qDebug() << "イベントフィルターで Escape キーの離しを検知しました。";
                // ここで Escape キーが離されたときの処理を行う
                return true; // イベントをこれ以上伝播させない
            }
        }
        // その他のイベントは通常通り処理させる
        return QObject::eventFilter(watched, event);
    }
};

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

    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);
    scene.addItem(new QGraphicsRectItem(-20, -20, 40, 40));

    QGraphicsView view(&scene);
    view.show();
    view.setFocus();

    MyEventFilter *filter = new MyEventFilter;
    view.installEventFilter(filter); // ビューにイベントフィルターをインストール

    return a.exec();
}

この例では、MyEventFilter クラスが QGraphicsView にインストールされ、Escape キー (Qt::Key_Escape) が離されたときにメッセージを出力します。

QAction とキーショートカットを使用する

GUIアプリケーションでは、メニューバーやツールバーの QAction にキーショートカットを設定することで、特定のキー操作に対応するアクションを定義できます。キーが押されたり離されたりするタイミングではなく、ショートカットキーが押されたときに QActiontriggered() シグナルが発行されます。

  • 使用例

  • 欠点

    • グラフィックアイテムの内部状態に直接影響を与えるような、より細かい制御が必要な場合には不向きです。
    • 主にアプリケーションのコマンドや機能に関連するキー操作に適しています。
  • 利点

    • GUI要素とキー操作を関連付けやすく、ユーザーにとっても直感的です。
    • キーショートカットはプラットフォーム間で一貫性があります。
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QMenuBar>
#include <QAction>
#include <QDebug>
#include <QMainWindow>

class MainWindow : public QMainWindow {
public:
    MainWindow() {
        QGraphicsScene *scene = new QGraphicsScene(this);
        scene->setSceneRect(-100, -100, 200, 200);
        scene->addItem(new QGraphicsRectItem(-20, -20, 40, 40));

        QGraphicsView *view = new QGraphicsView(scene);
        setCentralWidget(view);
        view->setFocus();

        QMenuBar *menuBar = new QMenuBar(this);
        QMenu *fileMenu = menuBar->addMenu("ファイル");
        QAction *exitAction = new QAction("終了", this);
        exitAction->setShortcut(Qt::Key_Q); // 'Q' キーをショートカットに設定
        connect(exitAction, &QAction::triggered, this, &QMainWindow::close);
        fileMenu->addAction(exitAction);
        setMenuBar(menuBar);

        QAction *spaceAction = new QAction("スペースアクション", this);
        spaceAction->setShortcut(Qt::Key_Space); // スペースキーをショートカットに設定
        connect(spaceAction, &QAction::triggered, this, [](){
            qDebug() << "スペースキーショートカットがトリガーされました。";
        });
        addAction(spaceAction); // ウィンドウ全体でショートカットを有効にする
    }
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

この例では、MainWindow に 'Q' キーとスペースキーのショートカットを持つ QAction が追加されています。これらのキーが押されると、対応する triggered() シグナルが発行されます。

QInputMethod を使用する (高度なケース)

より複雑なテキスト入力や、IME (Input Method Editor) との連携が必要な場合には、QInputMethod クラスとその関連するイベントを扱うことがあります。ただし、これは通常のキー操作の検知とは異なる、より高度なトピックです。

どの方法を選ぶべきか?

  • メニュー項目やツールバーボタンなどのGUI要素とキー操作を関連付けたい場合は、QAction とキーショートカットを使用するのが適切です。
  • シーン全体またはビュー全体で一元的にキーイベントを処理したい場合や、既存のクラスを変更せずに処理を追加したい場合は、イベントフィルターが有効です。
  • 特定のアイテムの操作に関連するキーイベントであれば、QGraphicsItem::keyReleaseEvent() をオーバーライドするのが自然です。