クラスを作ろう!

 MFCが「クラス」というものの集まりだということは知っていても、「クラス」というものがどのような利点を持っているのかとか、どういう部分に使うべきなのかとか、いまいち分からない人も多いと思います。
 しかし!
 クラスは非常に便利なものです。これをMFCだけに使わせておくのはもったいないです。プログラムをクラスとして管理すると、全体的に見やすくなって管理が楽になります。クラスライブラリとして他のアプリケーションから使用することもできます。
 というわけで、今回は「自作クラス」の作製方法を紹介しましょう。

クラスの作製理由
 さて、いざ「クラスを作製しよう!」と思っても、何をクラスにすればいいのか分からないと思います。筆者は、次のうちひとつでも当てはまれば、クラスにして構わないと思います。

1:複雑な構造体で、関数による操作が面倒。
2:初期化や後処理が面倒なデータ。
3:演算子を使用したいデータ。

 例を挙げてみましょう。
 1はCRectCPointのような構造体から派生したクラスがあります。「幅」や「高さ」を取得するメンバ関数が備わっていることで、扱いが楽になります。
 2はCPenCWndのようなクラス。これらのクラスは、実際にはウィンドウズが管理するオブジェクトを簡単に操作できるようになっており、ハンドルの作製や破棄を自動的に行ってくれます。
 3はCStringで、「演算子のオーバーロード」という機能を使用することで文字列操作を直感的に行えるようにしています。

 さて、これらの例をふまえて、今回作製するクラスはNOTIFYICONDATA構造体から派生したクラスを作製することにします。この構造体は、タスクトレイにアイコンを表示するときに使用する構造体です。
 この構造体はメンバが複雑で、操作を行うときに専用のメンバ関数があると便利です。また、アプリケーションの終了時にアイコンを消去しなければならないという点で、自動的な破棄を行ってくれると便利です。さらに、いくつかのデータは演算子のオーバーロードを行うと便利になります。
 まさに、この構造体は「クラスにして!」と言わんばかりのターゲットです(笑)。今回はこの構造体を元にクラスを作製してみましょう。
 ちなみに、当然のことながら「タスクトレイにアイコンを表示する方法」を知らなければ理解できないと思うので、MFCチップスのタスクトレイにアイコンを表示して、メッセージを受け取る方法をあらかじめ読んでおいてください。

クラスの雛形を作る
 クラスを作製しようにも、どういうファイルにすればいいのか分からないという悩みは必要なし。クラスウィザードがちゃんと雛形を作ってくれます。便利な世の中ですねぇ。

 まず、ワークスペースを開き、クラス一覧を表示してください。次に、一覧の一番上にある「なんとかかんとか クラス」(「なんとかかんとか」にはプロジェクト名が入ります)を右クリックし、「クラスの新規作製」を選んでください。そうすると、ダイアログボックスが表示されるはずです。
 ですが、表示された状態では「MFCから派生したクラス」しか作製できません。そこで、ダイアログの上の方にある「クラスの種類」の中から「Generic クラス」を選んでください。ダイアログボックスの表示が変わるはずです。

 「クラス名」にはそのままクラスの名前を書き込みます。ここではKNotifyIconという名前にします。
 派生元にはNOTIFYICONDATAを入れてください。今回のクラスはNOTIFYICONDATA構造体から派生することにしましたから。
 用途からはpublicを選択してください。

 以上を書き込んだら「OK」を押してください(注意されちゃいますが、無視して結構です)。
 そうすれば、自動的にKNotifyIcon.cppKNotifyIcon.hが作製され、KNotifyIconクラスが表示されると思います。どんな雛形が作製されたのか確認してみてください。

さっそく使ってみよう!
 このクラスに色々機能を付ける前に、とりあえず使ってみましょう。使う場所はどこでもいいです。まぁ、ビュークラスので作製することにしましょう。まず、そのビュークラス(ここではCTestViewとします)のcppファイルの中でKNotifyIcon.hをインクルードし、ビュークラスにKNotifyIcon型のメンバ変数m_kNIconを追加してください。
 次に、コンストラクタ内に次のようなコードを書き込んでください。

