Qt をはじめよう! 第12回: シグナルとスロットを作成しよう

前回は QObject の派生クラスの作成について学びました。今回は前回作成した Object クラスにシグナルとスロットを作成してみましょう。

Qt のオブジェクトの使用方法について

前回の記事で Qt のオブジェクトのコピーコンストラクタと代入演算子についてのコメントをいただきましたので、あらためてここで解説します。

Qt では [qt QObject] を基底クラスとした様々な派生クラスを使用しますが、基本的にこれらのオブジェクトのインスタンスはヒープ上に new で生成して使用します。インスタンスの管理やメソッドなどへの受け渡しはポインタで行います。

Qt のオブジェクトモデルでは、各インスタンスは一意であり以下の理由によりコピーと代入を禁止しています。

  • [qt QObject objectName m=#objectName-prop] は各インスタンスに固有の値であるべきでコピーすべきではない
  • QObject の親子関係をコピーできない
  • 接続済みのシグナルやスロットをコピーすべきかどうかが不明のため
  • 動的に割り当てられたプロパティの値までコピーすべきかどうかが不明のため

同じ内容のインスタンスが必要な場合には別のインスタンスを生成し、各プロパティの値をコピーして使用してください。

Qt が提供する QObject の派生クラスでは、クラス定義の中で [qt "" Q_DISABLE_COPY(クラス名) l=qobject m=#Q_DISABLE_COPY] というマクロを使用してコピーコンストラクタと代入演算子を無効にしています。前回の例では登場しませんでしたが、QObject の派生クラスを作成する際にはこのマクロを使用して、コピーと代入を明示的に禁止するようにしましょう。

object.h

class Object : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Object)
public:
...
};

詳しくは Qt のドキュメント [qt "No copy constructor or assignment operator" l=qobject m=#no-copy-constructor] をご覧ください。

Object クラスに機能を追加

それでは今回の本題です。まず、シグナルとスロットを作成する前段階として作成した Object クラスを、int 型の値を1つ持つように変更しましょう。値を格納する private な変数と、public な取得メソッド、設定メソッドを追加します。

object.h に以下の宣言を追加してください。コメント(// [数字]) で示している行が今回追加する行です。

class Object : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Object)
public:
explicit Object(QObject *parent = 0);

int value() const; // [1]
void setValue(int value); // [2]

signals:

public slots:

private:
int m_value; // [3]
};

[1] 値を取得するメソッド

[2] 値を設定するメソッド

[3] 内部で値を保持するための変数

Qt の API のネーミングルールでは取得メソッドは小文字から始まり、get などのプレフィックスはつきません。設定メソッドは小文字の set から始まります。どちらもそれ以降はキャメルケースで名前が付けられます。ここではこの Qt のネーミングルールに従います。

object.cpp もこれに合わせて変更します。

#include "object.h"

Object::Object(QObject *parent)
: QObject(parent), m_value(0) // [1]
{
}

int Object::value() const // [2]
{
return m_value;
}

void Object::setValue(int value) // [3]
{
m_value = value;
}

[1] 内部変数を 0 で初期化

[2] 取得メソッドの実装:内部変数の値を返す

[3] 設定メソッドの実装:内部変数に値を代入

スロットの作成

上記で作成した setValue() メソッドをスロットにしましょう。
object.h

class Object : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Object)
public:
explicit Object(QObject *parent = 0);

int value() const;
signals:

public slots: // [1]
void setValue(int value);
private:
int m_value;
};

前回 Object クラスを作成した際に生成された public なスロット用のスコープ [1] にメソッドの宣言を移動します。setValue() は通常の C++ のメソッドですが、public slots スコープで宣言されることでスロットとして機能し、シグナルと接続することが可能となります。スロットはシグナルと接続した場合、そのシグナルに反応して実行されるようになります。

シグナルの作成

次にこの値が変更された場合に発生するシグナルを作成しましょう。

object.h

class Object : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Object)
public:
explicit Object(QObject *parent = 0);

int value() const;
signals:
void valueChanged(int value); // [1]
public slots:
void setValue(int value);
private:
int m_value;
};

同じく前回生成された signals スコープに、[1] 値が変わったことを通知するシグナル valueChanged(int value) を宣言します。このシグナルは第一引数で変更後の新しい値も通知するようにしています。

object.cpp の先ほど追加した setValue() メソッドを、値が変更されたことをシグナルで通知するように書き換えましょう。

