wxWidgets は Linux, Windows, Mac OS X など多くのプラットフォー ムに対応したオープンソースの GUI ツールキットである。本稿で は wxWidgets を用いてクロスプラットフォーム対応の GUI アプリ ケーションを開発する方法を紹介する。
はじめに
Unix の大きな魅力のひとつに、強力なコマンドライン処理がある。 zsh などのシェルと perl などのワンライナーを組み合せて、大量 のファイルを一気に処理するときなどは、「これぞコマンドライン の醍醐味」と感じる瞬間である。
一方、Unix の大きな不満のひとつに、凶悪なコマンドライン書法 がある。滅多に使わないコマンドのために、複雑なコマンドライン オプションをしぶしぶ覚えるときなどは、「勘弁してよ。GUI なら 楽なのに」と思うことも多い。
コマンドラインと GUI で、どちらがどのようなときに有利かは一 概には言えないが、前者の方が覚えるのが面倒で後者の方がとっつ きやすいという傾向はあるようだ。筆者の場合は、Unix を覚え始 めた当初はマウス操作を極力避けるというコマンドライン信奉者で あったが、次のような理由で次第に GUI のツールを多く使うよう になってきた。
- GNOME や KDE などのデスクトップ環境が進歩してきた。
- GUI の方が楽な作業を行うことが増えてきた。
- 複雑なコマンドラインオプションなどを覚える気力が衰えてきた。
このような変化を受けて、自分が作るソフトウェアもコマンドライ ンベースのものだけでなく、GUI も作ろうという気が湧いてきた。 GUI の開発は、コマンドラインベースと比べて手間がかかる*1 ため、開発者 からしてみると嬉しくない面もあるが、その分ユーザが楽をできる。 多くのユーザに使ってもらえるという利点も大きい。古くから GUI プログラミングに慣れ親しんでいる方から見れば「何を今さら」と いう話だが、筆者の場合はずいぶん時間がかかってしまった。
本稿では、筆者が GUI のプログラムを開発するにあたって選択し た wxWidgets *2 という GUI ツー ルキットを取り上げ、クロスプラットフォーム*3 対応の GUIアプリケーションを開発す る方法を紹介する。といっても wxWidgets は大規模なツールキッ トであり、1回の記事ではとても全貌を紹介しきれるものではない。 そこで、本稿では、wxWidgets を用いて GUI アプリケーションを 開発するにあたって足掛かりとなるノウハウを駆け足で紹介するこ とにしたい。
GUIツールキット選び
筆者は自分のプログラム用の GUI ツールキットを選択するに当たっ て、次の条件を満たすものを探した。
- Unix と Windows の両方で使えること (クロスプラットフォーム)。
- 各プラットフォームにおいて GUI のルックアンドフィールが調和すること。
- C または C++ のライブラリとして使えること。
- オープンソースであること。
1 の条件はいきなり厳しいが、筆者は日常的に Unix と Windows の両方を利用しているため欠かせない条件である。また、せっかく GUI のアプリケーションを作るなら、ユーザ数の多い Windows プ ラットフォームは外せない、という動機もある。
2 の条件もなかなか難しい。クロスプラットフォーム対応の GUIツー ルキットには、ボタンなどの GUI 部品を独自のデザインで描画す るものも多いため、Windows 上なのに Unix 風のウィンドウが表示 されてしまう、といったケースが見られる。筆者は GUI のルック アンドフィールが調和していないと気になってしまうため、外せな い条件に含めた。
3 の条件は、C または C++ でネイティブの実行ファイルを作りた い、という要求からきている。 Java や C# などを使うと、実行時 にそれぞれのランタイム環境が別途必要になるが、これを避けたかっ た。「このソフトウェアを使うには .NET の再頒布パッケージ (23.7 MB) を事前にインストールしてください」というのでは、使っ てみようという敷居がかなり高くなってしまう。また、筆者の場合 は元々 C 言語で書いたプログラムにGUI を追加したいという事情 もあった。
4 の条件は、オープンソースのソフトウェアを開発するにあたって、 利用するライブラリもオープンソースで揃えたかったからである。 オープンソースの意味については「オープンソースの定義」のサイ ト*4を参 照していただきたい。
以下に筆者が検討した GUI ツールキット (GUI ツールキットを提 供する統合開発環境も含む) について簡単に紹介する。
Win32API
Win32API は Windows が備えている API である。クロスプラット フォームという要件をいきなり満たしていないが、特別なライブラ リを使わなくても Windows 用の GUI アプリケーションを作成でき るという利点がある。しかし、Win32API は低いレイヤーの非常に 込み入った API であるため (たとえば、ウィンドウを作成する CreateWindow の引数は 11 個もある)、これを今から覚えてプログ ラムを書くのは大変そうだ、という理由で見送った。
Ultimate++
Ultimate++*5 はクロスプラッ トフォーム対応の統合開発環境である。 C++ で記述されている。 特徴として、新しいプロジェクトであること (最初の 0.0.0 が公 開されたのは 2003年11月)、API がかなりすっきりしていることな どが挙げられる。ウェブサイトに掲載されている wxWidgets との 比較*6 では、同様 のプログラムを 1/3 程度のコードで実現している。しかし、
- いろいろ対策を試みたが日本語の文字化けが解決しなかった。
- ボタンなどの GUI 部品を独自のデザインで描画している。
という点が気になったため、残念ながら利用を見送った。
WideStudio
WideStudio*7 は平林俊一氏が 開発したクロスプラットフォーム対応の統合開発環境である。C++ で記述されている。特徴として、ビジュアルウィンドウエディタと 呼ばれるGUI ビルダーを搭載すること、 Perl, Python, Ruby など のスクリプト言語にも対応していることなどが挙げられる。しかし、 こちらも GUI 部品を独自のデザインで描画しているため、残念な がら利用を見送った。
GTK+
GTK+*8 は、 GIMP*9 や GNOME*10 などで利用されている GUI ツールキットである。C 言語で記述されており、主に Unix 上で広 く利用されている。特徴として、テーマという仕組みによりルック アンドフィールの切り替えが容易であること、C言語で本格的なオ ブジェクトシステムを構築していること、などが挙げられる。
GTK+ も GUI部品を独自に描画しているため、Windows 上で普通に GTK+ を利用すると、スクロールバーやボタンなどの外観が Windows の標準と異なってしまう。この点は、 GTK-Wimp*11 というテー マを使えば解決できる。GTK-Wimp は GTK+ のルックアンドフィー ルを Windows そっくりにする。
Windows 上の GTK+ の例 (テーマなし)
Windows 上の GTK+ の例 (GTK-Wimp テーマ)
とはいうものの、Windows そっくりにするために別途テーマが必要 であることと、GTK+ は Windows 上ではあまり使われていない、と いう点が気になったため、残念ながら利用を見送った。
Qt
Qt*12 は TrollTech が開発したクロスプラットフォーム対応の GUI ツー ルキットである。特徴として、イベント処理にシグナルとスロット という仕組みを用いること、メタオブジェクトコンパイラという仕 組みを用いて C++ を拡張している*13 ことなどが挙げられる。洗練さ れた API やコードの品質などの点で評価が高いが、残念ながら Windows 版はフリーではないため利用を見送った。
wxWidgets
以上のような候補との比較検討を行って最終的に残ったのが、 wxWidgets である。wxWidgets についてはこれから詳しく述べる。
wxWidgets とは
wxWidgets はクロスプラットフォーム対応の GUI ツールキットで ある。C++ で記述されている。
wxWidgets の大きな特徴として、各プラットフォームのルックアン ドフィールに調和した GUI アプリケーションを作成できる、とい う点が挙げられる。移植性も非常に高く、wxWidgets を使って1つ のソースコードを書けば、Windows や Linux などのプラットフォー ムでほぼそのままビルドできる。
wxWidgets はボタンなどの GUI 部品の描画を自前では行わず、各 プラットフォームのネイティブな GUI ツールキットに描画せてい る。具体的には、 Windows 上では Win32API によって描画し、 Linux 上では GTK を使って描画する *14。言い換えれば wxWidgets は各プラットフォームのネイティブなツールキットへの ラッパーとして動作する、と見ることができる。
また、プラットフォームの差異を気にせずに、ファイル選択などの 標準的なダイアログを扱える API を備えている点も、ルックアン ドフィールの調和に貢献している。たとえば、wxFileDialog とい うクラスを用いると、Windows 上では Windows の標準ダイアログ (GetOpenFileName) が用いられ、Linux 上では GTK の標準ダイア ログ (GtkFileSelection) が用いられる。標準ダイアログが存在し ないプラットフォームでは wxWidgets が互換ダイアログを用意し ている。ドラッグアンドドロップについても wxDropTarget などの クラスがプラットフォーム間の差異を吸収している。
Windows 上のファイル選択ダイアログ
Linux 上ファイル選択ダイアログ (GTK+ 2)
wxWidgets のその他の特徴を以下にまとめる。
- クラスライブラリは GUI だけでなく、リストやハッシュ、ソ ケットなどの便利なクラスも多く備えている。わかりやすい設 計で、API も覚えやい。
- 網羅的なリファレンスマニュアルが整備されている。 PDF版のリファレンスマニュアルは 1,838ページもある。 筆者は HTML版をタブブラウザ (Firefox*15) で参照している。日本語への翻訳を行っているプロジェクト *16もある。
- 豊富なサンプルコードが用意されている。.cpp だけで数えて 108 ファイル 6万行。大半の API に対してわかりやすいサン プルコードが提供されている。マニュアルとともに大変役に立つ。
- 移植性を重視するため、テンプレートやネームスペース、例外 などの比較的新しい C++ の機能は利用しない。
- Python*17, Ruby*18 などのスクリプト言 語用のバインディングが提供されている。それぞれ wxPython、 wxRuby と呼ばれている。本稿では C++ による開発を取り上げ るが、手軽に GUI アプリケーションを作りたい場合は wxPython などのスクリプト言語を使った方がよい。
wxWidgets のサイトには、 AOL の製品をはじめとする多くの商用 アプリケーションが応用例として掲載されている。クロスプラット フォーム対応とは無関係に、Windows 専用のアプリケーションの開 発にも広く用いられている。wxWidgets を使った本格的なオープン ソースのソフトウェアとしては Audacity *19 や Tortoise CVS*20などがある。
wxWidgets の開発は 1992 年に Julian Smart 氏によって開始され た。wxWidgets は元々 wxWindows という名前で公開されていたが、 Microsoft からの要求に応じて 2004年2月に名前を変更している *21。ウェブ上の情報を 探すときに、「wxWidgets なんとか」で見つからない場合は 「wxWindows なんとか」で検索し直すとよい。
wxWidgets の情報源としては本家のサイトとリファレンスマニュア ルが大変充実しているため、他のサイトのお世話になることはそれ ほど多くない。日本語の情報源としては次のようなサイトがある。
- wxWindows 日本語プロジェクト (マニュアルの翻訳などを行っている) *22
- wxWindows――無名だが成熟したGUIツールキット (SunWorld Online) *23
- wxWindows の概要 (IBM developerWorks) *24
wxGlade とは
GUI ツールキットは wxWidgets に決まったが、 GUIのコードをす べて手で書くのは避けたい。ちょうどいい GUI ビルダー はないかと探したところ wxGlade*25 が見つかった。
wxGlade は wxPython*26 で書かれ た GUI ビルダーである。マウスを使って GUI をデザインして、そ の結果を wxPython および wxWidgets のコードとして出力できる。 Visual Studio などの GUI ビルダーを使った経験のある方なら、 大体同じ感覚で扱える。
インストール
wxWidgets と wxGlade および関連するツールのインストール方法 を紹介する。
Debian GNU/Linux の場合
Debian GNU/Linux (sid) の場合は、root 権限で
# apt-get install libwxgtk2.4
と実行すれば GTK+ 1.2 ベースの wxWidgets のインストールが完 了する。GTK+ ベースの wxWidgets には GTK+ 1.2 を使ったものと GTK+ 2.x を使ったものの 2種類あるが、 GTK+ 2.x の対応はまだ 実験段階にあるようだ。Debian では GTK+ 1.2 ベースのもののみ がパッケージ化されている。
wxGlade は wxPython に依存しているため、wxPython もインストー ルしておく。
# apt-get install libwxgtk2.4-python
wxGlade は残念ながらパッケージ化されていないため、wxGlade の サイトから wxGlade-0.3.3.tgz (原稿執筆時点の最新版) を入手す る必要がある。一般ユーザの権限で展開すればすぐに動くのでイン ストールの手間はかからない。
% tar zxf wxGlade-0.3.3.tar.gz # 展開して % cd wxGlade-0.3.3 # 移動して % python wxglade.py # 実行すれば動く
Fedora Core 2 の場合
Fedora Core 2 の場合も Debian のときと同様に wxWidgets と wxPython を apt-get でインストールできる。
# apt-get install wxGTK2 wxPythonGTK2
しかし、原稿執筆時点では Fedora Core 2 の wxGTK2 (GTK+ 2.x ベースの wxWidgets 2.4.2) は使えない状態となっており、wxGTK2 のライブラリを使おうとすると次のようなエラーが発生する。
/usr/lib/libwx_gtk2-2.4.so: undefined reference to `_gtk_accel_group_detach' /usr/lib/libwx_gtk2-2.4.so: undefined reference to `_gtk_accel_group_attach' /usr/lib/libwx_gtk2-2.4.so: undefined reference to `_gtk_rc_context_get_default_font_name'
これは wxGTK2 が参照していた GTK+ の内部的な関数が非公開となっ たために発生しているエラーである。この問題は最新の wxWidgets 2.5.2 では解決しているが、Fedora Core 2 の wxGTK2 パッケージ は古いバージョン (2.4.2) を使っているため問題が発生する。
仕方がないので筆者は、wxPython のサイトから wxPythonGTK-py2.3-2.5.1.5-1.i386.rpm を入手して、次のように 実行してインストールした。
# rpm -i wxPythonGTK-py2.3-2.5.1.5-1.i386.rpm
このパッケージには wxGTK に相当するライブラリも含まれている ため、別途 wxGTK をインストールする必要はない。
wxGlade については Fedora Core 2 でもパッケージ化されていな いため、Debian のときと同様にソースを展開して利用する。
なお、wxGlade 0.3.3 は wxPython のバージョンとの相性が悪いと、 起動時に segmentation fault が起きて動かないことがある。その 場合は応急処置として、 clipboard.py の次の行をコメントアウト すると、コピーアンドペーストの機能は働かなくなるものの、他の 機能は動作するようになる。このバグは開発者に認識されているた め、近い将来に解決すると思われる。
_widget_data_format = wxCustomDataFormat("wxglade_widget")
Windows の場合
Windows の場合は wxWidgets 以前に gcc などの基本的な開発環境 から整えなければならない。これはなかなか面倒な作業である。
Windows 上で動作する gcc には主に Cygwin*27版と MinGW*28版がある。 cygwin 版は Unix のシステムコールの多くを Windows 上でエミュ レーする機能を持つが、Cygwin の機能を利用したプログラムの実 行には cygwin1.dll が必要になる*29。一 方、MinGW はサポートしている Unix のシステムコールは限られて いるものの、単体で動く実行ファイルを素直に作成できる。本稿で は MinGW 版を扱う。
MinGW 版の gcc を使うには以下のようなファイルを MinGW のサイ トから入手してインストールする。バージョンはいずれも原稿執筆 時点での最新版である。
MinGW のサイトのトップページに該当するファイルが見当たらな いときは [View ALL Project Files] というリンクをクリックする とダウンロード可能な全ファイルを表示できる。
- MSYS-1.0.11-2004.04.30-1.exe (端末とシェルおよび最小限のUnixコマンド)
- GUI のインストーラの途中でコマンドプロンプトが表示され る。英語で「post install を続けるか」と訊かれるので y と答え、 次に 「MinGW をインストール済みか」と訊かれるので n と 答える。すると、「/etc/fstab に c:/mingw /mingw を加 えよ」という旨のメッセージが表示される。次に 「MinGW-1.1 の /bin/make は使えないうんぬん」というメッ セージが表示されるが、これは気にしなくてよい。
- MinGW-3.1.0-1.exe (gcc とへッダ、ライブラリなど)
- GUI のインストーラで簡単にインストールできる。MinGW の インストールが終わったら MSYS の端末を立ち上げて cd /etc; mv fstab.sample fstab のように実行して fstab ファイルを作成する。デフォルトで MinGW を C:\MinGW に インストールしている場合はこれで MSYS のシェルから /mingw/bin/gcc などが見えるようになる。
- msysDTK-1.0.0.exe (cvs, ssh, perl など)
- GUI のインストーラで簡単にインストールできる。
以上の環境を整えた上で wxWidgets のインストールを行う。 wxWidgets のサイトから wxMSW-2.5.2.zip を入手して MSYS のホー ムディレクトリの下*30 に展開し、MSYS のシェルから次のよ うにビルドする。
% ./configure --disable-shared --disable-threads % make
ここで、--disable-shared を指定しないと、筆者の環境 (Windows XP Professional) ではビルドに失敗した。また、 --disable-threads を指定しないと wx-config --libs の出力に -mthreads という gcc 用のオプションが含まれてしまうので注意 が必要である。後述するように wxWidgets のライブラリのリンク には wx-config --libs の出力を利用する。MinGW のgcc は -mthreads を指定されると自動的にmingwm10.dll という DLL をリ ンクするため、実行ファイルの動作に mingwm10.dll が別途必要に なってしまう。実行ファイルにどのDLL がリンクされているかは MSYS のシェルから次のように実行して調べられる。
% objdump -p foo.exe | grep DLL
この後、Python、 wxPython、 wxGlade の最新版を各サイトから入 手してインストールする。原稿執筆時点ではそれぞれ Python-2.3.4.exe、 wxPythonWIN32-2.5.1.5-Py23.exe、 wxGlade-0.3.3-setup.exe が最新であった。これらは GUI のイン ストーラで簡単にインストールできる。
以上、 Windows 上の開発環境を整備する方法を紹介した。おそら く、この説明を読んで「なんて面倒なんだ」と感じた方は多いだろ う。実際、筆者も面倒であった。元々は本稿のタイトルに「GUIア プリを手軽に作ろう」という表現を使うつもりでいたが、この下準 備の手間を考えて、「手軽に」は自粛した。
GUI 版の hello, world の作り方
それでは、さっそく wxWidgets と wxGlade を使って、GUI 版の hello, world を作ってみよう。
まず、wxGlade を立ち上げると、次のような 3つのウィンドウが表 示される。左上はボタンやチェックボックスなどのGUI 部品を選択 するウィンドウ (以降、パレットウィンドウと呼ぶ)、左下はオブ ジェクトのプロパティを設定するウィンドウ (以降、プロパティウィ ンドウと呼ぶ)、右上はオブジェクトのツリー構造を表示するウィ ンドウ (以降、ツリーウィンドウと呼ぶ)、である。初期状態では ツリーウィンドウに Application というオブジェクトが 1つ表示 されている
アプリケーションのウィンドウ (wxWidgets ではフレームと呼ばれ るが本稿ではウィンドウで統一する) を1つ作るには、パレットウィ ンドウの一番左上のウィンドウのアイコンをクリックする。すると、 次のようなダイアログが表示されるので、 OK を押す。
その結果、次のようなウィンドウが表示され、ツリーウィンドウに frame_1 と sizer_1 というオブジェクトが追加される。
作成されたウィンドウ
ツリーウィンドウに frame_1 と sizer_1 が加わる
frame_1 はウィンドウのオブジェクト、sizer_1 はサイザーと呼ば れるオブジェクトである。サイザーという言葉は耳慣れないが、 GUI部品のサイズを自動調整してくれるもの、と考えればよい。
では、サイザーの上にパネルを乗せてみよう。パレットウィンドウ の一番上の真中のアイコンをクリックし、次に frame_1 の斜線の 領域 (サイザーの領域) をクリックする。すると、斜線だった部分 がグレーのパネルになる。
このパネルの上に hello, world とラベル (wxWidgets ではスタ ティックテキストと呼ばれるが本稿ではラベルに統一する) を乗せ れば目的達成である。なぜウィンドウにラベルを直接乗せずにパネ ルの上に乗せるかというと、パネルなしのウィンドウは背景色が濃 いグレーであるため、ラベルの文字が読みにくくなってしまうから だ。
ラベルはパレットウィンドウの真中の「A」というアイコンで選択 できる。しかし、ラベルを選択して、次にパネルの上をクリックし てみても、ラベルはパネルの上に乗ってくれない。wxWidgets には 次のような制約があるからである。
- ボタンやラベルなどのほとんどの部品はサイザーの上にしか乗らない。
- サイザーはパネルやフレームの上にしか乗らない。
よって、パネルの上にラベルを乗せたいときは、まずパネルの上に サイザーを乗せて、その上にラベルを乗せればよい。サイザーはパ レットウィンドウの一番下の右から2番目のアイコンで選択できる。 サイザーをパネルに乗せる際に次のようなダイアログが表示される ので OK を押す。
すると、再び frame_1 に斜線領域が表示されるので、ここにラベ ルを乗せればよい。
デフォルトではラベルの文字列は "label_1" となっているので、 プロパティウィンドウの Widgets というタブを開いて、"hello, world" に修正する。
ラベルのプロパティを変更
ラベルの表示が hello, world に変わった
図体のでかいウィンドウの左上にちょこんと hello, world と表示 されていて締まりがないが、ひとまず気にせずに、ツリーウィンド ウで frame_1 を選択し、frame_1 のプロパティウィンドウから Preview というボタンを押してみよう。
すると、hello, world のラベルにぴったりした小さいウィンドウ が表示される。これは例の「GUI部品のサイズを調節してくれる」 サイザーがラベルのサイズを元にウィンドウのサイズを調整してく れているためである。hello, world と比べてウィンドウの横幅が 広いのは Windows のタイトルバーのボタンを配置するために横幅 を要するからだ。hello, world の部分を修正して hellooooooooooooooooooooooooo, world にすれば、ちゃんとラベルに合っ たサイズのウィンドウになる。
hellooooooooooooooooooooooooo, world の場合
ファイルへ保存
以上のような操作で GUI版の hello, world ができ上がった。でき 上がった GUI はパレットウィンドウのメニューの「File→Save As...」で保存できる。ここでは hello.wxg という名前で保存して おく。
C++ のコードを生成するには Application のプロパティウィンド ウで次のような設定を行ったのちに、Generate Code ボタンを押す。
- Name と Class のチェックボックスをオンにする。
- Top Window で frame_1 を選択する。
- Language で C++ をチェックする。
- Output path で保存先のファイル名を指定する (たとえば C:\msys\1.0\home\satoru\hello.h)。
ビルドと実行
wxGlade で生成した hello.cpp と hello.h をビルドするには次の ように実行する。
% g++ -c hello.cpp `wx-config --cppflags` % g++ -o hello hello.o `wx-config --libs`
Windows 上ででき上がった hello.exe を実行すると次のようなア プリケーションが立ち上がる。
筆者の環境では hello.exe のサイズは 3.8MB だった。wxWidgets 関連のライブラリ一式を静的にリンクしているため、実行ファイル はかなり大きくなる。
% strip hello.exe
のように strip コマンドをかけると 2.2 MB になった。
同じソースコードを Debian GNU/Linux で同様にビルドして実行す ると、次のようなアプリケーションが立ち上がる。ソースコードに 何ら手を加えずに Windows と Linux の両方でビルドできた。
各ファイルの内容
wxGlade は、GUI の情報を XML で保存する。再び GUI を修正した いときは、wxglade hello.wxg のように実行してこの XML ファイ ル開く。Windows ならエクスプローラから .wxg ファイルをダブル クリックすればよい。XML を不用意に編集すると壊れてしまう危険 性があるので、できるだけ直接はいじらない方がよい。
一方、生成された C++ のソースコード hello.cpp と hello.h は、 // begin wxGlade から // end wxGlade で囲まれた領域を除けば いくら編集しても平気である。再び wxGlade を立ち上げてコード を上書きで生成した場合は、この領域外のコードはそのまま保持さ れる。ボタンが押されたときは何々を行う、といった処理はこの hello.cpp と hello.h に加えていけばよい。
少々長くなってしまうが、wxWidgets と wxGlade のおおまかな感 触を掴んでもらうために、 hello.cpp、hello.h、hello.wxg の各 ファイルの内容をそれぞれ以下に示す。
なお、hello.cpp と hello.h の中の frame_1, sizer_1, panel_1, label_1 といった変数名は、ツリーウィンドウで表示されていたオ ブジェクト名と対応している。オブジェクトの変数名はプロパティ ウィンドウの Common タブにある Name の欄で変更できる。
hello.cpp
// -*- C++ -*- generated by wxGlade 0.3.2 on Wed Jun 16 00:17:34 2004 #include "hello.h" MyFrame::MyFrame(wxWindow* parent, int id, const wxString& title, const wxPoint& pos, const wxSize& size, long style): wxFrame(parent, id, title, pos, size, wxDEFAULT_FRAME_STYLE) { // begin wxGlade: MyFrame::MyFrame panel_1 = new wxPanel(this, -1); label_1 = new wxStaticText(panel_1, -1, wxT("hello, world")); set_properties(); do_layout(); // end wxGlade } void MyFrame::set_properties() { // begin wxGlade: MyFrame::set_properties SetTitle(wxT("frame_1")); // end wxGlade } void MyFrame::do_layout() { // begin wxGlade: MyFrame::do_layout wxBoxSizer* sizer_1 = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sizer_2 = new wxBoxSizer(wxHORIZONTAL); sizer_2->Add(label_1, 0, 0, 0); panel_1->SetAutoLayout(true); panel_1->SetSizer(sizer_2); sizer_2->Fit(panel_1); sizer_2->SetSizeHints(panel_1); sizer_1->Add(panel_1, 1, wxEXPAND, 0); SetAutoLayout(true); SetSizer(sizer_1); sizer_1->Fit(this); sizer_1->SetSizeHints(this); Layout(); // end wxGlade } class MyApp: public wxApp { public: bool OnInit(); }; IMPLEMENT_APP(MyApp) bool MyApp::OnInit() { wxInitAllImageHandlers(); MyFrame* frame_1 = new MyFrame(0, -1, ""); SetTopWindow(frame_1); frame_1->Show(); return true; }
hello.h
// -*- C++ -*- generated by wxGlade 0.3.2 on Wed Jun 16 00:17:34 2004 #include <wx/wx.h> #include <wx/image.h> // begin wxGlade: ::dependencies // end wxGlade #ifndef HELLO_H #define HELLO_H class MyFrame: public wxFrame { public: // begin wxGlade: MyFrame::ids // end wxGlade MyFrame(wxWindow* parent, int id, const wxString& title, const wxPoint& pos=wxDefaultPosition, const wxSize& size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE); private: // begin wxGlade: MyFrame::methods void set_properties(); void do_layout(); // end wxGlade protected: // begin wxGlade: MyFrame::attributes wxStaticText* label_1; wxPanel* panel_1; // end wxGlade }; #endif // HELLO_H
hello.wxg
<?xml version="1.0"?> <!-- generated by wxGlade 0.3.2 on Wed Jun 16 02:30:29 2004 --> <application path="hello.h" name="app" class="MyApp" option="0" language="C++" top_window="frame_1" encoding="ISO-8859-1" use_gettext="0" overwrite="0" use_new_namespace="1"> <object class="MyFrame" name="frame_1" base="EditFrame"> <style>wxDEFAULT_FRAME_STYLE</style> <title>frame_1</title> <object class="wxBoxSizer" name="sizer_1" base="EditBoxSizer"> <orient>wxVERTICAL</orient> <object class="sizeritem"> <flag>wxEXPAND</flag> <border>0</border> <option>1</option> <object class="wxPanel" name="panel_1" base="EditPanel"> <style>wxTAB_TRAVERSAL</style> <object class="wxBoxSizer" name="sizer_2" base="EditBoxSizer"> <orient>wxHORIZONTAL</orient> <object class="sizeritem"> <border>0</border> <option>0</option> <object class="wxStaticText" name="label_1" base="EditStaticText"> <attribute>1</attribute> <label>hello, world</label> </object> </object> </object> </object> </object> </object> </object> </application>
もっと GUI アプリケーションらしく
さて、この hello, world にもう少し手を加えてもっと GUI アプ リケーションらしくしてみよう。まず、メニューをつけるには frame_1 のプロパティウィンドウの Widget タブの Has MenuBar をチェックする。すると、ツリーウィンドウに frame_1_menubar というオブジェクトが追加され、frame_1_menubar のプロパティウィ ンドウが表示される。
Has MenuBar をチェック
frame_1_menubar が追加される
メニューバーのプロパティ
ここで、Edit Menus... を押すとメニューエディタが表示される。 メニューの項目を追加するには Add を押して、適切なLabel を設 定する。ここでは「File→Exit」という階層メニューを追加するた めに、 &File と E&xit という項目を追加している。&File のよう に定義すると F に下線が引かれ、キーボードショートカット Alt+F が使えるようになる。Exit については E&xit\tCtrl+Q と定 義してキーボードショートカット Ctrl+Q も使えるようにしている。
E&xit を &File の下位に位置づけるには、左側の領域で E&xit を 選択して、その下にある > ボタンで階層を右にずらせばよい。ま た、E&xit には、のちほどイベント処理用のメンバ関数を定義する ために Menu_File_Exit という Id を設定しておく。
このようにしてメニューを定義すると、 frame_1 に次のようなメ ニューバーが追加される。
次に、hello, world というラベル (label_1) の下に OK ボタンを 付け加えてみよう。そのためにはまずボタンを置く場所を用意する 必要がある。部品の置き場はサイザーにスロットを追加すれば確保 できる。次のようにツリーウィンドウの sizer_2 で右クリックし て Add Slot を選択すればよい。
すると、スロットが追加されてボタンの置き場ができる。ところが、 ここでは意に反して右側にスロットが追加されてしまっている。
これは、サイザーを作る際のダイアログでそのまま OK を押すと 横方向 (Horizontal) に伸びるサイザーが作成されるためである。 そこで、sizer_2 のプロパティを開き、Class の横の ... ボタン を押して、サイザーの種類を wxBoxSizer (wxVERTICAL) に変更す れば縦方向 (Vertical) に伸びるサイザーに変更できる。
あとはこの部分にボタン (パレットウィンドウの左端の上から2番目) を乗せればよい。ボタンの上のテキストを変更するには、ラベルの ときと同様にプロパティウィンドウの Widgets というタブで設定 する。
この状態で frame_1 のプレビューを表示すると次のようになる。
ご覧の通り、hello, world のラベルと OK ボタンがくっつくすぎ ている。OKボタンの右側の空間も気になる。そこで、レイアウトを 調整して、hello, world の周りにはある程度の空白を開け、OKボ タンはウィンドウの横幅に合わせたい。
まず、ラベルの周囲に 8ピクセルの空白を開けるには label_1 の プロパティウィンドウの Layout タブ内にある、Border の値を 8 に設定し wxALL をチェックする。ついでに、センタリングするに は wxALIGN_CENTER_HORIZONTAL をチェックする。
次に、 OKボタン の横幅をウィンドウに合わせる。OKボタンのプロ パティウィンドウの Layout タブ内にある、wxEXPAND をチェック すればよい。ついでに、ボタンの周囲にも 8 ピクセルの空白も開 けておく。今回は上方向の空白を入れないようにwxALL ではなく wxLEFT、 wxRIGHT、 wxBOTTOM をそれぞれチェックする。
これでだいぶレイアウトが整ってきたが、もうひとがんばりして、 ボタンのサイズを大きくしてみたい。ボタンのサイズを大きくする には、プロパティウィンドウの Common タブにある Size を絶対値 で指定する方法と、ウィンドウのサイズとの相対サイズで指定する 方法がある。ここでは後者の相対サイズ指定の方法をとることにす る。相対サイズで指定するメリットは、ウィンドウのサイズの変更 に自動的に追随して部品の大きさも変わるという点にある。
部品の大きさを相対サイズで指定するには、さきほどの Layout タ ブにあった Option の値を用いる。たとえば、ボタンをラベルの2 倍の大きさにしたい場合はラベルの Option の値を 1 に設定し、 ボタンの Option を 2 にすれば、 1 対 2 の比率になる。
以上で hello, world の GUI の改良を終わりにする。最終的なプ レビューは次のようになる。
今回は hello2.wxg という名前で GUI 情報を保存し、hello2.h、 hello2.cpp という名前でコードを生成する。コードを生成する際 には Application のプロパティウィンドウにある Enable gettext support をチェックしておいてほしい。gettext support の意味に ついては後述する。
イベント処理の追加
新しく生成されたコード hello2.cpp をコンパイルしてみよう。次 のようなエラーが表示されるはずだ。
% g++ -c hello2.cpp `wx-config --cppflags` hello2.cpp: In constructor `MyFrame::MyFrame(wxWindow*, int, const wxString&, const wxPoint&, const wxSize&, long int)': hello2.cpp:16: `Menu_File_Exit' undeclared (first use this function) hello2.cpp:16: (Each undeclared identifier is reported only once for each function it appears in.)
これは、さきほど「File→Exit」というメニュを追加する際に設定 した Menu_File_Exit という ID (定数) が未定義ゆえに起きるエ ラーである。この ID の定義は手動で hello2.cpp に記述する必要 がある。具体的には、hello2.cpp の最初の #include "hello2.h" の直後に次のようなコードを加える。
enum { Menu_File_Exit };
これでひとまずコンパイルが通るようになる。
では次に「File→Exit」がクリックされたときに呼ばれるメンバ関 数の定義を始めよう。これは慣れるまでなかなか厄介な作業である。 まず、 hello2.h の MyFrame クラスの宣言の中の
protected: // begin wxGlade: MyFrame::attributes wxStaticText* label_1; wxButton* button_1; wxPanel* panel_1; wxMenuBar* frame_1_menubar; // end wxGlade
の部分の直後に
DECLARE_EVENT_TABLE()
という行を追加する。wxWidgets ではイベントテーブルという仕組 みを使ってイベントとメンバ関数を対応づける。 DECLARE_EVENT_TABLE() は、このクラス (MyFrame) 用にイベント テーブルを定義すると宣言するためのものである。
続けて、hello2.h の
public: // begin wxGlade: MyFrame::ids // end wxGlade
の直後に
void OnExit(wxCommandEvent& event);
という行を追加する。これは Exit メニューが選択されたときに呼 び出されるメンバ関数 MyFrame::OnExit() のプロトタイプ宣言で ある。
次に、hello2.cpp の方の修正に移る。さきほどの enum の直後に 次のコードを追加する。
BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(Menu_File_Exit, MyFrame::OnExit) END_EVENT_TABLE() void MyFrame::OnExit(wxCommandEvent& WXUNUSED(event)) { Close(TRUE); }
BEGIN_EVENT_TABLE() 〜 END_EVENT_TABLE() でイベントテーブル を定義している。ここではメニューイベントの Menu_File_Exit と いうID に MyFrame::OnExit() というメンバ関数を対応づけている。
MyFrame::OnExit() はウィンドウを閉じてそのままアプリケー ションをするだけの関数である。
以上のような修正を行った上で、再び hello2 をビルドすると、 「File→Exit」メニューの選択でアプリケーションが終了するはず だ。
ところで、イベントとメンバ関数の対応づけにイベントテーブルを 用いる wxWidgets の方法は GTK+ や Qt などの他の GUI ツールキッ トの経験のあるプログラマからは評判が悪いようだ。イベントテー ブルを定義するスタイルは MFC (Microsoft Foundation Class Library) の影響を強く受けている。
メッセージの国際化
wxWidgets はメッセージの国際化の枠組みとして GNU gettext *31 互 換の方式を採用している。メッセージカタログ *32 は gettext とフォー マットを採用しているものの、アプリケーション実行時のメッセー ジの国際化の処理は wxWidgets のライブラリ内で行っており、 gettext のライブラリは利用しない。メッセージカタログの作成に は GNU gettext の xgettext と msgfmt コマンドを用いる。
ここでは gettext の詳細には触れず wxWidgets でメッセージを国 際化するための最小限の方法を紹介するにとどめたい。gettext に ついては inoue 氏による gettext memo *33 が 詳しい。Windows の場合は MinGW のサイトから gettext-0.11.5-2003.02.01-1.exe と libiconv-1.8.0-2003.02.01-1.exe (ともに原稿執筆時点の最新版) を入手してインストールしておく必要がある。
まず xgettext コマンドを使って、メッセージカタログの雛形とな る ja.po ファイルを作る。
% xgettext -k_ hello2.cpp -o ja.po
すると、次のような内容のファイルが作成されるので、まず始めに ファイル先頭のへッダ部分を埋めてから、msgstr の部分に日本語 の翻訳メッセージを書き込んでいく。ファイルはへッダ部分の Content-Type: で指定した文字コードに合わせて保存する。 Windows なら Shift_JIS を指定しておけばよい。
# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Free Software Foundation, Inc. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. (中略) #: hello2.cpp:24 msgid "hello, world" msgstr ""
一通り翻訳が完了したら、ja というディレクトリを作り、 msgfmt コマンドで ja/hello2.mo というファイルを作成する。
% mkdir ja % msgfmt -c -o ja/hello2.mo ja.po
これでメッセージカタログの作成は完了した。
次に hello2.cpp とhello.h の修正に移る。まず hello2.h の DECLARE_EVENT_TABLE() の行の直後に
wxLocale locale;
という行を追加する。そして、hello2.cpp の
// begin wxGlade: MyFrame::MyFrame
の行の直前に次の2行を追加する。
locale.Init(wxLANGUAGE_DEFAULT); locale.AddCatalog(wxT("hello2"));
これで hello2 をビルドし直してシェルから ./hello2 のように実 行すれば、日本語ロカールの環境では日本語のメッセージで表示さ れるはずだ。
日本語のメッセージで表示 (Windows)
日本語のメッセージで表示 (Linux)
この例では hello2 が存在するディレクトリから ./hello2 と実行 して、サブディレクトリにあるメッセージカタログ ja/hello2.mo を参照した。しかし、Linux 上で /usr/local/bin/hello2 などに 実行ファイルをインストールする場合には、メッセージカタログを /usr/local/share/locale/ja/LC_MESSAGES/hello2.mo のような適 切な場所にインストールする必要がある。autoconf *34 と automake*35 を使うとこの辺の処理を自動化できる。
一方、Windows の場合はどこにメッセージカタログを置くか悩まし い。筆者はひとまず実行ファイルと同じディレクトリに locale と いうディレクトリを作って、この下に格納することにした。実行ファ イルのフルパスはGetModuleFileName で参照でき、これを元に実行 ファイルのディレクトリを調べられる。実行ファイルのディレクト リが分かったら、そこにカレントディレクトリを移し (chdir し)、 locale ディレクトリを見るように locale.AddCatalog() の直前で 次のようなコードを呼び出す。
locale.AddCatalogLookupPathPrefix("locale");
chdir する代わりに "実行ファイルのディレクトリ/locale" とい うパスを AddCatalogLookupPathPrefix で追加してもよい。
Windows XP スタイルの適用
これまでの Windows 用 hello.exe と hello2.exe のスクリーン ショットをよく見ると、Windows 2000 時代のルックアンドフィー ルであることに気づく。
グラデーションを多用した Windows XP スタイルのルックアンド フィールにするには次のように実行すればよい。リソースファイル wx.rc の中にWindows XP スタイルを適用するための魔術が組み込 まれている。
% cp /local/include/wx/msw/wx.rc . % windres --include-dir /local/include wx.rc -o resource.o % g++ -o hello2 hello2.o resource.o `wx-config --libs`
改良版 hello, world のまとめ
以上で hellow, world の改良を終わりにする。ここまでの作業を まとめると次のようになる。
- wxGlade で GUI をデザインして C++ のコードを生成する。
- 生成されたコードにイベント処理用のメンバ関数を追加する。
- メッセージの国際化を行う。
- Windows XP スタイルの適用を行う。
最小限の Makefile は次のようになる。
CXX = g++ .cpp.o: $(CXX) `wx-config --cppflags` -c -o $@ $< hello2: hello2.o $(CXX) -o $@ hello2.o `wx-config --libs`
アプリケーションの規模が大きくなったら autotools (autoconf、 automake、libtool の総称) などのツールを導入して Makefile を 保守するとよい。autotools については『GNU Autoconf/Automake/Libtool』*36 という書籍が参 考になる。
おわりに
本稿では、wxWidgets を用いてクロスプラットフォーム対応の GUI アプリケーションを開発する方法を紹介した。hello, world に毛 が生えた程度までしか扱うことができなかったが、wxGlade の基本 的な操作と wxWidgets のイベント処理の要領が飲み込めれば、簡 単な GUI アプリケーションならすぐに作れるようになるはずであ る。wxWidgets は完成度の高さの割には、日本ではあまり流行って いないようだ*37。本稿を読ん で wxWidgets をちょっとやってみようかな、と思ってもらえると うれしい。
コラム: zphoto の GUI 版を作る気になった理由
筆者は 2年ほど前に zphoto *38 というツールを開発 し、細々と保守をしながら公開を続けている。zphoto はデジカメ などの写真からウェブベースのフォトアルバムを作成するツールで ある。Flash の .swf ファイルを生成するのが特徴だ。
zphoto はそこそこの好評を得たが、Unix 用のコマンドラインのツー ルであることと、多数のライブラリに依存しているという条件が重 なり、試すにはかなり敷居の高い代物であった。Windows 版もあれ ばいいのに、という意見もいただいたが、自分が使えればいいやと 思って聞き流していた。
そんなあるとき、 MeCab*39 という 形態素解析システム *40を作っている知人から「MeCab のようなマニアックなツールでも Windows 版 (GUIなし) の方がダ ウンロード数多いんだよね」という話を聞いた。
そのときは、へぇ、という程度だったが、しばらくして、試しに zphoto の Windows 版でも作ってみるか、という気が湧いてきた。 いきなり GUI をつけるのは大変そうなので、ひとまずコマンドラ インのまま移植することにした。
そして、移植が一段落して実験的に公開してみると、予想以上の反 応が得られた。よし、 GUI 版を作ってみよう、と決めたのはこの ときである。
zphoto の GUI 版は 2004 年の 5月に完成した。原稿執筆時点 (6 月半ば) までのダウンロード数は Windows 版の方が断然多い。
- Windows 版 4,097件
- ソース配布 1,421件
特に、「窓の杜」*41 で紹介していただい たときは、zphoto のサイトに一晩で 1,000 件近くのアクセスがあっ た。窓の杜からの訪問者数は累計で 7,000 件を越えている。Unix 用のツールではこういうことはなかなかなさそうである。
というわけで、ユーザは増えたし、wxWidgets も覚えたし (ついで にこんな記事まで書いてるし)、 GUI 版の開発はやった甲斐が大い にあった。調子に乗って「コマンドラインのツールばっかり作って いてはダメですよ」なんてことまで言い出しかねない始末である (何を今さら…)。