Qt 5.4 ことはじめ #2

前回の日記に続き、Qt を触っていました。 Qt 5.4 で C++ のコーディングをしていたので忘れないうちにノートしておきます。 今回やったことは:

  • C++14 に対応する
  • Qt でプラットフォーム特有のコードを書く
  • Qt のバージョンを識別する
  • QMake でインクルードパスの指定
  • QMake に依存するライブラリとして libcurl (cURL) を追加する
  • Qt Network を使う
  • QTimer::timeout のシグナルを一回だけ呼ばれるようにする
  • QString から UTF-8 の std::string に変換する
  • Qt で JSON をパーシングする
  • Qt のアサーション
  • C++ から QML のオブジェクトを取得する

C++14 に対応する

Qt 5.4 または Qt Creator で C++14 に対応するには次のように QMake ファイル (.pro) に追加します。

CONFIG += c++14

同様に C++11 の場合は:

CONFIG += c++11

ただし、OS X の場合、これだけでは C++14 を使うことができません。 以下の行を QMake ファイルに追加する必要があります。

QMAKE_MACOSX_DEPLOYMENT_TARGET=10.9

QMAKE_MACOSX_DEPLOYMENT_TARGET は アプリケーションの動作環境を OS X 10.9 以上に指定するビルド設定です。これは Xcode で言えば MACOSX_DEPLOYMENT_TARGET に相当します。

Note:
Qt に限らず、例えば libc++ を使う場合は OS X 10.7 以上でないと Xcode でビルドできません。今回は C++14 を使うため 10.9 を指定しています。

Qt でプラットフォーム特有のコードを書く

Qt にはプラットフォーム特有の機能にアクセスする手段が用意されています。 プラットフォームごとのコードは次のように書くことができます:

#ifdef Q_WS_WIN
// Windows
#endif

#ifdef Q_WS_MAC
// Mac OS X
#endif

#ifdef Q_WS_X11
// X11
#endif

#ifdef Q_WS_QWS
// QWS
#endif

WS は使用しているウィンドウシステム (Window System) を示しています。 Q_WS_QWS というのは、組み込み Linux 向けの Qt Window System (QWS) のときに有効になるプリプロセッサです。

ウィンドウシステムではなく OS を識別するには同様に:

#ifdef Q_OS_WIN32
// Windows
#endif

#ifdef Q_OS_MAC
// Mac OS (= Darwin)
#endif

#ifdef Q_OS_LINUX
// Linux
#endif

他にも Q_OS_UNIXQ_OS_OPENBSD, Q_OS_CYGWIN などが識別可能です。OS に関しては環境によって複数定義されるようです。例えば次のコードは Mac OS X で有効です。

#if defined(Q_OS_UNIX) && defined(Q_OS_MAC) && defined(Q_OS_DARWIN)
    qDebug() << "This platform is unix";
#endif

Qt のバージョンを識別する

QT_VERSION を使って、使用している Qt のバージョンを識別することができます。次の例では Qt のバージョンが 5.4.1 以上かどうかを判断しています。

#if QT_VERSION >= 0x050401
    qDebug() << QString::number(QT_VERSION, 16);
#endif

QMake でインクルードパスの指定

Qt が提供するモジュール以外のライブラリを使うことがあります。インクルードパスを通すには QMake ファイルに次のように指定します。

INCLUDEPATH += path/to/rapidjson/include

複数のパスを指定する場合は:

INCLUDEPATH += path/to/rapidjson/include path/to/entityx/include

また空白 (white space) を含む行を指定する場合は次のように指定します:

INCLUDEPATH += "path/to/my headers"

プラットフォームごとにパスを指定することもできます。次の例は、win32 と unix 環境でパスを指定しています。

win32:INCLUDEPATH += C:/path/to/include
unix:INCLUDEPATH += usr/include

QMake に依存するライブラリとして libcurl (cURL) を追加する

QMake ファイル (.pro) の LIBS-lcurl を追加します。

LIBS += -lcurl

ライブラリパスが通ってない環境も考えられます。例えば Win32 環境などに対応するには次のように記述します。

win32 {
    lessThan(QT_MAJOR_VERSION, 5) {
        INCLUDEPATH += /path/to/curl
    }
} else {
    LIBS += -lcurl
}

これで Qt Creator 上で libcurl が使えようになります。試しに次のコードで動作確認できます。

extern "C" {
#include <curl/curl.h>
}

int main(int argc, char *argv[])
{
  CURL* curl = curl_easy_init();
  curl_easy_cleanup(curl);
  return 0;
}

別途 libcurl を追加せずとも、実のところ Qt にはネットワークライブラリとして Qt Network が用意されています。

Qt Network を使う

Qt 5 にはネットワークプログラミングを簡単に扱うことができる Qt Network モジュール が用意されています。

HTTP, TCP, UDP, SSL とよく使われるプロトコルはサポートしているようです。 何が嬉しいかというと、Qt 5 の開発環境さえ入れていればこの Qt Network の機能がどこでも使えることです。 ビルドするときに、あれがない・これがないと悩む必要はありません。Qt SDK へのファイルパスがわかれば、依存関係は QMake が解決してくれます。ポータビリティに優れています。

Qt Network モジュールを使うには QMake に network を追加します。