CTestView::CTestView()
{
	//////////////////////////////////
	// アイコンをタスクトレイに表示します。
	m_kNIcon.cbSize = sizeof( NOTIFYICONDATA );	//構造体のサイズです。
	m_kNIcon.uID = 0;	//識別ナンバーです。
	m_kNIcon.hWnd = GetSafeHwnd();	//メッセージを受け取るウィンドウのハンドルです。
	m_kNIcon.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;	//設定です。
	m_kNIcon.hIcon = AfxGetApp()->LoadIcon( IDR_MAINFRAME );	//表示するアイコンです。
	m_kNIcon.uCallbackMessage = WM_USER_NTFYICON;	//送ってもらうメッセージです。
	lstrcpy( m_kNIcon.szTip, "てすとぉ" );	//チップの文字列です。

	::Shell_NotifyIcon( NIM_ADD, &m_kNIcon );	//タスクトレイに表示します。	
}
	

 もちろんWM_USER_NTFYICONはどっかで定義しておいてください。
 さらにデストラクタに次のコードを書き込んでください。

CTestView::~CTestView()
{
	::Shell_NotifyIcon( NIM_DELETE, &m_kNIcon );
}
	

 これで終わり、ビルドして実行してみてください。
 と、これじゃぁNOTIFYICONDATA構造体と一緒じゃないかと思われるでしょうが、それを確認して欲しかったのです。クラスは構造体からも派生でき、派生したクラスは元の構造体とまったく同じように振る舞えるのです。これは、派生元がクラスでも同じことです。

 ただし、ひとつだけ注意して欲しいことは、このようなことができるのは派生タイプがpublicだからということです。これがprivateの場合には、この例のように、ビュークラスからNOTIFYICONDATAの各メンバへとアクセスすることはできません。すべて、KNotifyIconクラスの中からしかアクセスできなくなります。

 なぜこのような機能がついているのでしょうか。「privateなんて不便なだけじゃないの?」と思われるかもしれません。しかし、ここにオブジェクト指向の良さ」が存在するのです
 このクラスをいくつかのアプリケーションで使用していたとします。さて、ウィンドウズ98が発売され、NOTIFYICONDATA構造体のメンバが変わってしまいました! public指定している場合には、すべてのアプリケーションを書き換えなければなりません。
 これがprivateとして指定されていたら、各メンバ変数にアクセスするための関数は、引数や戻り値はそのままに、中身だけ変えればいいのです。各アプリケーションはまったく手を加えずに済むわけです。

 とはいえ、実際にはprivateであることのデメリットも存在します。単純なデータの場合、そのデータごとにメンバ変数を作製するのは面倒ですし、その関数を呼び出すオーバーヘッドも少し気になります。
 また、NOTIFYICONDATA構造体がすでに存在しているため、KNotifyIconクラスでも同じアクセス方法を許しておいた方が分かりやすいという面もあります。

 これらのメリットとデメリットを考えて、publicかprivateかを決めるのがいいでしょう。

「クラス」を設計する!
 クラスを作るからにはやっぱりいろんな機能を組み込みたい! そういう場合には、あらかじめ「どんな感じになるか」イメージを持っておいた方がいいでしょう。単純に機能を箇条書きにしてみるだけでも十分。あとで「この機能は追加した」とかチェックするのにも使えます。
 今回のKNorifyIconクラスには、次のような機能を着けることにしました。

1:アイコンの追加を関数ひとつでできるようにし、ついでに引数を省略できるようにする。
2:アイコンのIDを自動的に作製するようにする。
3:デストラクタで自動的にアイコンを削除するようにする。
4:ツールチップ文字列の追加を簡単にする。

 中には「ホントにこれ必要かな……」と思うようなものもあったりしますが、まぁ今回は「クラスの作り方」ということなので勘弁してください。作っても使わなきゃいいんだし(汗)。

コンストラクタにて
 コンストラクタでは初期化を行います。今回のクラスではあまり重要ではありませんが。

 まず、KNotifyIconのメンバ変数として、BOOL型のm_bAddedを作製してください。アクセス指定はprivateです。そして、コンストラクタに以下のコードを書き込んでください。

KNotifyIcon::KNotifyIcon()
{
	memset( this, 0, sizeof( *this ) );

	m_bAdded = FALSE;	//追加していない状態にします。

	cbSize = sizeof( NOTIFYICONDATA );	//構造体のサイズです。
}
	

 まず最初に、クラス全体を0で埋めて初期化します。構造体を使用していたときには「すべてのメンバを必ず埋める」という事にしていたので、このランタイムの使用はオーバーヘッドとして嫌われるかもしれません。でも、こうした方が安全でしょう。
 m_bAddedは、「追加しているかしていないか」というフラグに使用します。コンストラクタが呼び出された時点ではもちろん追加されていないのでFALSEを入れておきます。
 また、NOTIFYICONDATA構造体のメンバcbSizeには、あらかじめ構造体のサイズを入れておきます。このメンバはどんな状況であろうとこの値に決まっているためです。こういうとき、コンストラクタは便利です。

