Qtのコントロール - キーボード

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

Qt Widgetでのキーボードの入力制御は、ユーザインターフェースの重要な要素である。
基本的には、QKeyEventクラスを使用して、キーボードイベントを処理する。

主な方法として、keyPressEventメソッドおよびkeyReleaseEventメソッドをオーバーライドすることが挙げられる。
これらのメソッドは、キーの押下あるいはリリースした時に呼び出される。

例えば、特定のキーが押下された時に任意の動作を実行する場合、以下に示すように記述する。

 void MyWidget::keyPressEvent(QKeyEvent *event)
 {
    if (event->key() == Qt::Key_Return) {
       // [Enter]キーが押下された場合
    }
    else {
        QWidget::keyPressEvent(event);
    }
 }


また、ショートカットキーを設定する場合は、QShortcutクラスを使用する。
これにより、特定のキーの組み合わせに対してアクションを割り当てることができる。

フォーカスの制御も重要な要素である。
[Tab]キーを使用してウィジェット間を移動できるようにするには、setFocusPolicyメソッドを使用してフォーカスポリシーを設定する。

キーボードイベントをフィルタリングする場合は、eventFilterメソッドを使用することもできる。
これにより、特定のウィジェットに対するキーボードイベントをインターセプトして、カスタム処理を行うことが可能である。

アクセシビリティの観点からは、キーボードナビゲーションを適切に実装することが重要である。
これには、ショートカットキーの提供、論理的なタブ順序の設定、フォーカスインジケータの実装等が含まれる。

使用例としては、カスタムキーシーケンスの実装やテキスト入力のカスタマイズ等がある。
これらは、より複雑なユーザインターフェースや特殊な入力要件がある場合に役立つ。

キーボードの入力制御を実装する場合は、異なるプラットフォームや言語での動作の違いに注意する必要がある。
Qtでは、多くの場合これらの違いを抽象化するが、特定のケースでは追加の対応が必要になることがある。


単一キーの取得

単一キーが押下された場合を取得するには、keyPressEventメソッドを使用する。

ただし、フォーカスを持つオブジェクトに破棄されたイベントは、keyPressEventメソッドで受け取れない。
イベント一括処理で破棄されたイベントは受け取れない。

※注意

  • 基底クラスの呼び出し
    未処理のキーイベントを適切に処理するため、デフォルトケースで基底クラスのkeyPressEventを呼び出すことが推奨される。
  • キーの繰り返し
    長押しによるキーの繰り返しを区別する場合は、QKeyEventクラスのisAutoRepeatメソッドを使用する。
  • イベントの受理
    特定のキーイベントを完全に処理したことを示すため、QKeyEventクラスのacceptメソッドを呼び出す。

    イベントを完全に処理した後、それ以上の伝播を望まない場合は、QKeyEventクラスのacceptメソッドを呼び出す。
    イベントを処理した後、他のウィジェットやハンドラにも処理の機会を与える場合は、QKeyEventクラスのignoreメソッドを呼び出す、あるいは、何もしない。


 // Mainwindow.h
 
 // ...略
 
 protected:
    void keyPressEvent(QKeyEvent *pEvent);


 // Mainwindow.cpp
 
 // 押下したキー名をデバッグ出力する
 // その他のキーは、16進数を出力する
 void MainWindow::keyPressEvent(QKeyEvent *pEvent)
 {
    switch (pEvent->key()) {
       case Qt::Key_Escape:
          qDebug() << "Esc keyPress";
          pEvent->accept();
          break;
       case Qt::Key_Return:
          qDebug() << "Return keyPress";
          break;
       case Qt::Key_Enter:
          qDebug() << "Enter(keypad) keyPress";
          pEvent->accept();
          break;
       case Qt::Key_Home:
          qDebug() << "Home keyPress";
          pEvent->accept();
          break;
       case Qt::Key_Left:
          qDebug() << "Left keyPress";
          pEvent->accept();
          break;
       case Qt::Key_Down:
          qDebug() << "Down keyPress";
          pEvent->accept();
          break;
       case Qt::Key_Space:
          qDebug() << "Space keyPress";
          pEvent->accept();
          break;
       case Qt::Key_F1:
          qDebug() << "F1 keyPress";
          pEvent->accept();
          break;
       case Qt::Key_0:
          qDebug() << "0(keypad) keyPress";
          pEvent->accept();
          break;
       default:
          qDebug("keyPress %x", pEvent->key());
          QMainWindow::keyPressEvent(pEvent);  // 基底クラスの呼び出し
          break;
    }
 
    // 自動繰り返しを区別する場合
    if (pEvent->isAutoRepeat()) {
       qDebug() << "Key is auto-repeating";
    }
 }



