Qt ではじめる GUI プログラミング

Qt の世界へようこそ - Qt はクロスプラットフォームの GUI ツールキットです。この入門ガイドでは簡単なメモ帳プログラムの作成を通して基本的な Qt の知識を説明します。このガイドを読み終わるころには Qt の概要および API ドキュメントを参照しながら開発に必要な情報を集めることができるはずです。

Hello Notepad

最初のサンプルではウィンドウにテキストエリアを作成して表示します。これは GUI を持つもっとも簡単な Qt プログラムと言えます。

サンプル1

これがソースコードです:

  1. #include <QApplication>
  2. #include <QTextEdit>
  3.  
  4. int main(int argv, char **args)
  5. {
  6.     QApplication app(argv, args);
  7.  
  8.     QTextEdit textEdit;
  9.     textEdit.show();
  10.  
  11.     return app.exec();
  12. }

それでは1行ずつ見ていきましょう。最初の2行でサンプルプログラムに必要な2つのクラス、 QApplication [doc.qt.nokia.com]QTextEdit [doc.qt.nokia.com] のヘッダファイルをインクルードしています。すべての Qt のクラスはその名前のヘッダファイルを持っています。

6行目で QApplication [doc.qt.nokia.com] オブジェクトを作成しています。このオブジェクトはアプリケーション全体のリソースを管理し、 GUI を持つ Qt プログラムを動作させるのに欠くことのできない存在です。Qt はコマンドラインオプションを受け取るため、 argvargs が必要です。

8行目で QTextEdit [doc.qt.nokia.com] オブジェクトを作成しています。テキスト入力は GUI の視覚的な構成要素の1つです。Qt では通常これらをウィジェットと呼んでいます。ウィジェットは他にもスクロールバー、ラベル、ラジオボタンなどがあります。ウィジェットは他のウィジェットのコンテナ (入れ物) になることができます。たとえばダイアログやメインウィンドウなどです。

9行目でテキスト入力を画面に表示しています。ウィジェットはコンテナとしても機能しますので (たとえばツールバーやメニュー、ステータスバーなどのウィジェットを持つ QMainWindow [doc.qt.nokia.com])、ウィジェット単体を独立したウィンドウに表示することが可能です。ウィジェットはデフォルトでは非表示になっています。 show() [doc.qt.nokia.com] 関数を使ってウィジェットを表示してください。

11行目で QApplication [doc.qt.nokia.com] のイベントループを開始します。 Qt アプリケーションが動いているとき、イベントが生成されてアプリケーションのウィジェットに送られます。具体的にはマウスボタンを押し込んだ、キーの押下などのイベントです。テキスト入力のウィジェットでテキストを入力したとき、ウィジェットはキー入力イベントを受け取り、打ち込まれたテキストを描画することで応答します。

アプリケーションの起動は、コマンドプロンプトを開いて、.cpp ファイルのあるディレクトリに移動した後、次のコマンドを入力してプログラムをビルドしてください。

  1. qmake -project
  2. qmake
  3. make

その結果、part1 ディレクトリに実行ファイルが生成されます (Windows の場合は make のかわりに nmake を使う必要があります。さらに実行ファイルは part1/debug または part1/release に置かれます)。qmake は Qt のビルドツールで、設定ファイルを受け取ります。 qmake-project オプションを使用するとその設定ファイルを生成してくれます。渡された設定ファイル (拡張子は .pro) からプログラムをビルドするための make ファイルを生成します。独自に .pro ファイルを書く方法については後ほど説明します。

関連するドキュメント

終了ボタンを追加する

実際のアプリケーションでは通常より多くのウィジェットが必要です。そこで QPushButton [doc.qt.nokia.com] をテキスト入力の真下に配置してみましょう。このボタンは押されるとメモ帳プログラムが終了します。

サンプル2

それではソースコードを見ていきます。

  1. #include <QtGui>
  2.  
  3. int main(int argv, char **args)
  4. {
  5.     QApplication app(argv, args);
  6.  
  7.     QTextEdit textEdit;
  8.     QPushButton quitButton("Quit");
  9.  
  10.     QObject::connect(&quitButton, SIGNAL(clicked()), qApp, SLOT(quit()));
  11.  
  12.     QVBoxLayout layout;
  13.     layout.addWidget(&textEdit);
  14.     layout.addWidget(&quitButton);
  15.  
  16.     QWidget window;
  17.     window.setLayout(&layout);
  18.  
  19.     window.show();
  20.  
  21.     return app.exec();
  22. }

1行目ではすべての Qt の GUI クラスを含む QtGui [doc.qt.nokia.com] ヘッダをインクルードしています。

10行目では終了ボタンが押されたときにアプリケーションが終了するように Qt のシグナルとスロットの仕組みを使っています。
スロットは実行時に名前 (通常の文字列) で呼び出される機能です。シグナルは呼び出されたときに登録されたスロットを起動する機能です。これを Qt では、スロットをシグナルにつないで、シグナルを送信する、と言います。