2:アイコンのIDを自動的に作製するようにする。
 順番が無茶苦茶ですが気にしないでください。
 アイコンのIDとはNOTIFYICONDATA構造体のメンバuIDのことです。この値はアプリケーション(正確にはウィンドウ)が複数のアイコンを登録するときに、どのアイコンかを識別するための整数です。
 この値を自動的に作製するようにします。どういうことかというと、アイコンをみっつ登録しなければならないときに、KNotifyIconクラスのメンバ変数をみっつ作製すると、このみっつのメンバ変数に別々の値が自動的に割り振られるというふうにするということです。

 これはつまり、KNotifyIconクラスのすべての変数で共通の変数を持つということです。その変数に最初に0を入れておき、このクラスがひとつ増えるごとに、共通の変数をインクリメントしていけばいいわけです。
 このような機能を持つ変数をスタティック変数と呼びます。では、この変数を実際に追加してみましょう。

 クラスウィザードには、スタティック変数を直接宣言する機能がないので、少し手作業が必要になります。
 まずいつも通りの方法でメンバ変数を追加します。型はUINT、変数名はms_uiID、アクセスはprivateにしてください。
 作製したら、KNotifyIcon.hを開き、この変数の宣言の最初にstaticを着けてください。次のような行になるはずです。

	static	UINT ms_uiID;
	

 ところがこれだけではまだ不十分なのです。KNotifyIcon.cppのコンストラクタの上くらいに、次の行を加えてください。

UINT	KNotifyIcon::ms_uiID = 0;	//staticな変数を初期化します。

//////////////////////////////////////////////////////////////////////
// 構築/消滅
//////////////////////////////////////////////////////////////////////
	

 スタティック変数は、個々のクラスからアクセスできます。そのため、個々のクラスから初期化しようとすると、いつ初期化されるのか、もしくはすでに初期化されているのか分からなくなってしまいます。
 そこで、このようなグローバル変数的な宣言方法を取る必要があります。

 実は、「まー初期化なんて必要ないわ」と思っても、この宣言は必要です。言ってみれば、これがスタティック変数の実体なのでしょう。スタティック変数は、グローバル変数をクラスの中に取り込んだものとでも言えるかもしれません。

4:ツールチップ文字列の追加を簡単にする。
 NOTIFYICONDATA構造体のメンバszTipは、アイコンの上にカーソルを持ってきたときに表示されるツールチップの文字列です。
 ところがこれがちょっと問題です。通常、この手の変数はLPCTSTRなのですが、このメンバは64文字のchar型、つまりあらかじめ文字列を入れるための領域が確保されているのです。そのため、CStringなどで操作するのがちょっと大変で、文字列をコピーするAPIlstrcpy()を使用しています。
 そこで、この文字列の追加を簡単にできるような機能を追加してみましょう。

 まずメンバ関数を作製しましょう。戻り値はBOOL、関数はSetTip( LPCTSTR p_pchTipStr )、アクセス指定はpublicです。作製したら次のようなコードを書き込んでください。

BOOL KNotifyIcon::SetTip( LPCTSTR p_pchTipStr )
{
	if( sizeof( szTip ) <= _tcslen( p_pchTipStr ) )
		return FALSE;	//文字列が長すぎます。

	_tcscpy( szTip, p_pchTipStr );	//文字列をコピーします。

	return TRUE;	//前の文字列を返します。
}
	

 この関数は、受け取った文字列の長さをチェックし、szTipに入りきらない場合には何もしないようにしています。入りきるのなら、szTipに文字列をコピーしします

 この辺の設計、実は悩みます。
 まず引数です。今回はLPCTSTRにしました。理由は、通常のTCHR文字列CStringの両方が使えるからです。MFCでは文字列を引数として取るときにはCStringの方が多いようですが、この場合には新たにメモリを確保するためその分時間がかかります。それを防ぐのが、同様にMFCでよく使われているCStringへの参照です。これなら余分な時間は掛かりませんが、反面LPCTSTRは渡すことができません。MFCではそれをカバーするため、これとLPCTSTRを使ったものと二通りの関数を作製しています。
 また、使用している関数はランタイムライブラリ、しかもマルチバイトやワイドバイトに対応したものです。ですが、szTipcharなので、ワイドバイト(Unicode)の場合には問題になります。

 これらの部分については、皆さんで実際に試してみて、TPOも考えて最善の方法を取るようにしてください。

 さて、これだけではちょっとつまらないので、演算子のオーバーロードというものを試してみましょう。どんな感じかというと、

	KNotifyIcon 	kNIcon;

	kNIcon = "あうあう";
	

 としただけで、今作ったSetTip()と同じ機能、つまり文字列のコピーができるような機能です(ホント言うと、こういう設計方法は間違ってるとは思うんだけど、今回は「クラスの作り方」がテーマだから、ね)。

 「関数の追加」ダイアログで、関数の型をvoid、宣言をoperator=(LPCTSTR p_pchTipStr)としてください。アクセス指定はpublicです。
 そして、関数には次のコードを書き込んでください。

