では、今回は誰でもがその恩恵にあずかれる可能性があり、かつ基礎的な実習になると思われる UserType.dat
を作成するツールを作りながら WTL の特徴を述べていきます。
まず、UserType.dat とは何か?という話ですが、これは Microsoft
の統合開発環境(IDE)において、ソースのキーワードに着色を行うために使われるキーワードを列挙したファイルで
Visual Studio
6.0 (VC 6.0)では、
(Install
Folder)\Microsoft Visual Studio\Common\MSDev98\Bin
Visual
Studio.net (VC.net)では、
(Install
Folder)\Common7\IDE
にキーワードの入った
UserType.dat というファイルを置けば、IDE 内で着色することができるようになります。
注)さっき公式のサイトで似たツールを発見(がっくし)
-
概要設計
実際に実装する機能は以下のとおりにします(あとは適当に改良してください)
-
ソースファイルからキーワードを抽出する機能
-
抽出したキーワードを選別する機能
-
既存の
UserType.dat の読み込み
-
UserType.dat
の書き込み
ファイルの読み書きには UI
としてコモンダイアログを使い、実装としては IO 関係の関数(STL)を使うと楽だと思います。
また、ソースファイルからのキーワードの抽出は、簡単な字句解析で十分だと思いますし、問題・改善点があれば、少しずつ機能を追加して行くつもりです。
アプリケーションは簡単に
Keyword Maker と名づけました。
ソースとバイナリをトップから入手して各自でいろいろいじって試してください。突っ込みやバグ報告、要望等はプログラムの掲示板へお願いします。
-
実装
わかっている人には基本だと思うのですが、プログラムの手順とかコツとかを書いていきます。
-
メッセージマップ:マクロとハンドラ
まずソースの
maindlg.h を見てください。ここで、Windows のイベントを制御します。
ボタンのクリックや特定のイベントに対応して動作を実行させたい場合にはメッセージマップという部分に記述します。
メッセージマップは
BEGIN_MSG_MAP(CMainDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
NOTIFY_ID_HANDLER(IDC_KEYWORDTREE, OnNotifyKeywordTree)
// COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
・
・
・
END_MSG_MAP()
という部分に相当しますが、これはいわゆる SDK の
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM );
に相当します。つまり、SDK で
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch( message ) {
case WM_CREATE:
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// メニュー選択の解析:
switch( wmId ) {
case IDM_FILEOPEN:
case IDM_FILENEW:
case IDM_SAVEOVER:
・
・
・
default:
return DefWindowProc( hWnd, message, wParam, lParam );
}
break;
case WM_SIZE:
break;
case WM_PAINT:
hdc = BeginPaint (hWnd, &ps);
// TODO: この位置に描画用のコードを追加してください...
EndPaint( hWnd, &ps );
break;
case WM_DESTROY:
PostQuitMessage( 0 );
break;
default:
return DefWindowProc( hWnd, message, wParam, lParam );
}
return 0;
}
と書いていたのに相当すると思ってください(すごい楽でしょ?)
これらの switch 文がメッセージクラッカー(windowsx.h 参照)によって HANDLE_WM_COMMANDや
HANDLE_MSG といったマクロに置き換えられたのと同じように、WTL(ATL)では、COMMAND_ID_HANDLER や
MESSAGE_HANDLER というマクロを使ってこの switch 文を作り、Windows
からのメッセージに対応すると考えてください。
そして、それぞれのマクロに対応する実装が、
// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
の部分で用意されているプロトタイプを使います。具体的に KeywordMaker で使っているひとつの実装を例にとって説明します。
KeywordMaker
では「抽出開始」というボタン(ID:IDSTART)を押したら、抽出処理を行ないたいので、これに対するマクロをメッセージマップに追加します。大きく分けて3種類あるマクロ(メッセージ系、コマンド系、通知メッセージ系)のうち、この場合は特定のコマンド
ID に対応した処理をしたいので、コマンド系のマクロを使います。
くどいようですが、SDK では
case WM_COMMAND:
wmId = LOWORD(wParam);
switch( wmId ) {
case IDSTART:
と書いたのと同義だと思ってください。
注)コマンド系のマクロには、COMMAND_ID_HANDLER の他にも、いろいろあるので MSDN の『ATL
マクロとグローバル関数』のトピックを参考にしてください。
追加したマクロは太字の部分
BEGIN_MSG_MAP(CMainDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
NOTIFY_ID_HANDLER(IDC_KEYWORDTREE, OnNotifyKeywordTree)
// COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(IDSTART, OnStart)
END_MSG_MAP()
になります。
一つ目の引数は
メッセージを受け取る対象の ID で、二つ目の引数はその ID のハンドラ(つまりその ID
のメッセージが来た場合に呼ばれるプログラムの実装部分の関数名)を指定します。ここでは、OnStart
と名づけたので、先ほどのコマンド系のマクロのハンドラのプロトタイプを使って、maindlg.h に
LRESULT OnStart(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
を追加します。OnStart の中の実装は maindlg.h に書いてしまってもいいですし、maindlg.cpp
の方に実装しても構いません。私は maindlg.cpp に実装するのが好みです。
-
ダイアログに貼り付けたコントロールの制御
簡単なコマンドハンドラを記述できるようになったと思うので、次にそのハンドラ内での実装をしていくわけですが、当然その実装の過程で、ダイアログやウィンドウに貼り付けた(もしくは動的に生成した)オブジェクトにアクセスしたくなります。
例えば、KeywordMaker の OnStart では ListView に対する操作をします。WTL
ではこれら通常使われるクラス(この場合は CListView)に対するラッパークラスがあるので、これを使って操作します。やり方はいろいろあると思うのですが、Attach
と Detach を使うやり方について述べます。
まず、対応するクラスのインスタンス変数を宣言します。
CListViewCtrl cListView;
この
CListViewCtrl というクラス名は、AtlCtrls.h
というヘッダファイルから参照します。プロジェクトのワークスペースにこのヘッダを追加しておくと、Class View
で参照できるので便利です。
AtlCtrls.h の ClistViewCtrl の実装部分は、
// CListViewCtrl
template <class TBase>
class CListViewCtrlT : public TBase
{
public:
// Constructors
・
・
・
};
typedef CListViewCtrlT<CWindow> CListViewCtrl;
こんな感じに始まって、最後にこんな別名が宣言してあります。つまり、CListViewCtrlT のテンプレート引数はたいてい
CWindowなので、いちいち使う時に、
CListViewCtrlT<CWindow> cListView;
と書くのではなくて、
CListViewCtrl cListView;
と簡潔に書けるよ(両者は同じ意味)ということです。
さて、ここでは宣言しただけですので、この cListView
は何も指していません。そのため、既存のリストビューと結びつける必要があります。このためのメソッドが Attach です。Attach
というメンバ関数は CListViewCtrlT が継承している TBase クラス、つまり、ここでは CWindow(デフォルト)に実装されています。
宣言を見てみると、(AtlWin.h)
void Attach(HWND hWndNew)
{
ATLASSERT(::IsWindow(hWndNew));
m_hWnd = hWndNew;
}
となっています。ここで、SDK を思い出して欲しいのですが、SDK ではこのような作業をするときに、
HWND hWndListView;
hWndListView = GetDlgItem(IDC_FILELIST);
といったような操作をして
ListView(IDC_FILELIST)のウィンドウハンドルを取得し、このハンドルに対して SendMessage
を使っていろいろなやり取りを行っていました。
WTL では、ベースとなる CWindow がメンバ変数として m_hWnd
を保持しているため、ウィンドウハンドルを表面上意識することなくプログラムすることが出来ます。
それが以下のコードです。
cListView.Attach(GetDlgItem(IDC_FILELIST));
これによって、GetDlgItem の戻り値であるウィンドウハンドルで初期化された cListView を使って、SendMessage
をラッパーした各種メンバ関数を使うことが出来ます。
例えば、いまリストビューに何個のデータがあるかを調べるために、メンバ関数の GetItemCount() を呼ぶとしましょう。この
GetItemCount() の実装を AtlCtrls.h で確認すると、単に SendMessage
を実行しているだけなのがわかると思います。しかも、SendMessage
に必要な最低限の引数のみが、メンバ関数の引数として実装されているので、より簡単にプログラムすることが可能です。
良くあるエラー
コードを実装してリンクした段階で LNK2001
外部シンボル・・・というエラーが出る時があります。これは、ヘッダ宣言された関数の実装部がない時に起こります。
私が良くやるミスは(私だけか)、本来 .cpp において
LRESULT CChildFrame::OnUpdate(int idCtrl, LPNMHDR /*pnmh*/, BOOL& bHandled)
などとしなくてはならないところを
LRESULT OnUpdate(int idCtrl, LPNMHDR /*pnmh*/, BOOL& bHandled)
と、ヘッダからコピーしたままにしてコンパイルした時などです
以上、駆け足ですがサンプルプログラム KeywordMaker の WTL らしいところを解説しました。
第3回へ
|