#include <QDebug> // [1]
...
void Object::setValue(int value)
{
if (m_value == value) return; // [2]
m_value = value;
qDebug() << "value of" << objectName() << "is changed to" << m_value; //[3]
emit valueChanged(m_value); // [4]
}

[1] 下記の [3] で使用する [qt "" qDebug l=qtglobal m=#qDebug] に必要なヘッダファイルをインクルード

[2] 現在の値と新しい値が同じ場合には処理をせずメソッドから抜けるようにします。これは値が変更されていない場合にシグナルが発生するのを防ぐためです。

[3] 値の変更をデバッグメッセージに出力

[4] valueChanged() シグナルを発生させます。引数には変更後の値を渡します。

ここで使用している emit は Qt のキーワードで、マクロで定義されています。実態は空文字列で、emit による影響は何もありません。しかし、Qt のプログラミングでシグナルを発生させる際には、シグナルの発生が直感的にわかりやすくなるように、このような形で使用します。

また、シグナルの実装は moc により自動で行なわれるため、実装する必要はありません。

Object クラスのスロットに接続しよう

それではいよいよ Object のインスタンスを生成し、作成したシグナル、スロットの動作確認をしてみましょう。Example クラスのスライダーの値が変更された際に Object のインスタンスの値が変更されるようにします。
example.cpp

#include "example.h"
#include "object.h" // [1]
...
Example::Example(QWidget *parent)
: QWidget(parent)
{
...
Object *object1 = new Object(this); // [2]
object1->setObjectName("object 1"); // [3]
connect(slider, SIGNAL(valueChanged(int)), object1, SLOT(setValue(int))); // [4]
}

[1] Object クラスのヘッダファイルをインクルード

[2] Object のインスタンス object1 を生成

[3] object1 のオブジェクト名を設定

[4] slider のシグナル valueChanged(int) を object1 のスロット setValue(int) に接続

それでは実行してみてください。画面上でスライダーの値を変更した際には以下の順番で処理が行なわれます。

  1. slider の valueChanged() シグナルが発生
  2. object1 の setValue() スロットが呼ばれる
  3. object1 の値が変更される
  4. object1 がデバッグメッセージを出力
  5. object1 の valueChanged() シグナルが発生

Qt Creator では Alt+3(Mac では Command+3) で表示されるアプリケーション出力に以下のようなデバッグメッセージが表示されるはずです。

value of "object 1" is changed to 10
value of "object 1" is changed to 20
value of "object 1" is changed to 30

Object クラスのシグナルを接続しよう

上記で追加した部分のコードを以下のように書き換えてみましょう。

    Object *object1 = new Object(this);
object1->setObjectName("object 1");
Object *object2 = new Object(this);
object2->setObjectName("object 2");
connect(slider, SIGNAL(valueChanged(int)), object1, SLOT(setValue(int)));
connect(object1, SIGNAL(valueChanged(int)), object2, SLOT(setValue(int)));

Object の別のインスタンス object2 を生成し、object1 の valueChanged シグナルを object2 の setValue スロットに接続してみました。

この場合、スライダーの値が変わった際には以下の順で処理が行なわれます。

  1. slider の valueChanged() シグナルが発生
  2. object1 の setValue() スロットが呼ばれる
  3. object1 の値が変更される
  4. object1 がデバッグメッセージを出力
  5. object1 の valueChanged() シグナルが発生
  6. object2 の setValue() スロットが呼ばれる
  7. object2 の値が変更される
  8. object2 がデバッグメッセージを出力
  9. object2 の valueChanged() シグナルが発生

アプリケーション出力に object1 と object2 の値の変更のデバッグメッセージが出力されていることを確認しましょう。

まとめ

  • シグナルとスロットは QObject のサブクラスの中で作成する
  • スロットは public slots/protected slots/private slots のスコープに定義する
  • スロットは通常の C++ の関数として実装する
  • スロットは通常の C++ の関数として使用できる
  • シグナルは signals スコープに定義する
  • シグナルは moc により自動で実装される

おわりに

今回は独自のシグナルとスロットを作成し使用してみました。シグナルもスロットも簡単に作成できるので、是非色々なシグナルやスロットを作成して、使い方に馴染んでいただければと思います。以下について今回の応用として是非チャレンジしてみてください。

  • Example クラスに object2 の valueChanged() シグナルが発生した際に何かの処理をするためのスロットを作成してみてください。
  • object2 の valueChanged() を object1 の setValue() に接続した場合には処理がどうなるか確認してみてください。
  • moc_object.cpp を開いて Object の valueChanged() シグナルが本当に実装されているかを確認してみてください。

Blog Topics:

Comments