void KNotifyIcon::operator =(LPCTSTR p_pchTipStr)
{
	SetTip( p_pchTipStr );
}
	

 演算子をオーバーロードする場合には、演算子の前にoperatorを着けます。戻り値は好きな型を指定できます。引数も指定できますが、複数の型に対応したい場合には、その分だけ関数を作製する必要があります(同じような形でHWND型が渡される場合やHICON型が渡された場合に、適切なメンバへと値を格納するため)。また、オーバーロードする演算子によって引数の数が変わります。その辺はC++の解説書を参考にしてください。

1:アイコンの追加を関数ひとつでできるようにし、ついでに引数を省略できるようにする。
 まずはメンバ関数を作製します。型はBOOL、宣言はAdd(HWND p_hWnd, UINT p_uiMsg, HICON p_hIcon, CString p_cTipStr, UINT p_uiID, UINT p_uiFlags)としてください。アクセス指定はpublicです。

 この引数の並び順が、構造体の解説と違うことに注意してください。引数の省略を行う場合、省略する引数は最後の方にまとめる必要があります。先頭の引数をいきなり省略するといったことはできないのです。そのため、省略した方が便利と思われる引数を後ろの方にまとめてあるというわけです。
 ではどのようにして引数の省略を行うかというと、クラス内のメンバ関数の宣言を次のように書き換えればOKです。

	BOOL Add(HWND p_hWnd, UINT p_uiMsg, HICON p_hIcon, LPCTSTR p_pchTipStr
		, UINT p_uiID = 0, UINT p_uiFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP );
	

 このように宣言の引数に値を渡せば、その引数は省略可能になります。そして、省略した場合には、この値が自動的に格納されるというわけです。
 今回省略した引数は、p_uiIDとp_uiFlagsです。p_uiIDNOTIFYICONDATAuIDにあたる引数で、このIDは自動的に作製するシステムを組み込んであるため、省略できるようにしています。また、p_uiFlagsNOTIFYICONDATAuFlagsにあたります。このフラグはアイコンの持つ機能を設定するものですが、省略したときに渡される設定が最も標準的で、まずこれ以外の設定は選ばないだろうと思われたので省略できるようにしました。

 この辺の設計、つまりどの引数を省略するかという部分も、皆さんで考えてみてください。
 で、実際の関数の中身はというと、

BOOL KNotifyIcon::Add(HWND p_hWnd, UINT p_uiMsg, HICON p_hIcon, LPCTSTR p_pchTipStr, UINT p_uiID, UINT p_uiFlags)
{
	if( m_bAdded != FALSE )
		return FALSE;	//すでに追加されています。

	if( SetTip( p_pchTipStr ) == FALSE )
		return FALSE;	//文字列が長すぎます。

	m_bAdded = TRUE;	//追加したことを示しておきます。

	hWnd = p_hWnd;	//イベントと関連づけるウィンドウです。
	uCallbackMessage = p_uiMsg;	//アイコンに関するイベントの識別子です。
	hIcon = p_hIcon;	//アイコン。
	uFlags = p_uiFlags;	//状態の設定。

	if( p_uiID == 0 )	//引数が省略されているのなら……
		p_uiID = ++ms_uiID;	//クラスごとに別のIDを設定します。

	uID = p_uiID;	//アイコンの識別ナンバーです。

	return 	::Shell_NotifyIcon( NIM_ADD, this );	//タスクトレイに表示します。

}
	

 二重登録しないようチェックした後、先ほど作製したSetTip()を使用して文字列をコピーします。次に引数を構造体のメンバへと格納していきます。
 アイコンのIDの格納で、引数に0が渡されている場合には、スタティックメンバ変数のms_uiIDをインクリメントし、IDとして使用します。こうすれば、1番目のクラスは1が、2番目のクラスは2が割り当てられるというわけです。
 ただし、この関数を他の人が使う可能性があるのなら、そういったことをちゃんと明記しておくべきでしょう。引数のp_uiIDに0を渡してはいけないこととか、IDのインクリメントは関数を追加する時点で行われるとかです。もちろん「ソースコードを見りゃ分かるだろ」なんて考えてはいけません。
 また、これは自分自身で使う場合にも言えることです。3ヶ月も経てば関数の仕様など忘れてしまいます。ちゃんとコメントとして書いておきましょう。

