ATLを使わないWTLもどき
正式名称 Natsuhaze Windows Class Library (Nwcl)
written by shinsuke okazaki
最新版はなつはぜソフトウェアから
ATLもWTLも知らない人には何のことやらさっぱりわからないと思いますが、このライブラリはWindowsのユーザーインターフェイス開発用C++クラスライブラリです。
フリーソフトです。(vectorもどきを内部で使っています)。WTLのコードを一部拝借しています。といっても著作権を主張するにはあまりも自明なコントロールクラスのメンバ関数のみです。
WTLのライセンスが難解なのですが多分商利用も問題ないでしょう。著作権がらみで問題と思われる方はWTLのメンバー関数を使うのではなく、Winodws
SDK(ヘッダファイル)にある、Windowsのコントロール制御マクロ(後述)を使えばいいですが、1〜2行程度のものばかりなので内容は実質同じものでしょう。
一応お決まりの言葉ですが、どんな使い方をしても構いませんが使用したことによる損害は補償しません。無保証です。
開発経緯
時々Windows Template
Library(WTL)を使って開発しているのですが、どうも最新版のVisualStudio Express(2010)だとうまくビルドしてくれない。WTLはATL(Active Template Class Library)を必要とするのですが、このATLのバージョンが低いとコンパイルがうまくいかないのです。
結局WTLは自由に使えるといいながら、製品版の一部(ATL)を使うことが必須なライブラリで、真に自由なライブラリではないのです。
WTLを使うためにわざわざ製品版の最新のVisualStudioを買うのも馬鹿らしいので、ちょっと作ってみるかと思ってこしらえたものが、このライブラリです。だから製品版のVisualStudioを持っている人には意味のないライブラリです。貧乏人のためのライブラリといったらいいでしょうか?(笑)
MFC(Microsoft Foundation Class Library)で開発している人でも簡単なGUIしか作らないのであればNwclでも十分対応できると思いますし、データ交換変数に関して言えばMFC以上です。
---------------
特徴:
1.現在ダイアログとプロパティーシートのみ対応。モードレスダイアログは不対応(私はマルチスレッドを使って同じことを実現しているので必要を感じない)。
まあGUI作るといえば、ほとんどこればっかりでしょうから。
2. 現在すべてのコントロール(ボタンやエディットボックスなど)のカプセル化クラスとそのメンバ関数を実装していないが、WTLのWin32コントロールのカプセル化クラス名と同一クラス名にしてあるので、WTLのソースコードをそのままコピー&ペーストすれば(若干変更必要。後述)そのまま使える。
なので、WTLの開発者が新たに覚えることは非常に少ない。
ver1.1からActiveXコントロールにも対応しました
3.WTL、ATLに依存していないので、コンパイラやMicrosoftの意向に左右されることない。
可搬性も高いと思われる
4.MFCより優れたデータ交換クラスを実装。この部分だけが唯一のオリジナルといえる所。MFCと同じ要領で使える。またMFCのデータ交換クラスと使い勝手が同じなのでMFCを使っている人にもわかりやすい。WTLはこのデータ交換部分は非常に貧弱で、WTLだけしか知らない人なら、Nwclのデータ交換クラスを見たら相当強力と思う。
この変数は、コントロールとそれに対応するデータ交換変数が完全に一対一の対応で、事実上普通の変数とみなしてコーディングすることが可能。ラジオボタンやチェックボックスのデータ交換変数なら、メッセージハンドラ上にこれらのコントロールに関連する初期化・終了処理をまったく記述する必要なし。MFCで言うところのUpdateData()に相当する関数をいちいち呼び出す必要なし。
ただし、独自データ交換マップを記述する必要がある(要するにごちゃごちゃした初期化処理、終了処理はその内部で記述されている)。
以下、Nwclのダイアログクラスの定義例
class CTestDialog :public CDialog{ public: //データ交換変数 //定義方法 //DDX_var<コントロールに対応したクラス名, リソースID,データ交換変数(オプション)> 変数名 DDX_var edit_ctrl; DDX_var check_box; DDX_var radio_button; //データ交換マップ BEGIN_DDX_MAP //上で宣言した変数とウィンドウハンドルを関連付ける DDX_BIND_HWND(edit_ctrl) DDX_BIND_HWND(check_box) DDX_BIND_HWND(radio_button) //値の制限を設定 DDX_SET_MINMAX(edit_ctrl,_T("エディットボックス"),0,200) END_DDX_MAP //メッセージマップ //(要はこの中身がダイアログプロシージャなのだ) BEGIN_DLG_MSG_MAP //ダイアログ初期化時のメッセージハンドラ定義 MSG_INITDLG(OnInitDialog) END_DLG_MSG_MAP int OnInitDialog(){return 1;}//メッセージハンドラの実装部 };
ウィンドウプロシージャ例:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)//メッセージの種類
{
case WM_COMMAND:
switch(LOWORD(wParam)){
case IDC_COPY:VirtualFunction();break;
case IDC_CUT:VirtualFunction2();break;
case IDC_DELETE:VirtualFunction3();break;
}
}
}
//以下省略
この部分を#define文で書いたものがメッセージマップなのです。そしてこのウインドウプロシージャやダイアログプロシージャをいかにC++のクラス内に取り込むというのが厄介なのです。ウインドウプロシージャはスタティック関数なので、C++のメンバ関数として使う場合、トリッキーなことをしなくてはなりません。
NWCLの使い方。
初期化
インクルード
#include "nwcl.h"
nwcl.hがライブラリのコードが入ったヘッダファイルです。
ソースコードに以下の文を必ず入れてください。1つだけ定義すればいいです。
extern "C"{ class NWCWinApp app_obj; }; WinMain()関数などのスタートアップ関数直後にアプリケーションのインスタンスハンドルとアプリケーション名を入力してください。 nwcl::app_obj.set_instance(hInstance); nwcl::app_obj.app_name = _T("アプリケーション名"); インスタンスハンドルの入力は必須です。 クラス定義 ダイアログボックス
class ダイアログクラス名 :public CDialog<ダイアログクラス名,リソースID>{ }; プロパティページ class プロパティページクラス名 :public CPropertyPage<プロパティページクラス名,リソースID>{ };
各クラス名は、上位クラスであるテンプレートクラスに記述するクラス名と同一にしてください。
リソースIDは、ダイアログボックス、プロパティページのリソースIDを指定します。
データ交換変数の宣言フォーマット
DDX_var<コントロールに対応したクラス名,リソースID,データ交換変数の型(オプション)> 変数名;
コントロールに対応したクラス名はnwcl.hを見てください。
データ交換変数の型は、整数型をいくつか用意していますが、intにしておけば問題は出にくいはずです。他にベクターもどきで定義されているString(文字列)を指定可能です。
言うまでもないと思いますが、データ交換変数の型はリストビューなど複雑なコントロールには対応していません。
データ交換関数のマップ
BEGIN_DDX_MAP
DDX_BIND_HWND(データ交換変数)
//値の制限を設定
DDX_SET_MINMAX(データ交換変数,_T("コントロール名"),最小値,最大値)
END_DDX_MAP
データ交換変数をまったく定義していない場合でもクラス定義内に以下の定義は必須です。
BEGIN_DDX_MAP
END_DDX_MAP
理由は必ずこの定義内にある関数を呼び出すからです。
この定義はデータ交換変数とそれに対応するコントロールのウィンドウハンドルを関連付け、値の制限幅も指定します
DDX_SET_MINMAXマクロを定義する際、データ交換変数の型がint型でないとエラーが出る場合があります。
メッセージマップの定義
最低でも
BEGIN_DLG_MSG_MAP
END_DLG_MSG_MAP
をクラス内に定義(単に書き込むだけ)する必要があります。
この2つの定義内にメッセージハンドラを記述します。
BEGIN_DLG_MSG_MAP //WM_INITDIALOGメッセージに対応するメッセージハンドラ MSG_INITDLG(メッセージハンドラ) //WM_COMMANDメッセージ処理 COMMAND_HANDLER_BEGIN //通知メッセージごとのコマンドハンドラ COMMAND_NOTIFY_HANDLER(リソースID,通知コード,メッセージハンドラ) //WM_COMMANDメッセージ処理終了 COMMAND_HANDLER_END //コモンコントロール専用のメッセージハンドラ(WM_NOTIFYメッセージ処理) CMMNCTRL_NOTIFY_HANDLER_BEGIN //汎用 CMMNCTRL_NOTIFY_ID_HANDLER(リソースID,通知コード ,メッセージハンドラ) //プロパティシートのOKや適用ボタン専用のメッセージハンドラ CMMNCTRL_NOTIFY_HANDLER(通知コード,メッセージハンドラ) CMMNCTRL_NOTIFY_HANDLER_END END_DLG_MSG_MAP
INT_PTR Procedure(UINT message,WPARAM wParam,LPARAM lParam ){ switch(message) { //MSG_INITDLG() case WM_INITDIALOG :return handler(); //#define COMMAND_HANDLER_BEGIN case WM_COMMAND:{ //#define COMMAND_NOTIFY_HANDLER(cmd_id,notify_code,handler) if(LOWORD(wParam)==cmd_id && HIWORD(wParam)==notify_code){ handler(); return 0;} } //#define CMMNCTRL_NOTIFY_HANDLER_BEGIN case WM_NOTIFY:{LPNMHDR nmhdr = (NMHDR*)lParam; //CMMNCTRL_NOTIFY_ID_HANDLER if(nmhdr->idFrom ==id && nmhdr->code==notification){ handler(lParam);return 0;} } } }
注意:ver1.1からダイアログのOnOkハンドラは仮想関数になり、明示的に何も記述しなくてもよくなりました。
以前は明示的に記述しないとダイアログが消えない問題がありました
実際の使用方法はサンプルコードを参照してください。
クラスライブラリのカスタマイズ方法
コントロールクラスのカスタマイズ方法は、単純にWTLのコントロールクラス(WTLのインストール先のatlctrls.h内にある)のメンバ関数をコピー&ペーストして、ATLASSERT部分をassertに変更するだけでほとんどが使用可能になります。 ヘッダファイルnwcl.h には必要と思われる部分しかメンバ関数のコピーを行っていません。 WTLのメンバ関数使用が問題、もしくは嫌なら、昔からあるコントロールを制御するマクロをメンバ関数にしてもいいでしょう。どちらにせよ、コードに違いはほとんどありません。
例:選択したリストビューの項目数を取得するマクロ: UINT ListView_GetSelectedCount( HWND hwnd); を、メンバ関数にする
(class CListViewCtrl内で記述する)
UINT GetSelectedCount(){ return ListView_GetSelectedCount(m_hWnd);}
要は、マクロの接頭語に相当するコントロール名を削除したものをメンバ関数にして、引数はマクロのウィンドウハンドルを削ったものにすればいいのです。上の例だと引数はゼロになります。そして定義部分で、上位クラスのCWindowにあるm_hWndを引数にしてマクロを呼び出せばいいのです。 各コントロールのマクロの一覧は検索サイトで "msdn Control Library"と検索して、たぶん最初に出てくるmsdn(Microsoft Developer Network)内のControl Libraryを見れば、全コントロールのメッセージIDと通知メッセージ、マクロを見ることができます。
これ以外に独自にメンバ関数を定義することももちろん可能です。これを自由に行うことができれば本当にWTLやMicrosoftの意向に左右されません。Nwclには、私が作ったいくつかのメンバ関数があります。演算子のオーバーロードを使ったものが見つかるはずです。長ったらしい名前のメンバ関数を使うより、演算子を使ったほうが見やすくなります。私が作った中で一番コードが長い独自メンバ関数は、ラジオボタンのデータ交換変数がらみのコードです。これはMFCと使い勝手を同じにするために少々長くなってしまいました。
ActiveXコントロール利用のための実装方法
Ver1.1からActiveXコントロールを簡単にダイアログなどで使用することができます。これはEternal WindowsというサイトのFlashコントロールを使ったサンプルコードを改変して実現したものです。(サイト管理者に許諾は取ってあります)。この場を借りてEternal Windowsの管理者に御礼申し上げます。
ActiveXコントロールを使用する場合は、nwclcom.hをnwcl.hの後でインクルードしてください。
ActiveXコントロールクラスを使用するのに最低限必要なのは、ActiveXコントロールを実装しているCLSID(クラスID)で、それを操作するためには、それにアクセスするためのインターフェイスIDも必要になります。イベント(正確にはイベントシンク)も実装できます。これはWin32の用語で例えるならコールバックもしくはウィンドウプロシージャのようなものです。要するにこれらのIDを見つけてしまえば後は簡単に実装できるのです。
1.リソースエディタにダイアログに表示する枠を決める。
1.1CLSIDの取得
Express版でないVisualC++に付属のリソースエディタでは「ActiveXコントロールの挿入」と言うメニューがありますが、nwclではこれで挿入したコントロールを最終的には利用してはなりません。「最終的には」と言う意味は、いったん設定して、「ある」情報を取り出してから、消して欲しいと言うことです。それはCLSIDの事です。このメニューでリソースファイル(.rcファイル)にはCLSIDが記述されますので、テキストエディタから.rcファイルを読み込んでCLSIDを取り出します。”{}”で囲まれた英数字とハイフンが混じった文字列がCLSIDです。この方法だと楽にCLSIDを調べることができます。(VisualBasicやVisualC#のリソースファイルでも同様にクラスIDが記述されているかもしれません。)
VisualStudioが使えない場合はSDKなどに付属のOLE/COM Object Viewを使用して探す必要があります。詳しくはnwclcom.hの最初のコメント部分に書かれてありますので参照してください。
ちなみにWebBrowserコントロールのCLSIDは{8856F961-340A-11D0-A96B-00C04FD705A2}となります。
1.2表示領域を決める
CLSIDを取得できたら、リソースエディタで挿入したActiveXコントロールを削除します。その代わりにテキストコントロールを挿入します。表示領域を変えてください。テキストコントロールのリソースIDは通常ID_STATICになりますが、一意な名前になるよう変更してください。サンプルコードではIDC_STATIC_IEとしました。
2クラス宣言と、ActiveXコントロールのインスタンス作成方法
ActiveXコントロールクラスの宣言は以下の通り
CActiveXCtrl< /*イベントシンクのクラス名(オプション)*/ > *axctrl;
必ずポインタ変数で宣言してください。サンプルコードでは、CIPtr<>というインターフェイスポインタを適切に解放するテンプレートクラスで囲ってあってポインタ変数のように見えないのですが、CIPtr<>クラス内部でポインタ変数として宣言しています。
イベントクラスを入れないのであれば、テンプレートクラス名を記述する<>内に、何も書く必要はありません。デフォルトクラスが適用されます。
そして、このクラスオブジェクトはnew演算子で必ずインスタンスを作成する必要があります。(これはComponent Object Modelの仕様)
具体的には以下のようになります。
CActiveXCtrl< > *iptr; iptr = new CActiveXCtrl<>( /*Microsoft Web Browser CLSID*/ L"{8856F961-340A-11D0-A96B-00C04FD705A2}", //リソースID、IDC_STATIC_IEの示す領域に表示 GetDlgItem(IDC_STATIC_IE) //オーバーラップウィンドウのハンドルも指定可。その際CActiveXCtrl<>::OnSize(x,y)で表示領域の変更可 ); if(iptr){ //iptrを操作するためのインターフェイスポインタの取得 } //最後に必ずRelease()を実行。(これはこのコントロールを乗せているウィンドウの終了処理時に行う) iptr->Release();3インターフェイスポインタの宣言と使用
実際に操作するにはインターフェイスIDも必要になります。このIDはサンプルの例だとIWebBrowser2インターフェイスが使用できます。ActiveXコントロールごとに実装されているインターフェスは違いますが、IDispatchインターフェイスは必ず実装されていますので、このアドレスを取り出せば一応操作はできます。 ただ、コードが直感的ではありません。すべてinvokeと言うメソッド名で操作します。
IWebBrowser2 *web; //QueryInterface「2」はActiveX特定のインターフェイスポインタを取り出すときに使用 iptr->QueryInterface2(IID_PPV_ARGS(&web));//WebBrowser コントロールからIWebBrowser2インターフェイスを取得 IDispatch *web2; //IDispatchインターフェイスポインタを取得 iptr->QueryInterface2(IID_PPV_ARGS(&web2));クラスID(CLSID)が、どんなインターフェイスを持って、どんな操作(メソッド)を行えるかはOLE/COM Object Viewで探せばわかります。
VARIANT v; v.bstrVal = SysAllocString(L"http://natsuhaze.jp/software/"); v.vt = VT_BSTR; web->Navigate2(&v,NULL,NULL,NULL,NULL); SysFreeString(v.bstrVal);
となります。
4.イベントシンクの実装
これはnwclcom.hに定義済みのCActiveXEventクラスの派生クラスを用意することで容易に実装できます。最低限必要なのは、実装しているイベントシンクを示すID(UUID)を使用して派生クラスを作成し、その派生クラス内でInvoke()の再定義することです。IDの探索方法はnwclcom.hの上部コメントに書いてあるので参照してください。実際の例は、サンプルプロジェクトを参照してください。
最後に
このクラスライブラリはWin32の初期のC言語の開発手法を知っていれば、内部構造を容易に理解できるはずです。また、これを使って開発する際は、各コントロールのメッセージを知っていることが鍵となります。それはWTLのWindowsコントロールクラスを見れば明らかです。
しかしWTLのソースコードだけを見ては通知メッセージがわかりません。言うまでもなく、通知メッセージとは、ボタンが押されたとか、フォーカスが変化したとかいうWindowsが発するメッセージのことです。これらのメッセージを処理することも必要なので、把握しておくことも必要です。
結局それらもmsdnのホームページ内のControl Library以下に記載されていますので参照してください。
英語がわからない人で製品版のVisualStudioを持っている人なら、MFCのWindowsコントロールをカプセル化したクラスのソースコードを見て通知コードや各種メッセージの使い方を知るとよいでしょう。