Windowsプログラミング †
ダイアログボックス †
ダイアログの閉じ方と対応するイベントハンドラ †
操作 | イベントハンドラ |
Enter | OnOK() |
Esc | OnCancel() |
Alt+F4 |
タブオーダの保存形式 †
リソースファイル(*.rc)内でコントロールが定義される順番が、そのままタブオーダとして解釈される。
他のコントロールと連携が必要な場合の例 †
異なるコントロールを連携させて使用する場合がある。連携させるには、タブオーダを連続させる必要がある。
- ラジオボタンのグループ化
グループ化したいラジオボタンのタブオーダを連続させ、先頭のラジオボタンのプロパディでグループをTRUEにする。
- スピンコントロールのバディコントロール
エディットボックスのタブオーダをスピンコントロールの直前の値にすることで、そのエディットボックスをスピンコントロールのバディコントロールとして設定することができる。
この場合、値の取得やコントロールの操作は全てCSpinButtonCtrl変数に対して行い、CEditは使用する必要がない。
コントロールへのドラッグ&ドロップの優先順位 †
複数のコントロールが重なっている場合、タブオーダの小さいコントロールが優先。
グループボックスコントロールを使用する際などは注意。
クラスウィザードに表示されていないメッセージ †
クラスウィザードに目的のメッセージが表示されていない場合、表示内容がフィルタリングされている可能性がある。
「詳細設定オプション」で「メッセージフィルタ」を変更することで、目的に合ったメッセージを表示できる。
例)ダイアログクラスで初期状態ではフィルタリングされているメッセージ。
- WM_DROPFILES
ファイルのドラッグ&ドロップ
- WM_ACTIVATE
ウィンドウのアクティブ/非アクティブ
コントロールのフォーカス移動 †
CWnd::SetFocus() ではなく、CDialog::GotoDlgCtrl() を用いる。
CWnd::SetFocus()を用いてもフォーカスは移動するが、画面描画上は移動していないように見えてしまうことがある。
1
2
3
| | m_BtCtrl.SetFocus(); GotoDlgCtrl(&m_BtCtrl);
|
ダイアログ表示直後に指定コントロールへフォーカス設定するには、OnInitDialog() で GotoDlgCtrl() を呼び出した後、OnInitDialog() の戻り値を FALSE にする必要がある。
リソースエディタ上でコントロールのフォントを指定した際の文字化け †
コード上でフォントの設定をすると直ることがある。
ツールバーを使用するには †
通常、ツールバーはSDIかMDIのアプリケーションにしか用意されないが、以下の手順でダイアログにも使用できる。
※ダイアログに配置するコントロールの位置は、ツールバーの範囲を考慮する必要がある。また、ドッキング可能にすることはできない。
<ツールバーの作成>
- ツールバーのリソースを用意する。
- ダイアログクラスのメンバ変数として以下を定義しておく。
- CXxxDlg::OnInitDialog()でツールバーを作成する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| | CRect rcClient;
CSize sizeToolBar;
if(!m_wndToolBar.Create(this)){
return FALSE;
}
if(!m_wndToolBar.LoadToolBar(IDR_TOOLBAR)){
return FALSE;
}
GetClientRect(rcClient);
sizeToolBar = m_wndToolBar.CalcFixedLayout(FALSE, TRUE);
m_wndToolBar.MoveWindow(0, 0, rcClient.Width(), sizeToolBar.cy);
|
<ツールバーのボタン押下時のイベントハンドラ定義>
クラスウィザードは対応していないため、自分で定義する必要がある。(以下、ボタンのIDを ID_XXXとする)
- ダイアログクラスのヘッダのメッセージマップ部分に関数のプロトタイプを作る。
1
2
3
4
5
6
7
8
| | virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnXxx(); DECLARE_MESSAGE_MAP()
|
- ダイアログクラスのソースに関数 OnXxx() を定義する。
- ダイアログクラスのソースのメッセージマップ部分に定義した関数を登録する。
1
2
3
4
5
6
7
| | BEGIN_MESSAGE_MAP(CMStepEditorDlg, CDialog)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_COMMAND(ID_XXX, OnXxx) END_MESSAGE_MAP()
|
イベントハンドラ活用 †
F1押下時のヘルプの無効化 †
アプリケーションクラスのメッセージ処理を修正することで、無効化できる。
1
2
3
4
5
6
7
| | BEGIN_MESSAGE_MAP(CXxxApp, CWinApp)
END_MESSAGE_MAP()
|
ダイアログベースアプリケーションの終了コード †
アプリケーションのCWinAppを継承したクラスのExitInstance()をオーバーライドし、戻り値を設定すればよい。
WM_INITDIALOGの戻り値 †
通常、ウィンドウプロシージャやダイアログプロシージャの戻り値は、メッセージを処理したか否かをBOOL型で返すが、WM_INITDIALOGだけは戻り値に特別な意味がある。
TRUE を返すと、タブオーダーが一番若いコントロールに自動的にフォーカスが当てられる。
FALSE を返すと、何も行われない。(自分でフォーカスを当てるコードを書く必要がある。)
ダイアログ生成時に明示的にあるコントロールにフォーカスを当てたい場合や、ダイアログを非表示で生成しておき後で表示するような場合は、FALSEを返す必要がある。(後者のケースは、ダイアログ生成時にフォーカスが奪われてしまうのを防ぐため)
CDialogBar派生クラスでOnInitDialog()を実装する方法 †
CDialogBarクラスにはOnInitDialog()が用意されていないため、自分でON_MESSAGE()でWM_INITDIALOGをハンドルする必要がある。
参考:http://support.microsoft.com/kb/185672/ja
MFCによりダイアログが自動的に中央に移動する †
MFCで作られたダイアログは、自動的に中央に移動する機能が組み込まれている。
通常、OnInitDialog()でMoveWindow()等を呼び出し、明示的に表示位置を移動している場合は中央に移動することはないが、ある条件を満たすと中央に移動させられてしまう。
例として、(0, 0)の位置に移動しようとしたときに現象が発生する。
以下の条件のいずれかを満たしている場合は中央移動は行われない(一部抜粋)
- 画面の表示位置を(0, 0)以外にしている
- ダイアログテンプレート(リソース)のスタイル設定に DS_CENTER, DS_CENTERMOUSE, DS_ABSALIGN のいずれかのスタイルが設定されている
- ダイアログテンプレート(リソース)のXY位置設定が(0, 0)ではない
詳細は関連するMFCの関数を参照。
- ::_AfxPostInitDialog()
- CDialog::CheckAutoCenter()
コントロール †
エディットボックス内の改行の注意点 †
- 複数行を許可していなくても、Ctrl+Enterで改行することができてしまう。
- 文字列取得の際、改行コードは"\r\n"として取得されるため、取得した文字列をテキストモードで開いたファイルに書き込む場合は注意。('\n'が"\r\n"に展開されるため、"\r\r\n"となってしまう)
コンボボックスのドロップダウンリストのサイズ変更 †
ダイアログエディタ上で、コンボボックスの右端の▼マークをクリックすると、ドロップダウンリストのサイズ変更状態になる。
リストビューコントロールのヘッダのソート記号 †
リストビューのヘッダに対して以下の定数を設定する。
HDF_SORTUP | 昇順(▲) |
HDF_SORTDOWN | 降順(▼) |
※この機能はXP以降の機能なので、VC6.0(1998年)時点では未対応。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| |
HDITEM hdItem; CHeaderCtrl* pHrCtrl;
::ZeroMemory(&hdItem, sizeof(HDITEM));
hdItem.mask = HDI_FORMAT;
pHrCtrl = m_LcTest.GetHeaderCtrl();
pHrCtrl->GetItem(lSubItem, &hdItem);
hdItem.fmt &= ~HDF_SORTDOWN; hdItem.fmt |= HDF_SORTUP;
pHrCtrl->SetItem(lSubItem, &hdItem);
|
ツリービューの全展開 †
ツリービューコントロールを全展開する関数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| | void ExpandAllTree(CTreeCtrl *pTreeCtrl, HTREEITEM hParent){
HTREEITEM hChild;
if(hParent == NULL){
if((hParent = pTreeCtrl->GetRootItem()) == NULL){
return;
}
}
if(pTreeCtrl->ItemHasChildren(hParent) == TRUE){
pTreeCtrl->Expand(hParent, TVE_EXPAND);
hChild = pTreeCtrl->GetChildItem(hParent);
while(hChild != NULL){
ExpandAllTree(pTreeCtrl, hChild);
hChild = pTreeCtrl->GetNextSiblingItem(hChild);
}
}
}
|
スピンコントロールの値変更イベント †
スピンコントロールの値が変更された場合のイベントハンドラは、バディされたエディットコントロールのEN_CHANGEイベントで行う。
しかし、スピンコントロールとエディットコントロールを自動でバディする設定の場合は、OnInitDialog()よりも前に自動的にバディされたタイミングでEN_CHANGEイベントが発生してしまう。すると、まだ用意されていないコントロールにアクセスされてしまい、実行時エラーになってしまうことがある。
対処策としては、自分でエディットコントロールにバディするコードを書くか、フラグを用意してOnInitDialog()コール前ならEN_CHANGEイベントハンドラで何も処理しない設計にしておく。
リッチエディットコントロール使用前の準備 †
リッチエディットコントロール(CRichEditCtrl)を使用する場合は、通常のエディットコントロール(CEdit)と違って予め準備をしておく必要がある。
- コントロール自体の初期化
リッチエディットコントロールを使用する前に、アプリケーション内で1度だけMFCのグローバル関数 AfxInitRichEdit() を呼び出す必要がある。
呼び出すタイミングは、最初にリッチエディットコントロールを使用するダイアログの表示コードの直前 (ダイアログベースアプリケーションなら、CXxxApp::InitInstance())でよい。
- 使用するイベントのマスク設定
リッチエディットコントロールは、デフォルトでイベントマスクが ENM_NONE (イベント通知なし) に設定されており、ダイアログにイベントが通知されてこない。
ダイアログのOnInitDialog()で、明示的に使用するイベントのマスク設定を行う必要がある。
1
2
3
4
| |
m_ReCtrl.SetEventMask(m_ReCtrl.GetEventMask() | ENM_CHANGE);
|
エディット/リッチエディットコントロールのワード区切り †
単語境界やワードラップ機能は、どのように文字列のワード区切りを判断しているかに依存する。
ワード区切りの方法を変更するには、以下のキーワードを調べる。(まだ試していない)
- EM_SETWORDBREAKPROC
- EditWordBreakProc
コントロールの「通知」の有無 †
コントロールのプロパティの「通知」の有無の違いによって、イベントハンドラがコールされる順番を検証。
(CWnd::Create()でコントロールを作成する場合は、「SS_NOTFY」フラグに該当する。)
- 通知OFFの場合
- 親ダイアログの PreTranslateMessage()
- 親ダイアログの OnLButtonDown()
- 通知ONの場合
- コントロールの PreTranslateMessage()
- 親ダイアログの PreTranslateMessage()
- コントロールの OnLButtonDown()
- 親ダイアログに追加したコントロールクリック時のイベントハンドラ OnXXXX()
ActiveXコントロールの登録状況の確認 †
ActiveXコントロールを使用したMFCアプリケーションを作成すると、デフォルトの動作ではActiveXコントロールが登録されていないと何の表示も行われずに終了してしまう。
それだと、なぜアプリケーションが起動しなかったのかが分からないため、この方法を用いてエラーメッセージを表示するとよい。
- チェック関数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| | BOOL IsControlRegister(CLSID clsid)
{
BOOL bRegistered = FALSE;
CString strClsid;
strClsid.Format("{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
clsid.Data1,
clsid.Data2,
clsid.Data3,
clsid.Data4[0], clsid.Data4[1],
clsid.Data4[2], clsid.Data4[3], clsid.Data4[4], clsid.Data4[5], clsid.Data4[6], clsid.Data4[7]
);
CString strKey;
strKey.Format("CLSID\\%s", strClsid);
HKEY hKey = NULL;
if(::RegOpenKeyEx(HKEY_CLASSES_ROOT, strKey, 0, KEY_READ, &hKey) == ERROR_SUCCESS){
bRegistered = TRUE;
RegCloseKey(hKey);
}
return bRegistered;
}
|
- 使用例
1
2
3
4
5
6
7
| |
if(!IsControlRegister(dlg.m_xxxx.GetClsid())){
::MessageBox(NULL, "XXXX ActiveX コントロールがインストールされていません", "エラー", MB_OK | MB_ICONERROR);
}
|
コントロールのサブクラス化 †
サブクラス化の方法 †
※サブクラス化とは、C++の「クラスの継承」のことではない。
既存のウィンドウプロシージャを、独自のウィンドウプロシージャに差し替えることを指す。
サブクラス化に用いた派生クラス内での初期処理 †
OnCreate()はコールされないため、初期処理を記述することはできない。
派生クラスでは、PreSubclassWindow()をオーバライドして、そこに初期処理を書く。
元のクラスが構築されて(OnCreate)、サブクラス化が行われるのであって、派生クラスが直接構築されるわけではない。
MFCを継承した自作クラスへのイベントハンドラの追加 †
MFCを継承している場合は、クラスウィザードに派生クラスを認識させておく必要がある。
(認識されているかどうかは、クラスウィザードの「クラス名」に表示されるかどうかで判断できる)
認識されていない場合は、以下の操作ができない。
- コントロールのメンバ変数追加時に、型の候補とする
- クラスビューからWindowsメッセージハンドラを追加する
プロジェクト内でクラスを作成した場合は自動的に認識されるが、外部から持ってきたコードを用いる場合は、
一度clwファイルを削除してクラスウィザードを作り直すときに、ファイルを含める必要がある。
※ちなみに、MFC継承クラスをさらに継承した場合は上記作業を行っても不可。仕様上は問題なくイベントハンドラを実装できるはずなので、手動で行うしかない?
ウィンドウ †
ウィンドウ操作関数一覧(一部) †
操作 | Win32API | MFC |
Set | Get | Set | Get |
ウィンドウ情報 | - | ::GetWindowInfo | - | CWnd::GetWindowInfo |
親ウィンドウ | - | ::GetParent | - | CWnd::GetParent |
関連ウィンドウ | - | ::GetWindow | - | CWnd::GetWindow |
スタイル | ::SetWindowLong | ::GetWindowLong | CWnd::ModifyStyle | CWnd::GetStyle |
拡張スタイル | CWnd::ModifyStyleEx | CWnd::GetExStyle |
インスタンスハンドル | - | - |
ウィンドウプロシージャ | - | - |
(特記) | 使用可否 | ::EnableWindow | ::IsWindowEnable | CWnd::EnableWindow | CWnd::IsWindowEnable |
表示状態 | ::ShowWindow | ::IsWindowVisible | CWnd::ShowWindow | CWnd::IsWindowVisible |
ウィンドウクラス登録 | ::RegisterClassEx | - | - | - |
ウィンドウクラス登録解除 | ::UnregisterClass | - | - | - |
コントロールID | - | ::GetDlgCtrlID | CWnd::SetDlgCtrlID | CWnd::GetDlgCtrlID |
ドラッグ&ドロップ許可 | ::DragAcceptFiles | - | CWnd::DragAcceptFiles | - |
アクティブ | ::SetActiveWindow | ::GetActiveWindow | CWnd::SetActiveWindow | static CWnd::GetActiveWindow |
位置 | スクリーン座標 | ::MoveWindow | ::GetWindowRect | CWnd::MoveWindow | CWnd::GetWindowRect |
クライアント座標 | - | ::GetClientRect | - | CWnd::GetClientRect |
位置データ | ::SetWindowPlacement | ::GetWindowPlacement | CWnd::SetWindowPlacement | CWnd::GetWindowPlacement |
Zレベル | ::SetForegroundWindow | ::GetForegroundWindow | CWnd::SetForegroundWindow | static CWnd::GetForegroundWindow |
::SetWindowPos | - | CWnd::SetWindowPos | - |
中央へ移動 | - | - | CWnd::CenterWindow | - |
キャプション | ::SetWindowText | ::GetWindowText | CWnd::SetWindowText | CWnd::GetWindowText |
透明度 | ::SetLayeredWindowAttributes | ::GetLayeredWindowAttributes | CWnd:: SetLayeredWindowAttributes | CWnd:: GetLayeredWindowAttributes |
フォント | - | - | CWnd::SetFont | CWnd::GetFont |
操作 | 閉じる | ::DestroyWindow | - | CWnd::DestroyWindow | - |
最大化 | ::ShowWindow | ::IsZoomed | CWnd::ShowWindow | CWnd::IsZoomed |
最小化 | ::ShowWindow ::CloseWindow | ::IsIconic | CWnd::ShowWindow CWnd::CloseWindow | CWnd::IsIconic |
メッセージ送信(同期) | ::SendMessage | - | CWnd::SendMessage | - |
メッセージ送信(非同期) | ::PostMessage | - | CWnd::PostMessage | - |
再描画 | ::UpdateWindow | - | CWnd::UpdateWindow | - |
ウィンドウの中央表示 †
タイトルバーの高さの取得 †
OSの設定(フォントやテーマ等)に依存する。
1
| | ::GetSystemMetrics(SM_CYCAPTION)
|
コンソールウィンドウの使用 †
::AllocConsole(), ::FreeConsole()を用いる。
コンソールへの入出力をするためには、標準入出力を割り当てるか専用の関数でハンドルを取得する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| | ::AllocConsole();
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
freopen("CONIN$", "r", stdin);
printf("test\n");
DWORD dwRet;
char *str = "test";
HANDLE hStdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);
::WriteConsole(hStdOut, str, strlen(str), &dwRet, NULL);
|
ウィンドウの描画更新の抑制 †
CWnd::LockWindowUpdate() と CWnd::UnlockWindowUpdate() で、処理を挟み込む。
1
2
3
4
5
| | LockWindowUpdate()
UnlockWindowUpdate()
|
大きなツリービューへのアイテムの追加など、時間がかかる処理は描画更新を抑制することで、画面のチラつきを防止できる。
小さいアイコン(16x16)の設定 †
大きいアイコン(32x32)と小さいアイコン(16x16)の両方を用意してあるのに、小さいアイコンがうまく適用されないことがある。
その場合は、OnInitDialog() 等のアイコン設定処理で、小さいアイコン設定の処理をコメントアウトするとよい。
1
2
| | SetIcon(m_hIcon, TRUE);
|
※正式な修正方法ではないかも。
メニューの項目の取得 †
取得の手順。
- メニューを持つウィンドウを識別
- メニューバーを取得
- 必要があればサブメニューを取得していき、目的の項目を持つサブメニューまで辿る
- 目的のメニュー項目の情報を取得(/設定)する
メニューの項目は、0から始まるインデックスで位置を指定する
サブメニュー内は、それぞれで0から始まるインデックスを持つ。
メニュー項目の取得は、MENUITEMINFO構造体を介して行う。
MENUITEMINFO::cbSize に構造体のサイズを設定し、必要なメンバ変数に対応したフラグを MENUITEMINFO::fMask に設定する。
- Win32API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| |
HMENU hMenu; HMENU hSubMenu; MENUITEMINFO menuItemInfo;
hMenu = ::GetMenu(hWnd);
hSubMenu = ::GetSubMenu(hMenu, 0);
::ZeroMemory(&menuItemInfo, sizeof(MENUITEMINFO));
menuItemInfo.cbSize = sizeof(MENUITEMINFO); menuItemInfo.fMask = MIIM_ID | MIIM_STATE;
::GetMenuItemInfo(
hSubMenu, 2, TRUE, &menuItemInfo);
|
- MFC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| |
CMenu *pSubMenu; MENUITEMINFO menuItemInfo;
pSubMenu = pWnd->GetMenu()->GetSubMenu(0);
::ZeroMemory(&menuItemInfo, sizeof(MENUITEMINFO));
menuItemInfo.cbSize = sizeof(MENUITEMINFO); menuItemInfo.fMask = MIIM_ID | MIIM_STATE;
pSubMenu->GetMenuItemInfo(
2, &menuItemInfo, TRUE);
|
メニューバーの描画更新 †
メニュー項目に変更を行っても自動的に描画更新されないため、明示的に描画更新する必要がある。
- Win32API
::DrawMenuBar()
- MFC
CWnd::DrawMenuBar()
指定ウィンドウの描画更新 †
ウィンドウの描画更新を促すには、WM_PAINTを発行する必要がある。ただし、ウィンドウに直接WM_PAINTを送るのではなく、下記の手順を踏む必要がある。
1
2
3
| | pWnd->InvalidateRect(NULL); pWnd->UpdateWindow();
|
ウィンドウの一部だけを再描画させる場合は、CWnd::InvalidateRect()の第一引数にNULLではなく、RECT*型の領域を渡す。
メッセージボックスの簡易的な最前面化 †
最前面ウィンドウを親に持つメッセージボックスを作成すればよい。
親ウィンドウは非表示にしておけば、メッセージボックスのみ表示される。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| | CWnd *pDmyWnd;
TCHAR szClassName[256];
::GetClassName(this->GetSafeHwnd(), szClassName, 256);
pDmyWnd = new CWnd();
pDmyWnd->CreateEx(WS_EX_TOPMOST, szClassName, NULL, 0, CRect(0, 0, 10, 10), this, 0);
::MessageBox(pDmyWnd->GetSafeHwnd(), "test", "test", MB_OK | MB_ICONINFORMATION);
pDmyWnd->DestroyWindow();
delete pDmyWnd;
|
ウィンドウの再表示 †
ウィンドウの再表示を行う際、::ShowWindow()の第2引数に「SW_SHOWNORMAL」や「SW_RESTORE」を渡すだけでは、ウィンドウの状態(最小化、最大化)によって動作が異なってしまう。(ヘルプを読むと「SW_SHOWNORMAL」でよさそうだが。。)
以下のように場合分けをすることで解決できる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| | int nShowCmd;
if(::IsIconic(hWnd)){
nShowCmd = SW_RESTORE;
} else if(::IsZoomed(hWnd)){
nShowCmd = SW_SHOWMAXIMIZED;
} else {
nShowCmd = SW_SHOW;
}
::ShowWindow(hWnd, nShowCmd);
|
タイトルバーを常にアクティブ色にする †
再描画のたびにウィンドウメッセージでアクティブ通知を行う。
1
2
3
4
5
6
7
| | void CXxxDlg::OnPaint()
{
:
PostMessage(WM_NCACTIVATE, TRUE);
:
}
|
メッセージ専用ウィンドウ †
ウィンドウメッセージの処理を行いたいがウィンドウの実体は必要無いというときは、メッセージ専用ウィンドウを利用することができる。
(ウィンドウ列挙等の対象からも外れる)
使用する場合は、予め下記の定数を設定しておく必要がある。
#define WINVER 0x0500 // 0x0500以上ならOK
- Win32API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| | HWND hMsgWnd;
static const char* szWndClassName = "DUMMY_CLASS";
WNDCLASSEX wcx;
ZeroMemory(&wcx, sizeof(WNDCLASSEX));
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.lpfnWndProc = DefWindowProc;
wcx.hInstance = hInst;
wcx.lpszClassName = szWndClassName;
BOOL bRegist = FALSE;
WNDCLASS wcx_dmy;
ZeroMemory(&wcx_dmy, sizeof(WNDCLASSEX));
if(!::GetClassInfo(wcx.hInstance, wcx.lpszClassName, &wcx_dmy) && ::RegisterClassEx(&wcx)){
::CreateWindowEx(0, szWndClassName, "", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
}
|
- MFC
1
2
3
4
5
| |
pWnd = new CWnd();
CString strWndClassName = ::AfxRegisterWndClass(NULL);
pWnd->CreateEx(0, strWndClassName, "", 0, 0, 0, 0, 0, HWND_MESSAGE, 0);
|
ウィンドウメッセージ †
自作メッセージ †
自作のメッセージの定義は、以下の2通り。
- WM_USER + n
プライベートなウィンドウに送る自作メッセージの定義。コモンダイアログなどは既に使用している場合があるので注意。
- WM_APP + n
自作アプリケーションから任意のウィンドウに送る自作メッセージの定義。
メッセージフック処理 †
- Win32API
自分でウィンドウプロシージャを定義する。
1
2
3
4
5
6
7
8
9
10
11
12
| | LRESULT CALLBACK WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
switch(uMsg) {
case WM_DESTROY :
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
|
アクティブウィンドウに対してキーメッセージを送信 †
1
2
3
| | keybd_event(VK_xx, 0, KEYEVENTF_EXTENDEDKEY, 0); keybd_event(VK_xx, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
|
「ALT+F」などのメッセージも、キー押下と解放のタイミングと組み合わせで実現可能。「PrntScrn」や「NumLock」も可能。
Windowsアプリケーションが終了する仕組み †
- ユーザが終了処理を行う(×を押すなど)
↓ WM_CLOSE
- ::DestroyWindow()
↓ WM_DESTROY
- ::PostQuitMessage()
↓ WM_QUIT
- WinMain()やスレッド内で用いられている::GetMessage() が FALSE を返すことにより、メッセージループを抜け、処理が終了する。
メッセージによるコマンドの実行 †
ウィンドウにコマンドIDを付与した WM_COMMAND メッセージを送ることで実行できる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| |
::PostMessage(hWnd, WM_COMMAND, (WPARAM)MAKELONG((WORD)nID, (WORD)0x0000), 0);
::PostMessage(hWnd, WM_COMMAND, (WPARAM)MAKELONG((WORD)nID, (WORD)0x0001), 0);
|
アプリ間で使用する任意メッセージの受信 †
- グローバル変数として、メッセージIDを保持しておく
1
2
3
| | static const UINT uiWmTest = ::RegisterWindowMessage("TEST");
|
- ウィンドウクラスのヘッダに以下のようにイベントハンドラを追加
1
2
3
4
5
6
| | :
afx_msg LRESULT OnTest(WPARAM=0, LPARAM=0); DECLARE_MESSAGE_MAP()
|
- ウィンドウクラスのソースに以下のようにメッセージマップとイベントハンドラの実装を追加
1
2
3
4
5
6
| | BEGIN_MESSAGE_MAP(CXxx, CXxxParent)
:
ON_REGISTERED_MESSAGE(uiWmTest, OnTest) END_MESSAGE_MAP()
|
1
2
3
4
5
6
| | LRESULT CXxx::OnTest(WPARAM wParam, LPARAM lParam){
MessageBox("TEST");
return 0;
}
|
ウィンドウサイズの制限(WM_GETMINMAXINFO) †
マウスドラッグやWin7のスナップ機能などによるウィンドウサイズの変更時に、ウィンドウサイズを制限する場合は、WM_GETMINMAXINFO をハンドルする。
コモンダイアログ †
ファイル選択(保存)ダイアログとカレントディレクトリ †
- ファイル選択ダイアログを使用すると、カレントディレクトリが変更される。(OFN_NOCHANGEDIRフラグを使用すれば防げる)
- OPENFILENAME::lpstrInitialDir (初期ディレクトリ) に無効な文字列を指定したときは、カレントディレクトリが使用される。
ファイル保存ダイアログの拡張子自動付与機能 †
- OPENFILENAME::lpstrDefExt (デフォルト拡張子) よりも、OPENFILENAME::lpstrFilter (フィルタ)に指定した文字列が優先される。
- OPENFILENAME::lpstrDefExt (デフォルト拡張子) にNULLを指定すると、拡張子自動付加は行われない。
※デフォルト拡張子は「.」を含まない。
ファイル選択(保存)ダイアログ使用時にアプリケーションが落ちる現象 †
ファイル選択ダイアログを使用していると、呼び出し元のアプリケーションごと強制終了してしまう現象が発生している。PCによって起きたり起きなかったりする。
- 発生条件
AdobeReader7.0がインストールされていること。
- 現象
現象は、あるアプリケーションでファイル選択ダイアログを立ち上げ(メモ帳の開くなど)、何かのファイル(フォルダでは起きない)にカーソルを合わせ、ツールチップを表示させる。
一度ファイル選択ダイアログを閉じて再度同じ操作をすると、ツールチップが表示される瞬間に強制終了される。
- 対処法
根本的な解決策は分かっていない。AdobeReader7.0が原因という話もあるが、アンインストールしても改善しなかった。
自作のアプリケーションであれば、ファイル選択ダイアログを起動する前に、CoInitialize()を呼ぶと落ちなくなる。ただし、CoInitialize() を使用するのであれば、対応する CoUninitialize() を忘れないこと。
既存のアプリケーション(メモ帳など)では、フォルダオプションでツールチップ表示機能をOFFにするしかない。
- 原因
本当に原因なのかは未確認だが、ネットに pdfshell.dll が原因ではないかという記述を見つけた。下記の何れかに存在する。
→ (AdobeReaderインストールフォルダ)\ActiveX
→ C:\Program Files\Common Files\Adobe\Acrobat\ActiveX
文字列処理 †
マルチバイト文字とワイド文字のサンク機能 †
Wwindowsプログラミングをする上で、文字列を「マルチバイト」と「ワイド文字」のどちらで扱うかに気をつける必要がある。
Win32APIやMFCには、これらの文字セットの切り替えを容易にした仕組み(thunk)が用意されており、これに則ってプログラミングすることで、どちらの文字セットを使用するかをコンパイルスイッチで切り替えることができる。
- 全般
| サンク | マルチバイト文字(MBCS) | ワイド文字(Unicode) |
文字 文字列 | _T('~') _T("~") | '~' "~" | L'~' L"~" |
変数型 | TCHAR | CHAR char | WCHAR wchar_t |
LPTSTR TCHAR* | LPSTR char* | LPWSTR wchar_t* |
LPCTSTR const TCHAR* | LPCSTR const char* | LPCWSTR const wchar_t* |
Win32API 関数(例) | MessageBox() | MessageBoxA() | MessageBoxW() |
Win32API 構造体(例) | OPENFILENAME | OPENFILENAMEA | OPENFILENAMEW |
MFC クラス(例) | CString/ CStringT(※VC6.0では未対応) | CStringA | CStringW |
- C標準関数(一部)
分類 | サンク | マルチバイト文字(MBCS) | ワイド文字(Unicode) |
文字列 | _tcscat | strcat | wcscat |
_tcschr | strchr | wcschr |
_tcscmp | strcmp | wcscmp |
_tcscpy | strcpy | wcscpy |
_tcscspn | strcspn | wcscspn |
_tcslen | strlen | wcslen |
_tcsncat | strncat | wcsncat |
_tcsncmp | strncmp | wcsncmp |
_tcsncpy | strncpy | wcsncpy |
_tcspbrk | strpbrk | wcspbrk |
_tcsrchr | strrchr | wcsrchr |
_tcsspn | strspn | wcsspn |
_tcsstr | strstr | wcsstr |
_tcstod | strtod | wcstod |
_tcstok | strtok | wcstok |
_tcstol | strtol | wcstol |
_tcstoul | strtoul | wcstoul |
_tcsicmp | _stricmp | _wcsicmp |
_tcsnicmp | _strnicmp | _wcsnicmp |
_tcslwr | _strlwr | _wcslwr |
_tcsupr | _strupr | _wcsupr |
入出力 | _tprintf | printf | wprintf |
_ftprintf | fprintf | fwprintf |
_stprintf | sprintf | swprintf |
_vtprintf | vprintf | vwprintf |
_vftprintf | vfprintf | vfwprintf |
_vstprintf | vsprintf | vswprintf |
_tscanf | scanf | wscanf |
_ftscanf | fscanf | fwscanf |
_stscanf | sscanf | swscanf |
_gettchar | getchar | getwchar |
_fgettc | fgetc | fgetwc |
_getts | gets | _getws |
_fgetts | fgets | fgetws |
_puttchar | putchar | putwchar |
_fputtc | fputc | fputwc |
_putts | puts | _putws |
_fputts | fputs | fputws |
変換 | _ttoi | atoi | _wtoi |
_ttol | atol | _wtol |
_ttof | atof | _wtof |
ファイル | _tmkdir | _mkdir | _wmkdir |
_trmdir | _rmdir | _wrmdir |
_tremove | remove | _wremove |
_trename | rename | _wrename |
_tfopen | fopen | _wfopen |
_tpopen | _popen | _wpopen |
_tfullpath | _fullpath | _wfullpath |
_tmakepath | _makepath | _wmakepath |
_tsplitpath | _splitpath | _wsplitpath |
文字 | _istalnum | isalnum | iswalnum |
_istascii | isascii | iswascii |
_istcntrl | iscntrl | iswcntrl |
_istalpha | isalpha | iswalpha |
_istdigit | isdigit | iswdigit |
_istgraph | isgraph | iswgraph |
_istlower | islower | iswlower |
_istprint | isprint | iswprint |
_istpunct | ispunct | iswpunct |
_istspace | isspace | iswspace |
_istupper | isupper | iswupper |
_totupper | toupper | towupper |
_totlower | tolower | towlower |
その他 | _tsystem | system | _wsystem |
CStringオブジェクトの変換 †
constな文字列型(LPCTSTR)への変換しか許されていない。
それ以外は、変換関数が用意されていないため。
文字列のコンバート (VBのStrConv()相当) †
::LCMapString()で、ひらがな/カタカナ、半角/全角、大文字/小文字の相互変換が可能。
ファイルパス長関係のマクロの意味 †
これらは、「バイト数」ではなく「文字数」を定義したもの。
マルチバイト文字を扱う場合は注意が必要。
マクロ名 | 値 | 意味 |
_MAX_PATH | 260 | フルパス |
_MAX_DRIVE | 3 | ドライブ |
_MAX_DIR | 256 | ディレクトリ |
_MAX_FNAME | 256 | ファイル名(拡張子除く) |
_MAX_EXT | 256 | 拡張子(「.」含む) |
↑ <stdlib.h> に定義されている
各種文字列型の相互変換 †
| 変換元 src |
char [] | (STL) string | CString |
変換先 dst | char [] | | strcpy(dst, src.c_str()); | strcpy(dst, src); |
(STL) string | dst = src; dst = string(src); string dst(src); | | dst = src.c_str(); dst = CString(src.c_str()); CString dst(src.c_str()); |
CString | dst = src; dst = CString(src); CString dst(src); | dst = src.c_str(); dst = CString(src.c_str()); CString dst(src.c_str()); | |
BSTR | dst = ::SysAllocString(src); : ::SysFreeString(dst); | dst = ::SysAllocString(src.c_str()); : ::SysFreeString(dst); | dst = src.AllocSysString(); : ::SysFreeString(dst); |
CComBSTR | dst = src; dst = CComBSTR(src); CComBSTR dst(src); | dst = src.c_str(); dst = CComBSTR(src.c_str()); CComBSTR dst(src.c_str()); | dst.Attach(src.AllocSysString()); |
| 変換元 src |
BSTR | CComBSTR |
変換先 dst | char [] | ::WideCharToMultiByte(CP_ACP, 0, (OLECHAR*)src, -1, dst, sizeof(dst)-1, NULL, NULL); | 直接変換する方法はない |
(STL) string | 直接変換する方法はない | 直接変換する方法はない |
CString | dst = src; dst = CString(src); CString dst(src); | dst = (BSTR)src; dst = CString((BSTR)src); CString dst((BSTR)src); |
BSTR | | dst = src.Copy(); : ::SysFreeString(dst); |
CComBSTR | dst = src; dst = CComBSTR(src); CComBSTR dst(src); | |
マルチバイト文字の判定 †
_ismbblead(),_ismbbtrail() を使用すると、「その文字がマルチバイト文字の第1バイト/第2バイトに相当するか」をチェック可能。
しかし、マルチバイト文字の第1バイトと第2バイトの取り得る数値の範囲は重複しているため、この関数では「そのバイトが第1バイトと第2バイトのどちらか」という判定には使えない。
例)「り」(0x82E8) の第2バイトである「0xE8」は、第1バイト/第2バイトどちらでも使用される値である。
その場合は、_ismbslead(), _ismbstrail() を使用し、文脈から判定させることで解決できる。
ただし、指定した文字列全体のチェックを行うため、パフォーマンスは低いことに注意。
ファイル処理 †
ファイル(ドライブ、ディレクトリ)の存在確認 †
ファイルのプロパティダイアログの表示 †
ファイルを右クリックして「プロパティ」を選択したときのダイアログのこと。::ShellExecuteEx() を使用する。
フォルダの属性変更 †
なぜかできない。CFile::SetStatus() だと例外が発生し、::SetFileAttributes() だと何も起こらず変更もされない。
デバイスコンテキスト/画像処理 †
用途別のデバイスコンテキスト †
MFCクラス | 用途 | 備考 |
CPaintDC | 更新領域のみに対して描画する場合に使用する。 | ・コンストラクタでBeginPaint()、デストラクタでEndPaint()が呼び出されているため、クリッピングリージョンが自動的に設定される。(WS_CLIPSIBLINGSスタイルで兄弟ウィンドウをクリップにしている場合など、自動的に適用される) ・OnPaint()の処理内でしか使用できない。OnDraw()の引数で渡されるpDcも元はこれ。(OnDraw()はOnPaint()から呼び出されている) |
CClientDC | ウィンドウのクライアント領域全体に対して描画する場合に使用する。 | ・コンストラクタでGetDC()、デストラクタでReleaseDC()が呼び出される。 ・領域のクリッピング等は自前で行う必要がある。 |
CWindowDC | ウィンドウの非クライアント領域(キャプションやメニューなど)も含めた領域全体に対して描画する場合に使用する。 | ・コンストラクタでGetWindowDC()、デストラクタでReleaseDC()が呼び出される。 ・領域のクリッピング等は自前で行う必要がある。 |
ダブルバッファリング †
1つ1つの描画処理を行うたびに画面が更新されてしまうことによってチラつきが発生してしまう。そこで、描画処理は裏画面に対して行っておき、描画処理が終わった時に画面更新を1度だけ行うことでチラつきを防ぐ手法がダブルバッファリング。以下はダブルバッファリングの実装例。
- ウィンドウ(ダイアログ)クラスのメンバ変数として以下を定義しておく。
1
2
3
4
5
6
| |
CDC m_memDC; CBitmap m_memBmp; CBitmap *m_pOldBmp; CRect m_PcRect;
|
- アプリケーションの初期処理で、デバイスコンテキストと裏画面の設定を行う。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| | CDC *pDC;
m_PcCtrl.GetClientRect(&m_PcRect);
pDC = m_PcCtrl.GetDC();
m_memDC.CreateCompatibleDC(pDC);
m_memBmp.CreateCompatibleBitmap(pDC, m_PcRect.Width(), m_PcRect.Height());
m_pOldBmp = m_memDC.SelectObject(&m_memBmp);
m_PcCtrl.ReleaseDC(pDC);
|
- アプリケーションの終了処理で、デバイスコンテキストと裏画面の解放を行う。
1
2
3
4
5
6
| | m_memDC.SelectObject(m_pOldBmp);
m_memBmp.DeleteObject();
m_memDC.DeleteDC();
|
- CWnd::OnPaint() 等の再描画処理で、裏画面からの転送処理を行う。
1
2
3
4
5
6
| | CPaintDC dc(this);
dc.BitBlt(0, 0, m_PcRect.Width(), m_PcRect.Height(), &m_memDC, 0, 0, SRCCOPY);
|
- 実際の描画処理。
描画は裏画面に対して行い、最後にウィンドウの再描画を促す。
1
2
3
4
5
| |
InvalidateRect(NULL, FALSE); UpdateWindow();
|
ウィンドウキャプチャ(ハードコピー) †
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| |
CDC capDC; CBitmap capBmp; CBitmap *pBmpOld; CRect wndRect;
CWindowDC wndDC(pWnd);
pWnd->GetWindowRect(&wndRect);
capDC.CreateCompatibleDC(NULL);
capBmp.CreateBitmap(wndRect.Width(), wndRect.Height(), wndDC.GetDeviceCaps(PLANES), wndDC.GetDeviceCaps(BITSPIXEL), NULL);
pBmpOld = capDC.SelectObject(&capBmp);
capDC.SetMapMode(MM_TEXT);
capDC.BitBlt(0, 0, wndRect.Width(), wndRect.Height(), &wndDC, 0, 0, SRCCOPY);
capDC.SelectObject(pBmpOld);
capDC.DeleteDC();
capBmp.DeleteObject();
|
ビットマップのコピー †
メモリ上のビットマップを複製する方法。
- MFC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| | CBitmap bmpDst; BITMAP bmpData; BYTE *bmpBits;
if(bmpSrc.GetBitmap(&bmpData)){
bmpBits = new BYTE[bmpData.bmWidthBytes * bmpData.bmHeight];
if(bmpSrc.GetBitmapBits(bmpData.bmWidthBytes * bmpData.bmHeight, bmpBits) != 0){
bmpDst.CreateBitmapIndirect(&bmpData);
bmpDst.SetBitmapBits(bmpData.bmWidthBytes * bmpData.bmHeight, bmpBits);
}
delete[] bmpBits;
}
|
- Win32API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| | HBITMAP hBmpDst; BITMAP bmpData; BYTE *bmpBits;
if(::GetObject(hBmpSrc, sizeof(BITMAP), &bmpData) != 0){
bmpBits = new BYTE[bmpData.bmWidthBytes * bmpData.bmHeight];
if(::GetBitmapBits(hBmpSrc, bmpData.bmWidthBytes * bmpData.bmHeight, bmpBits) != 0){
hBmpDst = CreateBitmapIndirect(&bmpData);
::SetBitmapBits(hBmpDst, bmpData.bmWidthBytes * bmpData.bmHeight, bmpBits); }
delete[] bmpBits;
}
|
※SetBitmapBits()の替わりにSetDIBits()を使う必要があるらしい。
DCの内容をビットマップファイルとして保存 †
ビットマップファイルは次のような構造になっている。
- BITMAPFILEHEADER
- BITMAPINFOHEADER
- (カラーパレット)
- ピクセルデータ
上記の順番でバイナリデータをファイルに保存していくことで、ビットマップファイルを作成できる。
以下に、24bitフルカラーでのビットマップファイルの保存処理の例を示す。
※フルカラーの場合、カラーパレットは格納しない。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
| |
DWORD dwFileAttr; HANDLE hFile; DWORD dwWritten; DWORD dwInfoHdrSize; DWORD dwScanDataSize; BITMAPFILEHEADER bmpFileHeader; BITMAPINFOHEADER *pBmpInfoHdr; BYTE *pHeaderBuffer; BYTE *pScanDataBuffer; BITMAP bmp; BOOL bRet;
GetObject(hBmp, sizeof(BITMAP), &bmp);
dwInfoHdrSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 0; pHeaderBuffer = new BYTE[dwInfoHdrSize];
memset(pHeaderBuffer, 0, dwInfoHdrSize);
pBmpInfoHdr = (BITMAPINFOHEADER*)pHeaderBuffer;
pBmpInfoHdr->biSize = sizeof(BITMAPINFOHEADER); pBmpInfoHdr->biBitCount = 24; pBmpInfoHdr->biWidth = bmp.bmWidth; pBmpInfoHdr->biHeight = bmp.bmHeight; pBmpInfoHdr->biPlanes = 1; pBmpInfoHdr->biCompression = BI_RGB; pBmpInfoHdr->biSizeImage = 0; pBmpInfoHdr->biXPelsPerMeter = 0; pBmpInfoHdr->biYPelsPerMeter = 0; pBmpInfoHdr->biClrUsed = 0; pBmpInfoHdr->biClrImportant = 0;
GetDIBits(hDC, hBmp, 0, bmp.bmHeight, NULL, (LPBITMAPINFO)pBmpInfoHdr, DIB_RGB_COLORS);
dwScanDataSize = pBmpInfoHdr->biSizeImage;
pScanDataBuffer = new BYTE[dwScanDataSize];
GetDIBits(hDC, hBmp, 0, bmp.bmHeight, pScanDataBuffer, (LPBITMAPINFO)pBmpInfoHdr, DIB_RGB_COLORS);
bmpFileHeader.bfType = 0x4d42; bmpFileHeader.bfReserved1 = 0; bmpFileHeader.bfReserved2 = 0; bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + dwInfoHdrSize; bmpFileHeader.bfSize = dwScanDataSize + bmpFileHeader.bfOffBits;
hFile = CreateFile(szFilePath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile != INVALID_HANDLE_VALUE){
WriteFile(hFile, &bmpFileHeader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL); WriteFile(hFile, pHeaderBuffer, dwInfoHdrSize, &dwWritten, NULL); WriteFile(hFile, pScanDataBuffer, dwScanDataSize, &dwWritten, NULL); CloseHandle(hFile);
}
delete[] pScanDataBuffer;
delete[] pHeaderBuffer;
|
描画時の兄弟ウィンドウのクリッピング †
WS_CLIPSIBLINGSスタイルを指定すると、OnPaint()やOnDraw()に用意される標準のデバイスコンテキストは兄弟ウィンドウがクリップ(兄弟ウィンドウの上に描画処理を行わないようにリージョンを除外すること)された状態となる。理由は、BeginPaint() によって作られたDCのため。(WS_CLIPCHILDRENスタイルの場合も同様に、子ウィンドウがクリップされた状態になると思われる。)
しかし、CClientDCでクライアント領域全体のデバイスコンテキストを取得してそれに対して描画を行うような場合は、常に領域全体が描画対象になっているため、必要に応じて自前でクリップ処理を作成するしかない。
自作コントロールの描画処理を行う場合などは注意が必要。
以下、兄弟ウィンドウをクリップする処理を自前で作成する場合の例。
(※コントロール描画用のデバイスコンテキストとして CClientDC型 のメンバ変数 m_pDC が用意されているものとする。)
- コントロールの描画処理
1
2
3
4
5
6
7
8
9
10
11
12
13
| | if((this->GetStyle() & WS_CLIPSIBLINGS) != 0){
::EnumChildWindows(GetParent()->GetSafeHwnd(), ClipSiblingsProc, (LPARAM)this);
}
if((this->GetStyle() & WS_CLIPSIBLINGS) != 0){
m_pDC->SelectClipRgn(NULL);
}
|
- 兄弟ウィンドウのクリッピング処理(static CALLBACK にしておくこと)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| | BOOL CXxxCtrl::ClipSiblingsProc(HWND hwnd, LPARAM lParam)
{
CXxxCtrl *pThisWnd = (CXxxCtrl *)lParam;
CWnd *pWnd = CWnd::FromHandle(hwnd);
if( pThisWnd->GetParent() == pWnd->GetParent() && pThisWnd != pWnd){
CRect rect;
pWnd->GetWindowRect(&rect);
pThisWnd->ScreenToClient(&rect);
pThisWnd->m_pDC->ExcludeClipRect(&rect);
}
return TRUE;
}
|
フォント †
フォントを変更する際の注意点 †
フォントの設定にCFontクラスのオブジェクトを用いる場合、メンバ変数にしておく必要がある。
ローカル変数だと、スコープを抜けたときに変数が自動的に破棄され、デストラクタの働きによってフォントの内容が削除されてしまうため、正しくフォントが反映されない。
単位系 †
コンピュータの世界でよく用いられる長さに関する単位。
単位 | 説明 | 換算 |
pixel | 文字や画像を表示する際の最小要素。画素。 ディスプレイ上ではdotと同義。 | - |
Twip | もとはVBでフォーム設計する際に用いられる長さの単位。 リッチエディットコントロール上ではこの単位がもちいられる。(要確認) mmやinchよりも細かく指定できる。 | 1Twip = 1/20point = 1/1440inch |
point | フォントの大きさを表す | 1point = 1/72inch |
inch | 一般的に使われる長さ。コンピュータの世界ではmmより使われる頻度が高い? | - |
dpi | dot per inch。1inchあたりのdot数。 ディスプレイ上では1inchあたりのpixel数と同義。 PCの解像度は通常96dpi。 | - |
セル高さと文字高さ †
アクセント記号を含めた高さをセル高さ(Cell Height)、含めない高さを文字高さ(Char Height)と呼ぶ。
(MSのサイトから抜粋 http://support.microsoft.com/kb/32667/ja)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
O O
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/\
/ \
/ \
_ _ _ _ _ _ _ _ _ /______\ _ _ _ ___ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/ \ / \|
/ \ | |
_ _ _ _ _ _ _ _ /_ _ _ _ _ _ \ _ \___/| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\___/_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|- External Leading
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _
|
O O |- Internal Leading
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _
| /\
| / \
| / \
| /______\ _ _ _ ___ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Char Height -| / \ / \|
(Em) | / \ | |
| /_ _ _ _ _ _ \ _ \___/| _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| |
| |
_ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _\___/_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| |
| O O |
| |
| /\ |
| / \ |
| / \ |- Ascent
Cell Height -| /______\ ___ |
| / \ / \| |
| / \ | | |
| /_ _ _ _ _ _ \ _ \___/| _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _
| | |
| | |- Descent
_ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _\___/_ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _
LOGFONT構造体によるフォントサイズの指定 †
LOGFONT:lfHeightでフォントの高さ、LOGFONT:lfWidthでフォントの幅を指定する。
- lfHeight
0を指定すると、そのフォントのデフォルトサイズを指定したことになる。
正数を指定すると、セル高さを指定したことになる。
負数を指定すると、その絶対値を文字高さとして指定したことになる。
実際には、そのフォントで使用できるサイズのうち、指定された値を超えない最大のサイズが使用される。
指定する値の単位は論理単位(マッピングモード依存)。通常はMM_TEXTマッピングモードなので、論理単位=ピクセル数。
フォントのpoint数を用いて指定する場合は、以下の変換式を用いる。
1
2
| | lfHeight = -::MulDiv(lPoint, GetDeviceCaps(hDC, LOGPIXELSY), 72);
|
- lfWidth
0を指定すると、フォントの種類と lfHeight から適切な値が自動決定される。
必ずしも lfHeight/2 となるわけではないことに注意。
lfWidth に 0 を指定することで実際に幅がいくつになるかは、CDC::GetTextMetrics() を使用して TEXTMETRIC::tmAveCharWidth を確認してみるのが良い。等幅フォントの場合は、全ての文字がこの幅に設定されることになる。
DLL †
リソースのみを含むDLL †
ソースコード(エントリポイント)を持たないDLLを作るには、プロジェクト設定の「リンク」タブの「一般」カテゴリで、「プロジェクトオプション」に "/NOENTRY" を追加する。
DLL内での実行ファイルのパス取得 †
WIN32APIの ::GetModuleFileName() を使用することで、実行ファイルのパスを取得できる。
DLL内部のコードから ::GetModuleFileName() を使用した場合、第1引数にDLLのインスタンスハンドルを渡せばDLLのパスが取得できるが、NULLを渡した場合は呼び出し元のEXEのパスの取得となる。
DLLからモードレスダイアログを表示 †
DLLからモードレスダイアログを表示する場合、タブストップやキー入力等の処理がデフォルトで行われない。→参考
モーダルダイアログのようにタブストップ等を処理させるには、メッセージをフックする必要がある。
- グローバルな変数に、対象となるダイアログクラスオブジェクトのアドレスを渡しておく。
- ダイアログクラスのヘッダファイルで、フックハンドルとフックプロシージャを定義する。
1
2
3
4
5
6
7
| | public:
HHOOK m_hHook;
private:
static long CALLBACK GetMsgProc(int, WPARAM, LPARAM);
|
- CXxxDlg::OnInitDialog() でフックプロシージャを登録する。
1
2
| | m_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)CXxxDlg::GetMsgProc, NULL, ::GetCurrentThreadId());
|
- CXxxDlg::OnDestroy() でフックプロシージャを破棄する。
1
2
| | ::UnhookWindowsHookEx(m_hHook);
|
- CXxxDlgクラスのソースで、フックプロシージャを実装する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| | long CXxxDlg::GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
LPMSG lpMsg;
lpMsg = (LPMSG)lParam;
if(nCode >= 0 && wParam == PM_REMOVE){
if((lpMsg->message >= WM_KEYFIRST && lpMsg->message <= WM_KEYLAST)){
if(g_pDlg->IsDialogMessage(lpMsg)){
lpMsg->message = WM_NULL;
lpMsg->lParam = 0;
lpMsg->wParam = 0;
}
}
}
return ::CallNextHookEx(g_pDlg->m_hHook, nCode, wParam, lParam);
}
|
DLL内でMFCリソースの使用 †
DLL内でダイアログを作成して表示するコードを用意して使用する場合、exportする関数の先頭に以下のコードを記述する必要がある。(COMのDLLも同様)
1
| | AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
EXEからexportした関数が呼び出された状態では、リソースハンドル等はEXE用のものを使用する状態になっているため、DLL内では正しい処理が行われない。
上記のコードによって、モジュール状態がDLL用のものに切り替わるため、正しい処理が行える。また、スコープを抜けるときに元の状態に戻してくれるため、EXE側に処理が帰った後も問題がない。
DLLのデバッグ方法 †
DLLは親となるプロセスにロードされるため、呼び出し側のEXEが必要になる。
[注意点]
- DLLはDebug版で用意しておく必要がある。
- DLLが動的リンクされている場合、::LoadLibrary()によってDLLが呼び出し側EXEにロードされた後でないと、DLL側のソースにブレークポイントが貼れない
- 呼び出し側EXEも含めてデバッグ
呼び出し側EXEのソースが手元にある場合に有効。
- 呼び出し側EXEとデバッグ対象DLLのプロジェクトを1つのワークスペースにまとめておく。
- 呼び出し側EXEのデバッグを開始する。
- デバッグ対象DLL内のコードにブレークポイントを貼っておき、その位置まで実行する。
- 呼び出し側EXEを指定してデバッグ
呼び出し側EXEが単純に起動できる場合に有効。
- デバッグ対象DLLのプロジェクトを開き、プロジェクト設定の「デバッグセッションの実行可能ファイル」に呼び出し側EXEのパスを指定する。
- デバッグ対象DLL内のコードにブレークポイントを貼っておき、デバッグを開始する。
- 呼び出し側EXEのプロセスにアタッチしてデバッグ
呼び出し側EXEの起動タイミングを制御できない場合(常駐など)に有効
- 呼び出し側EXEが起動された状態にする。
- VisualStudioを新規に起動し、「プロセスにアタッチ」で呼び出し側EXEのプロセスを指定してデバッグを開始する。
- VisualStudioにデバッグ対象DLLのソースファイルをドラッグ&ドロップし、ブレークポイントを貼り、その位置まで実行する。
LIB †
libファイルのリンク †
プロジェクトにlibファイルをリンクするには以下の3つの方法がある。
- プロジェクトにlibファイルを追加する。コマンドラインでのビルドであれば、オブジェクトファイルと一緒にlibファイルを指定する。
- プロジェクトの設定でリンク指定する。
- プリプロセッサで指定する。
1
| | #pragma comment(lib, "xxx.lib")
|
自作のスタティックライブラリ †
自作クラスなどの汎用的なコードは、スタティックライブラリ化しておくと再利用しやすい。
- スタティックライブラリ側のプロジェクト
- スタティックライブラリのプロジェクトを作り、ヘッダとソースを追加する。
- ヘッダファイルの置き場所はあらかじめ決めておく。複数のライブラリのプロジェクトがあっても、ヘッダは一ヵ所にまとめる。
- 出力ファイルであるLibファイルもあらかじめ決めた場所に出力する。Debug版のLibファイルも、名前を変えて同じ場所に出力させる。
- アプリケーション側のプロジェクト(Libファイルを使用する側)
- プロジェクトの設定で、リンクするLIBファイルを設定する。(Debug版はDebug版のLibファイルを使用する)
- ワークスペースに使用するライブラリのプロジェクトを含め、さらに依存関係を設定しておくと常に最新のライブラリファイルをリンクできる。
- VC++の設定
- ヘッダファイル、Libファイルを置いたパスをディレクトリ設定に追加する。
リソーススクリプト †
その他 †
実行対象のWindowsバージョン †
Win32API等のOSの機能を使用する場合、OSのバージョンによって利用できる機能と利用できない機能が存在するため、例えばWinXPから実装された機能を使用するアプリケーションは、Win2kでは動作が保証されないことになる。
つまり、アプリケーションを作成する場合、実行対象のWindowsの最小バージョンを決めておき、それより後のバージョンで実装された機能を使用しないようにする必要がある。
そこで、Windows用のヘッダファイルには以下のマクロが用意されており、定義された値によって機能が限定されるように作られている。アプリケーションを作る場合、使用する機能によってはこのマクロの値を適宜設定する必要がある。(stdafx.h内のWindows用のヘッダファイルのインクルードよりも前に定義する)
- WINVER
実行対象のWindowsの最小バージョン
- _WIN32_WINNT
実行対象のWindowsNTの最小バージョン
- _WIN32_IE
実行対象の環境にインストールされているIEの最小バージョン
- Windowsの最小バージョンの定義
Windows | WINVER _WIN32_WINNT |
Windows 95 | 0x0400 |
Windows 98 | 0x0410 |
Windows Me | 0x0500 |
Windows NT 4.0 | 0x0400 |
Windows 2000 | 0x0500 |
Windows XP Windows Server 2003 | 0x0501 |
Windows XP SP2 Windows Server 2003 SP1 | 0x0502 |
Windows Vista | 0x0600 |
Windows 7 | 0x0601 |
Windows 8 | 0x0602 |
- IEの最小バージョンの定義
IE | _WIN32_IE |
Windows 95 Windows NT 4.0 | 0x0200 |
Internet Explorer 4.0 | 0x0400 |
Internet Explorer 4.01 | 0x0401 |
Internet Explorer 5.0, 5.0a, 5.0b | 0x0500 |
Internet Explorer 5.01 | 0x0501 |
Internet Explorer 5.5 | 0x0550 |
Internet Explorer 6.0 | 0x0600 |
Internet Explorer 6.0 SP1 | 0x0601 |
Internet Explorer 6.0 SP2 | 0x0603 |
Internet Explorer 7.0 | 0x0700 |
Internet Explorer 8.0 | 0x0800 |
Internet Explorer 9.0 | 0x0900 |
Internet Explorer 10.0 | 0x0A00 |
参考URL
コマンドライン引数 †
二重起動防止 †
CWinApp 派生クラスの InitInstance() で、ウィンドウ起動処理に以下を追加。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| | CString strExeName(m_pszExeName);
strExeName.MakeUpper();
CMutex mutex(FALSE, strExeName);
if(!mutex.Lock(0)) {
HWND hWnd;
if((hWnd = ::FindWindow(NULL, "対象ウィドウのタイトル")) != NULL){
::ShowWindow(hWnd, SW_RESTORE);
::SetForegroundWindow(hWnd);
}
return FALSE;
}
mutex.Unlock();
|
※ <afxmt.h>をインクルードする必要がある。
※ ミューテックス作成時に渡す文字列は大文字小文字を区別することに注意。
処理中の砂時計カーソル †
カーソルの変更 †
外部プログラムの起動 †
- ::CreateProcess()
実行可能モジュールを新しいプロセスで起動する関数。
基本的に第1引数は NULL にしておき、第2引数の文字列の1つ目のトークンとして実行コマンド名も指定する方が無難。
理由は、第1引数に指定したコマンドは、フルパス指定かカレントディレクトリに実体を持つ必要がある。第2引数であれば、コマンドプロンプトと同様、パスが通った場所も検索されるため。
- ::ShellExecute()
シェルにコマンドを実行させる関数。用途は実行ファイルの起動に限らない。
関連付けられたアプリケーションでファイルを開くことも簡単にできる。
Win32APIやMFCのメンバ関数の戻り値 †
- BOOL型 (int型)
- エラーの詳細を知るためには、::GetLastError() を使用する必要がある。
- 戻り値を TRUE(1)と比較してはいけない。条件が「真」であることの定義は「0ではない」ことなので、関数が成功した場合に「1」を返している保障はないことに注意。
- HRESULT型 (long型)
- 成否の判定には、SUCCEEDED() / FAILED() マクロを用いる。
(値の正負を判断しているだけ。つまり、HRESULT型は最上位ビットが成否を表している。)
- 戻り値からエラーの詳細を知ることができる。
- HRESULT型には、S_FALSE という、「エラーではないが失敗」という微妙なニュアンスを表す値が存在する。上記マクロを使用した場合、「真」と判断されるため注意。
クリップボードの読み書き †
- サンプルコード
- テキストの読み込み
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| |
HGLOBAL hMem; char *pMem;
if(IsClipboardFormatAvailable(CF_TEXT)){
return;
}
if(!::OpenClipboard(NULL)){
return;
}
if((hMem = (HGLOBAL)::GetClipboardData(CF_TEXT)) == NULL){
::CloseClipboard();
return;
}
pMem = (char *)::GlobalLock(hMem);
strncpy(pStr, pMem, nMaxCount);
pStr[nMaxCount] = '\0';
::GlobalUnlock(hMem);
::CloseClipboard();
|
- テキストの書き込み
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| |
HGLOBAL hMem; char *pMem;
hMem = ::GlobalAlloc(GHND, strlen(pStr) + 1);
pMem = (char *)::GlobalLock(hMem);
strcpy(pMem, pStr);
::GlobalUnlock(hMem);
if(::OpenClipboard(NULL)){
::EmptyClipboard();
::SetClipboardData(CF_TEXT, hMem);
::CloseClipboard();
} else {
::GlobalFree(hMem);
}
|
- クリップボードを閉じた後でのデータの変更
クリップボードにはハンドル(グローバルな領域のアドレスを指す)が設定されているだけであり、ビットマップや文字列のデータそのものがコピーされているわけではない。
そのため、クリップボードを閉じた後であっても、データを変更するとクリップボードの指す先の内容を変更したことになってしまう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| |
if(::OpenClipboard(hWnd)){
if(::EmptyClipboard()){
::SetClipboardData(CF_BITMAP, hBmp);
}
::CloseClipboard();
}
|
アクセラレータ †
アプリケーションのショートカットキーを実現するアクセラレータの使用方法。
- リソースにアクセラレータテーブルを追加する。(例:IDR_ACCELERATOR)
- アクセラレータを関連付けるウィンドウ(ダイアログ)クラスの CWnd::PreTranslateMessage() をオーバライドし、以下のコードを追加する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| | BOOL CxxxxDlg::PreTranslateMessage(MSG* pMsg)
{
HACCEL hAccKey;
hAccKey = ::LoadAccelerators(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_ACCELERATOR));
if (hAccKey != NULL){
if (::TranslateAccelerator(this->m_hWnd, hAccKey, pMsg)){
return TRUE;
}
}
return CDialog::PreTranslateMessage(pMsg);
}
|
- アクセラレータテーブルにショートカットキーを登録する。(例:「Ctrl+C」→ ID_ACCEL_CTRL_C)
- クラスウィザードで、アクセラレータをウィンドウ(ダイアログ)クラスに関連付け、COMMANDメッセージのイベントハンドラを作成する。
タスクトレイ常駐 †
タスクトレイに常駐するアプリケーションの基本的な作成方法。
- リソースに、タスクトレイに表示するアイコンと、ポップアップ用のメニューを追加する。
- アイコン
アイコンを切り替える場合は、複数用意しておく。(例:IDR_MAINFRAME, IDR_SUBICON)
- ポップアップメニュー
通常のメニューと同様に追加する。ポップアップ用メニューはサブメニューとして定義する必要がある。(例:IDR_POPUPMENU)
- ポップアップメニューの項目のイベントハンドラを実装しておく。
- ウィンドウ(ダイアログ)クラスのメンバ変数として以下を定義しておく。
1
| | NOTIFYICONDATA m_notifyIcon;
|
- タスクトレイのアイコンがクリックされた場合のユーザ定義メッセージを定義する。
1
| | #define WM_USER_ACTION (WM_USER + 100)
|
- アプリケーションの初期処理で、メインウィンドウを非表示し、タスクトレイにアイコンを設定する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| | ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW); SetWindowPos(&wndTop, 0, 0, 0, 0, SWP_HIDEWINDOW);
memset(&m_notifyIcon, 0, sizeof(NOTIFYICONDATA));
m_notifyIcon.cbSize = sizeof(NOTIFYICONDATA); m_notifyIcon.uID = 0; m_notifyIcon.hWnd = m_hWnd; m_notifyIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; m_notifyIcon.hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); m_notifyIcon.uCallbackMessage = WM_USER_ACTION; lstrcpy(m_notifyIcon.szTip, "MAINモード");
::Shell_NotifyIcon(NIM_ADD, &m_notifyIcon);
|
- CWnd::WindowProc() 等のウィンドウプロシージャにユーザ定義メッセージのハンドラを追加する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| | POINT pntCur; CMenu mainMenu; CMenu *pPopupMenu;
switch(message){
case WM_USER_ACTION:
::GetCursorPos(&pntCur);
if(lParam == WM_LBUTTONDOWN){
mainMenu.LoadMenu(IDR_MENU_POPUP);
pPopupMenu = mainMenu.GetSubMenu(0);
if(pPopupMenu != NULL){
SetForegroundWindow();
pPopupMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pntCur.x, pntCur.y, this);
PostMessage(WM_NULL);
}
} else if(lParam == WM_RBUTTONDOWN){
} else if(lParam == NIN_BALLOONSHOW){
} else if(lParam == NIN_BALLOONTIMEOUT){
} else if(lParam == NIN_BALLOONUSERCLICK){
} else if(lParam == NIN_BALLOONHIDE){
}
break;
}
|
- アプリケーションの終了処理で、タスクトレイからアイコンを削除する。
1
2
| | ::Shell_NotifyIcon(NIM_DELETE, &m_notifyIcon);
|
- タスクトレイのアイコンに対する処理の例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| | m_notifyIcon.uFlags |= NIF_INFO; m_notifyIcon.uTimeout = 3000; m_notifyIcon.dwInfoFlags = 0L; lstrcpy(m_notifyIcon.szInfoTitle, "タイトル"); wsprintf(m_notifyIcon.szInfo, "メッセージ"); ::Shell_NotifyIcon(NIM_MODIFY, &m_notifyIcon);
m_notifyIcon.hIcon = AfxGetApp()->LoadIcon(IDR_SUBICON); lstrcpy(m_notifyIcon.szTip, "SUBモード"); ::Shell_NotifyIcon(NIM_MODIFY, &m_notifyIcon);
|
プロセス間共有メモリ †
通常、異なるアプリケーション間では別々のメモリ空間が使用されるため、メモリを共有することができない。(アプリAのメモリアドレス0x00001111は、アプリBのメモリアドレス0x00001111とは別の領域である)
そこで、OS側に仮想的なメモリ空間を作成してもらい、そのメモリ空間をそれぞれのアプリケーションが自プロセスのメモリ空間にマッピングして使用することで、あたかも同じメモリを操作しているように振る舞うことができる。
アプリAが共有メモリの内容を変更すると、それをマッピングしている全てのプロセスの共有メモリが同じように変更される。
変更が即時反映されるファイルを複数プロセスで同時にオープンしているイメージ。共有メモリは実体がない(正確にはOSが管理するメモリ空間に実体を持つらしい)ため、一意な名前をつけて識別する必要がある。
- 共有ファイル作成に使用する変数等の定義
1
2
3
4
5
| | #define FMAP_KEY "TEST_FMAP" #define FMAP_SIZE (sizeof(char) * 100)
HANDLE hFileMap; char *pData;
|
- 共有ファイルの使用開始
1
2
3
4
5
6
7
| | hFileMap = ::CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, FMAP_SIZE, FMAP_KEY);
pData = (char*)::MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, FMAP_SIZE);
|
- 共有ファイルの使用終了
1
2
3
4
5
6
7
| | ::UnmapViewOfFile(pData);
pData = NULL;
::CloseHandle(hFileMap);
hFileMap = NULL;
|
アプリケーションのアイコン設定 †
- エクスプローラ上で表示されるexeファイルのアイコン
アプリケーションのリソースファイル(*.rc)内で定義されているアイコンの内、IDが最小のものが使用される。
コンソールアプリケーションのアイコンを変更したい場合も、アイコンを定義したリソースファイルとともにビルドすることで実現できる。
- ウィンドウ左上のアイコン
コード中で明示的にアイコンを設定する必要がある。
コンソールアプリケーションの場合は変更不可。
- Win32API
::RegisterClassEx() に指定するウィンドウクラス(WNDCLASS)に、アイコンハンドルを登録する。
- MFC
CWnd::SetIcon() を使用してアイコンを設定する。
アプリケーションのバージョン情報の取得 †
対象の実行ファイルのパスから、バージョン情報を取得できる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| |
DWORD dwZero = 0;
DWORD dwVerInfoSize;
dwVerInfoSize = ::GetFileVersionInfoSize(szPath, &dwZero);
if(dwVerInfoSize == 0) {
return FALSE;
}
BYTE *pVerInfo;
pVerInfo = new BYTE[dwVerInfoSize];
if(pVerInfo == NULL){
return FALSE;
}
::GetFileVersionInfo(szPath, dwZero, dwVerInfoSize, pVerInfo);
void *pvVersion;
UINT VersionLen;
CString strFileVersion;
::VerQueryValue(pVerInfo, "\\StringFileInfo\\041104b0\\FileVersion", &pvVersion, &VersionLen);
strFileVersion = (LPCTSTR)pvVersion;
delete[] pVerInfo;
return TRUE;
|
CreateProcess()を使用する場合のリダイレクトの方法 †
外部コマンドを起動して標準出力をリダイレクトする方法。(test.exe > stdou.txt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| |
BOOL bRet;
DWORD dwEndCode;
PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&si, sizeof (si));
si.cb = sizeof(si);
si.dwFlags |= STARTF_USESTDHANDLES; si.hStdOutput = (HANDLE)hTxtFile;
bRet = ::CreateProcess(NULL, "test.exe", NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
if(bRet){
::WaitForSingleObject(pi.hProcess, INFINITE);
::GetExitCodeProcess(pi.hProcess, &dwEndCode);
}
|
※標準出力のリダイレクトを追記させる場合は、予めファイルを追記モードで開き、シークを終端に移動しておく手順が必要。(test.exe >> stdou.txt)
同様の手順で、標準入力や標準エラー出力もリダイレクト可能。
ファイルのアイコン表示(エクスプローラと同じ表示) †
- アイコンを単体で取得する場合
1
2
3
4
5
6
7
8
9
| | SHFILEINFO sfi
HICON hIconSmall;
hIconSmall = ::SHGetFileInfo(ファイルパス, 0, &sfi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_SMALLICON);
HICON hIconLarge;
hIconLarge = ::SHGetFileInfo(ファイルパス, 0, &sfi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_LARGEICON);
|
- アイコンをイメージリストとして取得する場合(システムイメージリストの取得)
1
2
3
4
5
6
7
8
9
| | SHFILEINFO sfi
HIMAGELIST hImgListSmall;
hImgListSmall = (HIMAGELIST)SHGetFileInfo((LPCSTR)"C:\\", 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
// 大きいアイコン(32x32)
HIMAGELIST hImgListLarge;
hImgListLarge = (HIMAGELIST)SHGetFileInfo((LPCSTR)"C:\\", 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
|
1
2
3
4
5
| | SHFILEINFO sfi
SHGetFileInfo(ファイルパス, 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX);
|
リストビュー、ツリービュー、タブなどのコントロールにアイコンを表示する場合は、イメージリストを使用した方法を使用する必要がある。
あらかじめ、CListCtrl::SetImageList()、ListView_SetImageList() などでイメージリストをコントロールに登録しておき、CListCtrl::InsertItem()、ListView_InsertItem() でアイテムを挿入する際にアイコンのインデックスを指定することで表示される。
※コントロールにシステムイメージリストを使用する場合は、ウィンドウスタイルに LVS_SHAREIMAGELISTS (リストビューコントロールの場合) を設定しておくこと。
TCPサーバーの通信切断検知 †
TCP通信を行っている際、LANケーブルが抜けるなどで通信切断されてしまってもサーバー側はしばらく復帰を待ってしまいなかなか通信切断を検知してくれない。
この動作は、Keep-Aliveパケットを投げて通信が復帰しないことを確認して諦めるまでの時間に依存し、下記3つのパラメータによって時間が決定される。
パラメータ | 説明 | デフォルト値(Linux) |
TCP_KEEPIDLE | Keep-Aliveを投げ始めるまでの待機時間(秒) | 7200 |
TCP_KEEPINTVL | Keep-Aliveを投げる間隔(秒) | 75 |
TCP_KEEPCNT | Keep-Aliveを投げる回数 | 9 |
デフォルトだと、7200 + 75 * 9 = 7875秒 待機してしまうことになるので、もっと早く切断を検知してほしい場合は下記のようにパラメータを変更しておく必要がある。
1
2
3
4
5
6
7
8
9
10
11
12
| |
int option = 1; setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &option, sizeof(option));
option = 5;
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &option, sizeof(option));
option = 1;
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &option, sizeof(option));
option = 5;
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &option, sizeof(option));
|
参考:https://iww.hateblo.jp/entry/20081030/setsockopt
同期処理/排他制御 †
| セマフォ | ミューテックス | クリティカルセクション |
同時に所有できる数 | 上限を設定可能 | 1つまで | 1つまで |
使用可能な範囲 | プロセスをまたいで使用可能 | プロセスをまたいで使用可能 | 同一プロセス内でのみ使用可能 |
Win32API | オブジェクトの作成 | CreateSemaphore | CreateMutex | InitializeCriticalSection |
所有権の取得 | WaitForSingleObject に代表される待機関数 | EnterCriticalSection |
所有権の破棄 | ReleaseSemaphore | ReleaseMutex | LeaveCriticalSection |
C/C++ネイティブ †
C/C++共通 †
定義済みプリプロセッサマクロ †
カテゴリ | マクロ名 | 意味 |
ANSI C (C90) | __STDC__ | 標準規格のC言語であることを示す K&Rとの区別に用いる |
__FILE__ | ソースファイルパス |
__LINE__ | ソースの行番号 |
__TIMESTAMP__ | コンパイル日時 |
__DATE__ | コンパイル日 |
__TIME__ | コンパイル時刻 |
C99 | __func__ | 関数名 |
C++ | __cplusplus | C++であることを示す 値が1とは限らない |
文字セット (どちらか一方) | _MBCS | マルチバイト文字セットであることを示す |
_UNICODE | ワイド文字(Unicode)セットであることを示す |
ビルド構成 (どちらか一方) | _DEBUG | Debug版 |
NDEBUG | Release版 |
const修飾子の効力 †
ある関数Aでconst修飾された引数を、さらに関数Bの非constな引数として渡すことができてしまう。もちろん関数B内で値の書き換えも可能。
コンパイラの警告レベルによっては、警告は出力される。
CALLBACK関数の定義 †
CALLBACK関数は、グローバル関数やstaticメンバ関数などの静的な関数として定義する必要がある。
その他、関数ポインタを使用する仕組みも同様。
クラスのメンバ関数はインスタンスが生成されるまでアドレス空間のどの位置に配置されるかが決まっていないため、ポインタにアドレスを格納できない。
ワイルドカード(*, ?)を用いた文字列検索 †
参考:http://katsura-kotonoha.sakura.ne.jp/prog/c/tip00003.shtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| | BOOL strmatch(const char *ptn, const char *str)
{
BOOL bmatch = FALSE;
switch(*ptn){
case '\0':
bmatch = (*str == '\0');
break;
case '*':
if(!(bmatch = strmatch(ptn+1, str))){
if(*str != '\0'){
bmatch = strmatch(ptn, str+1);
}
}
break;
case '?':
if(*str != '\0'){
bmatch = strmatch(ptn+1, str+1);
}
break;
default:
if(*ptn == *str){
if(*str != '\0'){
bmatch = strmatch(ptn+1, str+1);
}
}
break;
}
return bmatch;
}
|
関数シグニチャのマングルについて †
C++では、関数のオーバーロードを実現するために、関数シグネチャ(定義)から関数名、引数の型、修飾子などの情報を混合したシンボルを作成し、関数を一意に識別している。
このシンボル名を作成することをマングルと呼ぶ。
マングルの方法は、コンパイラによって異なる。
VC++を使用する場合はマイクロソフトの規約にしたがってマングルされる。先頭が?、末尾がZの形式(?Func@CClass@@QAEXH@Z)
逆に、シンボルを関数名に戻すことをデマングルと呼ぶ。
VC++の場合、Win32APIの::UnDecorateSymbolName()を使用することでデマングルが可能。
参考URL:
http://kone.vis.ne.jp/diary/diaryb13.html#080315
http://www.kegel.com/mangle.html
http://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B_Name_Mangling
構造化例外のキャッチ †
例えば、メモリアクセス違反(アクセスバイオレーション)などが構造化例外。
C++のtry-catch構文でキャッチするためにはコンパイルオプション「/EHa」が必要。(VC6の場合は設定画面に項目がないため、自分で追記する必要がある)
ただし、発生する例外はCException型ではないことに注意。以下のように全ての例外をキャッチすれば捕まるが、詳細情報は取れない。
1
2
3
4
5
| | try{
} catch(...) {
printf("例外発生\n");
}
|
Visual Studio を使用している場合は、MS独自仕様の __try-__except構文で構造化例外をキャッチできる。
これはC言語のソースでも可能で、コンパイルオプションも不要。
1
2
3
4
5
6
7
8
9
| | #include <excpt.h>
:
__try{
} __except(EXCEPTION_EXECUTE_HANDLER) {
printf("例外発生\n");
}
|
C++ †
static関数/変数 †
CPPファイルからCファイルに定義された関数を呼び出す †
関数のシグネチャがCとCPPで異なるため、単純なextern宣言では呼び出せない。(リンクエラーになる)
1
2
3
4
5
6
7
8
| | extern "C" ~~
extern "C"{
~~
~~
}
|
メンバ初期化子(:) †
クラスのメンバ変数を初期化する際、コンストラクタで初期値を代入することもできるが、これは厳密な意味での「初期化」ではなく、値の「代入」である。
厳密に初期化を行う場合は、メンバ初期化子「:」を用いる。コンストラクタ初期化子とも呼ばれる。
- コンストラクタでの「代入」
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| | class CChild
{
private:
int m_count;
int m_num;
CChild()
{
m_count = 0;
m_num = 100;
}
}
|
- メンバ初期化子による「初期化」
1
2
3
4
5
6
7
8
9
10
11
| | class CChild
{
private:
int m_count;
int m_num;
CChild() : m_count(0), m_num(100)
{
}
}
|
また、メンバ初期化子を用いることで基底クラスの初期化(と引数の指定)も可能。
1
2
3
4
5
6
7
8
| | class CChild : public CParent
{
private:
CChild() : CParent(1) {
}
}
|
※メンバ初期化子で複数のメンバを初期化する際に実行される順序は、初期化子に書いた順番ではなくメンバ変数として宣言した順番。
実行時型情報(RTTI) †
typeid演算子によって、オブジェクトが何のクラスのインスタンスなのかを実行時に知ることができる。
使用するには、プロジェクトの設定で「ランタイムタイプ情報(RTTI)を有効にする」にチェックを付けることでコンパイルオプション「/GR」を指定しなければならない。
メンバアクセス演算子でprivateメンバにアクセス †
自身のクラス型のオブジェクトのprivateメンバは、たとえインスタンスが異なっていたとしても「.」「->」でアクセス可能。
例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| | class CSample
{
private:
int num;
void Test0()
{
printf("Test\n");
}
public:
void Test1(CSample &obj)
{
printf("%d\n", obj.num); }
void Test2(CSample *pObj)
{
pObj->Test0(); }
};
|
コーディングの慣例 †
MFC派生クラスのヘッダファイル †
VC6で生成されるMFC派生クラスのヘッダファイルの例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| | class CSample : public CBase
{
DECLARE_DYNAMIC(CSample)
public:
public:
public:
public:
protected:
private:
};
|
説明
| 概要 | 説明 | 追加 |
① | CObjectクラスの 機能の使用宣言 | 全てのMFCクラスの基底クラスであるCObjectの機能を使用する場合は、hファイルに DECLARE_DYNAMIC マクロを追加する必要がある。 また、その場合はcppファイルにも対応する IMPLEMENT_DYNAMIC マクロを追加する必要がある。 VC6では、自動的に追加される訳ではないので注意。 | 任意 |
② | コンストラクション | コンストラクタやCWnd::Create()のような、そのクラスのオブジェクトを使用する前に必ずコールする必要のあるメンバ関数を定義する。 | 任意 |
③ | アトリビュート | そのクラスの属性(プロパティ)を制御する公開メンバ変数や、Set/Getメンバ関数を定義する。 Getメンバ関数はconstにすべきである。 | 任意 |
④ | オペレーション | そのクラスのオブジェクトに対して、何らかの操作を行わせるための公開メンバ関数を定義する。 | 任意 |
⑤ | オーバーライド | MFC基底クラスの仮想関数のオーバーライドを定義する。 | ウィザード |
⑥ | インプリメンテーション (公開メンバ) | そのクラスの実装のための公開メンバ(通常、クラスの使用者が知る必要のないメンバ)を定義する。 | ウィザード |
⑦ | インプリメンテーション (被保護メンバ) | そのクラスの実装のための被保護メンバ(通常、クラスの使用者が知る必要のないメンバ)を定義する。 | ウィザード |
⑧ | Window メッセージハンドラ | Windowメッセージハンドラ(メンバ関数)を定義する。 | ウィザード |
⑨ | インプリメンテーション (非公開メンバ) | そのクラスの実装のための非公開メンバ(通常、クラスの使用者が知る必要のないメンバ)を定義する。 | ウィザード |