環境
この記事は以下の環境で動いています。
項目 | 値 |
---|---|
CPU | Core i5-8250U |
Ubuntu | 20.04 |
ROS | Noetic |
Qt | 5.12.8 |
インストールについてはROS講座02 インストールを参照してください。
またこの記事のプログラムはgithubにアップロードされています。ROS講座11 gitリポジトリを参照してください。
概要
前回はQtのQLabelだけを使いましたが、今回はまずいろいろなWidgetを使っていきます。
そのために、まず1つのウィンドウにいろいろなWidgetを詰める方法を説明します。前回のプログラムでは1つのウィンドウには1つのwidgetを表示することしかできません。しかしlayout機能を使うと複数の子Widgetを持つ親Widgetを作成することができます。
また、QtではGUIでイベントが起きたときに通知する仕組みがあります。これをSIGNALとSLOTと呼びます。
Layout
ソースコード
Widgetを横に積んでいくQHBoxLayout、縦に積んでいくQVBoxLayout、グリッド上に配置するQGridLayoutの3つの例を説明します。
c++
#include <QApplication>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGridLayout>
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QWidget* window1 = new QWidget;
QPushButton* button1A = new QPushButton("Button 1A");
QPushButton* button1B = new QPushButton("Button 1B");
QPushButton* button1C = new QPushButton("Button 1C");
QHBoxLayout* layout1 = new QHBoxLayout;
layout1->addWidget(button1A);
layout1->addWidget(button1B);
layout1->addWidget(button1C);
window1->setLayout(layout1);
window1->show();
QWidget* window2 = new QWidget;
QPushButton* button2A = new QPushButton("Button 2A");
QPushButton* button2B = new QPushButton("Button 2B");
QPushButton* button2C = new QPushButton("Button 2C");
QVBoxLayout* layout2 = new QVBoxLayout;
layout2->addWidget(button2A);
layout2->addWidget(button2B);
layout2->addWidget(button2C);
window2->setLayout(layout2);
window2->show();
QWidget* window3 = new QWidget;
QPushButton* button3A = new QPushButton("Button 3A");
QPushButton* button3B = new QPushButton("Button 3B");
QPushButton* button3C = new QPushButton("Button 3C");
QGridLayout* layout3 = new QGridLayout;
layout3->addWidget(button3A,0,0);
layout3->addWidget(button3B,0,1);
layout3->addWidget(button3C,1,0,1,2);
window3->setLayout(layout3);
window3->show();
return app.exec();
}
16行目のlayout1->addWidget(button1A);
で、layout1の子にbutton1Aを追加します。このように追加するだけで自動的にレイアウトが生成されます。
またGridを使う方法だとlayout3->addWidget(button3C,1,0,1,2);
のように後ろに引数が増えます。1,0,1,2
の最初の2つはy(下方向が正)とx(右が正)の位置で、残りの2つは幅と高さです。
CMakeList.txt
c++ファイルのビルドの設定を追加します。
target_link_libraries(qt_basic2
${catkin_LIBRARIES}
${QT_LIBRARIES}
)
ビルド
cd ~/catkin_ws
catkin build
実行
以下のコマンドを実行すると以下のような画面が表示されます。
各ターミナルごとに実行前にsource ~/catkin_ws/devel/setup.bashを実行する必要があります。
rosrun qt_lecture qt_basic2
SIGNALとSLOT
何かしらのイベント(ボタンクリック、テキストボックスの文字が更新された)をSIGNALと呼び、そのイベントによって駆動されるものをSLOTとQtの世界では呼びます。
ソースコード
c++
#include <QApplication>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLineEdit>
#include <QLabel>
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QWidget* window = new QWidget;
QVBoxLayout* layout = new QVBoxLayout;
QPushButton* button = new QPushButton("Quit");
QLineEdit* edit = new QLineEdit("");
QLabel* label = new QLabel("");
layout->addWidget(button);
layout->addWidget(edit);
layout->addWidget(label);
window->setLayout(layout);
QObject::connect(button, &QPushButton::clicked, &app, &QApplication::quit);
QObject::connect(edit, &QLineEdit::textChanged, label, &QLabel::setText);
window->show();
return app.exec();
}
- 24、25行目の
QObject::connect()
関数ががイベントの通知、実行をしている部分です。- 24行目はbuttonのclicked()のSIGNALが発生したときにappのquit()のSLOTを実行するという部分です。
- 25行目はeditのtextchange()のSIGNALが発生したときにlabelのsetText()のSLOTを実行するという部分です。この2つの関数はそれぞれQstringの引数を持っています。この引数がSIGNALからSLOTに送られます。
- connect関数は上記のようなFunctionベースの書き方とは別に以下のようなStringベースの書き方もあります。機能的には同じですがStringベースだと名前を間違えてもビルド時に検出されないという欠点があります。上記のようなFunctionベースの書き方が推奨です。
QObject::connect(button, SIGNAL(clicked()), &app, SLOT(quit()));
QObject::connect(edit, SIGNAL(textChanged(QString)), label, SLOT(setText(QString)));
CMakeList.txt
add_executable(qt_basic3 src/qt_basic3.cpp)
target_link_libraries(qt_basic3
${catkin_LIBRARIES}
${QT_LIBRARIES}
)
ビルド
cd ~/catkin_ws
catkin build
実行
以下のコマンドを実行すると以下のような画面が表示されます。
rosrun qt_lecture qt_basic3
quitを押すと画面が閉じます。またテキストボックスに文字を入れるとその下に同じ文字が表示されます。
コメント
今回はQwidgetが持っているSIGNALをほかのQwidgetが持っているSLOTに繋ぐという方法をとりました。自作のCallback関数をSLOTとして繋ぐためにはこの後説明するクラス化が必要です。