スポンサーリンク

C++で最小限のGUIアプリを作る方法 ~wxWidgetsの基礎~

記事内に広告が含まれています。

前回記事でMSYS2環境にwxWidgetsをインストールする方法と、サンプルを実行する方法を説明しました。前回記事までの話で、MSYS2にwxWidgetsは導入できたことにはなります。

ですがどうせなら、サンプルを実行するだけではなく、自分の思い通りのプログラムを作れるようになりたいところ。

ということで、今回はwxWidgetsの考え方やら、プログラミング方法やらを解説していきます。

「思い通りのプログラムを作れるようになりたい」とは言えども、勉強するときには何かしら目標とするプログラムが無いと抽象的な説明ばかりになってしまって分かりづらくなってしまいます。それに何より、退屈な説明になりかねません。

説明している僕側からしても、実際にプログラムを組みながらでないと、解説し忘れをしてしまうかもしれません(僕は忘れっぽいので、それでもまだ解説し忘れが多々あるでしょうが、プログラムを組まずに説明していくよりはマシかと)。

分かりやすさと、解説の十分性を確保したいので、この記事(というか、記事シリーズ)では、作りたいアプリケーションをあらかじめ決めておきます。そして、そのアプリケーションを作っていく上で必要な考え方であったり、プログラムの組み方であったりを解説するという方針で進めていきます。

とはいえ、あまりにも中身のロジックが複雑すぎては、そのロジックにばかり注意が向いてしまって、肝心のwxWidgetsの使い方を勉強しづらくなるというデメリットもあります。

なので、今回の記事シリーズでは、最終的に電卓を作ることを目指してみます(詳しい要件は最初の節で)。

※この記事シリーズではMSYS2を前提にプログラムを説明していきます。wxWidgetsはクロスプラットフォーム開発が可能なライブラリとなっているので、MSYS2以外でも通用するような説明になっているとは思いますが、それはあくまでも副次的なものです。一応、書き手の前提は「デフォルト設定でインストールしたMSYS2にwxWidgetsをインストールして動作させている」であるとご理解ください。

スポンサーリンク

何を作るか

電卓アプリケーションを作ろうとはお伝えしましたが、それだけではまだ明確になっていない部分が大量にあります。そこで、その不明確な部分をこの節で決めておいて、以降のプログラム作成の指針としたいと思います。つまりもうちょい細かな仕様決定をしようと。

とは言え、今回の場合は仕様自体には特に意味が無いので(どんな仕様であっても、最終的にwxWidgetsの解説さえできればいいので)、大雑把かつ適当に決めていきます。

図1. GUIの完成予想図

最終的に、こんな画面のアプリケーションを作ることを目指します。

まず画面は大きく左右に二分割されています。その左側に入力ボックスが2つとボタンが4つあって、入力ボックスが2つの下に、4つのボタンを配置するようにします。各ボタンには四則演算記号を表示して、そのボタンを押すと上の2つのボックスに入力した数値が計算されるようにします。

例えば、「+」と書かれたボタンを押すと、上下の入力ボックスに入力された数値を足し合わせるといった感じです(他の四則演算記号が書かれたボタンについても同様です)。

そして計算結果を右側の四角いボックスに表示します。例えば、上のボックスに「13」、下のボックスに「21」と入力されている状態で「+」ボタンを押すと、右側のボックスに「34」と表示されるようにします。

右側のボックスには記録が続けられていくだけで、ユーザー側の望んだタイミングで削除することは出来ません。そして、入力できる数字は2つだけです。

入力ボックスには数値以外の文字列や、数値をスペース区切りで入力されることなど、色々なパターンが考えられますが、極力エラー処理等はしないことにします。wxWidgetsの解説という目的から大幅に離れてしまいそうなので。

電卓としては何ともショボいですが、こういう感じのアプリケーション(四則電卓とでも呼びましょうか)を最終的に作ることを目的とします。で、この四則電卓を作りながらwxWidgetsを解説していこうと。

※図について。上の方に、少し不自然に広い余白がありますが、そこにはウィンドウの枠があると考えてください。本来なら、バツボタンも描くか、そもそもウィンドウの枠の中しか考えない(つまり、図上でウィンドウの枠を表現しない)かのどちらかにすべきでしょうが。

最小限のソースコード

最終目標は決まりまったので、あとは目標に向けて色々と機能を追加していくだけです。

ですが、wxWidgetsを使ったことがない方は、wxWidgetsでのプログラムの書き方から知る必要があります。

