Qt をはじめよう! 第19回: 独自ウィジェットを作成しデザイナで使用しよう

前回 はタブオーダーの設定について学びました。今回は独自ウィジェットを作成し、それらのデザイナでは標準で提供 されていない ウィジェットをデザイナで使用する簡単な方法を学びましょう。

独自ウィジェットについて

Qt では [qt "ラベル" l=qlabel] や [qt "ボタン" l=qpushbutton] のような基本的なものから [qt "リストビュー" l=qlistview] や [qt "ウェブのコンテンツを表示するためのウィジェット" l=qwebview] など [qt "多種多様なウィジェット" l=gallery] を提供しています。これらのウィジェットをそのまま使用するだけではなく、あるウィジェットの派生クラスを作成して機能を拡張したり、いくつかのウィジェットを組み合わせて新しいウィジェットを作成したり、さらには全く新しいウィジェットを作成することも可能です。今回は最初の2つのケースを例に、独自クラスを作成し、それらをデザイナで使用する方法について解説します。

プロジェクトの作成

今回は新たなプロジェクトを作成するところから始めましょう。プロジェクトの作成方法は以前にも解説していますが、Qt Creator のバージョンアップに伴い若干変更されている点がありますので、今回は Qt Creator 2.2.1 を用いて再度説明します。

はじめに、メニューから「ファイル(F)」→「ファイル/プロジェクトの新規作成(N)…」 を選択してください。

テンプレートを選択してください: テンプレートを選択してください:

プロジェクトの中から「Qt ウィジェット プロジェクト」を選択し、「Qt GUI アプリケーション」を選択して「選択(C)…」をクリックしてください。

プロジェクト名とパス プロジェクト名とパス

ターゲット設定 ターゲット設定

名前を「promotion」と変更し「次へ(N)>」をクリックしてください。パスは任意のディレクトリを指定してください。この際、日本語やスペースなどは含めないようにしましょう。
次に、使用する Qt のバージョンを選択します。Qt SDK のインストールの方法などによって、様々なターゲットやバージョンの Qt がここの一覧に現れることがありますが、上記のスクリーンショットを参考に Qt SDK に含まれるデスクトップ向けの Qt を選択し「次へ(N)>」をクリックしてください。

クラス情報 クラス情報

プロジェクト管理 プロジェクト管理

クラス名を「Promotion」に、基底クラスを「QWidget」に変更してください。
今回も GUI で UI のデザインを作成するため、「フォームを生成する(G)」のチェックはつけたままにしてください。
最後に、ファイルの追加先と追加されるファイルを確認し、「完了(F)」をクリックするとプロジェクトの作成は完了です。

プロジェクトが作成されました プロジェクトが作成されました

これからこの空のプロジェクトに独自のウィジェットを作成して、デザイナで利用していきます。

ウィジェットの派生クラスの作成

それではデザイナから使用するウィジェットを作成しましょう。[qt QWidget] は [qt QObject] の派生クラスのため、既に 第11回 で学んだ QObject の派生クラスの作成方法も参考にしてください。

