見出し画像

Qt5再入門: QMainWindow

QMainWindowは、GUIアプリケーションではポピュラーな、タイトルとフレームを備えたウィンドウウィジェットを提供します。

まず、ヒエラルキーを見てみます。

スクリーンショット 2021-08-12 20.18.08

QObjectは、Qt全体で重要なクラスです。シグナル/スロット機構やオブジェクト管理などを提供します。
QPaintDeviceは、2次元空間の描画機能を抽象化したものになります。
QWidgetは、画面に自身を描画する基礎的な部品クラスです。多くの具体的な部品クラスは、QWidgetを元に継承しています。

QMainWindowもそんなQWidgetから継承された一つで、メインウィンドウとして、メニューやステータスバー、ツールバーやドックウィジェットなどを、独自のレイアウトで格納できるように拡張されています。

開発者がQMainWindowを利用する場合は、原則継承して使用します。この回のサンプルコードを再掲します。

mainwindow.hヘッダーファイル

// mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "bookmarkmodel.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
 Q_OBJECT

public:
 MainWindow(QWidget *parent = nullptr);
 ~MainWindow();
 void load();

private:
 Ui::MainWindow *ui_;
 BookmarkModel *pModel_;
};

#endif // MAINWINDOW_H

このコード自体は、Qt Creator(下図)というIDEのウィザードによって自動的に生成されたものに、いくつかの書き足しがしてあります。

スクリーンショット 2021-08-13 07.39.40

UIファイル

先ほどのコードに、次のような記述が見られると思います。

namespace Ui { class MainWindow; }

「Ui::MainWindow」というクラスが宣言のみされています。これは、次に紹介する「UIファイル」から作成されるC++クラスのプレースホルダーとなります。

Qt Creatorのウィザードは、UIファイルというXML構造のテキストファイルも作成します。

スクリーンショット 2021-08-13 08.01.30

UIファイルは、Qt CreatorまたはQt Designer(下図)というアプリケーションで、編集できます。

スクリーンショット 2021-08-13 07.41.20

見た目を確認しながらウィジェットを編集でき、それを保存したものがUIファイルです。UIファイルはビルドする過程でC++ソースコードに変換されます。そのクラスがUi::MainWindowとなるわけです。

Q_OBJECTマクロ

MainWindowクラスの定義中にある、Q_OBJECTマクロは、QObjectクラスから派生したサブクラスの定義中に宣言するもので、これでシグナル/スロット機構などの重要な機能がインプリメントされます。

シグナル/スロット機構については、またいずれ紹介したいと思います。

mainwindow.cppソースファイル

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
 : QMainWindow(parent)
 , ui_(new Ui::MainWindow)
{
 ui_->setupUi(this);

 pModel_ = new BookmarkModel(this);
 ui_->bookmarksView->setModel(pModel_);
 load();

 connect(
       ui_->actionE_xit, &QAction::triggered,
       this, &MainWindow::close
       );
}

MainWindow::~MainWindow()
{
 delete ui_;
}

void MainWindow::load() {
 // ダミーデータ
 pModel_->appendRow(
       QList<QStandardItem*>()
       << new QStandardItem("Local")
       << new QStandardItem("")
       << new QStandardItem("")
       );
 pModel_->appendRow(
       QList<QStandardItem*>()
       << new QStandardItem("Unicorn2")
       << new QStandardItem("unicorn2/chiburu9")
       << new QStandardItem("")
       );
}

ソースファイルでは、クラスMainWindowのインプリメントコードを記述します。

コンストラクタの初期段階では、先ほど紹介したUI::MainWindowクラスが生成されが、このMainWindowクラスに取り込んでいます。

