2018-07-30

Raspberry Pi3を使ったリアル波形描画(3) Qt + QCustomPlot で静止グラフを描いてみる

Qt(キュート)とは何か。簡単に解説する。

【出典】
https://ja.wikipedia.org/wiki/Qt
https://www.sra.co.jp/qt/point/

Qt([kjuːt] キュート)とは、クロスプラットフォームアプリケーションフレームワークである。The Qt Company(英語版)とQt Project(英語版)によって開発されている。キュー・ティーと発音されることもあるが公式にはキュートである。
GUIツールキットとして広く知られているQtであるが、コンソールツールやサーバのような非GUIプログラムでも広く使用されている。

ライセンスには商用版とオープンソース版があり、現在のオープンソース版のライセンスはLGPLおよびGPLである。商用版を購入するとQt商用ライセンス(Qt Commercial License)でソフトウェアを開発することができる。LGPL版は、2009年3月にリリースされたQt 4.5から提供され始めた。これによりQtは営利企業にとってもより使いやすいライブラリーとなった。

QtはC++で開発されており、単独のソースコードによりX Window System(Linux、UNIX等)、Windows、macOS、組み込みシステムといった様々なプラットフォーム上で稼働するアプリケーションの開発が可能である。またコミュニティーにより多言語のバインディングが開発されており、JavaからQtを利用できるようにしたQt Jambi、さらにQtをRuby、Python、Perl、C#などから利用できるようにしたオープンソースのAPIが存在する。

このように開発が容易であり高速、スタイリッシュなQtはライセンスが多様なこともあり、KDEを始めとするオープンソースのアプリケーションに限らず、商業アプリケーションでの採用例も多く様々な分野で使用されている。

【開発元】
トロールテック (1991-2008)
ノキア (2008-2011)
ディジア(英語版) (2012-2014)
Qt Project(英語版) (2011-現在)

Qtを使用している主なソフトウェア

  • Autodesk Maya 2011 - 3DCGソフト
  • Avidemux - 動画編集ソフト
  • FreeCAD - CADソフト
  • Google Earth - 地球儀ソフト
  • KDE - デスクトップ環境
  • Muse - シーケンサ
  • MuseScore - 楽譜編集プログラム
  • Nuke - デジタル合成ソフト
  • ParaView - データ可視化ソフト
  • QGIS - GISソフト
  • Rosegarden - 楽譜編集プログラム
  • Skype - コミュニケーションソフト
  • モバイルWnn - 日本語入力システム
Google Earth や Skype が Qt で開発されていたとは知らなかった。

【Qt が開発効率を高める点】

  • Qt Creator を使うことで,GUI を直感的に設計することができる。(Visial Basic に似ている)
  • Qt Creator のテキストエディタが優れいて,必要なクラスやメソッドなどを予測入力(先頭の数文字入力すると候補を表示)してくれる。

Qt SIGNAL/SLOTの実践(値の連携)

Qt の重要な機能として SIGNAL/SLOT の機能がある。まずは、実際にどんなことができるのかやってみよう。

Qt Creator でQt ウィジェットアプリケーションのプロジェクトを作成する。





【下線部を入力する。】

#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QSpinBox *spin = new QSpinBox();
    QDial *dial = new QDial();

    spin->setGeometry(200, 200, 100, 20);   // X座標,Y座標,横の長さ,縦の長さ
    dial->setGeometry(200, 300, 100,100);   // X座標,Y座標,横の長さ,縦の長さ

    spin->show();
    dial->show();

    return a.exec();
}

実行すると下記のように表示される。ダイアルとスピンボックス値は連動していない。


スピンボックスとダイアルの値を SIGNAL/SLOT で連携させる

下線部を追加する。スピンボックスの値が変化したら,ダイアルの値をセットし,ダイアルの値が変化したら,スピンボックスの値をセットするように,SIGNAL/SLOTをつなげる。

#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QSpinBox *spin = new QSpinBox();
    QDial *dial = new QDial();

    QObject::connect(spin, SIGNAL(valueChanged(int)), dial, SLOT(setValue(int)));
    QObject::connect(dial, SIGNAL(valueChanged(int)), spin, SLOT(setValue(int)));

    spin->setGeometry(200, 200, 100, 20);   // X座標,Y座標,横の長さ,縦の長さ
    dial->setGeometry(200, 300, 100,100);   // X座標,Y座標,横の長さ,縦の長さ

    spin->show();
    dial->show();

    return a.exec();
}