quit() [doc.qt.nokia.com] はアプリケーションを終了する QApplication [doc.qt.nokia.com] のスロットです。 clicked() [doc.qt.nokia.com]QPushButton [doc.qt.nokia.com] が押されたときに送信されるシグナルです。静的な QObject::connect() [doc.qt.nokia.com] 関数はスロットをシグナルにつなぎます。2つのマクロ SIGNALSLOT はシグナルとスロットの関数シグニチャを受け取ります。そしてシグナルを送信するオブジェクトと受け取るオブジェクトのポインタも渡します。

12行目では QVBoxLayout [doc.qt.nokia.com] オブジェクトを生成しています。前述の通り、ウィジェットは他のウィジェットを含むことができます。子ウィジェットの位置やサイズを直接指定することもできますが、通常はレイアウトを使うのが簡単です。レイアウトはウィジェットの子要素の位置とサイズを管理します。 例えば QVBoxLayout [doc.qt.nokia.com] は子要素を縦の列に配置します。

13行目と14行目ではテキスト入力とボタンをそれぞれレイアウトに追加しています。
そして17行目でウィンドウにそのレイアウトを設定しています。

関連するドキュメント

QWidget の派生クラスを作る

ユーザがアプリケーションを終了するとき、本当に終了したいかどうかを確認するダイアログを表示したいとします。このサンプルでは QWidget [doc.qt.nokia.com] の派生クラスを定義して終了ボタンにつなぐためのスロットを追加します。

サンプル3

それではソースコードを見ていきます。

  1. class Notepad : public QWidget
  2. {
  3.     Q_OBJECT
  4.  
  5.     public:
  6.         Notepad();
  7.  
  8.     private slots:
  9.         void quit();
  10.  
  11.     private:
  12.         QTextEdit *textEdit;
  13.         QPushButton *quitButton;
  14. };

Q_OBJECT マクロはクラス定義の先頭に置く必要があり、このクラスが QObject であることを宣言します (QObject [doc.qt.nokia.com] を継承しているので当然ではありますが)。 QObject [doc.qt.nokia.com] は通常の C++ クラスにさまざまな機能を追加します。特にクラス名とスロット名は実行時に利用されます。同様にスロット引数の種類についても知ることができます。

9行目 (実際のソースコードでは13行目) で quit() スロットを宣言しています。slots マクロを使えば簡単です。 これでシグニチャの一致するシグナル (ここでは引数を1つも持たないシグナルなら何でも) を quit() スロットに接続できます。

main() 関数で GUI の構築やスロットの接続を行うかわりに、 Notepad クラスのコンストラクタを使います。

  1.          Notepad::Notepad()
  2.          {
  3.              textEdit = new QTextEdit;
  4.              quitButton = new QPushButton(tr("Quit"));
  5.  
  6.              connect(quitButton, SIGNAL(clicked()), this, SLOT(quit()));
  7.  
  8.              QVBoxLayout *layout = new QVBoxLayout;
  9.              layout->addWidget(textEdit);
  10.              layout->addWidget(quitButton);
  11.  
  12.              setLayout(layout);
  13.  
  14.              setWindowTitle(tr("Notepad"));
  15.          }

上のクラス定義のように、 QObject [doc.qt.nokia.com] へのポインタを使います (textEditquitButton)。通常、 QObject [doc.qt.nokia.com] は常にヒープ上に作成し、それらをコピーはしないようにしてください。

ユーザに表示される文字列が tr() [doc.qt.nokia.com] 関数で囲まれています。この関数はアプリケーションを他の言語 (英語や中国語) 向けに提供するときに必要になります。ここではこれ以上触れませんが、関連ドキュメントの Qt Linguist を参照してみてください。

関連するドキュメント

.pro ファイルを作成する

qmake-project オプションを使うかわりに、このサンプルプログラムのための .pro ファイルを書いてみましょう。

  1. HEADERS =  notepad.h
  2. SOURCES =  notepad.cpp \
  3.            main.cpp

以下のシェルコマンドでサンプルプログラムをビルドします。

  1. qmake
  2. make

QMainWindow を使う

QMainWindow [doc.qt.nokia.com] はメニュバー、ドック、ツールバー、ステータスバーを追加するための独自のレイアウトが備わっているため、多くのアプリケーションがその恩恵を受けるでしょう。中央のエリアにはどのような種類のウィジェットでも配置することができます。今回のサンプルプログラムではテキスト入力がそこに置かれます。

サンプル4

それでは新しい Notepad クラスの定義を見ていきましょう。

  1.  #include <QtGui>
  2.  
  3.  class Notepad : public QMainWindow
  4.  {
  5.      Q_OBJECT
  6.  
  7.  public:
  8.      Notepad();
  9.  
  10.  private slots:
  11.      void open();
  12.      void save();
  13.      void quit();
  14.  
  15.  private:
  16.      QTextEdit *textEdit;
  17.  
  18.      QAction *openAction;
  19.      QAction *saveAction;
  20.      QAction *exitAction;
  21.  
  22.      QMenu *fileMenu;
  23.  };