ということで、まずはGUI画面を表示させてみましょう。wxWidgets流の「Hello, World!」ですな。

ソースコードを次にお見せしますが、今回はいつもとは違って、少し大きめのプログラムになってしまう都合上、複数のファイルに分割しています(その方が分かりやすいと思ったので)。

なので、実際にご自身で実行してみたい場合は分割コンパイルをできる必要があります。なお、分割コンパイルの話とは別に、GNUコンパイラを利用している場合に必要なビルドオプションについて、ソースコードの下の方で説明しています。併せて読んでくださいませ~。

main関数が無かったり(つまり、プログラムがどこから始まるのか分からなかったり)、newで確保したメモリをdeleteしていなかったりと、気持ち悪く感じる部分が多々あるでしょうが、wxWidgetsのプログラムとしてはこれで正常なので我慢してください(ソースコードの解説は次の次の節以降にあるので、ソースコードの意味が分からないという気持ち悪さも我慢してください)。

ソースコードは以下のようになります。

Application.hpp

/********************************
* Application.hpp
*********************************/

#pragma once

#include <wx/app.h>

class Application : public wxApp
{
private:
public:
  virtual bool OnInit();  // 初期化用の関数を virtual を付けてオーバーライド
};

// プロトタイプ宣言みたいなもの
wxDECLARE_APP( Application );

Application.cpp

#include "Application.hpp"

// OnInit 関数が false を返したら、イベント監視には入らず、即終了となる
bool Application::OnInit()
{
  // 親クラスの OnInit 関数で問題が起きていたら false を返し、即終了
  if( !wxApp::OnInit() ) return false;

  // ウィンドウを作る(メモリ上にオブジェクト化しただけで、画面上に表示されるわけではない)
  wxFrame* main_window = new wxFrame( nullptr, -1, wxT("Hello World!"), wxDefaultPosition, wxDefaultSize );

  // 先ほど作ったウィンドウを画面上に表示する
  main_window -> Show();

  // true を返すと、ここからイベント監視のアイドル状態が始まる。
  return true;
}

Main.cpp

/********************************
* Main.cpp
* 最小限のプログラム
*********************************/

#include <wx/app.h>
#include "Application.hpp"

////////////////////////////////
// 実質的には main 関数の宣言
//
// リファレンスでは、アプリケーションクラスが定義されているファイルで
// 利用するように言われているが、とりあえずこれで問題が無かったことと、
// Main.cpp に書いた方が分かりやすいと感じことから、 Main.cpp に書いている。
//
// リファレンスにもある通り、
// このマクロの最後にはセミコロンを付ける必要がある。
////////////////////////////////
wxIMPLEMENT_APP(Application);

このソースコードをコンパイルすれば最小限のアプリケーションを実行できますが、wxWidgetsを利用したプログラムをコンパイルするには、ビルドオプションが必要になります。そのビルドオプションとは次の2つです。

`wx-config --cxxflags`
`wx-config --libs`

上の方はオブジェクトファイルを作るとき(狭義のコンパイルの方)に、下の方はリンク時に付けてください。このプログラムに限らず、wxWidgetsを利用する場合は、いつもこのビルドオプションが必要です。

今紹介した方法でビルドしてできた実行ファイルをMSYS2のコマンドラインから「./○○.exe」というように実行してもらえれば、タイトルに「Hello, World!」と書かれただけのウィンドウが起動するはずです。

このアプリケーションでできることは、どのウィンドウにも共通して出来る操作だけです。例えば、ばつボタンを押してウィンドウを閉じたり、画面の端をドラッグしてウィンドウのサイズを変えたりといったようなことです。

つまり、ウィンドウとして成り立つための最小限の機能しか備えていません。ですが、それでもwxWidgetsを利用するにあたって重要なことがいくつかあるので、そちらを次の節で説明していきます。

スポンサーリンク

最小限のアプリケーションを作ってHello, World!

上の節でアプリケーションとして成立し得るギリギリの機能しか備えていないアプリケーションを作りました。そのアプリケーションを理解するという方法で、wxWidgetsを解説していきます。

先ほど作ったアプリケーションは、いわば最小限のアプリケーションなわけですが、それでもwxWidgetsに触れたことがない方には、不可解な点が多くあったことと思います。

