Qt (2)-2 シグナルをまとめて処理する
概要 :
以前の記事で、
シグナルとスロットについて、記述しました。今回は、その続編になります。
今回のテーマは、複数のシグナルを一つにまとめて処理するためのクラスQSignalMapperについて、簡単なサンプルと解説を記述します。
関連記事:
Qt(2)シグナルとスロットを調べる
ここで使用したサンプルソースコード:
簡単な画面サンプルを作ります
この複数のシグナルをまとめて扱うために簡単な画面を作ってみます。
単純に、テキスト入力とボタンの組み合わせを3つ並べたものを作ってみましょう。
今回は、レイアウトは、あまり意識しませんので、フォームレイアウトで単純に作ってみましょう。
では、サンプルのソースコードを見てみましょう。
[MultiSignal.h]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| #include <QtGui>
class MultiSignal : public QWidget
{
Q_OBJECT
public:
MultiSignal(QWidget *parent);
public slots:
signals:
private:
QLabel *m_title[3];
QLineEdit *m_filename[3];
QPushButton *m_button[3];
};
|
[MultiSignal.cpp]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| #include "MultiSignal.h"
MultiSignal::MultiSignal(QWidget *parent) : QWidget(parent)
{
QFormLayout *playout=new QFormLayout;
int ni;
QString stitle(tr("test-input-%1"));
QString sbutton(tr("%1"));
for(ni=0;ni<3;ni++){
// create parts
m_title[ni]=new QLabel(stitle.arg(ni+1),this);
m_filename[ni]=new QLineEdit(this);
m_button[ni]=new QPushButton(sbutton.arg(ni+1),this);
// set of layout
QHBoxLayout *hboxLayout = new QHBoxLayout;
hboxLayout->addWidget(m_filename[ni]);
hboxLayout->addWidget(m_button[ni]);
playout->addRow(m_title[ni],hboxLayout);
}
setLayout(playout);
/*
+--------------------------------------------+
|+------------------------------------------+|
|| +---------------+------------+||
|| m_title[0] | m_filename[0] |m_button[0] |||
|| +---------------+------------+||
|| +---------------+------------+||
|| m_title[1] | m_filename[1] |m_button[1] |||
|| +---------------+------------+||
|| +---------------+------------+||
|| m_title[2] | m_filename[1] |m_button[2] |||
|| +---------------+------------+||
|+------------------------------------------+|
+--------------------------------------------+
*/
}
|
これを実行すると、以下のような画面が表示されます。
画面でよくあるパターンを考えてみます。
ボタンをクリックすると、ファイルダイアログが表示され、ファイルを指定できすようにしてみましょう。
ファイルが指定されたら、QLineEditへ、ファイルパスの情報を設定します。
まずは、ボタン0のみを作成してみましょう。
[MultiSignal.h]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| #include <QtGui>
class MultiSignal : public QWidget
{
Q_OBJECT
public:
MultiSignal(QWidget *parent);
public slots:
void OnClickButton0();
signals:
private:
QLabel *m_title[3];
QLineEdit *m_filename[3];
QPushButton *m_button[3];
};
|
[MultiSignal.cpp]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| #include "MultiSignal.h"
MultiSignal::MultiSignal(QWidget *parent) : QWidget(parent)
{
QFormLayout *playout=new QFormLayout;
int ni;
QString stitle(tr("test-input-%1"));
QString sbutton(tr("%1"));
for(ni=0;ni<3;ni++){
// create parts
m_title[ni]=new QLabel(stitle.arg(ni+1),this);
m_filename[ni]=new QLineEdit(this);
m_button[ni]=new QPushButton(sbutton.arg(ni+1),this);
// set of layout
QHBoxLayout *hboxLayout = new QHBoxLayout;
hboxLayout->addWidget(m_filename[ni]);
hboxLayout->addWidget(m_button[ni]);
playout->addRow(m_title[ni],hboxLayout);
}
setLayout(playout);
/*
+--------------------------------------------+
|+------------------------------------------+|
|| +---------------+------------+||
|| m_title[0] | m_filename[0] |m_button[0] |||
|| +---------------+------------+||
|| +---------------+------------+||
|| m_title[1] | m_filename[1] |m_button[1] |||
|| +---------------+------------+||
|| +---------------+------------+||
|| m_title[2] | m_filename[1] |m_button[2] |||
|| +---------------+------------+||
|+------------------------------------------+|
+--------------------------------------------+
*/
// connect
connect(m_button[0],SIGNAL(clicked()),this,SLOT(OnClickButton0()));
}
void MultiSignal::OnClickButton0()
{
QString sfileName = QFileDialog::getOpenFileName (
this,
tr("Choose a filename"),
"",
tr("All files (*.*)"),
0,
0
);
if(!sfileName.isEmpty()){
m_filename[0]->setText(sfileName);
}
}
|
これを実行してボタン"1"の名前をクリックすると以下のようにファイルダイアログが表示され
ファイルを選択できるようになります。
さて、ここからが本題です。
m_button[0]の処理と同じような処理をm_button[1],m_button[2]へ展開したい場合、
普通なら、同じ処理をいくつも書くことになります。
それをまとめるのに、いくつかの方法はありますが、connectに定義できるSLOTは、
実態のあるメソッド(テンプレートなどは使えません)でなければなりませんから、
何か、SLOTだけは、用意して、その後を、まとめることは、C/C++の知識があれば可能です。
ここでは、QtのクラスQSignalMapperを使ってみましょう。
QSignalMapperでシグナルをまとめて処理する
QSignalMapperは、シグナルを求めて一つのシグナルのようにマッピングしてくれるものです。
つまり、ここでのサンプルでは、m_buttonのclicked()というシグナルを一つのスロットで受けることが可能になります。
[MultiSignal.h]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| #include <QtGui>
class MultiSignal : public QWidget
{
Q_OBJECT
public:
MultiSignal(QWidget *parent);
public slots:
void OnClickButton(QWidget *);
signals:
private:
QLabel *m_title[3];
QLineEdit *m_filename[3];
QPushButton *m_button[3];
QSignalMapper *m_sigmap;
};
|
[MultiSignal.cpp]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| #include "MultiSignal.h"
MultiSignal::MultiSignal(QWidget *parent) : QWidget(parent)
{
m_sigmap=new QSignalMapper(this);
QFormLayout *playout=new QFormLayout;
int ni;
QString stitle(tr("test-input-%1"));
QString sbutton(tr("%1"));
for(ni=0;ni<3;ni++){
// create parts
m_title[ni]=new QLabel(stitle.arg(ni+1),this);
m_filename[ni]=new QLineEdit(this);
m_button[ni]=new QPushButton(sbutton.arg(ni+1),this);
// set of layout
QHBoxLayout *hboxLayout = new QHBoxLayout;
hboxLayout->addWidget(m_filename[ni]);
hboxLayout->addWidget(m_button[ni]);
playout->addRow(m_title[ni],hboxLayout);
// signal map
connect(m_button[ni], SIGNAL(clicked()), m_sigmap, SLOT(map()));
m_sigmap->setMapping(m_button[ni], m_filename[ni]);
}
setLayout(playout);
/*
+--------------------------------------------+
|+------------------------------------------+|
|| +---------------+------------+||
|| m_title[0] | m_filename[0] |m_button[0] |||
|| +---------------+------------+||
|| +---------------+------------+||
|| m_title[1] | m_filename[1] |m_button[1] |||
|| +---------------+------------+||
|| +---------------+------------+||
|| m_title[2] | m_filename[1] |m_button[2] |||
|| +---------------+------------+||
|+------------------------------------------+|
+--------------------------------------------+
*/
// connect
connect(m_sigmap, SIGNAL(mapped(QWidget *)),this, SLOT(OnClickButton(QWidget *)));
}
void MultiSignal::OnClickButton(QWidget *wlineedit)
{
QString sfileName = QFileDialog::getOpenFileName (
this,
tr("Choose a filename"),
"",
tr("All files (*.*)"),
0,
0
);
if(!sfileName.isEmpty()){
QLineEdit *pline = qobject_cast<QLineEdit *>(wlineedit);
if(pline){
pline->setText(sfileName);
}
}
}
|
簡単に解説しておきます。
QSignalMapperには、以下の順序で登録していきます。
- ボタンのクリックシグナル --> QSignalMapperのmapスロットへ
- ボタンとテキスト入力の関連付けを行います
- QSignalMapperのmappedシグナル --> 独自のOnClickButtonスロットへ
上記の1,2の処理をボタンの数だけ実施(cppの23,24行目)します。
最後のconnectをcppの44行目で実施しています。
QPushButton QSignalMapper MultiSignal
(m_button[]) (m_sigmap)
+-------------+
|clicked() - 0| +-------------+ +------------------------+
|clicked() - 1| --> |map() | --> |OnClickButton(QWidget *)|
|clicked() - 2| +-------------+ +------------------------+
+-------------+
また、スロットメソッドの引数に"QWidget *"が、追加されています。
これは、シグナルの発生元とマッピングされた情報が、入ってきます。
cppの24行目で設定しているように、m_buttonとm_filenameを関連付けを行っていますので、
このスロットの引数で渡されるものは、m_filenameが渡されます。
スロット内では、そのm_filenameが、何番目のm_filenameであっても処理は同じですから、以前の処理とほとんど同じになります。
違いは、qobject_castを使って安全にキャストするところぐらいです。
シグナルの発生元とマッピングできる情報は、QWidget以外にもint,QString ,QObject *が設定できます。
void setMapping ( QObject * sender, int id )
void setMapping ( QObject * sender, const QString & text )
void setMapping ( QObject * sender, QWidget * widget )
void setMapping ( QObject * sender, QObject * object )
今回の例では、QLineEdit(QWidget)を関連付けして処理をまとめてみましたが、
その時々で、別の関連付けを行えば、より効率的に処理を行うことができるようになります。
いかがだったでしょうか。
MFCなどでは、リソースのIDなどで区別して、まとめて処理したりしますが、Qtでは、
ID(int)以外にも、いろんな情報を関連付けて、処理できるようになっています。一度、使ってみるのも良いと思います。
何かお気づきの点があれば、コメントいただければ幸いです。
もっと、Qt関連について詳しく知りたい方は、以下の本なども良いと思います。
Qtに関する日本語の本が少ないですね。「入門書」は、さすがに、このページを読まれるくらいの方は不要だと思います。
やっぱり、本+ネット+試してみる!!の3本柱でやっていく以外にないように思います。
コメントをどうぞ