QT += network

QTimer::timeout のシグナルを一回だけ呼ばれるようにする

QTimer クラスを使うと簡単にネットワークのタイムアウト処理などを実装することができます。QTimer::timeout のシグナルでイベントを受け取ることができます。デフォルトでは QTimer::setInterval で指定したインターバル毎に何度もこのシグナルは呼び出されます。一度だけ呼び出されるようにするには QTimer::setSingleShot(true) を指定します。例えば次のようになります。

QTimer* timer = new QTimer(this);
timer->setInterval(std::chrono::milliseconds(100).count());
timer->setSingleShot(true);

connect(timer, &QTimer::timeout, [=]{
    qDebug() << "Timeout";
});

timer->start();

QString から UTF-8 の std::string に変換する

QString を UTF-8 の std::string に変換します。

QString source;
QByteArray byteArray = source.toUtf8();
std::string text = std::string(byteArray.data(), byteArray.size());

Qt で JSON をパーシングする

QJsonDocument を使うと JSON をパーシングしたり、JSON オブジェクトに変換したりすることができます。

QString jsonString;
QJsonParseError parseError;

auto jsonDocument = QJsonDocument::fromJson(jsonString, &parseError);

if (parseError.error != QJsonParseError::NoError) {
    qDebug() << "Error: invalid Json format";
    return;
}

Qt のアサーション

Qt にはデバッグ時の簡易テストとして Q_ASSERT が用意されています。

Q_ASSERT(std::string("42") == std::to_string(42));

リリースビルドなどにアサートを含めない場合は、QT_NO_DEBUG を定義します。

C++ から QML のオブジェクトを取得する

C++ から取得したい Qt Quick オブジェクトに QML 上で objectName プロパティを追加します。

ApplicationWindow {
  objectName: "MyWindow"
  title: qsTr("himarinkolshizukuesu")
}

C++ から QML オブジェクトを見つけるには QQmlApplicationEngine::rootObjects()Object::objectName() を使います。 次の例では、main.qml から MyWindow オブジェクトを取得して、title プロパティの値を変更しています。

QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

for (auto rootObject: engine.rootObjects()) {
  if (rootObject->objectName() == "MyWindow") {
    rootObject->setProperty("title", "beam");
    break;
  }
}

オブジェクトが見つからない場合、または子オブジェクトを取ってくる場合は QObject::findChild<T> で子オブジェクトを探します。

QObject* rootObject = engine.rootObjects().front();
QObject* qmlObject = rootObject->findChild<QObject*>("MyWindow");

例えば次のような関数を定義しておくと、QML のルートのほうにあるオブジェクトの中から任意の名前のオブジェクトを見つけることができます:

auto findFromQml = [](QQmlApplicationEngine & engine, QString const& objectName)-> QObject* {
  for (auto rootObject: engine.rootObjects()) {
    if (rootObject->objectName() == objectName) {
      return rootObject;
    }

    QObject* qmlObject = rootObject->findChild<QObject*>(objectName);
    if (qmlObject != nullptr) {
      return qmlObject;
    }
  }
  return nullptr;
};

QObject* mainWindow = findFromQml(engine, "MyWindow");
mainWindow->setProperty("title", "mybeam");

ListView の Model を C++ で記述する

Qt Quick ではビュー (QML) で表示したいデータを モデル (model) として指定することができます。 QML 上で次のようにモデルを定義します。

ListModel {
  id: myModel
  ListElement {
    name: "himarinko"
    color: "red"
  }
  ListElement {
    name: "l"
    color: "green"
  }
  ListElement {
    name: "shizukuesu"
    color: "blue"
  }
}

上のデータを ListView で表示するには次のように QML 上で model プロパティを指定します。

ListView {
  id: myListView
  model: myModel
  delegate: Item {
    Row {
      id: row1
      spacing: 10
      Rectangle {
        width: 40
        height: 40
        color: model.modelData.color
      }
      Text {
        text: model.modelData.name
        anchors.verticalCenter: parent.verticalCenter
      }
    }
  }
}

このモデルは、C++ から指定することもできます。

QQmlApplicationEngine engine;

QList<QObject*> dataList;
dataList.append(new MyDataObject("himarinko", "red"));
dataList.append(new MyDataObject("l", "green"));
dataList.append(new MyDataObject("shizukuesu", "blue"));

auto context = engine.rootContext();
context->setContextProperty("myModel", QVariant::fromValue(dataList));

engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

QML での ListElement にあたるのは、この例では MyDataObject になります。 定義例を次に示します。

class MyDataObject : public QObject {
    Q_OBJECT

public:
    explicit MyDataObject(QObject *parent = 0);
    ~MyDataObject();

    MyDataObject(QString const& nameIn, QString const& colorIn)
        : name_(nameIn), color_(colorIn)
    {}

    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)

public:
    QString const& name() const {
        return name_;
    }

    void setName(QString const& nameIn) {
        name_ = nameIn;
    }

    QString const& color() const {
        return color_;
    }

    void setColor(QString const& colorIn) {
        color_ = colorIn;
    }

private:
    QString name_;
    QString color_;

signals:
    void nameChanged();
    void colorChanged();

public slots:
};

参考文献

Leave a Reply