はい、承知いたしました。
QWidget入門に関する約5000語の詳細な記事を作成します。以下、記事本文です。
QWidget入門:QtでGUIアプリを作るための第一歩
はじめに
現代のソフトウェア開発において、グラフィカルユーザーインターフェース(GUI)はユーザー体験の中核をなす要素です。直感的で使いやすいGUIは、アプリケーションの価値を大きく高めます。C++で高機能かつ美しいGUIアプリケーションを開発したいと考えたとき、多くの開発者が真っ先に思い浮かべるのが「Qt(キュート)」フレームワークです。
Qtとは何か?
Qtは、クロスプラットフォームに対応したアプリケーション開発フレームワークです。これはつまり、「一度書いたコードが、Windows、macOS、Linux、さらにはAndroidやiOSといったモバイルプラットフォームでも、ほとんど修正なしに動作する」という強力な利点を持っていることを意味します。Qtは単なるGUIライブラリにとどまりません。ネットワーク、データベース、マルチメディア、XML処理など、アプリケーション開発に必要なあらゆる機能を網羅した、巨大で強力なエコシステムを提供します。
その中でも、デスクトップGUI開発の根幹をなすのが QWidget モジュールです。QWidgetは、ウィンドウ、ボタン、テキストボックスといった、画面上に表示されるあらゆるUI要素の基本となるクラスライブラリ群です。
なぜ今、QWidgetを学ぶのか?
QtにはQML(Qt Modeling Language)という、よりモダンで宣言的なUI記述言語も存在します。しかし、QWidgetには今なお学ぶべき大きな価値があります。
- 安定性と成熟度: QWidgetは長年の歴史を持ち、非常に安定しており、機能も豊富です。大規模で複雑なデスクトップアプリケーションの多くがQWidgetで構築されています。
- C++との親和性: QWidgetは純粋なC++でUIを構築するため、C++の強力な機能を最大限に活用できます。パフォーマンスが要求される処理や、既存のC++ライブラリとの連携が容易です。
- 基礎の理解: QWidgetのイベント処理メカニズムである「シグナルとスロット」は、Qt全体の根幹をなす概念です。これを理解することは、QMLや他のQtモジュールを学ぶ上でも非常に重要です。
この記事で学べること
この記事は、プログラミングの基礎(特にC++の基本文法)は理解しているものの、GUIプログラミングは初めて、あるいはQtは初めてという方を対象としています。この記事を最後まで読み終える頃には、あなたは以下の知識とスキルを習得しているはずです。
- Qt開発環境をセットアップする方法
QWidget
を使って最初のウィンドウを表示する方法- ボタンやラベルなどの基本的なUI部品(ウィジェット)を配置する方法
- レイアウト管理を用いて、ウィンドウサイズが変わっても崩れないUIを構築する方法
- Qtの核心的機能「シグナルとスロット」を使って、インタラクティブなアプリケーションを作成する方法
- UIを再利用可能な部品としてカプセル化するために、
QWidget
を継承したカスタムクラスを作成する方法 - Qt CreatorのUIデザイナを使って、直感的にUIを設計する方法
さあ、QtとQWidgetの世界への扉を開き、あなた自身のGUIアプリケーションを作るための第一歩を踏み出しましょう。
第1章:Qt開発環境の準備
何よりもまず、開発を始めるための環境を整える必要があります。ここでは、Qtの公式インストーラを使って、必要なツールを一括でインストールする方法を解説します。
1.1 Qtのダウンロードとインストール
- Qt公式サイトへアクセス: まず、ブラウザで Qtの公式サイト にアクセスします。
- ダウンロードページへ: トップページにある「Download. Try. Buy.」や「Download for free」といったボタンをクリックし、オープンソース版のダウンロードページに進みます。Qtは商用ライセンスとオープンソースライセンス(LGPLなど)を提供していますが、学習や個人プロジェクトではオープンソース版で十分です。
- Qt Online Installerのダウンロード: ダウンロードページで「Qt Online Installer」をダウンロードします。これは、必要なコンポーネントを選択してインストールするためのダウンローダー兼インストーラーです。
- インストーラの実行とQt Account: ダウンロードしたインストーラを実行します。インストールを進めるにはQt Accountでのログインが必要です。アカウントを持っていない場合は、その場で無料で作成できます。
- コンポーネントの選択: インストールで最も重要なのが、このコンポーネント選択画面です。ここで、開発に必要なものを選択します。
- Qtバージョン: 最新のLTS(Long Term Support)版を選択するのが一般的です。安定性が高く、長期間サポートされます。
- コンパイラとツール: ここが少し複雑です。
- Windows:
- MinGW: GCC(GNU Compiler Collection)のWindows移植版です。追加のツールをインストールする必要がなく、手軽に始められます。初心者にはこちらがおすすめです。
- MSVC (Microsoft Visual C++): MicrosoftのVisual Studioと連携するためのコンポーネントです。すでにVisual Studioをインストールしている場合や、Windowsネイティブの開発にこだわりたい場合はこちらを選択します。
- macOS: Clang (XCode) を選択します。事前にAppleのXCodeをApp Storeからインストールしておく必要があります。
- Linux: GCCを選択します。
build-essential
などのパッケージを事前にインストールしておく必要があります。
- Windows:
- Qt Creator: Qtの公式統合開発環境(IDE)です。コードエディタ、デバッガ、UIデザイナなどがすべて含まれているため、必ずチェックを入れてください。
初心者の方は、最新LTSバージョンの下にある「MinGW」(Windowsの場合)と、「Qt Creator」にチェックが入っていることを確認してインストールを進めてください。インストールには数GBのディスク容量と、それなりの時間がかかります。
1.2 Qt Creatorの紹介
インストールが完了したら、Qt Creatorを起動してみましょう。Qt Creatorは、Qt開発のために最適化された非常に強力なIDEです。
- ウェルカムモード: 起動すると最初に表示される画面です。最近のプロジェクトやセッション、サンプルプロジェクトなどに素早くアクセスできます。
- エディットモード: C++やQMLのコードを記述するメインの画面です。強力なコード補完、シンタックスハイライト、静的解析機能などを備えています。
- デザインモード: GUIを視覚的に設計するための画面です。ウィジェットをドラッグ&ドロップで配置し、プロパティを編集できます。この記事の後半で詳しく触れます。
- デバッグモード: ブレークポイントを設定したり、変数の値を確認したりしながら、プログラムを一行ずつ実行できるデバッグ機能です。
- プロジェクトモード: ビルド設定や実行設定を管理します。
1.3 最初のプロジェクトを作成する
Qt Creatorを使って、最初のプロジェクトを作成してみましょう。
- Qt Creatorを起動し、「ファイル」→「新規ファイルまたはプロジェクト」を選択します。
- 「Application (Qt)」から「Qt Widgets Application」を選択し、「選択」ボタンをクリックします。
- プロジェクト名(例:
FirstApp
)と場所を入力します。 - ビルドシステムは「qmake」がデフォルトで選択されているはずです。そのまま次へ進みます。
- クラス情報では、クラス名(
QMainWindow
がデフォルト)、ヘッダファイル名、ソースファイル名などが自動で生成されます。今は何も変更せず、次へ進みます。 - 翻訳ファイルの指定は、今は不要なのでそのまま次へ進みます。
- キットの選択画面では、先ほどインストールしたコンポーネント(例:
Desktop Qt x.x.x MinGW 64-bit
)が選択されていることを確認し、「完了」ボタンをクリックします。
これで、GUIアプリケーションの雛形が自動的に生成されました。左側のプロジェクトツリーには、main.cpp
、mainwindow.h
、mainwindow.cpp
、mainwindow.ui
といったファイルが作成されているはずです。
画面左下にある緑色の再生ボタン(▶)をクリックしてみてください。コンパイルとビルドが実行され、空のウィンドウが一つ表示されれば、開発環境の準備は成功です。
第2章:最初のウィンドウ – QWidgetの基本
プロジェクトを自動生成するのではなく、もっともシンプルな形からQtアプリケーションがどのように動作するのかを理解しましょう。ここでは、たった一つのファイルで「Hello, World!」と表示されたウィンドウを作成します。
Qt Creatorで先ほど作成したプロジェクトは一旦閉じて、「ファイル」→「新規ファイルまたはプロジェクト」から「Non-Qt Project」→「Plain C++ Application」を選択して、SimpleWindow
という名前のプロジェクトを作成してください。これにより、余計なファイルがない、まっさらな状態から始められます。
生成されたmain.cpp
の中身をすべて削除し、以下のコードを貼り付けてください。
“`cpp
include
include
include
int main(int argc, char *argv[])
{
// 1. QApplicationオブジェクトの生成
QApplication app(argc, argv);
// 2. ウィンドウとなるQWidgetオブジェクトの生成
QWidget window;
window.setWindowTitle("My First App");
window.resize(400, 300);
// 3. ラベルウィジェットの生成と設定
QLabel label("Hello, World!", &window);
label.move(150, 140);
// 4. ウィンドウの表示
window.show();
// 5. イベントループの開始
return app.exec();
}
“`
このコードを理解するために、プロジェクトファイル(.pro
ファイル)を少し編集する必要があります。プロジェクトツリーからSimpleWindow.pro
を開き、以下の行を追加してください。
pro
QT += widgets
これは、このプロジェクトがQtのwidgets
モジュールを使用することをビルドシステム(qmake)に伝えるための記述です。これを書かないと、<QApplication>
などが見つからずコンパイルエラーになります。
編集したら、緑の再生ボタンを押して実行してみましょう。「My First App」というタイトルのウィンドウが表示され、その中に「Hello, World!」というテキストが表示されるはずです。
2.1 コードの詳細解説
この短いコードには、Qtアプリケーションの根幹をなす重要な要素が詰まっています。一つずつ見ていきましょう。
1. QApplication
オブジェクト
cpp
QApplication app(argc, argv);
QApplication
は、すべてのQt GUIアプリケーションに必ず1つだけ必要なクラスです。その役割は多岐にわたりますが、主に以下の責務を担っています。
- イベントループの管理: ユーザーのマウスクリック、キー入力、ウィンドウのリサイズといったイベント(出来事)を受け取り、適切なウィジェットにディスパッチ(配送)します。
- アプリケーション全体の設定: アプリケーションのスタイル、フォント、コマンドライン引数の解釈など、グローバルな設定を管理します。
- リソース管理: アプリケーションの実行に必要なリソースを初期化・解放します。
main
関数の引数であるargc
とargv
をコンストラクタに渡すことで、Qtは-style
などのQt固有のコマンドライン引数を解釈できるようになります。
2. QWidget
オブジェクト
cpp
QWidget window;
window.setWindowTitle("My First App");
window.resize(400, 300);
QWidget
は、画面上に表示されるすべてのUIコンポーネント(ボタン、ラベル、テキストボックス、そしてウィンドウ自身も)の基底クラスです。言わば「UI部品の素」です。
ここでは、QWidget
のインスタンスをwindow
という名前で作成しています。この時点では、このwindow
はまだトップレベルのウィンドウとして扱われます(親がいないため)。
setWindowTitle()
: ウィンドウのタイトルバーに表示されるテキストを設定します。resize()
: ウィンドウの初期サイズをピクセル単位で指定します(幅400ピクセル、高さ300ピクセル)。
3. QLabel
オブジェクトと親子関係
cpp
QLabel label("Hello, World!", &window);
label.move(150, 140);
QLabel
は、テキストや画像を表示するためのウィジェットです。コンストラクタの第一引数に表示したいテキストを、第二引数に親ウィジェットへのポインタを渡しています。
QLabel label("Hello, World!", &window);
この第二引数&window
が非常に重要です。Qtでは、ウィジェット同士が親子関係を持つことができます。この場合、label
はwindow
の子ウィジェットになります。親子関係には以下の特徴があります。
- 描画: 子ウィジェットは、親ウィジェットの領域内に描画されます。
- 座標: 子ウィジェットの座標は、親ウィジェットの左上を原点(0,0)とします。
label.move(150, 140)
は、「親であるwindow
の左上から右に150ピクセル、下に140ピクセルの位置に移動する」という意味になります。 - メモリ管理: 親ウィジェットが破棄(デストラクト)されると、その子ウィジェットも自動的にすべて破棄されます。これにより、手動で
delete
を呼び出す必要がなくなり、メモリリークのリスクが大幅に減少します。main
関数が終了するとき、スタック上に確保されたwindow
オブジェクトが破棄され、それに伴い子であるlabel
も自動的に破棄されます。 - 表示: 親ウィジェットが表示される(
show()
される)と、その子ウィジェットも(非表示に設定されていない限り)一緒に表示されます。
4. window.show()
このメソッドを呼び出すことで、window
ウィジェット(と、その子であるlabel
ウィジェット)が画面上に表示されます。これだけではウィンドウは一瞬表示されて消えてしまいます。なぜなら、プログラムがすぐに次の行に進んで終了してしまうからです。
5. app.exec()
ここで登場するのが、QApplication
のexec()
メソッドです。これはイベントループを開始する命令です。
exec()
が呼び出されると、プログラムはここから先に進まなくなり、ユーザーからの入力やOSからの通知(イベント)を待ち続ける状態に入ります。マウスがクリックされれば、Qtはそのイベントを検知し、適切なウィジェットに通知します。ウィンドウの閉じるボタンがクリックされると、exec()
は終了し、アプリケーションが正常に終了するための値を返します。main
関数はその値をreturn
し、プログラム全体が終了します。
これが、Qt GUIアプリケーションの最も基本的なライフサイクルです。
第3章:ウィジェットを追加する – UIの構築
ウィンドウを表示できるようになったので、次はその中に様々なウィジェットを配置して、より実用的なUIを構築する方法を学びましょう。
3.1 様々なウィジェット
Qtは豊富なウィジェットを提供しています。ここでは代表的なものをいくつか紹介します。
QPushButton
: クリックできるボタン。QLineEdit
: 1行のテキスト入力フィールド。パスワード入力用のモードもあります。QTextEdit
: 複数行のテキストエディタ。QCheckBox
: オン/オフを切り替えられるチェックボックス。QRadioButton
: 複数の選択肢から一つだけを選ぶラジオボタン。QComboBox
: ドロップダウンリスト。QSlider
: スライダー。QProgressBar
: 進捗バー。
これらのウィジェットもQLabel
と同様に、親ウィジェットを指定して作成します。
3.2 レイアウト管理の重要性
前の章では、label.move(150, 140)
のように、ウィジェットの位置をピクセル単位で指定しました。これを絶対配置と呼びます。しかし、この方法には大きな問題があります。
もしユーザーがウィンドウのサイズを変更したらどうなるでしょうか? label
は元の位置に留まったままで、ウィンドウの中央からずれてしまいます。また、異なるOSや異なる画面解像度では、意図した通りに表示されないかもしれません。
この問題を解決するのがレイアウトマネージャです。レイアウトマネージャは、決められたルールに従って、子ウィジェットのサイズと位置を自動的に調整してくれる便利なクラスです。ユーザーがウィンドウサイズを変更すると、レイアウトマネージャがそれを検知し、ウィジェットを再配置してくれます。
Qtには主に以下のレイアウトマネージャがあります。
QHBoxLayout
: ウィジェットを水平(Horizontal)に一列に並べます。QVBoxLayout
: ウィジェットを垂直(Vertical)に一列に並べます。QGridLayout
: ウィジェットを格子状(Grid)に配置します。QFormLayout
: ラベルと入力フィールドのペアを2列で配置するのに適したレイアウトです。
3.3 レイアウトを使ったUI構築の実践
それでは、簡単なログインフォーム風のUIをレイアウトマネージャを使って作成してみましょう。main.cpp
を以下のように書き換えてください。
“`cpp
include
include
include
include
include
include
include
include
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// メインとなるウィンドウ
QWidget window;
window.setWindowTitle("Login Form");
// --- ウィジェットの作成 ---
// これらはすべてヒープ上に作成し、レイアウトが所有権を管理する
auto *usernameLabel = new QLabel("Username:");
auto *usernameEdit = new QLineEdit();
auto *passwordLabel = new QLabel("Password:");
auto *passwordEdit = new QLineEdit();
passwordEdit->setEchoMode(QLineEdit::Password); // パスワードを隠す
auto *loginButton = new QPushButton("Login");
auto *cancelButton = new QPushButton("Cancel");
// --- レイアウトの構築 ---
// 1. グリッドレイアウトでラベルと入力欄を配置
auto *formLayout = new QGridLayout();
formLayout->addWidget(usernameLabel, 0, 0); // 0行0列
formLayout->addWidget(usernameEdit, 0, 1); // 0行1列
formLayout->addWidget(passwordLabel, 1, 0); // 1行0列
formLayout->addWidget(passwordEdit, 1, 1); // 1行1列
// 2. 水平レイアウトでボタンを配置
auto *buttonLayout = new QHBoxLayout();
buttonLayout->addStretch(); // ボタンを右に寄せるためのスペーサー
buttonLayout->addWidget(loginButton);
buttonLayout->addWidget(cancelButton);
// 3. 垂直レイアウトで全体をまとめる
auto *mainLayout = new QVBoxLayout();
mainLayout->addLayout(formLayout); // グリッドレイアウトを追加
mainLayout->addLayout(buttonLayout); // 水平レイアウトを追加
// --- ウィンドウにレイアウトを設定 ---
window.setLayout(mainLayout);
window.show();
return app.exec();
}
“`
このコードを実行すると、ウィンドウサイズを変更しても各部品が適切に追従する、整ったフォームが表示されるはずです。
コードのポイント
- ヒープ上のオブジェクト: 今回、ウィジェットやレイアウトは
new
を使ってヒープ上に作成しています。auto *usernameLabel = new QLabel(...)
のように。これはなぜでしょうか?- レイアウトマネージャは、追加されたウィジェットや他のレイアウトの所有権を持つように設計されています。
window.setLayout(mainLayout)
が呼ばれると、window
はmainLayout
の親になります。mainLayout
は、追加されたformLayout
とbuttonLayout
の親になります。formLayout
とbuttonLayout
は、それぞれ追加されたウィジェット(usernameLabel
など)の親になります。- 最終的に、
window
が破棄されるとき、その子であるmainLayout
が破棄され、さらにその子たち…と連鎖的にすべてのオブジェクトがdelete
されます。そのため、手動でdelete
する必要はありません。
- レイアウトのネスト:
QVBoxLayout
の中にQGridLayout
とQHBoxLayout
を追加しているように、レイアウトは入れ子にすることができます。これにより、複雑なUIをブロック単位で構築できます。 addStretch()
:QHBoxLayout
に追加したaddStretch()
は、伸縮可能な空のスペース(スペーサー)を追加します。これにより、後続のウィジェット(この場合はボタン)が右側に押しやられ、一般的なUIデザイン(OK/Cancelボタンが右下にあるなど)を簡単に実現できます。
第4章:インタラクティブなアプリへ – シグナルとスロット
今までのアプリケーションは、見た目は整っていますが、ボタンをクリックしても何も起こりません。UIをインタラクティブにする、つまりユーザーのアクションに応答させる仕組みが、Qtの最も強力でユニークな機能の一つであるシグナルとスロットです。
4.1 シグナルとスロットの概念
伝統的なGUIツールキットでは、ユーザーのアクションを処理するために「コールバック関数」がよく使われます。しかし、コールバックは型安全でなかったり、処理の結合度が高くなりすぎたりする問題がありました。
Qtは、これを解決するために「シグナルとスロット」という、非常に柔軟で強力なメカニズムを導入しました。
- シグナル (Signal): あるオブジェクトで特定のイベントが発生したことを知らせるための信号です。例えば、
QPushButton
はクリックされたときにclicked()
というシグナルを発信(emit)します。シグナルは引数を持つこともできます(例:QSlider
は値が変化したときにvalueChanged(int)
というシグナルを発信する)。 - スロット (Slot): シグナルを受け取って実行される関数です。スロットは、通常のC++のメンバ関数、静的関数、あるいはラムダ式です。
- 接続 (Connect): シグナルとスロットを結びつける行為です。
QObject::connect()
関数を使って、「このオブジェクトのこのシグナルが発信されたら、あのオブジェクトのあのスロットを実行せよ」とQtのイベントシステムに登録します。
この仕組みの素晴らしい点は、シグナルを発信するオブジェクトは、どのスロットが接続されているかを知る必要がないということです。逆もまた然り、スロットを持つオブジェクトは、どのシグナルから呼び出されるかを知る必要がありません。これにより、コンポーネント間の疎結合が実現され、再利用性が高くメンテナンスしやすいコードを書くことができます。
4.2 connect
関数の使い方
それでは、実際にシグナルとスロットを使ってみましょう。ボタンをクリックしたら、コンソールにメッセージを出力し、ウィンドウのタイトルを変更する簡単な例を作成します。
main.cpp
を以下のように書き換えてください。
“`cpp
include
include
include
include
include // コンソール出力用
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("Signal Slot Example");
auto *button = new QPushButton("Click Me!");
auto *layout = new QVBoxLayout();
layout->addWidget(button);
window.setLayout(layout);
// シグナルとスロットの接続 (C++11 ラムダ式を使用)
QObject::connect(button, &QPushButton::clicked, &window, [&]() {
// --- ここからがスロットとして実行されるコード ---
qDebug() << "Button was clicked!";
static int count = 0;
count++;
window.setWindowTitle("Clicked " + QString::number(count) + " times");
// --- ここまで ---
});
window.show();
return app.exec();
}
“`
このコードを実行し、ボタンを何度かクリックしてみてください。コンソール(Qt Creatorの下部にある「アプリケーション出力」タブ)にメッセージが表示され、同時にウィンドウのタイトルが「Clicked 1 times」「Clicked 2 times」…と変化していくのがわかるはずです。
コードのポイント
cpp
QObject::connect(sender, &SenderClass::signalName, receiver, slot);
connect
関数は基本的にこの形を取ります。
- sender: シグナルを発信するオブジェクトへのポインタ。(例:
button
) - &SenderClass::signalName: 発信するシグナルのメンバ関数ポインタ。(例:
&QPushButton::clicked
) - receiver: スロットを持つオブジェクトへのポインタ。(例:
&window
) - slot: 実行されるスロット。ここではC++11のラムダ式を使用しています。
4.3 ラムダ式を使った接続
上記の例で使ったのが、ラムダ式によるスロットの実装です。
cpp
[&]() {
// ... 実行したい処理 ...
}
ラムダ式を使うと、スロットのためだけに新しい関数を定義する必要がなく、connect
を呼び出すその場で処理を簡潔に記述できます。これは現代的なC++でのQtプログラミングにおいて非常に一般的な手法です。
[]
: キャプチャ句と呼ばれます。このラムダ式の外側にある変数を、ラムダ式の中でどのように使うかを指定します。[]
: 何もキャプチャしない。[=]
: 外側の変数を値でキャプチャする(コピー)。[&]
: 外側の変数を参照でキャプチャする。[this]
:this
ポインタをキャプチャする(クラスのメンバ関数内で使う場合)。[&window, &button]
: 特定の変数だけを参照でキャプチャする。
今回の例[&]
では、ラムダ式の外側にあるwindow
オブジェクトなどを参照で利用できるようにしています。これにより、window.setWindowTitle(...)
のような呼び出しが可能になっています。static int count
は、このラムダ式が呼ばれるたびに値を保持し続けるためにstatic
修飾子を付けています。
シグナルとスロットは、Qtプログラミングの真髄です。この概念をマスターすることが、Qtを使いこなすための鍵となります。
第5章:カスタムウィジェットの作成 – QWidget
のサブクラス化
アプリケーションが複雑になってくると、関連するウィジェットやロジックを一つのまとまりとして扱いたくなります。例えば、「カウンター」という機能(数値を表示するラベルと、+1ボタン、リセットボタン)を、アプリケーションの複数の場所で再利用したい場合などです。
このような場合に有効なのが、QWidget
(またはそのサブクラス)を継承して、独自のカスタムウィジェットを作成する手法です。
5.1 なぜサブクラス化が必要か?
- カプセル化: 関連するUI部品とその動作ロジックを一つのクラスにまとめることで、内部の実装を隠蔽できます。これにより、コードの見通しが良くなります。
- 再利用性: 作成したカスタムウィジェットは、あたかも
QPushButton
のように、他の場所で簡単に再利用できます。 - 保守性: ある機能に関する修正が必要になった場合、そのカスタムウィジェットのクラスだけを修正すればよいため、メンテナンスが容易になります。
5.2 サブクラス化の実践:カウンターウィジェット
それでは、数値を表示するQLabel
、インクリメント(+1)するQPushButton
、リセットするQPushButton
を一つにまとめたCounterWidget
というカスタムウィジェットを作成してみましょう。
Qt Creatorで新しいプロジェクト「CustomWidgetApp」を「Qt Widgets Application」として作成します。QMainWindow
ベースで作成するとファイルが多くなるので、基底クラスをQWidget
に変更して作成するとシンプルになります。もしQMainWindow
で作成した場合は、mainwindow.h
, mainwindow.cpp
, mainwindow.ui
を一度削除し、以下の手順で新しいクラスを追加します。
- プロジェクトツリーでプロジェクト名を右クリックし、「新規追加…」を選択します。
- 「C++」カテゴリから「C++ Class」を選択します。
- クラス名に「
CounterWidget
」、基底クラスに「QWidget
」と入力します。「Include QObject」にチェックが入っていることを確認し、次へ進んで完了します。
これにより、counterwidget.h
とcounterwidget.cpp
が生成されます。
5.2.1 ヘッダファイル (counterwidget.h
) の編集
“`h
ifndef COUNTERWIDGET_H
define COUNTERWIDGET_H
include
// forward declaration
class QLabel;
class QPushButton;
class CounterWidget : public QWidget
{
Q_OBJECT // シグナル/スロットを使うクラスには必須のマクロ
public:
explicit CounterWidget(QWidget *parent = nullptr);
~CounterWidget();
private slots: // スロットを定義するためのセクション
void onIncrement();
void onReset();
private:
// このクラスが管理するウィジェットへのポインタ
QLabel m_valueLabel;
QPushButton m_incrementButton;
QPushButton *m_resetButton;
int m_counterValue;
};
endif // COUNTERWIDGET_H
“`
#include <QWidget>
: 基底クラスであるQWidget
をインクルードします。forward declaration
: ヘッダファイルでは、ポインタとして保持するだけのクラスはフルインクルードせず、前方宣言 (class QLabel;
) を使うのが良い習慣です。これによりコンパイル時間が短縮されます。Q_OBJECT
マクロ: これが非常に重要です。自作のクラスでシグナルやスロットを使いたい場合は、必ずprivate
セクション(またはpublic
,protected
)の直後にこのマクロを記述しなければなりません。 QtのMOC(Meta-Object Compiler)というツールが、このマクロを目印にして、シグナル/スロットを実現するための追加コードをビルド時に自動生成します。private slots:
: スロットとして使用するメンバ関数を宣言するセクションです。public
やprivate
と同じようにアクセス修飾子としても機能しますが、Qtに「これらはスロットである」と明示する役割もあります。(技術的にはprivate
セクションに宣言しても動作しますが、可読性のために分けるのが一般的です)- メンバ変数: このウィジェットが内包するUI部品へのポインタや、状態(カウンターの値)を保持する変数を宣言します。メンバ変数であることを示すために
m_
というプレフィックスを付けるのが一般的なコーディングスタイルの一つです。
5.2.2 ソースファイル (counterwidget.cpp
) の実装
“`cpp
include “counterwidget.h”
include
include
include
CounterWidget::CounterWidget(QWidget *parent)
: QWidget(parent)
, m_counterValue(0) // メンバ変数の初期化
{
// 1. ウィジェットのインスタンス化
m_valueLabel = new QLabel(QString::number(m_counterValue));
m_incrementButton = new QPushButton(“+1”);
m_resetButton = new QPushButton(“Reset”);
// 2. レイアウトの設定
auto *layout = new QHBoxLayout(this); // thisを渡すと自動でsetLayoutされる
layout->addWidget(new QLabel("Value:"));
layout->addWidget(m_valueLabel);
layout->addWidget(m_incrementButton);
layout->addWidget(m_resetButton);
// 3. シグナルとスロットの接続
connect(m_incrementButton, &QPushButton::clicked, this, &CounterWidget::onIncrement);
connect(m_resetButton, &QPushButton::clicked, this, &CounterWidget::onReset);
}
CounterWidget::~CounterWidget()
{
// このクラスがnewしたオブジェクトは、Qtの親子関係により自動でdeleteされるため
// デストラクタで明示的にdeleteする必要はない。
}
void CounterWidget::onIncrement()
{
m_counterValue++;
m_valueLabel->setText(QString::number(m_counterValue));
}
void CounterWidget::onReset()
{
m_counterValue = 0;
m_valueLabel->setText(QString::number(m_counterValue));
}
“`
- コンストラクタ:
m_counterValue(0)
: メンバ初期化子リストを使って、カウンターの初期値を0に設定します。- ウィジェットのインスタンス化、レイアウトの設定、シグナル/スロットの接続という、UI構築の3ステップをここで行います。UIのセットアップロジックがすべてこのクラス内にカプセル化されているのがわかります。
new QHBoxLayout(this)
: レイアウトのコンストラクタにthis
(CounterWidget
自身へのポインタ)を渡すと、this->setLayout(layout)
を自動的に呼び出してくれます。
- スロットの実装:
onIncrement
とonReset
は、それぞれカウンターの値を変更し、その結果をQLabel
に反映させる処理を記述します。m_valueLabel->setText()
でラベルのテキストを更新しています。
5.2.3 main.cpp
からカスタムウィジェットを呼び出す
最後に、作成したCounterWidget
をメインのウィンドウに表示します。main.cpp
を以下のように書き換えます。
“`cpp
include “counterwidget.h”
include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
CounterWidget w; // 作成したカスタムウィジェットをインスタンス化
w.setWindowTitle("Counter App");
w.show();
return a.exec();
}
“`
これで、CounterWidget
という部品を一つのウィジェットとして簡単に使えるようになりました。もし、カウンターを縦に3つ並べたい場合は、main.cpp
でCounterWidget
を3つ作り、QVBoxLayout
に追加するだけで実現できます。これがカスタムウィジェットの強力な点です。
第6章:Qt Creator UIデザイナの活用
ここまで、すべてのUIをC++コードで記述してきました。この方法はUIの構造を完全にコントロールできるという利点がありますが、複雑なUIになると、コードが長くなり、見た目を想像しながらコーディングするのが難しくなります。
そこで登場するのが、Qt Creatorに統合されたUIデザイナです。UIデザイナを使うと、ドラッグ&ドロップでウィジェットを配置し、プロパティエディタで見た目を調整するなど、視覚的・直感的にUIを設計できます。
6.1 UIファイル (.ui
) の仕組み
UIデザイナで作成したUIのレイアウト情報は、.ui
という拡張子の付いたXMLファイルに保存されます。この.ui
ファイルは、直接プログラムにコンパイルされるわけではありません。
ビルドプロセスの中で、uic
(User Interface Compiler) というツールがこの.ui
ファイルを読み込み、UIを構築するためのC++コードを自動的に生成します。そして、生成されたC++コードが、我々が書いた他のソースコードと一緒にコンパイルされるのです。
6.2 UIデザイナを使ったプロジェクトの作成と実践
それでは、第5章で作成したカウンターアプリを、今度はUIデザイナを使って再実装してみましょう。
- Qt Creatorで新規プロジェクトを作成します。テンプレートは「Qt Widgets Application」を選択します。
- クラス情報画面で、基底クラスを「
QWidget
」に変更し、クラス名を「DesignerCounter
」とします。「UIファイルを生成」のチェックボックスがオンになっていることを確認してください。 - プロジェクトが生成されると、
designercounter.h
、designercounter.cpp
に加えて、designercounter.ui
というファイルが作成されているはずです。
6.2.1 UIの設計
プロジェクトツリーから designercounter.ui
をダブルクリックして、デザインモードを開きます。
画面は大きく分けて4つの領域で構成されています。
- 左側(ウィジェットボックス):
QPushButton
やQLabel
など、使用可能なウィジェットが一覧表示されています。 - 中央(フォームエディタ): UIを実際にデザインするキャンバスです。
- 右上(オブジェクトインスペクタ): フォームに追加したウィジェットが階層構造で表示されます。
- 右下(プロパティエディタ): 選択中のウィジェットのプロパティ(テキスト、サイズ、オブジェクト名など)を編集できます。
以下の手順でカウンターのUIを作成します。
- ウィジェットボックスの「Display Widgets」から「Label」を2つ、フォームエディタにドラッグ&ドロップします。
- 同様に、「Buttons」から「Push Button」を2つドラッグ&ドロップします。
- 1つ目のラベルを選択し、プロパティエディタで
text
プロパティを「Value:」に変更します。 - 2つ目のラベルを選択し、
text
プロパティを「0」に、objectName
プロパティを「valueLabel
」に変更します。objectName
は、後でC++コードからこのウィジェットにアクセスするための重要な名前です。 - 1つ目のボタンを選択し、
text
を「+1」、objectName
を「incrementButton
」にします。 - 2つ目のボタンを選択し、
text
を「Reset」、objectName
を「resetButton
」にします。 - フォームエディタの何もないところをクリックしてフォーム自身を選択し、上部ツールバーにある「水平レイアウトに配置」のアイコンをクリックします。これで、すべてのウィジェットが
QHBoxLayout
に配置されます。
これでUIの見た目の設計は完了です。ファイルを保存(Ctrl+S)します。
6.2.2 C++コードとの連携
UIデザイナで作成したUIを、C++コードからどのように操作するのでしょうか。designercounter.h
を開いてみましょう。
“`h
// … (includes)
QT_BEGIN_NAMESPACE
namespace Ui { class DesignerCounter; } // ★
QT_END_NAMESPACE
class DesignerCounter : public QWidget
{
Q_OBJECT
public:
DesignerCounter(QWidget *parent = nullptr);
~DesignerCounter();
private:
Ui::DesignerCounter *ui; // ★
};
“`
注目すべきは、Ui::DesignerCounter
というクラスへのポインタ ui
がメンバ変数として宣言されている点です。この Ui::DesignerCounter
こそが、uic
がdesignercounter.ui
ファイルから自動生成したクラスです。このクラスは、UIデザイナで配置したウィジェットへのポインタ(valueLabel
, incrementButton
など)をメンバとして保持しています。
次に designercounter.cpp
を見てみましょう。
“`cpp
include “designercounter.h”
include “ui_designercounter.h” // ★ 自動生成されたヘッダファイル
DesignerCounter::DesignerCounter(QWidget *parent)
: QWidget(parent)
, ui(new Ui::DesignerCounter) // ★ uiオブジェクトのインスタンス化
{
ui->setupUi(this); // ★ UIのセットアップ
// シグナルとスロットの接続は手で書く
connect(ui->incrementButton, &QPushButton::clicked, this, &DesignerCounter::onIncrement);
connect(ui->resetButton, &QPushButton::clicked, this, &DesignerCounter::onReset);
// カウンター値のメンバ変数を追加する必要がある
// (ヘッダファイルに private: int m_counterValue; を追加)
m_counterValue = 0;
}
DesignerCounter::~DesignerCounter()
{
delete ui; // ★ uiオブジェクトの解放
}
// onIncrement, onResetスロットの実装は第5章と同様
// (ヘッダにスロット宣言、cppに実装を追加する)
// ただし、ラベルの更新は ui->valueLabel->setText(…) のように行う
“`
#include "ui_designercounter.h"
:uic
が生成したヘッダファイルをインクルードします。ui(new Ui::DesignerCounter)
: コンストラクタでui
オブジェクトをインスタンス化します。ui->setupUi(this)
: これが魔法の関数です。この関数が、UIデザイナで設計したウィジェットのインスタンス化、プロパティ設定、レイアウト設定などをすべて実行し、このクラス(this
)にセットアップしてくれます。- ウィジェットへのアクセス:
ui->valueLabel
やui->incrementButton
のように、ui
ポインタを通して、objectName
で指定した名前で各ウィジェットにアクセスできます。 - シグナルとスロットの接続: UIのロジック(ボタンがクリックされたら何をするか)は、手動で
connect
文を記述します。
このように、UIデザイナはUIの見た目を作る作業を劇的に効率化し、C++コードはUIの振る舞い(ロジック)を記述することに集中させてくれます。この分業が、Qt開発の一般的なスタイルです。
補足: UIデザイナにはシグナル/スロットを視覚的に接続する機能もありますが、複雑なロジックを扱う場合は、コードで明示的にconnect
する方が可読性や保守性の面で優れていることが多いです。
まとめと次のステップ
この記事では、QtとQWidgetを使ったGUIプログラミングの第一歩を踏み出しました。振り返ってみましょう。
- 環境構築: Qt Creatorをインストールし、最初のプロジェクトを作成しました。
- QWidgetの基本:
QApplication
とQWidget
がGUIアプリの根幹であること、親子関係によるメモリ管理の利便性を学びました。 - UIの構築: 様々なウィジェットと、リサイズに対応するためのレイアウト管理の重要性を理解し、実践しました。
- シグナルとスロット: Qtの核心であるイベント処理メカニズムを学び、UIをインタラクティブにする方法を習得しました。
- カスタムウィジェット: UIとロジックをカプセル化し、再利用性を高めるために
QWidget
をサブクラス化する方法を学びました。 - UIデザイナ: C++コードとUIデザインを分離し、開発効率を向上させるUIデザイナの基本的な使い方をマスターしました。
あなたは今、シンプルなデスクトップアプリケーションをゼロから構築するための基本的な知識とスキルを身につけました。しかし、Qtの世界は非常に広大で、ここからが本当の冒険の始まりです。
次に学ぶべきトピックとして、以下のようなものがあります。
QMainWindow
とQDialog
: メニューバー、ツールバー、ステータスバーなどを備えた本格的なアプリケーションウィンドウを作成するためのQMainWindow
や、OK/Cancelボタンを持つダイアログを作成するためのQDialog
。- イベント処理:
mousePressEvent()
やkeyPressEvent()
などの仮想関数をオーバーライドすることで、より低レベルなユーザー入力を処理する方法。 - カスタム描画:
QPainter
クラスを使って、ウィジェット上に独自の図形やグラフィックスを描画する方法。 - Qtのドキュメント: Qtには非常に高品質で網羅的なドキュメントが付属しています。Qt Creatorのヘルプモードや、Web上のQt Documentationは、あなたの最高の相棒になるでしょう。
- その他のQtモジュール: ネットワーク通信を行う
Qt Network
、データベースを扱うQt SQL
、マルチスレッドを簡単にするQt Concurrent
など、Qtが提供する豊富な機能を調べてみてください。
この記事が、あなたのGUIアプリケーション開発の旅における、楽しく実りある一歩となったことを願っています。Happy coding