Qt5再入門: QMainWindow
QMainWindowは、GUIアプリケーションではポピュラーな、タイトルとフレームを備えたウィンドウウィジェットを提供します。
まず、ヒエラルキーを見てみます。
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のウィザードによって自動的に生成されたものに、いくつかの書き足しがしてあります。
UIファイル
先ほどのコードに、次のような記述が見られると思います。
namespace Ui { class MainWindow; }
「Ui::MainWindow」というクラスが宣言のみされています。これは、次に紹介する「UIファイル」から作成されるC++クラスのプレースホルダーとなります。
Qt Creatorのウィザードは、UIファイルというXML構造のテキストファイルも作成します。
UIファイルは、Qt CreatorまたはQt Designer(下図)というアプリケーションで、編集できます。
見た目を確認しながらウィジェットを編集でき、それを保存したものが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>&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&xit</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
これだけだとちんぷんかんぷんですが、Qt Designerで表示するとこうなります。
表示したい画面を、見た目と同じ状態で編集できるのはとてもありがたいです。
そしてこれを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アプリケーション開発に取り組みやすい仕組みが提供されていることは、とても素晴らしいことだと思います。