覚え書きブログ

Qtの覚え書き(ジョイスティック編)

Qtで、下図のようにロボットを制御するためのジョイスティックのX, Y軸の位置と、ボタンの押下状態を表示するGUIアプリケーションを作成してみた。
f:id:hirotaka_hachiya:20160411185431p:plain

Qtには、ジョイスティックを扱うモジュールが無いため、今回はWindowsマルチメディアのAPI「mmsystem.h」を用いる。
手順を一応メモっておく。

1)まず、Qt_creatorで,「JoystickQT」という名前のDialogベースのプロジェクトを作成する。そして、Group Box、LabelおよびText Browserウィジェットを追加する。ここで、X、Y軸の位置とボタンの押下状態を表示するText Browserのウィジェットには、それぞれ「joyXtext」、「joyYtext」および「joyBTtext」というオブジェクト名を付与する。
f:id:hirotaka_hachiya:20160412095150p:plain


2)次に、ジョイスティックから位置を取得して、UIに送信するThreadクラスを定義する
具体的には、プロジェクトウィンドウにて、右クリックで「新しいファイルを追加」を選択し、「C++ Class」ファイルを選択する。そして、クラス名を「JoyThread」に設定し、以下のヘッダー「joythread.h」とメイン「joythread.cpp」を追加する。
f:id:hirotaka_hachiya:20160410194828p:plain

【joythread.h】

#ifndef JOYSTICKTHREAD_H
#define JOYSTICKTHREAD_H

#include <QThread>

//for joystick
#include <windows.h>
#include <mmsystem.h>

//inheritance of QThread to get joystick position
class JoyThread : public QThread
{
    Q_OBJECT

public:
    JoyThread();            //constructor
    ~JoyThread();           //destructor
    void stop();            //to stop thread

signals:
    //signal for sending joystick position
    void positionSignal(const QString &x, const QString &y, const QString &bt);

protected:
    void run(); //running process

private:
    volatile bool runFlag;  //flag for thread running (volatile: surpress optimization of compile)
    JOYINFOEX joyInfo;      //joystick interface
    int joyId;              //joystick id
};

#endif // JOYSTICKTHREAD_H

【joythread.cpp】

#include "joythread.h"

// constructor for setting joystick id (JOYSTICKID1) and initializing JOYINFOEX
JoyThread::JoyThread()
{
    runFlag = true;    // running flag ON

    // set joystick id to JOYSTICKID1
    this->joyId = JOYSTICKID1;

    // initialize JOYINFOEX
    joyInfo.dwSize = sizeof JOYINFOEX;
    joyInfo.dwFlags = JOY_RETURNALL;
}

// method for getting joystick position and sending to UI
void JoyThread::run()
{
    while(runFlag) // loop if runFlag on
    {
        // get joystick position
        if (joyGetPosEx(joyId, &joyInfo) == JOYERR_NOERROR){
            //convert to QString
            QString x  = "";
            QString y  = "";
            QString bt = "";
            x  = x.number(joyInfo.dwXpos);
            y  = y.number(joyInfo.dwYpos);
            bt = bt.number(joyInfo.dwButtons);

            // emit position signals
            emit positionSignal(x,y,bt);

            // sleep 100ms
            Sleep(100);
        }
    }
}

// method for stopping thred
void JoyThread::stop()
{
    runFlag = false; // running flag OFF
}

// destructor
JoyThread::~JoyThread()
{
    runFlag = false;
    wait();
}

ここで、クラスJoyThreadは、QThreadを継承している。概要は以下のようになる。

  • コンストラクタJoyThread()では、runFlagをtrueにし、ジョイスティックのIDを設定、JOYINFOEX変数を初期化。
  • run()では、100msごとにjoyGetPosExでジョイスティックのx,y位置とボタンの押下状態を取得。そして、シグナルpositionSignalで、x,y,btを送信。
  • stop()と、デストラクタで、runFlagをfalseに設定。

シグナルとスロットに関しては、以下のリンクに詳しく説明されている。
http://densan-labs.net/tech/qt/chapter3.html
http://blog.qt.io/jp/2010/06/17/signals-and-slots-2/

3)プロジェクトファイル「JoystickQT.pro」に、以下のようにライブラリ「winmm.lib」のパスを追加
win32:LIBS += "C:/Program Files (x86)/Microsoft SDKs/Windows/v7.1A/Lib/x64/winmm.lib"

4)メインウィンドウクラスのDialogに、JoyThreadのシグナルpositionSignalからx,y,btを受け取り、Text Browserに表示するスロットprintPositionSlotを追加
具体的には、以下のようにヘッダー「Dialog.h」とメイン「Dialog.cpp」を修正する。

【Dialog.h】

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
#include "joythread.h"

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = 0);
    ~Dialog();

private slots:
    //slots for printing joystick position
    void printPositionSlot(const QString &x,const QString &y,const QString &bt);

private:
    Ui::Dialog *ui;
    JoyThread myJoyThread;    //joystick thread with id 0
};

#endif // DIALOG_H

【Dialog.cpp】

#include "dialog.h"
#include "ui_dialog.h"

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);

    //start joystick thread
    myJoyThread.start();

    //connect JoyThread::positionSignal to Dialog::printJoyPosition
    connect(&myJoyThread, &JoyThread::positionSignal, this, &Dialog::printPositionSlot);
}

void Dialog::printPositionSlot(const QString &x, const QString &y, const QString &bt)
{
    ui->joyXtext->setText(x);
    ui->joyYtext->setText(y);
    ui->joyBTtext->setText(bt);
}

Dialog::~Dialog()
{
    delete ui;
}

ここで、クラスDialogは、QDialogを継承している。概要は以下のようになる。

  • dialog.hにて、JoyThreadオブジェクトのmyJoyThreadを宣言
  • コンストラクタにて、JoyThreadを開始し、JoyThreadのシグナルpositionSignalとスロットprintPositionSlotを接続。
  • スロットprintPositionSlotにて、x,y,btそれぞれをText BrowserのjoyXtext、joyYtext、joyBTtextに出力。

― シグナルpositionSignalとスロットprintPositionSlotとのQObject::connectにより結びつけられている。
connectの各引数は、以下の通り。
第1引数:シグナルを発行するオブジェクトアドレス(const QObject *sender)
第2引数:シグナル名(const char *signal)
第3引数:スロットで受信するオブジェクトアドレス(const QObject *receiver)
第4引数:スロット名(const char *method)

5)プロジェクトの「ビルド」と「実行」を実行する。
*proファイルの修正が反映されずリンクのエラーがでる場合がある。その場合は、手動でgmakeを実行すると解決する。


本プロジェクト一式は、下記からダウンロードすることができる。
https://drive.google.com/open?id=0B3uB4w2FEJbIMzBHMVJHUXBqV1k


Qtには、ジョイスティックを扱うモジュールが無いので、Windows独自のAPIを用いて対応した。そのため、残念ながら本コードはクロスプラットフォーム対応になっていない(Windowsでしか動かない)。
スマートフォンではジョイスティックを扱うことはまずないので、ジョイスティック需要はあまりないので、今後もQtは対応しなさそうだ。逆に言えば、ジョイスティック操作はWindowsMacおよびLinuxの3つに対応すればいいだけなので、Macroで3つの環境を切り替えるプログラムを自力で書いてもそれほど大変ではないのだろう。