モディファイアキーとの組み合わせ

[Ctrl]キーあるいは[Shift]キー等のモディファイアキーと組み合わせる場合は、QKeyEventクラスのmodifiersメソッドを使用する。

※注意
モディファイアキーと組み合わせた特定のアクションを実行する場合、そのアクションが完全にイベントを消費するならば、acceptメソッドを実行する。
モディファイアキーの状態を記録または確認するだけの場合、acceptメソッドは実行せずに、そのイベントを他のハンドラへ渡すほうがよい。

 // Mainwindow.h
 
 // ...略
 
 protected:
    void keyPressEvent(QKeyEvent *pEvent);


 // Mainwindow.cpp
 
 // 押下したキー名をデバッグ出力する
 // その他のキーは、16進数を出力する
 void MainWindow::keyPressEvent(QKeyEvent *pEvent)
 {
    // モディファイアキーの制御
    if (pEvent->modifiers() & Qt::ControlModifier) {
       if (pEvent->key() == Qt::Key_C) {
          // 特定のキーイベントのみを消費する場合
          qDebug() << "[Ctrl] + [C] pressed";
          pEvent->accept();
       }
       else {
          // ロギングのみを行う場合
          qDebug() << "Ctrl is pressed with" << pEvent->key();
       }
    }
    else {
       // それ以外の場合は、基底クラスを呼び出すことにより、他のウィジェットやハンドラがイベントを処理する機会を与えることが可能
       QMainWindow::keyPressEvent(pEvent);  // 基底クラスの呼び出し
    }
 }



複数のキーの取得

方法 1

1つのキーボードイベントで、複数のキーを取得する。

 // MainWindows.h
 
 class MainWindow : public QMainWindow
 {
 Q_OBJECT
 
 // ...略
 
 private:
    Ui::MainWindow *ui;
    bool m_bFirstRelease;
    QSet<Qt::Key> m_keysPressed;
 
 protected:
    keyPressEvent(QKeyEvent *pEvent)
    keyReleaseEvent(QKeyEvent *pEvent)
 
 // ...略
 
 };


 // MainWindow.cpp
 
 void MainWindow::keyPressEvent(QKeyEvent *pEvent)
 {
    m_bFirstRelease = true;
    m_keysPressed += pEvent->key();
 }
 
 void MainWindow::keyReleaseEvent(QKeyEvent *pEvent)
 {
    if(m_bFirstRelease)
    {
       processMultiKeys(keysPressed);
    }
 
    m_bFirstRelease = false;
 
    m_keysPressed -= pEvent->key();
 }


方法 2

押下したキーをQSetクラスに追加して、キーを離す時にQSetクラスから削除する。
イベントフィルタを使用して、主要なイベントを捕捉する。

 // MainWindow.h
 
 class MainWindow : public QMainWindow
 {
 Q_OBJECT
 
 // ...略
 
 private:
    Ui::MainWindow *ui;
    bool m_bFirstRelease;
    QSet<int> m_keysPressed;
 
 protected:
    bool eventFilter(QObject *obj, QEvent *pEvent);
    keyPressEvent(QKeyEvent *pEvent)
    keyReleaseEvent(QKeyEvent *pEvent)
 
 // ...略
 
 };


 // コンストラクタでイベントフィルタをインストールする
 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
 {
    installEventFilter(this);
 }
 
 bool MainWindow::eventFilter(QObject *obj, QEvent *pEvent)
 {
    if(pEvent->type() == QEvent::KeyPress)
    {
       m_keysPressed += ((QKeyEvent*)pEvent)->key();
 
       if(m_keysPressed.contains(Qt::Key_D) && m_keysPressed.contains(Qt::Key_W))
       {  // [D]キーと[W]キーを同時に押下する場合
          // ...処理を記述
       }
    }
    else if(pEvent->type() == QEvent::KeyRelease)
    {
       m_keysPressed -= ((QKeyEvent*)pEvent)->key();
    }
 
    return false;
 }