#include "ui_mainwindow.h"
// (中略)
MainWindow::MainWindow(QWidget *parent)
 : QMainWindow(parent)
 , ui_(new Ui::MainWindow)
{
 ui_->setupUi(this);

ui_mainwindow.hと言うファイルはビルドするまで存在しません。Qt Creatorでビルドすると、moc(メタオブジェクトコンパイラ)というツールによって、コンパイルに先立ちUIファイルがui_mainwindow.hに変換されます。その後コンパイラが実行され、Ui::MainWindowが生成されるという手順になります。

コンストラクタ内では、次のコードが実行されています。

ui_->setupUi(this);

これにより、MainWindowオブジェクトにUIファイルで定義したウィジェットが組み込まれます。

最後にデストラクタのコードで、Ui::MainWindowが破棄され、役目が終わります。

delete ui_; // 解放

UIファイルと生成されたヘッダーファイル

今回作成したUIファイルの中身を紹介しておきます。

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
 <property name="geometry">
  <rect>
   <x>0</x>
   <y>0</y>
   <width>800</width>
   <height>600</height>
  </rect>
 </property>
 <property name="windowTitle">
  <string>MainWindow</string>
 </property>
 <widget class="QWidget" name="centralwidget"/>
 <widget class="QMenuBar" name="menubar">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>21</height>
   </rect>
  </property>
  <widget class="QMenu" name="menu_File">
   <property name="title">
    <string>&amp;File</string>
   </property>
   <addaction name="actionE_xit"/>
  </widget>
  <addaction name="menu_File"/>
 </widget>
 <widget class="QStatusBar" name="statusbar"/>
 <widget class="QDockWidget" name="logWidget">
  <property name="windowTitle">
   <string>Log Console</string>
  </property>
  <attribute name="dockWidgetArea">
   <number>8</number>
  </attribute>
  <widget class="QWidget" name="dockWidgetContents">
   <layout class="QHBoxLayout" name="horizontalLayout">
    <item>
     <widget class="QTextBrowser" name="logConsole"/>
    </item>
   </layout>
  </widget>
 </widget>
 <widget class="QDockWidget" name="bookmarksWidget">
  <property name="windowTitle">
   <string>Bookmarks</string>
  </property>
  <attribute name="dockWidgetArea">
   <number>1</number>
  </attribute>
  <widget class="QWidget" name="dockWidgetContents_2">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QTableView" name="bookmarksView"/>
    </item>
   </layout>
  </widget>
 </widget>
 <action name="actionE_xit">
  <property name="text">
   <string>E&amp;xit</string>
  </property>
 </action>
</widget>
<resources/>
<connections/>
</ui>

これだけだとちんぷんかんぷんですが、Qt Designerで表示するとこうなります。

スクリーンショット 2021-08-12 21.05.50

表示したい画面を、見た目と同じ状態で編集できるのはとてもありがたいです。

そしてこれをmocがC++クラスに変換するとこうなります。

/********************************************************************************
** Form generated from reading UI file 'mainwindow.ui'
**
** Created by: Qt User Interface Compiler version 5.14.2
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/

#ifndef UI_MAINWINDOW_H
#define UI_MAINWINDOW_H

#include <QtCore/QVariant>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDockWidget>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QTableView>
#include <QtWidgets/QTextBrowser>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>

QT_BEGIN_NAMESPACE

class Ui_MainWindow
{
public:
   QAction *actionE_xit;
   QWidget *centralwidget;
   QMenuBar *menubar;
   QMenu *menu_File;
   QStatusBar *statusbar;
   QDockWidget *logWidget;
   QWidget *dockWidgetContents;
   QHBoxLayout *horizontalLayout;
   QTextBrowser *logConsole;
   QDockWidget *bookmarksWidget;
   QWidget *dockWidgetContents_2;
   QVBoxLayout *verticalLayout;
   QTableView *bookmarksView;

   void setupUi(QMainWindow *MainWindow)
   {
       if (MainWindow->objectName().isEmpty())
           MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
       MainWindow->resize(800, 600);
       actionE_xit = new QAction(MainWindow);
       actionE_xit->setObjectName(QString::fromUtf8("actionE_xit"));
       centralwidget = new QWidget(MainWindow);
       centralwidget->setObjectName(QString::fromUtf8("centralwidget"));
       MainWindow->setCentralWidget(centralwidget);
       menubar = new QMenuBar(MainWindow);
       menubar->setObjectName(QString::fromUtf8("menubar"));
       menubar->setGeometry(QRect(0, 0, 800, 21));
       menu_File = new QMenu(menubar);
       menu_File->setObjectName(QString::fromUtf8("menu_File"));
       MainWindow->setMenuBar(menubar);
       statusbar = new QStatusBar(MainWindow);
       statusbar->setObjectName(QString::fromUtf8("statusbar"));
       MainWindow->setStatusBar(statusbar);
       logWidget = new QDockWidget(MainWindow);
       logWidget->setObjectName(QString::fromUtf8("logWidget"));
       dockWidgetContents = new QWidget();
       dockWidgetContents->setObjectName(QString::fromUtf8("dockWidgetContents"));
       horizontalLayout = new QHBoxLayout(dockWidgetContents);
       horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
       logConsole = new QTextBrowser(dockWidgetContents);
       logConsole->setObjectName(QString::fromUtf8("logConsole"));

       horizontalLayout->addWidget(logConsole);

       logWidget->setWidget(dockWidgetContents);
       MainWindow->addDockWidget(Qt::BottomDockWidgetArea, logWidget);
       bookmarksWidget = new QDockWidget(MainWindow);
       bookmarksWidget->setObjectName(QString::fromUtf8("bookmarksWidget"));
       dockWidgetContents_2 = new QWidget();
       dockWidgetContents_2->setObjectName(QString::fromUtf8("dockWidgetContents_2"));
       verticalLayout = new QVBoxLayout(dockWidgetContents_2);
       verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));
       bookmarksView = new QTableView(dockWidgetContents_2);
       bookmarksView->setObjectName(QString::fromUtf8("bookmarksView"));

       verticalLayout->addWidget(bookmarksView);

       bookmarksWidget->setWidget(dockWidgetContents_2);
       MainWindow->addDockWidget(Qt::LeftDockWidgetArea, bookmarksWidget);

       menubar->addAction(menu_File->menuAction());
       menu_File->addAction(actionE_xit);

       retranslateUi(MainWindow);

       QMetaObject::connectSlotsByName(MainWindow);
   } // setupUi

   void retranslateUi(QMainWindow *MainWindow)
   {
       MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "MainWindow", nullptr));
       actionE_xit->setText(QCoreApplication::translate("MainWindow", "E&xit", nullptr));
       menu_File->setTitle(QCoreApplication::translate("MainWindow", "&File", nullptr));
       logWidget->setWindowTitle(QCoreApplication::translate("MainWindow", "Log Console", nullptr));
       bookmarksWidget->setWindowTitle(QCoreApplication::translate("MainWindow", "Bookmarks", nullptr));
   } // retranslateUi

};

namespace Ui {
   class MainWindow: public Ui_MainWindow {};
} // namespace Ui

QT_END_NAMESPACE

#endif // UI_MAINWINDOW_H

ここで、Qt DesignerでデザインしてからC++アプリとして動くまでの理屈を時系列で簡単にまとめておきます。

1. Qt CreatorまたはQt DesignerでUIを編集する。
2. mocがUIファイル(XML)を解析してC++のソースコード(UIクラス)に変換する。
3. UIクラスと関連付いているQMainWindowの継承クラスで、UIクラスを生成する(UIオブジェクト)。
4. UIオブジェクトでsetupUiメソッドを実行する。
5. 継承クラスに部品オブジェクトが構築される。

特筆すべきは、これら一連の動作とコードが、原則変更なしでLinuxやMacOSでも実行できるというのがすごいと思います。

まとめ

今回は、ウィザードで生成されたQMainWindowのサブクラスがどのようになっているのか、UIファイルを中心に紹介しました。C++ベースでありながらGUIアプリケーション開発に取り組みやすい仕組みが提供されていることは、とても素晴らしいことだと思います。

この記事が気に入ったらサポートをしてみませんか?