その不可解な点を解決するためには、おそらく次の7つくらいのポイントを理解しておけば大丈夫かと思います。

  1. main関数ではなく、wxApp::OnInit関数がエントリポイント(main関数を書かない)
  2. wxAppは抽象クラスとなっている
  3. wxIMPLEMENT_APP()に、wxAppの派生クラスを指定する
  4. wxDECLARE_APP()で、wxIMPLEMENT_APP()で使われている関数を宣言する
  5. wxFrameでウィンドウを作成する
  6. 途中でnewしたwxWidgetsのコントロール達は、途中でdeleteしなくてもいい
  7. wxWidgetsはイベント駆動型の設計になっている(次の記事で)

※ここで言う「コントロール」とは、GUIの構成要素のことです。例えばボタンであったり、入力ボックスであったりといったものです。「ウィジェット」と呼ぶ場合もあるそうですが、wxWidgetsというライブラリを使用している都合上、「ウィジェット」と呼ぶと紛らわしいと思ったので、この記事シリーズではGUIの構成要素を「コントロール」と呼ぶことにします。

逆に言えば、これらを理解しておけば、最低限のアプリケーションを作ることはできる(理論上)ということになります。他にも理解しておくべきことはありますが、そういったものはこれ以降の記事で順を追って解説していくと思います。

それと、この記事シリーズだけではwxWidgetsのすべてを解説することはできませんので、適宜wxWidgetsの公式サイトや、他の方のサイトを参考にされることをお勧めします。特に、wxWidgets公式のチュートリアルは分かりやすくておすすめです。

基本的には、まずは概念的な説明をしてから、その次に具体的な説明というように進んでいます。もし抽象的で分かりにくいと思う箇所があったら、後の方を軽く見ながら読み進めたら理解しやすいかもしれません。

wxWidgetsを利用する場合はmain関数(WinMain関数)を書かない

まず、上の節でも少しだけ触れましたが、一番大きな特徴がmain関数やWinMain関数を書かないということでしょう。

wxWidgetsを利用する場合、プログラムのエントリポイント(開始点)はwxAppという抽象クラスに定義されているOnInit()というメンバ関数になります。

wxWidgetsを使わないプログラムであれば、main関数を定義して、そのmain関数からプログラムが始まるものとしてソースコードを書いていました。そのような働きをする関数が、wxWidgetsを使う場合は実質的に、その開始点がwxApp::OnInit()に変わります。

wxApp::OnInit()でプログラムの開始点を作る

wxWidgetsを利用する場合、wxApp::OnInit()が実質的なエントリポイントになります。ただし、クラス内に定義されたメンバ関数を呼び出せるためには、メンバ関数がstatic指定されているか、もしくはクラスがインスタンス化されている必要があります。

内部の実装を変更するわけにはいかないので、wxAppをインスタンス化する方法一択ですね。しかし、wxAppは先ほども軽くお伝えした通り、抽象クラスとして定義されています(抽象クラスが分からなければ調べてください。ですが、差し当たってはインスタンス化できないクラスだと理解しておいてもらえれば十分かと思います)。

なので、まずは派生クラスを定義して、その派生クラスをインスタンス化する必要があります。

上の節でお見せしたソースコードではApplicationというクラスをwxAppクラスの派生クラスとして定義しています。

そして、抽象クラスに定義されている関数はすべて純粋仮想関数として定義されているので、派生クラスでオーバーライドする必要があります。

上のソースコードでも、ApplicationクラスでOnInit()をオーバーライドしていることが分かると思います。

マクロ関数でwxAppの派生クラスをインスタンス化する

上の項までの話で、wxAppクラスは抽象クラスだから派生クラスを作らなければいけないという部分についての解説は終わりました。次に、派生クラスをインスタンス化しなければいけないという話です。

wxWidgetsの場合は、wxIMPLEMENT_APP()というマクロ関数を利用してエントリポイントを作る必要があります(公式によればwxTheAppを利用する方法もあるらしいですが、僕はまだよく分かっていないので説明できません)。

wxIMPLEMENT_APP()の引数には、wxAppからの派生クラスを指定します。なので、今回の場合はApplicationを指定することになります(上のソースコードでも、wxIMPLEMENT_APP( Application )となっていますね)。

ところが、このwxIMPLEMENT_APPは公式リファレンスによると、内部でwxGetApp()という関数を利用しているそうですが、そのwxGetApp()関数はwxDECLARE_APP()というマクロ関数で宣言する必要があるとのことです。

wxDECLARE_APP()も、引数にはwxAppの派生クラスを指定するようになっています。なので、こちらもwxIMPLEMENT_APP()と同様にApplicationを指定しています。

これで、プログラムのエントリポイントを作ることができました。

