「私はド素人、内容には全く自身を持てません」状態からヨチヨチ歩き程度に上達できたと思う今日この頃なので、一旦内容を整理した。
VC++ ver.6 を使用中。
組込み型のintやcharなどは色がついてハイライトされて見やすいが、これをstringやその他自作クラスに適用したいと思うことがある。
そんな場合は、usertype.datというテキストファイルを作り、それの各行にハイライトさせたいキーワードを記述し、devenv.exeと同じディレクトリに保存すれば良い。私の環境では以下だった。
C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\usertype.dat
.net 2003 で、例えばダイアログのイベントハンドラを追加する場合は、プロパティから「メッセージ」のアイコンを選んでゴニョゴニョすれば良いが、ここにWM_INITDIALOGが無かったので腹が立った。
原因は、これはオーバーライドするハンドラだからなのであった。これを表示するにはクラスビューを表示させた状態(フロートにすると良い)でプロパティを選ぶ。すると直方体の「オーバーライド」ボタンが登場するので、ここからOnInitDialogを選択してゴニョゴニョすると良い。このあたりはプロパティウィンドウをフロートにしてF1でヘルプを呼ぶと書いてある。でも、ここまで気づくのは相当難しいのではないのか。
ヘルプからの抜粋:
「このオプションを選択せず、後で ActiveX コントロールをプロジェクトに追加する必要が生じた場合には、アプリケーションのメンバ関数 InitInstance に AfxEnableControlContainer の呼び出しを追加する必要があります。 」
いわゆるSDKプログラミング。これを知らねばはじまらない
SendMessage(hEdit,EM_SETSEL,0,-1); //全選択 SendMessage(hEdit,EM_SETSEL,-1,-1); //解除
でうまくいった。エディットコントロールについて。
d:\program files\microsoft visual studio\vc98\include\rpcasync.h(45) : warning C4115: '_RPC_ASYNC_STATE' : 関数の仮引数リストで構造体、共用体、列挙型の定義に使われました。件のヘッダを覗いてみたが、大元で正しく前方参照されていないのが原因のようだ。個々のファイルで
#pragma warning(disable : 4115)してもいいが、やはり前方参照
struct _RPC_ASYNC_STATE;を大元のヘッダに埋めこんでしまうのが根本的な解決なのだろう。ベンダ提供のヘッダを書換えるのはヤな感じだが、しかたがない。
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME
コンボボックスで、右の下矢印をクリックすると「びろーん」とドロップダウンリストが出てくる。項目が多いとこれはスクロールウィンドウになるわけだが、その位置をどう指定すればよいか?
そのためにはCB_SETTOPINDEXを投げればよい。ただしこれは、ドロップダウンリストが現れている時にのみ効果があるようだ。マウスクリックでリストを出す場合、ON_CBN_DROPDOWNが飛んではくるが、これはリストが現れる前の話なのでこのタイミングで作業しても意味がないようである。
結局、CB_SETTOPINDEXはいつ投げればいいのだろうか?
コントロールのウィンドウプロシージャとして、自前の関数を登録することができる。
WNDPROC PrevWndProc; LRESULT CALLBACK MyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if(uMsg == WM_LBUTTONDBLCLK){ MessageBox(GetParent(hWnd), TEXT("double-click hello"), TEXT(""), MB_OK); return 0; } return CallWindowProc(PrevWndProc, hWnd, uMsg, wParam, lParam); } void some_initialization_func(){ // ... HWND hwndTargetCtrl = GetDlgItem(hWndDlg, IDC_TARGET_CONTROL); PrevWndProc = (WNDPROC)GetWindowLong(hwndTargetCtrl, GWL_WNDPROC); SetWindowLong(hwndTargetCtrl, GWL_WNDPROC, (LONG)MyWndProc); // ... }
などとする。上の例では、コントロールにおけるダブルクリックはメッセージボックスに置き換わる。CallWindowProc してやればデフォルトの処理が実行されるので、記述するのは自分の欲しい機能だけでOK。
_splitpath() あるいは http://www3.ocn.ne.jp/~liquidz/tips/tips_cpp000.htm
全て http://homepage2.nifty.com/DSS/VCPP/API/SHBrowseForFolder.htm にて学んだこと。
#include <shlobj.h> static bool ChooseDirectory(LPSTR NewDir, HWND hwndOwner = NULL){ // see http://homepage2.nifty.com/DSS/VCPP/API/SHBrowserForFolder.htm BROWSEINFO bi; LPSTR lpBuffer; LPITEMIDLIST pidlBrowse; LPMALLOC lpMalloc = NULL; HRESULT hr = SHGetMalloc(&lpMalloc); if(FAILED(hr)) return false; if((lpBuffer = (LPSTR)lpMalloc->Alloc(_MAX_PATH)) == NULL) return false; bi.hwndOwner = hwndOwner; bi.pidlRoot = 0; // desktop is the root bi.pszDisplayName = lpBuffer; bi.lpszTitle = TEXT("Choose your favourite directory..."); bi.ulFlags = 0x0040; //BIF_NEWDIALOGSTYLE bi.lpfn = 0; bi.lParam = 0; pidlBrowse = SHBrowseForFolder(&bi); bool copied = false; if(pidlBrowse != NULL){ // not canceled if(SHGetPathFromIDList(pidlBrowse, lpBuffer)){ lstrcpy(NewDir, lpBuffer); copied = true; } lpMalloc->Free(pidlBrowse); } lpMalloc->Free(lpBuffer); lpMalloc->Release(); return copied; }
標準では、ダイアログプロシージャでWM_KEYDOWNは拾えないようだ。
わけのわからないバグは大抵スレッドに絡んでいるが、この前遭遇したわかりにくいバグ(私にとっては。多分初歩のミス)もやっぱりスレッドまわりだったのでメモ。
::WaitForSingleObject という関数があるが、これはカーネルオブジェクトでキッチリ同期をとってくれるスグレモノであるが、キッチリwaitしてくれたおかげでメッセージループも回らなくなり、これが原因でよくわからないバグになっていた。メッセージループ(ウィンドウ)を持つスレッドの場合は、代わりに::MsgWaitForMultipleObjectsExを用いて、カーネルオブジェクトを待ちつつメッセージを処理するべきである。よく見たらそのようにMSDNにも書いてあった。
ここで、CoInitializeなんかは間接的にウィンドウを作るので、明示的にウィンドウを作らないがCoInitializeを呼ぶスレッドなんかも、同様の理由で::WaitForSingleObjectを使うべきではないようだ。
ウィンドウプロシージャは大抵飛んでくるメッセージをさばくのにswitch文を使う。処理すべきメッセージなんてのは大抵沢山あるので、巨大なswitch文になりがちだが、これがまた美しくない。そこで、メッセージクラッカーと呼ばれているwindowsx.hで定義されている一連のマクロを使うと比較的すっきりとメッセージ処理が記述できる。直接APIを叩いてアプリケーションを作る場合にとても重宝する。
・・・と、Jeffrey RichterのAdvanced Windowsに書いてあった。試してみたが、ダイアログプロシージャを以下の様にすっきりと記述できて満足。
BOOL CALLBACK DialogProc(HWND hwnd, UINT uMsg, WPARAM wp, LPARAM lp){ switch(uMsg){ hiHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); hiHANDLE_DLGMSG(hwnd, WM_CLOSE, Dlg_OnClose); hiHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); hiHANDLE_DLGMSG(hwnd, WM_COPYDATA, Dlg_OnCopyData); } return FALSE; }
正しくは(ダイアログプロシージャの場合)、以下のようなマクロを前もって定義しておく必要がある。
///////////////////////////////////////////////////////////////////// // The macro (message cracker) is almost same as that of Jeffrey Richter's "CmnHdr.h". // Just the names of the variables are different from the original ones. #define hiHANDLE_DLGMSG(hwnd, message, fn) \ case(message): return (SetDlgMsgResult(hwnd, uMsg, \ HANDLE_##message((hwnd),(wp),(lp),(fn)))) /////////////////////////////////////////////////////////////////////
Dlg_OnInitDialogなどとあるのがハンドラの関数名である。これらのプロトタイプはwindowsx.hに与えられている。でもWM_COPYDATAのが書いてない。これは以下のようになる。
BOOL Dlg_OncopyData(HWND hwnd, HWND hwndFrom, PCOPYDATASTRUCT pcds)
ただし、SetDlgMsgResultはあんまり賢くないみたいで、キャスト関連でC4244の警告が出る。
メッセージクラッカー・ビューアなるものを作ってらっしゃる方もおられた。
FindFirstFileとFindNextFileを駆使する。例えばファイル数が知りたければ以下のようにする。ファイル名はWIN32_FIND_DATAのcFileNameでわかる。
int count_files(const TCHAR* mask){ WIN32_FIND_DATA w32fd; HANDLE hfind = FindFirstFile(mask, &w32fd); int i = 0; if (hfind != INVALID_HANDLE_VALUE) { do { i++; } while(FindNextFile(hfind, &w32fd)); } return i; }
VB6でいうところのDoEventsは以下(なのだそうだ。出典は失念)
MSG msg; if(::PeekMessage(&msg,NULL,0,0,PM_REMOVE)){ ::TranslateMessage(&msg); ::DispatchMessage(&msg); }
インポートライブラリを使ってビルド時に関数を参照する方法と、実行中に動的に関数を参照する方法とがある。
以前A社の計測装置を購入した際、USBによる操作用にDLLが添付されてきたことがあった(実はそれがお目当て)。しかし、言語のサポートとしてはVisual Basic用の標準モジュールのみで、インポートライブラリはもちろんCのヘッダもなかった。その時標準モジュールを参考にヘッダを書き、何とか動的呼び出しにまでこじつけたのが勉強したきっかけ。
お目当ての関数ポインタには、ちゃんと自ら型(引数、戻り値)を指定してやる。
DLLと .def ファイルがあればインポートライブラリが作れるみたいだ!? 中身は全く理解していないが
lib /def:my_def_file.def
とすれば .libができるみたい。実際にできた。The Code Project - CppSQLite - C++ Wrapper for SQLite - Databaseを参考にした。
ジワジワと操作を覚え、ジワジワと便利になってゆくもの
ある関数に必要なヘッダとライブラリはヘルプに記載されている。ヘッダは include すれば良い。ライブラリは
のいずれかで追加する(両者はどう違うのだ?)
#pragma comment(lib,"msxml2")
だけどインクルードパスの追加はできないみたい。
以下VC++超初心者のホームページから。
自分で適当に配列を作って、コントロールIDを格納するのが(実装と理解の観点で)一番お手軽
SDKに限った話ではないが、VCでグラフを実現するのはそう簡単なことではなさそうだ。自分で描画することはできるが面倒くさいのでパス。そこで既存のものを使いたくなるわけだが、標準でついてくるのはMSChartくらい。VBから使うのは簡単だが、VCから使うにはかなりのクセモノである上に文書も少ない。定番がどれなのかもよくわからない。CodeGuruでも乱立してるみたいだ。
National InstrumentsのMeasurement Studioを使って、計測制御用のプログラムが使い慣れた言語でラクラク書けてとても幸せだが、成果物の配布にあたっては「インストーラを作りなさい」とのことであった。そういえばインストーラについては全く知らなかったのでWeb上の文書をひととおり調べた結果が以下。
VC++付属のSpy++は色々調べるのに便利だが、フリーソフトのWinspectorはその機能を凌駕する。
非常に大雑把にいうと「手動MFC亜種」。実行ファイルが軽いという利点がある。MFC、ClassWizardの動作を見つめなおすきっかけにもなる。
業界標準のフレームワークだが、全くのWindowsプログラミング初学者が手を染めるべきではないと思う。(とりあえずは動作しても、何が何だかわからない状態になると思われる)
一行全体を選択するようにし、グリッドも引く。
m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
ある行を選択状態にする。「常に選択を表示」とするのを忘れないようにする。
m_list.SetItemState( next_index, LVIS_SELECTED | LVIS_FOCUSED , LVIS_SELECTED | LVIS_FOCUSED);
*.aps;*.bsc;*.dll;*.exe;*.exp;*.idb;*.ilk;*.lib;*.ncb;*.obj; *.ocx;*.pch;*.pdb;*.plg;*.res;*.sbr;*.tlb;*.tlh;*.tli;*.trg
は消してもよいのだそうだ。http://www.mtakahashi.com/old/a2725.html での A2705「もろ」氏。まあ、「ビルドの際に自動で生成されるから」という理由だから、よそから引っ張ってきたdll、exe、lib、tlh、tliなどなどは当然消したらダメ。
最初にAppWizardさんが作ってくれるが、後から自分で作りたい場合はどうすればよいか?
に記述が見つかった。指定するリソースIDのメニューを前もって作っておかねばうまくいかなかった。
フォントまわりは奥が深い。CreateFontの引数には泣きそうになる。よって、とりあえず動いたやつをメモとして貼る。CFontのメンバ関数とグローバルなAPIが同名だが、引数は同じっぽい。
CreateFont(20, 0, 0, 0, FW_NORMAL, FALSE, FALSE, 0, SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, _T("MS ゴシック"));
CFontなら以下のようなお手軽設定も可能。
hFont.CreatePointFont(15*10, _T("MS ゴシック"));
設定させるにはWM_SETFONTのwParamにHFONTをのせてやってメッセージを送る
HFONT hFont; ... ::SendMessage(hWnd, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE,0));
か、CWndのSetFontメンバを使って
SetFont(&hFont,TRUE); // CFont hFont;
とする。しかしCFontオブジェクトがローカル変数だとこれは失敗するので、CWndのメンバにしておくとよし。(ナレッジウェア Tips3 - SetFont)
使用する際には、stdafx.hに Afxcview.h をインクルードしておくこと。リストビュー(CListView)は正体がリストコントロール(CListCtrl)なビュークラスだそうだ。ダイアログ等にリストコントロールを直接貼り付ける時と違い、スタイルはリソースから指定できない。「レポート」にするにはPreCreateWindow内で
cs.style &= ~LVS_TYPEMASK; cs.style |= LVS_REPORT;
とする。その他SetExtendedStyleまわりはOnInitialUpdateに記述するとよし。(以上はClistViewの表示がうまくいかない・・・。における記述を引用)
「MFCでEnterキーが反応してしまう」のやりとりを参考にしました。
ダイアログのデフォルト動作では、Enterによって終了してしまう。これを変えるには例えば以下のようにする。
BOOL CxxxDlg::PreTranslateMessage(MSG* pMsg) { if(pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) return TRUE; return CDialog::PreTranslateMessage(pMsg); }
MFCではメッセージマップをClassWizardで楽々記述できるのが大変らくちんなのだが、ユーザー定義メッセージを扱う際には手動でなくてはいけない。
調べようと思ってとりあえず以下の文書を見つけたが、詳しい検証はしていない。
switch文を使っていたら上記のようなエラー(C2361)に遭遇した。あるcaseのみで使う変数をそこで宣言した場合に、残りのcaseでもスコープが残っているけどいいのか、というニュアンスなのだそうだ。こんな時は変数を宣言・使用している箇所をブロックの中に閉じ込めてしまえばよい。
・・・という内容を http://frog.raindrop.jp/knowledge/archives/000223.html にて学びました。
右クリックでポップアップメニューを出す方法を見て学んだ。このページに記述されていることではあるが、念のためこちらにも書く。何か処理をする(ハンドラを書く)ためにはメニューIDとハンドラとの対応をとってやらないといけない。このあたりはClassWizardにやらせるとラクだが、メニューIDを直接ソースに書くとClassWizardはそれに気づいてくれない。そこで、(実際にはどこにも使われないけど)メニューリソースをひとつ用意し、そこで適当な メニューIDを登録しておけばよい。するとClassWizardはそれを認識してくれるので、同じメニューIDを用いれば右クリックメニューのハンドラが楽に実装できる。
#include<windows.h> const char lpClassName[]="SDKTemplate"; // window class LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) { switch (msg) { case WM_COMMAND: switch(LOWORD(wp)) { //case BUTTON: //break; } break; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd , msg , wp , lp); } int WINAPI WinMain(HINSTANCE hInst , HINSTANCE hPrevInstance , PSTR lpCmdLine , int nCmdShow ) { HWND hwnd; MSG msg; WNDCLASS winc; winc.style = CS_HREDRAW | CS_VREDRAW; winc.lpfnWndProc = WndProc; winc.cbClsExtra = 0; winc.cbWndExtra = 0; winc.hInstance = hInst; winc.hIcon = LoadIcon(NULL , IDI_APPLICATION); winc.hCursor = LoadCursor(NULL , IDC_ARROW); winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); winc.lpszMenuName = NULL; winc.lpszClassName = lpClassName; if (!RegisterClass(&winc)) return 0; hwnd = CreateWindow( lpClassName , // class name TEXT("My SDK") , // window name WS_OVERLAPPEDWINDOW | WS_VISIBLE , 100 , 100 , // horizontal, vertical position 200 , 200 , // width and height NULL , // parent window NULL , // menu hInst , // application instance NULL // window-creation data ); if (hwnd == NULL) return 0; while (GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg); return msg.wParam; }
以上を参考に作成しました。
#include<windows.h> #include"resource.h" INT_PTR CALLBACK DialogProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { switch(msg){ case WM_INITDIALOG: // initialize return TRUE; case WM_COMMAND: switch(LOWORD(wp)){ case IDOK: EndDialog(hDlg,LOWORD(wp)); return TRUE; case IDCANCEL: EndDialog(hDlg,LOWORD(wp)); return TRUE; } break; case WM_CLOSE: PostQuitMessage(0); break; } return FALSE; } int WINAPI WinMain(HINSTANCE hinst, HINSTANCE, PTSTR pCmdLine, int nCmdShow) { DialogBox(hinst, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc); return 0; }
こちらはメッセージクラッカーを使用するもの。
#include<windows.h> #include <windowsx.h> #include"resource.h" ///////////////////////////////////////////////////////////////////// // The macro (message cracker) is almost same as that of Jeffrey Richter's "CmnHdr.h". // Just the names of the variables are different from the original ones. #define hiHANDLE_DLGMSG(hwnd, message, fn) \ case(message): return (SetDlgMsgResult(hwnd, uMsg, \ HANDLE_##message((hwnd),(wp),(lp),(fn)))) ///////////////////////////////////////////////////////////////////// void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify){ switch(id){ case IDCANCEL: EndDialog(hwnd, id); break; case IDOK: // do something break; } } BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lp){ // initialization code return TRUE; } void Dlg_OnClose(HWND hwnd){ EndDialog(hwnd, 0); } INT_PTR CALLBACK DialogProc(HWND hwnd, UINT uMsg, WPARAM wp, LPARAM lp){ switch(uMsg){ hiHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); hiHANDLE_DLGMSG(hwnd, WM_CLOSE, Dlg_OnClose); hiHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); } return FALSE; } int WINAPI WinMain(HINSTANCE hinst, HINSTANCE, PTSTR pCmdLine, int nCmdShow) { DialogBox(hinst, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc); return 0; }
調べ中。
WindowsのScripting Hostも魅力的な機能