これを実行すると,スピンボックスとダイアルが連動するようになる。
別々のオブジェクトが Qt の SIGNAL/SLOT のしくみでつながった例。

Qt のSIGNALとSLOTのしくみを理解する


SIGNALとSLOT
SIGNALとSLOTは、オブジェクト間の通信に使用されます。 SIGNALとSLOTのメカニズムはQtの中心的な機能であり、おそらく他のフレームワークが提供する機能と最も異なる部分です。 SIGNALとSLOTはQtのメタオブジェクトシステムによって可能になります。

前書き
GUIプログラミングでは、1つのウィジェットを変更すると、別のウィジェットに通知されることがよくあります。 より一般的には、あらゆる種類のオブジェクトがお互いに通信できるようにしたいと考えています。 たとえば、ユーザーが閉じるボタンをクリックすると、おそらくウィンドウのclose()関数が呼び出されます。

他のツールキットはコールバックを使用してこの種の通信を実現します。 コールバックは関数へのポインターなので、処理関数が何らかのイベントについて通知するようにするには、別の関数へのポインター(コールバック)を処理関数に渡します。 処理関数は、必要に応じてコールバックを呼び出します。 このメソッドを使用した正常なフレームワークは存在しますが、コールバックは直感的ではなく、コールバック引数の型の正確さを保証する上で問題が発生する可能性があります。

SIGNALとSLOT
Qtでは、コールバック手法の代わりにSIGNALとSLOTを使用します。 特定のイベントが発生すると、SIGNALが出力されます。 Qtのウィジェットにはあらかじめ定義されたSIGNALがたくさんありますが、ウィジェットをサブクラス化して独自のSIGNALを追加することができます。 SLOTは、特定のSIGNALに応答して呼び出される関数です。 Qtのウィジェットにはあらかじめ定義されたSLOTが多数ありますが、ウィジェットをサブクラス化して独自のSLOTを追加することで、関心のあるSIGNALを扱うことができます。


SIGNALと SLOTのメカニズムはタイプセーフです。SIGNALのシグネチャは、受信SLOTのシグネチャと一致する必要があります。 (実際には、余分な引数を無視することができるため、SIGNALは受け取ったSIGNALよりも短いシグネチャを持つ可能性があります)。シグネチャは互換性があるため、コンパイラは関数のポインタベースの構文を使用するときに型の不一致を検出するのに役立ちます。 文字列ベースのSIGNALおよびSLOT構文は、実行時に型の不一致を検出します。 SIGNALとSLOTは緩やかに結合されています。SIGNALを送信するクラスは、どのSLOTがSIGNALを受信するかを知らず、また気にもしません。 QtのSIGNALとSLOTの仕組みにより、SIGNALをSLOTに接続すると、適切なタイミングでSIGNALのパラメータでSLOTが呼び出されます。 SIGNALとSLOTは任意の種類の任意の数の引数を取ることができます。 彼らは完全にタイプセーフです。

QObjectまたはそのサブクラス(例えばQWidget )の1つを継承するすべてのクラスは、SIGNALとSLOTを含むことができます。 SIGNALは、他のオブジェクトにとって興味深い方法で状態を変更したときにオブジェクトによって放出されます。 これはすべてオブジェクトが通信するためのものです。 それは、何かがそれが放出するSIGNALを受信しているかどうかを知らない、または気にしない。 これは真の情報カプセル化であり、オブジェクトをソフトウェアコンポーネントとして使用できることを保証します。

SLOTはSIGNALを受信するために使用できますが、通常のメンバー機能でもあります。 オブジェクトがSIGNALを受信するかどうかをオブジェクトが知らないように、SLOTはSIGNALが接続されているかどうかを知りません。 これにより、本当に独立したコンポーネントをQtで作成できるようになります。

単一のSLOTに多くのSIGNALを接続することができ、SIGNALは必要な数のSLOTに接続することができます。 SIGNALを別のSIGNALに直接接続することも可能です。 (これは、最初のSIGNALが放射されるたびにすぐに2番目のSIGNALを放射します)。