方法 3

イベントフィルタを使用して、キーボードやマウスのイベントを一括で処理する。
ただし、全てのイベントを処理できるか不明である。

イベント処理の順番は、以下のような流れである。

  1. フォーカスされているオブジェクトのイベント処理
  2. イベントフィルタ(eventFilterメソッド)
  3. mousePressEventメソッドやkeyPressEventメソッド等


KeyPress関連のイベント等は、イベントフィルタの前にオブジェクトで消費されるため、
オブジェクトで使用されなかったイベントのみをイベントフィルタで取得することができる。

 // MainWindow.h
 
 // イベントフィルタとイベントメソッドを宣言する
 protected:
    bool eventFilter(QObject *object, QEvent *event);
 
 private:
    bool eventKeyPress(QKeyEvent *event);
    bool eventKeyRelease(QKeyEvent *event);


 // MainWindow.cpp
 
 // コンストラクタでイベントフィルタをインストールする
 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
 {
    ui->setupUi(this);
    installEventFilter(this);
 }
 
 // イベントごとに必要な処理を行う
 // trueを返すとイベントは破棄される
 // installEventFilterメソッドのオブジェクト(QObject)が第1引数として渡されるが、
 // ここでは使用しないため、名前を指定しない
 bool MainWindow::eventFilter(QObject *, QEvent *event)
 {
    bool bRtn = false;
 
    if(event->type() == QEvent::KeyPress)
    {
       bRtn = eventKeyPress(static_cast<QKeyEvent *>(event));
    }
    else if(event->type() == QEvent::KeyRelease)
    {
       bRtn = eventKeyRelease(static_cast<QKeyEvent *>(event));
    }
 
    return bRtn;
 }
 
 // キー押下時の処理
 // 特定のキーを判定してデバッグ出力する
 // その他のキーはfalseを返してイベントを残す
 // キーを押下している間のリピートキーは破棄する
 // イベントが取得できるかどうかは、フォーカス次第となる
 bool MainWindow::eventKeyPress(QKeyEvent *event)
 {
    if(event->isAutoRepeat())
    {
       return true;
    }
 
    switch(event->key())
    {
       case Qt::Key_Escape:
          qDebug() << "Esc press";
          break;
       case Qt::Key_Return:
          qDebug() << "Return press";
          break;
       case Qt::Key_Enter:
          qDebug() << "Enter(keypad) press";
          break;
       case Qt::Key_Home:
          qDebug() << "Home press";
          break;
       case Qt::Key_Left:
          qDebug() << "Left press";
          break;
       case Qt::Key_Down:
          qDebug() << "Down press";
          break;
       case Qt::Key_Space:
          qDebug() << "Space press";
          break;
       case Qt::Key_F1:
          qDebug() << "F1 press";
          break;
       case Qt::Key_0:
          qDebug() << "0(keypad) press";
          break;
       default:
          qDebug("press %x", event->key());
          return false;
    }
 
    return true;
 }
 
 // キーを離した時の処理
 // KeyReleaseイベントではオブジェクトをあまり消費しないため、イベントを取得できる可能性が高い
 bool MainWindow::eventKeyRelease(QKeyEvent *event)
 {
    if(event->isAutoRepeat())
    {
       return true;
    }
 
    switch(event->key())
    {
       case Qt::Key_Tab:
          qDebug() << "Tab release";
          break;
       case Qt::Key_Backtab:
          qDebug() << "Backtab release";
          break;
       default:
          qDebug("release %x", event->key());
          return false;
    }
 
    return true;
 }


方法 4

