0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

実習ROS 2 Qtを使う1

Last updated at Posted at 2024-10-22

環境

本記事は以下の環境を想定して記述している。

項目
OS Ubuntu 22.04
ROS ROS 2 Humble
Qt Qt5.15.3

概要

このページでは、Qtを利用したGUI表示の設定に関して説明する。また、Qtのプログラムを作成し、ROSのビルドツールでQtのプログラムをビルドする。ビルドした実行ファイルを起動し、GUIが表示できることを確認する。
このページは、ROS講座69 Qtを使う1(ビルドの設定)およびROS講座70 Qtを使う2(Layout、SIGNAL・SLOT)の一部の内容をROS 2対応させたものである。

Qtとは

QtはGUI開発用のフレームワークで、LinuxやWindows等の様々なプラットフォームに対応したGUIを開発できる。
ROS上で動作するGUIの開発にもQtが利用されている。たとえば、ROSのGUIツールであるrqtは「ROS Qt」のことであり、Qtを使って作成されている。ROS上でGUIを動作させると、GUIからトピックやサービスなどのROS機能を利用できたり、ROSの情報を可視化が可能になるなど、様々な利点がある。

Qtを利用したプログラムを再配布する際は、ライセンスに注意が必要である。
Qtは、商用ライセンスとオープンソースライセンスのいずれかに準拠し利用できる。オープンソースライセンスでは、主要なモジュールはLGPLでライセンスされているが、一部モジュールはGPLでのライセンスとなっている。(Qt Licensingを参照)

前準備

前提条件

このチュートリアルは、実習ROS 2 Pub&Sub通信を実行していることを前提に、Pub&Sub通信のチュートリアルで作成したワークスペースros2_lecture_wsを利用する。

ROS 2パッケージの作成

パッケージqt_basic1を作成する。

cd ~/ros2_lecture_ws/src
ros2 pkg create --build-type ament_cmake qt_basic1

Qtの環境構築

Qtを利用するため、以下のコマンドでqtbase5-dev qt5-qmakeをインストールする。
Qt公式のインストール手順には、qt5-defaultをインストールして環境を構築するとの説明がある。しかし、qt5-defaultはUbuntu16.04までのサポートのため、Ubuntu22.04では上記のパッケージを利用する。

sudo apt install qtbase5-dev qt5-qmake

QtにはQtCreatorと呼ばれる統合開発環境がある。QtCreatorは以下のような機能を持ち、効率的にGUIの開発ができる。

  • ソースコードの編集
  • GUIのデザイン作成
  • GUIの処理をローコードで設定
  • ソースコードのビルド
  • プログラムの実行やデバッグ

しかし、本講座では簡単なQtの開発に焦点を当て、QtCreatorは利用しない。

QWidgetを利用したウィンドウの表示

Qtのプログラムを動作させて、ウィンドウを表示する方法を説明する。
処理は実行せず、ウィンドウのみを表示する最小のソースコードを作成する。

ウィジェット

「ウィジェット」は、QtにおけるUI要素の基本単位である。ウィンドウそのものやウィンドウ内に表示されるテキスト、ボタン、スライダー、エディタなどは全てウィジェットと呼ばれる。
ウィジェットには親子関係を設定する。親を持たない最上位のウィジェットはウィンドウになり、ウィンドウ名や最小化、最大化等のボタンが表示される。また、子ウィジェットは親ウィジェットの範囲内に配置され、文字列やボタン、スライダー、エディタ等の要素が該当する。以下の図は、この記事内で作成するUIを例に、ウィジェットの親子関係を示したものである。参考:Qt日本語ブログ-Qt をはじめよう! 第8回: QWidget の親子関係を学ぼう

ウィジェットの親子関係の例

ウィジェットはQWidgetクラスもしくは、QWidgetのサブクラスである。Qtを使ったソースコードではウィジェット単位でインスタンスを作成し、ウィジェットの親子関係や見た目、応答処理などの設定を記述する。

簡単のため、以下では親ウィジェットを「ウィンドウ」、子ウィジェットを「オブジェクト」と記載する。

利用するQtの要素

ウィンドウの表示には、QApplicationとQWidgetという2つのクラスを利用する。

  1. QApplication
    QApplicationは、Qtの設定や実行処理を管理するクラスである。
    ウィンドウ数に関わらず、1つのQtアプリに対して1つのQApplicationクラスのインスタンスを作成する。このクラスのexec()関数を実行することで、ウィンドウの表示や応答処理といった各種の処理が行われる。
    より詳細な説明はQt公式ドキュメント:QApplication Classに記載されている。
  2. QWidget
    ウィンドウのGUI設定や処理の設定を行う基本になるクラスである。
    ウィンドウやウィンドウ内に配置するオブジェクト等のGUI設定、ユーザーからの入力に対する応答処理の設定などを行う。
    今回作成するウィンドウはオブジェクトや処理を持たないため、ウィンドウの表示のための設定のみ行う。関数show()を実行することで、QApplicationクラスのexec()を実行したときにウィンドウが表示される。
    より詳細な説明はQt公式ドキュメント:QWidget Classに記載されている。