3:デストラクタで自動的にアイコンを削除するようにする。
 デストラクタという関数は、このクラスで宣言された変数が破棄されるときに呼び出される関数です。クラスのメンバ変数を手動で破棄しなければならない場合には、とても重宝します。
 このクラスでは、タスクトレイからの削除を行うようにしましょう。そうすれば、アイコンを削除し忘れることもないでしょう。
 デストラクタはすでに作製されていると思います。次のコードを書き込んでください。

KNotifyIcon::~KNotifyIcon()
{
	Delete();	//タスクトレイから削除します。
}
	

 もちろんKNotifyIcon::Delete()は自分で作る関数です。「関数の型」はBOOL、「関数の宣言」はDelete()、「アクセス制御」はpublicです。この関数には次のようなコードを書き込んでください。

BOOL KNotifyIcon::Delete()
{
	if( m_bAdded == FALSE )
		return FALSE;	//まだ追加されてません。

	m_bAdded = FALSE;	//追加されていない状態にします。

	return ::Shell_NotifyIcon( NIM_DELETE, this );	//削除します。
}
	

 このように、デストラクタ内にコードを書き込まずに別の関数を作製するのには理由があります。このクラスを使おうとした人が、デストラクタではなく手動でアイコンを削除したいと考えるかもしれません。そういった時のために、publicなメンバ関数を作製しておきます。そして、デストラクタから呼び出すようにします。
 デストラクタにも同じコードを書けばいいじゃないか、というのは大きな間違いです。似たコードを複数の関数に書くと、アプリケーションのサイズが大きくなるだけでなく、メンテナンスが非常に大変になります。ひとつの関数にミスが見つかった場合、同じコードを持つすべての関数を書き換えなければなりません。もしそれらの関数ごとに変数や引数が違っていたとしたら、さらなるバグを生み出しかねません。
 もし「コードに直接埋め込んで、関数を呼び出すオーバーヘッドを減らしたい」というのであれば、マクロかインライン関数を使用しましょう。

完成後のクラスを使用してみる!
 では、実際にこの完成したクラスを試してみましょう。「構造体」として使用していたビュークラスのコンストラクタのコードは、次のようにすっきりとします。

CTestView::CTestView()
{
	m_kNIcon.Add( GetSafeHwnd(), WM_USER_NTFYICON
		, AfxGetApp()->LoadIcon( IDR_MAINFRAME ), "Test" );	//タスクトレイに表示します。	
}
	

 さらに、デストラクタのコードは削除してしまいましょうKNotifyIconのデストラクタが自動的にアイコンを削除してくれます。
 どうです、すっきりしたでしょう?

一例
 今回は実際の例を元に「クラスの作成方法」を紹介してみました。そのため、幾分詰め込みすぎた感があります。それに、細かい部分も飛ばしてしまいました。文法的な部分は各自調べておいてください。
 さて、今回のは、あくまでサンプルなのだということを忘れないでください。この通りに作る必要はまったくありません。プログラマの皆さんが自由に自分の思うとおりに作製すればいいのです。ですから、「皆さんで考えてください」というフレーズを何度か書きました。
 と言っても、ちゃんと「押さえておかなくちゃいけない部分」もあります。そういった部分はしっかりと書いたつもりです。「自分の思うとおり」に作っても、ちゃんと「押さえるところは押さえる」、それが理想ではないでしょうか。

 この講座が、自分でクラスを作る「きっかけ」にでもなれば、と思います。どんどんクラスを作って、どういう設計方法が一番いいのか考えてみてください。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.