一括処理でKeyPressイベントを取得する場合は、QEvent::ShortcutOverrideを使用して、標準ショートカットを上書きする。
ただし、同じキーが2つセット(PressとRelease)で来るので注意が必要である。

 // MainWindow.h
 
 class MainWindow : public QMainWindow
 {
 Q_OBJECT
 
 // ...略
 
 // イベントフィルタとメソッドを宣言する
 protected:
    bool eventFilter(QObject *obj, QEvent *pEvent);
 
 private:
    bool MyKeyPress(QKeyEvent *pEvent);
 
 // ...略
 
 }


 // MainWindow.cpp
 
 // コンストラクタでイベントフィルタをインストールする
 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
 {
    ui->setupUi(this);
    installEventFilter(this);
 }
 
 // ShortcutOverrideで標準ショートカットを上書きしてKeyPressを取得する
 bool MainWindow::eventFilter(QObject *obj, QEvent *pEvent)
 {
    bool bRtn = false;
 
    if(pEvent->type() == QEvent::ShortcutOverride)
    {
       bRtn = MyKeyPress(static_cast<QKeyEvent *>(pEvent));
    }
 
    return bRtn;
 }
 
 // キー押下時の処理
 // スイッチを使用して1つ破棄する
 bool MainWindow::MyKeyPress(QKeyEvent *pEvent)
 {
    static int sw = -1;
 
    if(pEvent->isAutoRepeat())
    {
       return true;
    }
 
    if(pEvent->key() == sw)
    {
       sw = -1;
       return true;
    }
 
    sw = pEvent->key();
 
    switch(pEvent->key())
    {
       case Qt::Key_Escape:
          qDebug() << "Esc ScutOver";
          break;
       case Qt::Key_Return:
          qDebug() << "Return ScutOver";
          break;
       case Qt::Key_Enter:
          qDebug() << "Enter(keypad) ScutOver";
          break;
       case Qt::Key_Home:
          qDebug() << "Home ScutOver";
          break;
       case Qt::Key_Left:
          qDebug() << "Left ScutOver";
          break;
       case Qt::Key_Down:
          qDebug() << "Down ScutOver";
          break;
       case Qt::Key_Space:
          qDebug() << "Space ScutOver";
          break;
       case Qt::Key_F1:
          qDebug() << "F1 ScutOver";
          break;
       case Qt::Key_0:
          qDebug() << "0(keypad) ScutOver";
          break;
       default:
          qDebug("ScutOver %x", pEvent->key());
          return false;
    }
 
    return true;
 }



複数キーとシグナル

以下の例では、[Ctrl] + [Shift] + [↓]キーを同時押下する時にシグナルを発行している。
シグナルを受信するメソッドは、メッセージボックスを表示してソフトウェアを終了する。

また、シグナルを使用せずにスロットを呼ぶことで同様のことが可能である。

 // Mainwindow.h
 
 class MainWindow : public QMainWindow
 {
 Q_OBJECT
 
 // ...略
 
 protected:
    bool eventFilter(QObject *obj, QEvent *pEvent);
 
 signals:
    void myCloseSIG();
 
 private slots:
    void quitProgram();
 
 // ...略
 
 }


 // MainWindow.cpp
 
 // コンストラクタでシグナルを終了処理関数に接続して、イベントフィルタをインストールする
 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
 {
    ui->setupUi(this);
 
    connect(this, SIGNAL(myCloseSIG()), this, SLOT(quitProgram()));
    installEventFilter(this);
 }
 
 // イベント処理
 // [↓]キーを押下する場合、[Shift]キーと[Ctrl]キーが押下されていることを確認してシグナルを発行する
 bool MainWindow::eventFilter(QObject *obj, QEvent *pEvent)
 {
    if(pEvent->type() == QEvent::ShortcutOverride)
    {
       QKeyEvent *eKey = static_cast<QKeyEvent *>(pEvent);
 
       if(eKey->key() == Qt::Key_Down)
       {
          if((eKey->modifiers() & (Qt::ShiftModifier)) && (eKey->modifiers() & (Qt::ControlModifier)))
          {
             emit myCloseSIG();
 
             return true;
          }
       }
    }
 
    return false;
 }


表. 併用キー[enum Qt::KeyboardModifier]

定義 意味
Qt::NoModifier なし
Qt::ShiftModifier Shift
Qt::ControlModifier Ctrl
Qt::AltModifier Alt
Qt::MetaModifier Meta
Qt::KeypadModifier Keypad