つくねの手帳

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

Widgetの回転と枠線について

今回は、組み込み開発などにおいて、LCDの関係などでWidgetを回転させたい。という時に、QGraphicsSceneとQGtaphicsViewを用いた回転方法と、その際にWidgetに対してついてしまう枠線について書きたいと思います。

例によって、Qtのversionは4.8です。

先ず、Widgetの回転についてです。
Widgetの回転は、対象のWidgetをQGraphicsSceneへ渡し、そのQGraphicsSceneをQGraphicsViewへ登録。QGraphicsViewのrotate関数で実現できます。

  QGraphicsScene *scene = new QGraphicsScene();
  scene->addWidget(/*回転させたいWidgetハンドル*/);

  QGraphicsView *view = new QGraphicsView(scene); // QGraphicsViewにsceneを登録
  view->rotate(90); // 90度回転

これで対象のWidgetを回転させられたかと思います。
ただ、このままだとWidgetに1pxの枠線がついてしまうと思います。

  setWindowFlags(Qt::FramelessWindowHint);

などをWidgetに指定しても、消せないのですが、これには理由があります。

先程、回転のためにQGraphicsViewを使用しました。
このクラスは親クラスをたどるとQFrameクラスを継承しています。
その為デフォルト1pxの枠線を描画してしまいます。
枠線を描画したくない場合は、以下のようにQGraphicsViewにオプション指定をしましょう。

  view->setFrameShape(QFrame::NoFrame);

こうすることで、もとのイメージのまま回転が可能です。

QtCreatorでデバッグ中にブレークポイントにとまるとアプリがクラッシュする

この現象はQtCreator2.5.2という古いバージョンで発生したものなので、現在のQtCreatorでは発生しないかもしれません。

内容はタイトルの通りで、QtCreatorでQtプロジェクトのデバッグ実行中に、特定の場所、タイミングでブレークポイントんとに止まるとアプリがクラッシュするというものです。

結論から言うと、QtCreatorの監視ウィンドウが原因でした。


QtCreatorにはデバッグ中に変数の中身を表示させる監視ウィンドウというものがあります。
中身を見たい変数を右クリックし、「監視ウィンドウに追加」のメニューを選択することで、QtCreatorが変数の中身を表示してくれます。
VisualStudioでいうローカル変数タブのイメージですね。

デバッグ時には便利なのですが、以下のようなコードを監視することで、タイトルの現象が起きてしまいます。

hoge.h

class hoge

public:

  hoge();
  ~hoge();
  
  bool getFirstItem(int* buf); 

private
  QVector<int> m_vector; // メンバ変数にQVectorを定義
hoge.cpp

hoge::hoge()
{
  this->m_vector.clear(); 

  int a = 0;
  this->getFirstItem(&a); // ここにブレークポイントを置くと落ちる。ブレークせずに走らせる場合は問題なし。
}

bool hoge::getFirstItem(int* buf)
{
  bool bRet = false;
  
  if(false == this->m_vector->isEmpty())
  {
    buf = this->m_vector[0]; // ここのthis->m_vector[0]を監視ウィンドウに追加
    bRet = true;
  }

  return bRet;
}


QVectorは可変配列ですが、通常の配列のようにでアクセスが可能です。

当然、Vectorの中身がない場合はエラーになるのですが、上記のようにコード上はチェックをしていても、
監視ウィンドウにm_vector[0]のような変数を追加しておくと、このVectorはメンバ変数なので、クラス内どこでも参照可能です。

そのため、クラス内のブレークポイントに止まった際に、問答無用でアクセスエラーを起こします。

この時に、QtCreatorがエラーログをはいてくれるのですが、"ASSERT failure in QVector::operator:"index out of range",file....."
と配列オーバーアクセスなのはわかるのですが、どこで起きているのかわからないので、コード上を探しても配列オーバーアクセスを起こすコードは見つからない。
ということが起こります。

そもそもVectorにたいして[]でアクセスするな。という話かもしれませんが、ブレークしたときにVectorのアクセスエラーで落ちる。
なんて現象に遭遇した場合は、一度監視ウィンドウをチェックするほうがいいかもしれません。

Android SutudioにKotlinを導入する

先日のGoogle I/OにてKotlinが正式サポートされるということで、以前から興味があったKotlinを導入してみようと思う。

IDEAndroid Studio 2.3.2で行う。

先ずはKotlinのプラグインをインストールする。

File > Settings
からPluginsを選択し、Install JetBrains pluginsを選択する。
f:id:tukunen13:20170603193414p:plain

Browse JetBrains Plugins画面の左上検索バーからKotlinを検索し、
Installを行う。
f:id:tukunen13:20170603193759p:plain


Android Studioを再起動し、ToolsのプルダウンにKotlinの項目が追加されていれば完了。

既存のJavaコードは、Codeのプルダウンから、Convert Java File to Kotlin Fileを選択すると自動的に変換をかけてくれる。

画面上部にKotlin not configuredと表示されていると思うので、Configureボタンをクリックする。
Configure Kotlin ProjectのダイアログはそのままOKをクリック。
これでKotlinでの環境構築は完了となる。

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関数でユーザーに許可を求めることもできます。