ソースファイルの作成

ディレクトリqt_basic1/src内に、QApplicationとQWidgetを使ってウィンドウを表示するプログラムqwidget.cppを作成する。

#include <QApplication>
#include <QWidget>

int main(int argc, char ** argv)
{
  QApplication app(argc, argv);
  QWidget * window1 = new QWidget;
  window1->show();
  return app.exec();
}

qwidget.cpp

作成したqwidget.cppの処理を説明する。
インクルード処理では、QApplicationクラスおよびQWidgetクラスを利用するために両者をインクルードする。

#include <QApplication>
#include <QWidget>

QApplicationクラスおよびQWidgetクラスのインスタンスを作成する。
ここで、window1newを利用してヒープ領域に作成している。Qtでは基本的にnewを使用してウィンドウやオブジェクトを作成し、ヒープ領域にメモリを確保する。参考:Qt日本語ブログ Qt をはじめよう! 第12回: シグナルとスロットを作成しよう

QApplication app(argc, argv);
QWidget * window1 = new QWidget;

作成したQWidgetクラスのインスタンスの表示を設定し、Qtの処理を実行する。

window1->show();
return app.exec();

なお、ROS 2のノードとして動作するためには、以下のような処理を実装する必要がある。(実習ROS 2 Pub&Sub通信を参照)

  • rclcpp::Nodeクラスを継承したクラスの作成
  • rclcpp::init()によるノードの初期化
  • rclcpp::spin()rclcpp::spin_once()によるノードの実行

このページで作成するプログラムでは上記の処理は実装しないため、ROS 2のビルドツールを使用してビルドするが、ROSノードとしては動作しない。

ビルドの設定

CMakeLists.txtおよびpackage.xmlにビルド設定を記述する。追加する行をdiff形式で以下に示す。

  1. CMakeLists.txt
    qwidget.cppのQt5CoreおよびQt5Widgetsへの依存を解決して、実行ファイルを作成する。また、ビルド結果をqt_basic1/install/に作成してROS 2コマンドで実行できるように設定している。

     # find dependencies
     find_package(ament_cmake REQUIRED)
    + # packages for Qt
    + find_package(Qt5Core REQUIRED)
    + find_package(Qt5Widgets REQUIRED)
    
     # uncomment the following section in order to fill in
     # further dependencies manually.
     # find_package(<dependency> REQUIRED)
    
    
    + add_executable(qwidget src/qwidget.cpp)
    
    + ament_target_dependencies(qwidget Qt5Core Qt5Widgets)
    
    + install(
    +   TARGETS
    +   qwidget
    +   DESTINATION lib/${PROJECT_NAME}
    +   )
    
     if(BUILD_TESTING)
       find_package(ament_lint_auto REQUIRED)
    
  2. package.xml
    qtbase5-devへの依存を記述する。

     <buildtool_depend>ament_cmake</buildtool_depend>
    
    + <build_depend>qtbase5-dev</build_depend>
    
     <test_depend>ament_lint_auto</test_depend>
     <test_depend>ament_lint_common</test_depend>
    

GUIの起動

作成したパッケージをビルドし、ros2 runコマンドでqwidgetを実行する。

cd ~/ros2_lecture_ws/
colcon build
. install/setup.bash 
ros2 run qt_basic1 qwidget

qwidgetを起動すると、以下のように何も表示されないウィンドウが1つ表示される。

qwidgetの実行結果

オブジェクトの表示

qwidgetで表示したウィンドウには何も表示されていない。実際にGUIを利用するためには、ウィンドウにユーザーとの対話に必要なオブジェクトを配置する必要がある。
ウィンドウ内にオブジェクトを配置する方法について説明する。オブジェクトはQtの機能の1つであるレイアウトを利用して配置する。

オブジェクトの使い方

前述のとおり、GUIに使用するオブジェクトはQWidgetクラスのサブクラスとして用意されている。例えば以下のようなオブジェクトがよく利用される。

オブジェクトを利用するには、オブジェクトに対応するクラスのインスタンスを作成し、レイアウトを利用してウィンドウに配置する。

レイアウトの使い方

レイアウトはオブジェクトの配置やサイズなどを管理するクラスである。一般にはオブジェクトを配置する際は、大きさや座標といった様々なパラメータをオブジェクトごとに指定しなければならない。それと比較して、レイアウトを使うことでより簡単にオブジェクトを配置できる。
レイアウトにはいくつかの種類がある。種類によってオブジェクトの配置や位置の指定方法が異なる。例えば、オブジェクトを水平方向に配置するQHBoxLayoutや、垂直方向に配置するQVBoxLayout、グリッドに配置を指定するQGridLayoutがある。
参考:Qt Documentation - Layout Management

オブジェクトを表示するソースコード

qt_basic1/src/以下に、レイアウトでオブジェクトを配置したウィンドウを表示するプログラムlayout.cppを作成する。
このプログラムを実行すると、以下の3つのウィンドウが表示される。

  • プッシュボタンが水平方向に配置されたウィンドウ
  • プッシュボタンがグリッド状に配置されたウィンドウ
  • 文字列とエディタが垂直方向に配置されたウィンドウ