ドキュメントの保存と開く処理のためにさらに2つのスロットを追加しました。これらのスロットは次のセクションで実装します。

メインウィンドウにおいてはしばしば同じスロットがさまざまなウィジェットから呼び出されます。具体的にはメニューアイテムとツールバーのボタンなどです。これをより簡単にするために Qt はさまざまなウィジェットに渡されて、1つのスロットに接続される QAction [doc.qt.nokia.com] を提供しています。たとえば QMenu [doc.qt.nokia.com]QToolBar [doc.qt.nokia.com] は同じ QAction [doc.qt.nokia.com] から メニューアイテムとツールボタンを作成できます。これがどのように動くのかちょっと見てみましょう。

これまで通り Notepad クラスのコンストラクタを GUI の構築に使います。

  1.  Notepad::Notepad()
  2.  {
  3.      saveAction = new QAction(tr("&Open"), this);
  4.      saveAction = new QAction(tr("&Save"), this);
  5.      exitAction = new QAction(tr("E&xit"), this);
  6.  
  7.      connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
  8.      connect(saveAction, SIGNAL(triggered()), this, SLOT(save()));
  9.      connect(exitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
  10.  
  11.      fileMenu = menuBar()->addMenu(tr("&File"));
  12.      fileMenu->addAction(openAction);
  13.      fileMenu->addAction(saveAction);
  14.      fileMenu->addSeparator();
  15.      fileMenu->addAction(exitAction);
  16.  
  17.      textEdit = new QTextEdit;
  18.      setCentralWidget(textEdit);
  19.  
  20.      setWindowTitle(tr("Notepad"));
  21.  }

QAction [doc.qt.nokia.com] は追加するウィジェットにラベルとして表示されるテキストと共に作成します (今回のサンプルではメニューアイテム)。ツールバーに追加するのであれば、そのアクションに アイコン [doc.qt.nokia.com] を持たせておくこともできます。

これでメニューアイテムがクリックされると、アクションが起動し、スロットが呼び出されます。

関連するドキュメント

ドキュメントの保存と読み込み

このサンプルでは1つ前のサンプルで追加した open()save() の2つのスロットを実装していきます。

サンプル5

open() スロットの方からはじめてみましょう:

  1.  QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "",
  2.      tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4.  if (fileName != "") {
  5.      QFile file(fileName);
  6.      if (!file.open(QIODevice::ReadOnly)) {
  7.          QMessageBox::critical(this, tr("Error"),
  8.              tr("Could not open file"));
  9.          return;
  10.      }
  11.      QString contents = file.readAll().constData();
  12.      textEdit->setPlainText(contents);
  13.      file.close();
  14.  }

まず最初のステップで開くファイル名をユーザに求めています。Qt にはユーザがファイルを選択できるダイアログである QFileDialog [doc.qt.nokia.com] があります。上の画像のダイアログは Kubuntu で表示させたものです。静的な getOpenFileName() [doc.qt.nokia.com] 関数はモーダルなファイルダイアログを表示し、ユーザがファイルを選択するまで制御を戻しません。ダイアログは選択されたファイルのファイルパスか、ユーザがキャンセルした場合は空の文字列を返します。

もし何らかのファイル名があるときは open() [doc.qt.nokia.com] を使ってファイルのオープンを試みて、正しく開くことができたら true を返します。ここではエラー処理について詳しく説明しませんが、関連ドキュメントを参照してみてください。ファイルが開けなかった場合、ここでは QMessageBox [doc.qt.nokia.com] を使ってエラーメッセージを表示します (詳しくは QMessageBox [doc.qt.nokia.com] クラスの説明を参照してください)。

今回はファイルのすべてのデータを QByteArray [doc.qt.nokia.com] として返す readAll() [doc.qt.nokia.com] 関数を使っています。 constData() [doc.qt.nokia.com] は配列のすべてのデータを const char* で返します (QString [doc.qt.nokia.com] にはそのコンストラクタがある)。そして内容がテキスト入力に表示されます。あとは close() [doc.qt.nokia.com] を呼び出してファイルディスクリプタを OS に返します。

次は save() スロットにとりかかりましょう。

  1.  QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "",
  2.      tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4.  if (fileName != "") {
  5.      QFile file(fileName);
  6.      if (!file.open(QIODevice::WriteOnly)) {
  7.          // error message
  8.      } else {
  9.          QTextStream stream(&file);
  10.          stream << textEdit->toPlainText();
  11.          stream.flush();
  12.          file.close();
  13.      }
  14.  }

テキスト入力の内容をファイルに書き込むとき、 QFile [doc.qt.nokia.com] オブジェクトをラップする QTextStream [doc.qt.nokia.com] クラスを使います。テキストストリームは QString をファイルに書き込むことができます。 QFile [doc.qt.nokia.com] はオブジェクト化されていない文字列データ (char*) のみを QIODevice [doc.qt.nokia.com]write() [doc.qt.nokia.com] 関数で受け付けます。

関連するドキュメント

  • ファイルと入出力デバイス: QFile [doc.qt.nokia.com], QIODevice [doc.qt.nokia.com]