つくねの手帳

C++およびAndroidアプリ開発メインで何か書きたい

QThreadとWorkerクラスの後処理

Qtでの非同期処理はQThreadクラスと自前のWorkerクラスで以下のように実装できる。

Worker.h

class Worker : public Qobject
{
    Q_OBJECT
public:
    Worker(); // 親を指定してしまうとスレッドに渡せなくなる。
    ~Worker();

public slots:
    // 作業用スロット

};

main.cpp

QThread* workerThread = new QThread(this);
Worker* worker = new Worker();

worker->moveToThread(workerThread);

connect(workerThread , SIGNAL(finished()) , workerThread , SLOT(deleteLater)):
connect(workerThread , SIGNAL(finished()) , worker , SLOT(deleteLater)):
// 作業用スロットのコネクト

workerThread->start();

スレッドに処理を依頼するときは、作業用スロットに対してシグナルを送ってあげることで実行できる。

アプリ終了時等でスレッドを止める際は、

workerThread->quit();

とすることで、QThreadクラスないでfinishedシグナルが発行され、workerクラスとスレッドがdeleteされる。


単発的な処理(ライフサイクルの短いスレッド)の場合は以下のように実装することで、外からQThreadのquit()を呼ぶことなく並列処理を実行できる。

Worker.h

public slots:
    void process();
signals:
    void processEnd();

Worker.cpp

void Worker::process()
{
    // 何かしらの処理

    // 処理終了
    emit this->processEnd():
}

main.cpp

QThread* workerThread = new QThread(this);
Worker* worker = new Worker();

worker->moveToThread(workerThread);

connect(workerThread , SIGNAL(started()) , worker , SLOT(process()));
connect(worker , SIGNAL(processEnd()) , workerThread , SLOT(quit()));
connect(worker , SIGNAL(processEnd()) , worker , SLOT(deleteLater()));
connect(workerThread , SIGNAL(finished()) , workerThread , SLOT(deleteLater()));

workerThread->start();

この場合、startが呼ばれたところからworkerクラスの処理が始まり、終了と同時に、workerクラスのprocessEnd→workerThreadのquit、それぞれのdeleteLaterがコールされ処理が完結する。

この流れをアプリ終了時等、メインスレッド側のデストラクタで呼び出すとスレッドのdeleteLaterによるスレッド削除が実行される前にデストラクタを抜けてしまうようで、QtCreatorのアプリケーション出力に"QThread: Destroyed while thread is still running"と警告される。

QThreadのquit始動でworkerクラスをdeleteする場合は問題ないので、何かしらの解決策がありそうだが、見つかっていない。

QStringからcharポインタを取り出す

今回はちょっとしたTips的な話。

例によってQt4.8環境です。

QStringは文字列をとても簡単に取り扱えるクラスですが、一度文字を渡してしまうと、
charとして取り出すのに一つ関数を余計に挟む必要があります。
取り出し方はこんな感じです。

#include "QString.h"

QString string = "moji";

char* pChar = string.toUtf8().data();


Qtで開発している以上そんなに使用頻度はないかもしれませんが、知ってると便利。

Qt 同じ単語に複数の翻訳をあてるのに少し頑張った話2

前回からの続きです。
例によって、Qtのバージョンは4.8.4です。


.tsファイルと.qmファイルを自動的に作れるようにexcelのマクロを組みます。
excelのシート上に、enum(識別子)として使うID、対応する表示文言を列挙します。
マクロでヘッダファイルにenumと文字列を出力。
さらに、.tsファイルも作成しますが、lupdateを使うと不都合があるため、べた書きします。


もし、.tsファイル作成時にlupdateを使うと、

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.0">
<context>
    <name>QWidget</name>

クラス化されていないファイルへQT_TR_NOOP()で文字列を記載していると、QWidgetとして.tsが作成されます。
しかし、の部分は実際にtr()で文字列をシステムに渡すクラスを指定する必要があります。
前回の記事であれば、setStringIdを実装しているクラスとなります。