ここでは [qt QLineEdit] の派生クラスを作成し、拡張する機能として [qt "text" l=qlineedit m=#text-prop] プロパティの文字列長を扱うことにします。
それではメニューから「ファイル(F)」→「ファイル/プロジェクトの新規作成(N)…」を選択してください。

テンプレートを選択してください: テンプレートを選択してください:

プロジェクト中から「C++」を選択し、「C++ クラス」を選択して「選択(C)…」をクリックしてください。

クラス名を入力してください クラス名を入力してください

プロジェクト管理 プロジェクト管理

クラス名を「LineEdit」に、基底クラスを「QLineEdit」に、型情報を「QWidget を継承」変更し、「次へ(N)>」をクリックしてください。
最後に、先ほど作成した「promotion.pro」に「lineedit.h」と「lineedit.cpp」の2つのファイルが追加されることを確認し、「完了(F)」をクリックしクラスを生成します。

それでは LineEdit クラスを変更して文字列長の取得関数とそれが変更された際に発生するシグナルを作成しましょう。この情報は読み取り専用なので、設定関数は作成しません。以下がソースコードと変更箇所になります。

lineedit.h
#ifndef LINEEDIT_H
#define LINEEDIT_H

#include <QLineEdit>

class LineEdit : public QLineEdit
{
Q_OBJECT
public:
explicit LineEdit(QWidget *parent = 0);
int textLength() const;

signals:
void textLengthChanged(int textLength);

private slots:
void updateTextLength(const QString &text);

private:
int m_textLength;
};

#endif // LINEEDIT_H

11行目
文字列長を返す取得関数 textLength() の宣言です。
14行目
文字列長が変更された際に発生するシグナル textLengthChanged() の宣言です。
17行目
基底クラス QLineEdit の文字列が変更された際に処理をするプライベートスロット updateTextLength() の宣言です。
20行目
文字列長を保持するためのプライベート変数 m_textLength の宣言です。
lineedit.cpp
#include "lineedit.h"

LineEdit::LineEdit(QWidget *parent) :
QLineEdit(parent),
m_textLength(0)
{
connect(this, SIGNAL(textChanged(QString)),
this, SLOT(updateTextLength(QString)));
}

int LineEdit::textLength() const
{
return m_textLength;
}

void LineEdit::updateTextLength(const QString &text)
{
if (m_textLength == text.length())
return;

m_textLength = text.length();
emit textLengthChanged(m_textLength);
}

5行目
m_textLength を 0 で初期化しています。
7〜8行目
QLineEdit で文字列が変更された際に updateTextLength() を呼び出すためにシグナルとスロットを接続します。
11〜14行目
文字列長取得関数である textLength() の実装です。現在の文字列長である m_textLength を返します。
16〜23行目
このプライベートスロットは 7〜8行目の connect() により、親クラスである QLineEdit の文字列に変更があった場合に呼ばれ、文字列長の再計算をします。
現在の文字列長と新しい文字列長を比較し(18行目)、変更がない場合には何もしません(19行目)。変更があった場合には m_textLengthに新しい値を設定し(21行目)、変更を通知するためのシグナル textLengthChanged() を発生させます(22行目)。

シグナル/スロットの作成の詳細は 第12回 で解説していますのでそちらをご覧ください。

デザイナで独自ウィジェットを使用する

それでは、作成した LineEdit を promotion.ui から使用してみましょう。
promotion.ui を開いて、下記のようなデザイナの画面を表示してください。

promotion.ui promotion.ui

デザイナでは自分で作成したウィジェットをそのまま直接配置して使用することはできません。直接配置できるようにするためには [qt "独自ウィジェット用のデザイナプラグインを作成し" l=designer-creating-custom-widgets] ウィジェットリストに追加する作業が必要で、Qt Creator ではそのためのプロジェクトのテンプレート(「ファイル/プロジェクトの新規作成(N)…」→「他のプロジェクト」→「Qt カスタム デザイナ ウィジェット」)も用意しています。しかし、これはプロジェクトに依らない汎用のウィジェットを作成して使う場合の上級者向けの方法のため、今回はもう少し簡単な「[qt "格上げ" l=designer-using-custom-widgets]」という方法を使用することにします。これは独自ウィジェットの代わりとなるデザイナで使用可能なウィジェットを最初に配置し、それを独自ウィジェットに変換する方法です。

まずはじめに、作成したクラスの基底クラスのウィジェットをさかのぼり、その中で一番近い、デザイナで配置可能なウィジェットを選び配置します。

継承関係は各クラスのドキュメントに記載されています。
"Inherits" で基底クラスを、 "Inherited by" で派生クラスを示します。LineEdit の継承関係を見ると
  • [qt QObject]
    • [qt QWidget] (QWidget は [qt QPaintDevice] も継承しています)
      • [qt QLineEdit]
        • LineEdit

となっており、LineEdit から見た場合に一番近い、デザイナで使用可能なウィジェットは QLineEdit にあたります。

継承関係はドキュメントに記載されています 継承関係はドキュメントに記載されています

 

このため、LineEdit の代わりに一度 QLineEdit をフォームに配置します。

QLineEdit を配置 QLineEdit を配置

それでは配置した QLineEdit を LineEdit に変更しましょう。Qt の GUI デザイナではこの操作を「格上げ(promotion)」と呼びます。
QLineEdit 上で右クリックをし、コンテキストメニューの中から「格上げ先を指定...」を選択します。

コンテキストメニューから「格上げ先を指定...」を選択 コンテキストメニューから「格上げ先を指定...」を選択

格上げされたウィジェット 格上げされたウィジェット

ダイアログの上部「格上げされたクラス」は格上げするためのウィジェットの一覧の情報です。現在はまだこれに関する情報は設定していないため、一覧に何もない状態です。
ダイアログの下部「新しい格上げされたクラス」で格上げするウィジェットを追加します。

格上げするクラスの登録

それでは格上げするクラスを登録しましょう。ダイアログの各項目(図中の赤で囲んだところ)を図の右の内容に従って編集してください。

格上げするクラスの追加 格上げするクラスの追加

ベースクラス名:
QLineEdit
格上げされたクラス名:
LineEdit
ヘッダファイル
lineedit.h
グローバルにインクルードする
チェックなし

太字の項目だけ変更すれば同じになるはずです。

これが QLineEdit クラスをヘッダファイル lineedit.h 内に定義されている LineEdit という名前のクラスに格上げ可能にする設定になります。「追加」ボタンをクリックし「格上げされたクラス一覧」にこの情報が登録されることを確認しましょう。

LineEdit の格上げ情報が登録されました LineEdit の格上げ情報が登録されました

「グローバルにインクルードする」のチェックは、デフォルトでは #include "lineedit.h" とインクルードされるヘッダファイルを #include <lineedit.h> とインクルードするための設定になります。今回の場合は widget.ui と同じフォルダに lineedit.h があるため、グローバルにはインクルードせず、 #include "~.h" の形式を採用します。このチェックは状況に応じて使い分けてください。

格上げをする

それではここで登録した格上げ情報を使用し、フォームに配置した QLineEdit を LineEdit に格上げしましょう。「格上げされたクラス一覧」の LineEdit の行を選択し、「格上げ」ボタンをクリックします。

格上げをクリック 格上げをクリック

LineEdit に格上げされました LineEdit に格上げされました

これにより QLineEdit だったウィジェットが LineEdit として扱われるようになりました。オブジェクトツリーで該当するウィジェットのクラス名が LineEdit に変わっており、このウィジェットのプロパティービューでもクラス名が LineEdit と表示されます。ここで、デザイナでの見た目上は QLineEdit のままであることに注意してください。

シグナル/スロットの接続

LineEdit の文字列長を表示するために今回は [qt QLCDNumber] クラスを使用します。このクラスは数字や一部のアルファベットを 7セグメントディスプレイ 形式で表示するためのウィジェットになります。

LCD Number を配置 LCD Number を配置

先ほど作成した LineEdit の textLengthChanged(int) シグナルを、LCD Number の [qt "display(int)" l=qlcdnumber m=#display] スロットに接続するためにシグナル/スロットの接続モードに移行します。

LineEdit のシグナルを LCD Number のスロットに接続 LineEdit のシグナルを LCD Number のスロットに接続

textLengthChanged(int) は表示されません textLengthChanged(int) は表示されません

この際、Qt のデザイナでは格上げされたクラスからはシグナルやスロットなどの情報を自動では読み込みませんので、LineEdit の textLengthChanged(int) シグナルは表示されません。格上げ後のクラスに作成してあるシグナル(やスロット)を使用するには 第16回 で学んだ方法で手動で追加する必要があります。

上記の画面で「編集...」ボタンをクリックし、シグナルに textLengthChanged(int) を追加します。

textLengthChanged(int) シグナルを追加 textLengthChanged(int) シグナルを追加

シグナル/スロットを接続 シグナル/スロットを接続

「OK(O)」ボタンをクリックし、戻ったダイアログで textLengthChanged(int)display(int) に接続します。

それでは確認してみましょう。プレビューでは格上げされたウィジェットは格上げ前のウィジェットと同じ動作をしますので、今回の場合は動作を確認できません。このため、プロジェクトをビルドし実行して確認する必要があります。アプリケーションを実行し、LineEdit に適当な文字を入力し、内容が変更された際に文字列長が QLCDNumber ウィジェットに表示されることを確認してください。

実行して文字列長が表示されることを確認 実行して文字列長が表示されることを確認

Qt Designer フォームクラスの作成

ここまでは、QLineEdit の派生クラス LineEdit 作成し、それをデザイナから使用する方法について学びました。次にいくつかのウィジェットを組み合わせた場合についても試してみましょう。

ここでは QLineEdit と QToolButton を組み合わせた簡単なウィジェットを作成します。機能の実装は省略しますが、ファイルのパスやディレクトリを指定するようなウィジェットを想定しています。

それでは Qt Creator の「ファイル/プロジェクトの新規作成」から「Qt」の「Qt Designer フォームクラス」を作成しましょう。
ここで「Qt Designer フォーム」の方は .ui ファイルだけを作成するテンプレートで、「Qt Designer フォームクラス」は .ui ファイルとそれを使用するクラス(の .h ファイルと .cpp ファイル)を作成するテンプレートです。.ui ファイルは Qt 独自の形式のため、「C++」ではなく「Qt」のカテゴリに分類されています。

テンプレートを選択してください: テンプレートを選択してください:

フォーム テンプレートを選択して下さい フォーム テンプレートを選択して下さい

フォームテンプレートは「Widget」を選択します。

 

クラス名を選択して下さい クラス名を選択して下さい

プロジェクト管理 プロジェクト管理

C++ クラスの作成時と同様に、クラス名を「FilePathWidget」に変更し、promotion.pro に filepathwidget.h と filepathwidget.cpp が追加されることを確認して「完了(F)」をクリックします。

filepathwidget.ui をデザインモードで開き、[qt QLineEdit] と [qt QToolButton] を配置し、フォーム上で水平方向に並べます。

ウィジェットを配置 ウィジェットを配置

sizePolicy の設定 sizePolicy の設定

また、フォームの [qt "sizePolicy" l=qwidget m=#sizePolicy-prop] の「縦方向のポリシー」を「[qt "Fixed" l=qsizepolicy m=#Policy-enum]」に設定しておきましょう。これは、このウィジェットをレイアウトの中で使用する際に縦方向には広がらないための設定になります。

それでは FilePathWidget を promotion.ui で使用してみましょう。promotion.ui をデザイナで開きます。
ここで、FilePathWidget の基底クラスは QWidget のため、今回は Widget をフォームに配置します。

Widget を配置 Widget を配置

この Widget を先ほどと同じ手順で FilePathWidget に格上げしましょう。

FilePathWidget を格上げ情報に登録 FilePathWidget を格上げ情報に登録

FilePathWidget に格上げ FilePathWidget に格上げ

FilePathWidget に格上げ完了 FilePathWidget に格上げ完了

今回の場合は基底クラスが QWidget のため、デザイナ上では枠だけの表示となります。
それでは、フォームにレイアウトを設定して、ビルドと実行をして確認してみましょう。

レイアウトを設定 レイアウトを設定

実行して確認 実行して確認

格上げした FilePathWidget になって表示されることを確認しましょう。
このように「格上げ」を用いる場合、デザイナ上では FilePathWidget のサイズ関連の情報([qt "sizeHint" l=qwidget m=#sizeHint-prop] や [qt "sizePolicy" l=qwidget m=#sizePolicy-prop] など)が使用する側のフォームに反映されないことに注意しましょう。このため、レイアウト適用後にフォームやウィンドウをリサイズしたときの見た目や動きがデザイナと実行時に異なる場合があります。デザイナではサイズ情報の無い Widget としてレイアウトが行われますが、実行時には格上げしたウィジェットのサイズ関連の情報を使用しレイアウトが行われます。

格上げの仕組みについて

最後に、格上げの仕組みを見てみましょう。
promotion.ui をサイドバーの「編集」ボタンを押して「編集モード」で開き、XML 形式のソースを見てみましょう。

「編集モード」でソースを表示 「編集モード」でソースを表示

ソースの中には以下のように格上げの情報が登録されています。
promotion.ui

<customwidgets>
<customwidget>
<class>LineEdit</class>
<extends>QLineEdit</extends>
<header>lineedit.h</header>
<slots>
<signal>textLengthChanged(int)</signal>
</slots>
</customwidget>

この情報を使用して、以下のように実際のウィジェットの情報が定義されています。

<widget class="LineEdit" name="lineEdit">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>113</width>
<height>27</height>
</rect>
</property>
</widget>

また、この ui ファイルから生成される ui_promotion.h を見てみると、自分で作成した LineEdit のヘッダファイル "lineeidt.h" がインクルードされ、setupUi() 関数の中では LineEdit のインスタンスが生成されていることが分かります。
ui_promotion.h

#include "lineedit.h"

...

lineEdit = new LineEdit(Promotion);

おわりに

今回は2種類の独自ウィジェットの作成方法とそれらをデザイナ上で使うためのの「格上げ」機能について勉強しました。
Qt でアプリケーションの開発をする際には本当によく使う便利な機能ですので、是非活用して下さい。
次回からは QWidget を派生した独自ウィジェットの作成を通して様々な機能を学んでいきましょう。


Blog Topics:

Comments