#include <QApplication>

// include layout
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGridLayout>

// include object
#include <QPushButton>
#include <QLineEdit>
#include <QLabel>

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");
  QGridLayout * layout2 = new QGridLayout;
  layout2->addWidget(button2A, 0, 0);
  layout2->addWidget(button2B, 0, 1);
  layout2->addWidget(button2C, 1, 0, 1, 2);
  window2->setLayout(layout2);
  window2->show();

  QWidget * window3 = new QWidget;
  QLabel * label = new QLabel("###Label###");
  QLineEdit * edit = new QLineEdit("###LineEdit###");
  QVBoxLayout * layout3 = new QVBoxLayout;
  layout3->addWidget(label);
  layout3->addWidget(edit);
  window3->setLayout(layout3);
  window3->show();

  return app.exec();
}

layout.cpp

  1. オブジェクトの作成
    以下では、ウィンドウ内に配置するオブジェクトのインスタンスを作成している。qwidget.cppのQWidgetクラスと同様に、インスタンスはnewを使用して作成する。

    QPushButton * button1A = new QPushButton("Button 1A");
    QPushButton * button1B = new QPushButton("Button 1B");
    QPushButton * button1C = new QPushButton("Button 1C");
    
    QPushButton * button2A = new QPushButton("Button 2A");
    QPushButton * button2B = new QPushButton("Button 2B");
    QPushButton * button2C = new QPushButton("Button 2C");
    
    QLabel * label = new QLabel("###Label###");
    QLineEdit * edit = new QLineEdit("###LineEdit###");
    
  2. オブジェクトの配置
    以下ではレイアウトを作成、オブジェクトの設定を行うともに、レイアウトをウィンドウに適用している。
    レイアウトのaddWidget()関数でレイアウトにオブジェクトを設定する。レイアウトの種類によっては引数を与えてオブジェクトの配置を設定できる。また、作成したレイアウトはQWidgetのsetLayout()関数でウィンドウに設定する。

    QHBoxLayout * layout1  = new QHBoxLayout;
    layout1->addWidget(button1A);
    layout1->addWidget(button1B);
    layout1->addWidget(button1C);
    window1->setLayout(layout1);
    
    QGridLayout * layout2  = new QGridLayout;
    layout2->addWidget(button2A,0,0);
    layout2->addWidget(button2B,0,1);
    layout2->addWidget(button2C,1,0,1,2);
    window2->setLayout(layout2);
    
    QVBoxLayout * layout3  = new QVBoxLayout;
    layout3->addWidget(label);
    layout3->addWidget(edit);
    window3->setLayout(layout3);
    
  3. ウィンドウの表示
    最後に、これまでに作成したウィンドウを表示するようにshow()によって設定し、exec()でQtを実行する。

ビルド設定

qwidgetと同様にCMakeLists.txtにビルド設定を追記する。新たに依存するパッケージは無いため、package.xmlへの追記は不要である。

 add_executable(qwidget src/qwidget.cpp)
+ add_executable(layout src/layout.cpp)

 ament_target_dependencies(qwidget Qt5Core Qt5Widgets)
+ ament_target_dependencies(layout Qt5Core Qt5Widgets)

 install(
   TARGETS
   qwidget
+   layout
   DESTINATION lib/${PROJECT_NAME}
   )

GUIの起動

作成したパッケージをビルドして、ros2 runコマンドでlayoutを実行する。

cd ~/ros2_lecture_ws/
colcon build
. install/setup.bash 
ros2 run qt_basic1 layout

layoutを起動すると、以下のような3つのウィンドウが表示される。ウィジェットの親子関係が設定されて、レイアウトの設定どおりにオブジェクトが配置されていることが分かる。

layoutの実行結果

なお、厳密にはウィンドウの子ウィジェットに各オブジェクトが設定されているわけではない。
レイアウトとウィンドウの間にも親子関係があり、setLayout()関数ではレイアウトとウィンドウ間の親子関係を設定している(ウィンドウを親に、レイアウトを子に設定)。ウィンドウとオブジェクト間の親子関係は、レイアウトを介して間接的に設定されている。
QWidgetクラスには親子関係を直接設定する方法もある。しかし、単純なGUIではレイアウトを用いるほうが記述が容易である。また、実習ROS 2はROS 2の解説を目的としているため本講座では取り扱わない。

メモリ管理について

ウィジェットが削除されるとき、子のウィジェットのメモリを自動的に解放する機能がある。したがって、オブジェクト等の作成時に確保したメモリを解放する必要はない。(参考:Qt日本語ブログQtをはじめよう!第7回:Qtのオブジェクトモデルを理解しよう
一方で、以下のようにメモリが解放されない場合もあるため、メモリリークの発生には注意が必要である。

  • 親を持たないウィジェットを作成したとき
  • 親子関係にあるウィジェットが相互に参照しあう循環参照の状態になったとき

前者は適切にdeleteを行うか適切な親子関係を設定する必要がある。また、後者は相互に参照しあうポインターの片方をQWeakPointerにすると循環参照を回避できる。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?