ちなみに、公式サイトを読むと、このwxDECLARE_APP()はプロトタイプ宣言のためのマクロ関数だということなので、ヘッダファイルに書いています。それに対して、wxIMPLEMENT_APP()は実装だというように書かれているので、cppファイルに書いています。ただ、C++ではプロトタイプ宣言をしなくても問題ない場合もあるので、wxDECLARE_APP()は必ずしも必要ではないかもしれません。

さて、ここまで話題に上がってきていたOnInit()ですが、オーバーライドしたからには、何かしらの処理を書くことになります。その処理とは、一言でいえばアプリの初期化です。OnInitという名前通りですな(「On」で「その上」、「Init」で「初期化」なので、OnInitでは、「初期化されているとき」みたいな意味合いになると思います)。

では、どんな初期化処理をする必要があるのかという話になります。

スポンサーリンク

ウィンドウを作る

気を付けていただきたいのですが、OnInit()に何も書かず、wxApp()の派生クラス(上のソースコードでいうところのApplicationクラス)をインスタンス化しただけでは、まだウィンドウを表示できません。

wxWidgets側からすれば、どんな大きさのウィンドウをどこに表示するのか、ウィンドウのタイトルは何にするのか、いくつのウィンドウを表示すればいいのか、そして、ウィンドウはどのタイミングで表示すればいいのか等が分からないからです。

そのため、ウィンドウを表示するためには、OnInit()でウィンドウを表示するために必要な情報をすべて書いておく必要があります。

具体的には上のソースコードを見てください。wxFrameというクラスをインスタンス化した後に、Show()というメンバ関数を呼び出していることが分かるでしょう。

このwxFrameがウィンドウに関する情報を管理するクラスになっています。

wxFrameでウィンドウを管理する

wxFrameはウィンドウを定義するためのクラスです。こちらはwxAppとは違って抽象クラスではないので、wxFrame自体をインスタンス化できます。引数は公式のリファレンスを見てください。ですが、ここでは、そのコンストラクタの部分だけを読めば十分かと思います。

要するに、サイズや表示する場所、ウィンドウのタイトルといった値を指定したら、あとはそれらを管理してくれると。

上のソースコードでも、wxFrameをnewしてインスタンス化していることと、様々な値を指定していることが分かると思います。

そうして作ったwxFrameのインスタンスを、Show()というメンバ関数で画面上に表示しています。Show()のリファレンスはこちらにあります(wxFrameの継承元であるwxWindowに定義されているので、リファレンスもwxWindowのページになっています)が、単に表示するときに使う関数なのだと考えておいていただければ、差し当たっては問題ないかと思います。

このインスタンスはnewで作られたインスタンスですから、ある程度C言語やC++言語に慣れた方であれば、反射的にdeleteを書きたくなると思います。ですが、wxWidgetsでは、ウィンドウが破棄されたときに、ウィンドウ自身を破棄してくれるようになっているので、deleteを書く必要はありません。

これについては、まだ話が具体例で解説できるほど進んでいないので、具体例は次回以降に回したいと思います。

OnInit()を抜けたら、wxWidgetsの内部で延々とイベント待ちをして、そのイベントごとに関連付けられた関数が呼び出されるという仕組みになっています。ですが、そこまで解説していると長くなりすぎるような気がするのと、こちらもやはりそこまで詳しく説明できるほど解説が進んでいないので、今回はここまでにしておきます。

まとめ

この記事で説明したことは、ざっくりとまとめると次のような感じです。

  • ビルドオプションに注意が必要
  • wxWidgetsには特有のソースコードの書き方がある

ビルドオプションについては、オブジェクトファイルの作成時とリンク時に、それぞれというオプションが必要だということでした。

wxWidgets特有の書き方については、この記事で説明できたのは次の5項目だけでした。

  1. main関数ではなく、wxApp::OnInit関数がエントリポイント(main関数を書かない)
  2. wxAppは抽象クラスとなっている
  3. wxIMPLEMENT_APP()に、wxAppの派生クラスを指定する
  4. wxDECLARE_APP()で、wxIMPLEMENT_APP()で使われているクラスを宣言する
  5. wxFrameでウィンドウを作成する

この5項目はwxWidgetsを使う上での基礎になります。

これらを理解できていれば、とりあえずウィンドウを表示する(つまり、Hello, World!できる)ので、かなり応用が利くようになりますので、理解しておいて損はないかと。

次回の記事で、イベント駆動型の話をして、入力ボックスとかボタンとかをウィンドウ上に配置すると思います。

タイトルとURLをコピーしました