SIGNALとSLOTが一緒になって、強力なコンポーネントプログラミングメカニズムを構成します。

SIGNAL
SIGNALは、オブジェクトの内部状態が何らかの方法で変化したときに、オブジェクトのクライアントまたは所有者にとって興味深い可能性のあるオブジェクトによって放出されます。 SIGNALはパブリック・アクセス関数であり、どこからでも送出することができますが、SIGNALとそのサブクラスを定義するクラスからのみ送出することをお勧めします。

SIGNALが放射されると、SIGNALに接続されたSLOTは通常、通常の関数呼び出しと同様にすぐに実行されます。 これが起こると、SIGNALとSLOTのメカニズムは、どのGUIイベントループからも完全に独立しています。 emit文に続くコードの実行は、すべてのSLOTが戻ったときに行われます。 キューに入れられた接続を使用する場合は、状況が少し異なります 。 そのような場合、 emitキーワードに続くコードは直ちに続き、SLOTは後で実行されます。

複数のSLOTが1つのSIGNALに接続されている場合、SIGNALが発信されたときに、接続されている順にSLOTが順次実行されます。

SIGNALはmocによって自動的に生成され、 .cppファイルに実装してはいけません。 彼らは決して戻り値の型を持つことはできません(つまりvoid使用しvoid )。

注記:私たちの経験によれば、SIGNALやSLOTは特別なタイプを使用しないと再利用可能です。 QScrollBar :: valueChanged ()が仮想QScrollBar :: Rangeなどの特殊な型を使用する場合、QScrollBar専用に設計されたSLOTにのみ接続できます。 異なる入力ウィジェットを一緒に接続することは不可能です。

SLOT
SLOTに接続されたSIGNALが放射されると、SLOTが呼び出されます。 SLOTは通常のC ++関数であり、通常呼び出すことができます。 その唯一の特殊な特徴は、SIGNALをそれらに接続できることです。

SLOTは通常のメンバ関数なので、直接呼び出されると通常のC ++の規則に従います。 しかし、SLOTとして、SIGNALSLOT接続を介して、アクセスレベルに関係なく、どのコンポーネントからでも呼び出すことができます。 これは、任意のクラスのインスタンスから発信されたSIGNALが、無関係のクラスのインスタンスでプライベートSLOTを呼び出させる可能性があることを意味します。

仮想SLOTを定義することもできますが、実際には非常に便利です。

コールバックと比較して、SIGNALとSLOTは、柔軟性が向上しているため、わずかに遅くなりますが、実際のアプリケーションの違いは重要ではありません。 一般に、いくつかのSLOTに接続されたSIGNALを放射することは、非仮想関数呼び出しを使用して、受信機を直接呼び出すよりも約10倍遅くなります。 これは、すべての接続を安全に反復する(つまり、後続の受信者が放出中に破棄されていないことを確認する)ために、接続オブジェクトの位置を特定するために必要なオーバーヘッドであり、パラメータを一般的な方法でマーシャルするために必要です。 10の非仮想関数呼び出しは多くのように聞こえるかもしれませんが、例えば、 new操作やdelete操作よりもはるかに少ないオーバーヘッドです。 シーンの背後にある文字列、ベクトルまたはリスト操作をnewまたはdeleteとすぐに、SIGNALとSLOTのオーバヘッドは、完全な関数呼び出しコストのごくわずかな部分しか原因としません。 SLOT内でシステムコールを行うたびにも同じことが言えます。 10以上の関数を間接的に呼び出すことができます。 SIGNALとSLOTのメカニズムのシンプルさと柔軟性は、ユーザーに気づかないほどのオーバーヘッドの価値があります。

signalsやslotsと呼ばれる変数を定義する他のライブラリは、Qtベースのアプリケーションとともにコンパイルされると、コンパイラの警告とエラーを引き起こす可能性があることに注意してください。 この問題を解決するには、問題のあるプリプロセッサシンボルを#undefします。

Qt Creator で SIGNAL SLOTを使ってみる

Qt Creator で新しいプロジェクトを作成する。
Qtコンソールアプリケーションを選択する。