.tsファイルができたら、lreleaseコマンドをたたいて.qmファイルを作成しておしまいです。


すごーくざっくりしている上に、コードサンプルがないのでひどくわかりにくい感じになってしまった…

androidアプリのお勉強1 WebApiを使った天気予報アプリを目指して

Android Studioを使用して、Androidアプリ作成勉強を兼ねて、WebApiを使った天気予報アプリを作りたいなと思います。

WebApiを使用するには、当然インターネットアクセスが不可欠です。
そこで、アプリからandroidOSへインターネットアクセス許可をもらう必要があります。
そのために必要な設定を指定する必要があります。それがパーミッションです。
今回は、そのパーミッションについて少し書こうかと思います。



先ずは、プロジェクトを作成し、Web Apiを使うためにパーミッションを追加します。
Project>app>src>main>AndroidManifest.xmlに以下のコードを追加します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="プロジェクト名">

<!-- これを追加 -->
    <uses-permission android:name="android.permission.INTERNET"/>

今回はWebApiを使うためのパーミッションなので、インターネットアクセスを許可するINTERNETを指定しましたが、このほかにも色々なパーミッションが存在します。

・READ_CONTACTS
 ユーザーのアドレス帳データの読出し

・WRITE_CONTACTS
 ユーザーのアドレス帳データ書き込み

・RECEIVE_SMS
 SMS受信の監視

ACCESS_COARSE_LOCATION
 携帯中継局、WiFiなどのロケーションプロバイダを使用する。

ACCESS_FINE_LOCATION
 GPS情報など精度の高いロケーションプロバイダを使用する。

・READ_CALWNDAR
 カレンダー情報の読出し

・WRITE_CALENDAR
 カレンダーへの書き込み、メール送信の機能も使用できる。

このほかにもかなりたくさんのパーミッションがあります。
利用したい機能に合わせて指定を増やす必要があります。


また、Android6.0からは、各パーミッションについてユーザーがインストール時に利用可否を個別に指定できるようになりました。

Android6.0未満の場合は、アプリにパーミッションを与える→インストールで、与えたパーミッションの全てが許可されていましたが、Android6.0からはユーザーが選べるのです。
そのため、許可されなかったパーミッションに対して、その機能が使えない可能性を考慮してロジックを組む必要があります。

パーミッションが使用できるかはcheckSelfPermission関数で確認できます。
さらに、requestPermissions関数でユーザーに許可を求めることもできます。

Qt 同じ単語に複数の翻訳をあてるのに少し頑張った話1

この記事はQt4.8.4基準で記載します。

Qtにはソース上の文字列を拾い上げて、翻訳用のファイルを作っておくことで、プログラム実行時に動的言語変換ができます。
具体的な手順は割愛しますが、

QLabel text;
text.setText(tr("string"));

のように、trでくくった文字列をlupdateで抜き出し、xml形式の.tsファイルを生成します。
その.tsファイルをQtLinguistで手動翻訳し、lreleaseで.qmファイルを作り、その.qmファイルをプログラムで読み込みます。


.qmファイルがどのように生成されているかわからないので、実際に動作させてみて確かめただけなのですが、.tsファイル上に同じ単語が複数ある場合、別々な翻訳をしても、一番上に書かれたもので表示されてしまうようです。

QLabel text;
text.setText(tr("start")); // スタートと表示したい

QLabel text2;
text2.setText(tr("start")); // 開始と表示したい

このような場合、以下のように.tsファイルを記述しても、一律スタートと表示されてしまいます。

<!-- 日本語でスタートと表示したいやつ。 -->
    <message>
        <location filename="xxx" line=""/>
        <source>start</source>
        <translation type="unfinished">スタート</translation>
    </message>

<!-- 日本語で開始と表示したいやつ。 -->
    <message>
        <location filename="xxx" line=""/>
        <source>start</source>
        <translation type="unfinished">開始</translation>
    </message>


そこで、ソース上に書く文字列は識別用のIDとしてユニークなものとして扱う手段を取りました。

下準備として、以下のものを用意しました。
・文字列を指定するenum
enumに対応する文字列 QT_TR_NOOP("")の形で記載
・指定されたenumから文字列をsetText(tr(""));へ渡すラッパー関数

typedef enum
{
  E_TEXT_1,
  E_TEXT_2
}stringId;


QString* stringData[] = 
{
  QT_TR_NOOP("E_TEXT_1"),
  QT_TR_NOOP("E_TEXT_2"),
};
void setStringId(stringId id)
{
  this->m_pLabel->setText(tr(stringData[id])); //実際は配列直指定はやめましょう
}

かなりざっくり書きましたが、いままで""でくくって文字列としていた場所をenumの文字列で書くイメージです。

この場合、文字列が増えるたびにenum、文字列定義、.ts/.qmファイル作成が発生してとても面倒です。
そこで、.ts/.qmファイル作成まで自動でやってしまえ!というのが次回のお話。

Qt クリック可能なQLabelの実装と独自シグナル/スロット

QLabelクラスはマウスクリック時動作の関数が用意されていませんが、独自にクリック時のシグナルを作成することでクリック時の動作を行いやすくなります。

// QClickableLabel.h

// QLabelの拡張クラス定義

class QClickableLabel : public QLabel
{
    Q_OBJECT // シグナル/スロット使用に必要

public:
    QClickableLabel();
    ~QClickableLabel();

signals:
    // クリック時に発生させるシグナル
    void clicked();

protected:
    // 左クリック(LButtonDown)時に呼ばれる
    void mousePressEvent(QMouseEvent *e);
};
//QClickableLabel.cpp


void QClickableLabel::mousePressEvent(QMouseEvent *e)
{
    // シグナル送信
    emit cliced();
}


このクラスを使用してLabelを作ることで、クリック時にclicedのシグナルが発生することになります。
connectも以下のようにできます。

// MyClass.h

// クリック可能ラベルクラス前方宣言
class QClickableLabel

class MyClass : public QWidget
{
    Q_OBJECT // シグナル/スロット使用に必要

public:
    MyClass();
    ~MyClass();

public slots:
    // クリック時に呼びたい関数
    void OnClickedLabel();

private:

    QClickableLabel* m_pLabel;
};
// MyClass.cpp

#include "MyClass.h"
#include "QClickableLabel.h"

MyClass::MyClass()
{
    // クリック可能ラベルクラス生成
    this->m_pLabel = new QClickableLabel();

    // m_pLabelのcliced()シグナル発生時に、this(自クラス)のOnClickedLabel()が呼ばれるようにする
    connect(this->m_pLabel , SIGNAL(cliced()) , this , SLOT(OnClickedLabel()));
}

void MyClass::OnClickedLabel()
{
    // クリック時に行う処理
}

installEventFilterとeventFilterでもイベントハンドリングはできます。
しかし、よく使うイベントや、同一クラス内オブジェクトが多く、eventFilterが煩雑化する場合などは、シグナルとスロットを実装したほうがよさそうです。

Qt QWidgetの派生クラスへのスタイルシート設定

今日は、先日少しはまったQWidgetを基底クラスとしたクラスへのスタイルシート設定について。

QWidgetクラスは背景色のスタイル設定がサポートされています。
しかし、QLabelなどのように、そのままsetStyleSheet()を呼んでも反映されません。
以下のように、Q_OBJECTの指定と、paintEventを処理する必要があります。

// MyWidget.h
#include <QWidget>

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    MyWidget();
    ~MyWidget();

    void paintEvent(QPaintEvent*); // これが必要
};
// MyWidget.cpp

// スタイルシート設定を反映させるためのペイントイベント処理
void MyWidget::paintEvent(QPaintEvent *)
{
    QStyleOption opt;
    opt.init(this);
    QPainter p(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

QLabelなどはそのクラス内で処理してくれているようなのですが、QWidgetクラスでは実装されていないようです。
※なぜQWidgetクラスで処理が実装されていないのかはいまいちわからいままです…

Qtの公式リファレンスにも記載されているので参考に。
Qt Style Sheets Reference | Qt 4.8