プロジェクト名とプロジェクトパスを設定する。

プロジェクト名の例:SignalSlot4
パスの例:C:\Qt\Projects\SignalSlot



プロジェクトファイルと main.cpp が生成される。

ソースファイルに 新しい ヘッダと cpp ファイルを追加する。Sources のフォルダを右クリックして 「新しいファイルを追加」を選択する。

C++ クラスを選択する。

クラス名を Counter とし,基底クラスを QObject にする。次へ。

counter.h と counter.cpp が追加された。

counter.h を修正する。下線が追加した行。

#ifndef COUNTER_H
#define COUNTER_H

#include

class Counter : public QObject
{
    Q_OBJECT
public:
    explicit Counter(QObject *parent = nullptr);
    
    int value();

signals:
    void valueChanged(int newValue);

public slots:
    void setValue(int value);
    
private:
    int m_value;
};

#endif // COUNTER_H

int value()の value にカーソルを合わせ右クリック→リファクタリング→counter.cpp に定義を追加を選択


counter.cpp にて 下記下線部を追記する。

#include "counter.h"

Counter::Counter(QObject *parent) : QObject(parent)
{
    m_value = 0;
}

int Counter::value()
{
    return m_value;
}

counter.h に戻って,setValue() にカーソルを合わせ右クリック→リファクタリング→counter.cpp に定義を追加を選択する。


counter.cpp にて 下記下線部を追記する。

#include "counter.h"

Counter::Counter(QObject *parent) : QObject(parent)
{
    m_value = 0;
}

int Counter::value()
{
    return m_value;
}

void Counter::setValue(int value)
{
    if (value != m_value){
        m_value = value;
        emit valueChanged(value);
    }
}

main.cpp に移って 下記,下線部を追記する。

#include
#include "counter.h"
#include "stdio.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Counter x, y;
    QObject::connect(&x, &Counter::valueChanged, &y, &Counter::setValue);

    x.setValue(12);     // x.value() == 12, y.value() == 12

    printf("x:%3d, y:%3d \n\n", x.value(), y.value());

    y.setValue(48);     // x.value() == 12, y.value() == 48

    printf("x:%3d, y:%3d \n\n", x.value(), y.value());


    return a.exec();
}

コンパイルして,正常終了することを確認する。(下記 かなづちのアイコンを押す)

緑の三角キーを押してプログラムを実行してみる。


プログラムの動きを図で確認してみる。

下記の SIGNAL/SLOT の設定により,Counter クラスオブジェクト x の setValue が呼ばれ,値が変わると valueChanged()が emit され,Counter クラスオブジェクト y の setValue が呼ばれて値が設定される。

    QObject::connect(&x, &Counter::valueChanged, &y, &Counter::setValue);

Counter クラスオブジェクト y の setValue が呼ばれても,SIGNAL/ SLOT でつながっていないので,x.value() の値は変わらない。
下記を追加すれば,x.value() の値も変わるようになる。

    QObject::connect(&y, &Counter::valueChanged, &a, &Counter::setValue);

Qt の SIGNAL/SLOT のしくみがだいたい分かっただろうか。SIGNAL/SLOT は QtCreator で作成した ボタンが押されたときのイベントで動作する ソフトウェアにするために頻繁に使用する。

以下で紹介するグラフ描画でももちろん使うので、どんなしくみでイベントが伝達されるのかをよく理解しておくとよい。

波形描画を行うために Qt に QCustomPlot を追加する

QCustomplot はグラフ描画のための無償提供されているツールで,qcustomplot.h と qcustomplot.cpp をプロジェクトに追加して,Qt Creator のプロジェクト設定に QT += core gui printsupport を追加すればよい。

他にもQtでグラフ描画する方法はあるらしいが、一番簡単そうだったのが QCustomplot を使う方法だった。

↓QCustomPlotダウンロードサイト
http://www.qcustomplot.com/index.php/download

QCustomplot でグラフを描いてみる

QCustomplot でグラフ描画を行うには、下記の動画のとおりにやってみるとよい。(英語だが非常に丁寧で分かりやすい)。

この動画の通りにやってみると Qt Creator の使い方も学ぶことができる。



今回は、ここまで。

0 件のコメント: