#author("2019-07-17T10:42:06+09:00","default:mat2umoto","mat2umoto") #author("2019-07-17T10:43:18+09:00","default:mat2umoto","mat2umoto") #contents *Windowsプログラミング [#aae2e34b] **ダイアログボックス [#ca2af524] ***ダイアログの閉じ方と対応するイベントハンドラ [#h0086e95] |操作|イベントハンドラ|h |Enter|OnOK()| |Esc|OnCancel()| |Alt+F4|~| ***タブオーダの保存形式 [#t04332d2] リソースファイル(*.rc)内でコントロールが定義される順番が、そのままタブオーダとして解釈される。 ***他のコントロールと連携が必要な場合の例 [#sa348c01] 異なるコントロールを連携させて使用する場合がある。連携させるには、タブオーダを連続させる必要がある。~ -ラジオボタンのグループ化 ~グループ化したいラジオボタンのタブオーダを連続させ、先頭のラジオボタンのプロパディでグループをTRUEにする。 -スピンコントロールのバディコントロール ~エディットボックスのタブオーダをスピンコントロールの直前の値にすることで、そのエディットボックスをスピンコントロールのバディコントロールとして設定することができる。~ この場合、値の取得やコントロールの操作は全てCSpinButtonCtrl変数に対して行い、CEditは使用する必要がない。 ***コントロールへのドラッグ&ドロップの優先順位 [#hc561ad0] 複数のコントロールが重なっている場合、タブオーダの小さいコントロールが優先。~ グループボックスコントロールを使用する際などは注意。 ***クラスウィザードに表示されていないメッセージ [#v3678744] クラスウィザードに目的のメッセージが表示されていない場合、表示内容がフィルタリングされている可能性がある。 「詳細設定オプション」で「メッセージフィルタ」を変更することで、目的に合ったメッセージを表示できる。 例)ダイアログクラスで初期状態ではフィルタリングされているメッセージ。 -WM_DROPFILES ~ファイルのドラッグ&ドロップ -WM_ACTIVATE ~ウィンドウのアクティブ/非アクティブ ***コントロールのフォーカス移動 [#h004c8db] CWnd::SetFocus() ではなく、CDialog::GotoDlgCtrl() を用いる。~ CWnd::SetFocus()を用いてもフォーカスは移動するが、画面描画上は移動していないように見えてしまうことがある。 #code(c){{ // ボタンのコントロール変数が m_BtCtrl の場合 m_BtCtrl.SetFocus(); // ←× GotoDlgCtrl(&m_BtCtrl); // ←○ }} ダイアログ表示直後に指定コントロールへフォーカス設定するには、OnInitDialog() で GotoDlgCtrl() を呼び出した後、OnInitDialog() の戻り値を FALSE にする必要がある。 ***リソースエディタ上でコントロールのフォントを指定した際の文字化け [#s8954204] コード上でフォントの設定をすると直ることがある。 ***ツールバーを使用するには [#m55f34a1] 通常、ツールバーはSDIかMDIのアプリケーションにしか用意されないが、以下の手順でダイアログにも使用できる。~ ※ダイアログに配置するコントロールの位置は、ツールバーの範囲を考慮する必要がある。また、ドッキング可能にすることはできない。 <ツールバーの作成> +ツールバーのリソースを用意する。 +ダイアログクラスのメンバ変数として以下を定義しておく。 #code(c){{ CToolBar m_wndToolBar; }} +CXxxDlg::OnInitDialog()でツールバーを作成する。 #code(c){{ 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); // 作成時は大きさが0なので設定する必要がある m_wndToolBar.MoveWindow(0, 0, rcClient.Width(), sizeToolBar.cy); }} <ツールバーのボタン押下時のイベントハンドラ定義> ~クラスウィザードは対応していないため、自分で定義する必要がある。(以下、ボタンのIDを ID_XXXとする) +ダイアログクラスのヘッダのメッセージマップ部分に関数のプロトタイプを作る。 #code(c){{{ // 生成されたメッセージ マップ関数 //{{AFX_MSG(CMStepEditorDlg) virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); //}}AFX_MSG afx_msg void OnXxx(); // @TODO:ここに追加 DECLARE_MESSAGE_MAP() }}} +ダイアログクラスのソースに関数 OnXxx() を定義する。 +ダイアログクラスのソースのメッセージマップ部分に定義した関数を登録する。 #code(c){{{ BEGIN_MESSAGE_MAP(CMStepEditorDlg, CDialog) //{{AFX_MSG_MAP(CMStepEditorDlg) ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_COMMAND(ID_XXX, OnXxx) // @TODO:ここに追加 END_MESSAGE_MAP() }}} ***イベントハンドラ活用 [#c7e6d076] -複数の操作から同じイベントハンドラを呼び出す場合 ~1つのイベントハンドラと対応する1つのリソースIDを用意し、そのIDを以下のような複数のリソースで共用することができる。同一IDを持っている処理は同じイベントハンドラが呼び出される。 --メニューの項目のID --ツールバーのボタンのID --アクセラレータのID --ストリングテーブルのID -別のIDを持つ操作から同じイベントハンドラを呼び出す場合 ~1つのイベントハンドラに対して複数のIDをメッセージマップに登録することができる。~ 例えば、2つのラジオボタンのどちらをクリックしたときも同じ処理を行いたい場合は、両方のクリックイベントに対して同じイベントハンドラ(関数)を指定する。 ***F1押下時のヘルプの無効化 [#na06dcc6] アプリケーションクラスのメッセージ処理を修正することで、無効化できる。 #code(c){{{ BEGIN_MESSAGE_MAP(CXxxApp, CWinApp) //{{AFX_MSG_MAP(CXxxApp) // メモ - ClassWizard はこの位置にマッピング用のマクロを追加または削除します。 // この位置に生成されるコードを編集しないでください。 //}}AFX_MSG // ON_COMMAND(ID_HELP, CWinApp::OnHelp) // ※ここをコメントアウト END_MESSAGE_MAP() }}} ***ダイアログベースアプリケーションの終了コード [#r5c7907b] アプリケーションのCWinAppを継承したクラスのExitInstance()をオーバーライドし、戻り値を設定すればよい。 ***WM_INITDIALOGの戻り値 [#t9de6ed9] 通常、ウィンドウプロシージャやダイアログプロシージャの戻り値は、メッセージを処理したか否かをBOOL型で返すが、WM_INITDIALOGだけは戻り値に特別な意味がある。~ TRUE を返すと、タブオーダーが一番若いコントロールに自動的にフォーカスが当てられる。~ FALSE を返すと、何も行われない。(自分でフォーカスを当てるコードを書く必要がある。)~ ~ ダイアログ生成時に明示的にあるコントロールにフォーカスを当てたい場合や、ダイアログを非表示で生成しておき後で表示するような場合は、FALSEを返す必要がある。(後者のケースは、ダイアログ生成時にフォーカスが奪われてしまうのを防ぐため) ***CDialogBar派生クラスでOnInitDialog()を実装する方法 [#x1b8412f] CDialogBarクラスにはOnInitDialog()が用意されていないため、自分でON_MESSAGE()でWM_INITDIALOGをハンドルする必要がある。~ ~ 参考:http://support.microsoft.com/kb/185672/ja ***MFCによりダイアログが自動的に中央に移動する [#ob241fd0] MFCで作られたダイアログは、自動的に中央に移動する機能が組み込まれている。~ 通常、OnInitDialog()でMoveWindow()等を呼び出し、明示的に表示位置を移動している場合は中央に移動することはないが、ある条件を満たすと中央に移動させられてしまう。~ 例として、(0, 0)の位置に移動しようとしたときに現象が発生する。~ ~ 以下の条件のいずれかを満たしている場合は中央移動は行われない(一部抜粋) -画面の表示位置を(0, 0)以外にしている -ダイアログテンプレート(リソース)のスタイル設定に DS_CENTER, DS_CENTERMOUSE, DS_ABSALIGN のいずれかのスタイルが設定されている -ダイアログテンプレート(リソース)のXY位置設定が(0, 0)ではない ~ 詳細は関連するMFCの関数を参照。~ -::_AfxPostInitDialog() -CDialog::CheckAutoCenter() **コントロール [#zd341b78] ***エディットボックス内の改行の注意点 [#p8b10388] -複数行を許可していなくても、Ctrl+Enterで改行することができてしまう。 -文字列取得の際、改行コードは"\r\n"として取得されるため、取得した文字列をテキストモードで開いたファイルに書き込む場合は注意。('\n'が"\r\n"に展開されるため、"\r\r\n"となってしまう) ***コンボボックスのドロップダウンリストのサイズ変更 [#g260819a] ダイアログエディタ上で、コンボボックスの右端の▼マークをクリックすると、ドロップダウンリストのサイズ変更状態になる。 ***リストビューコントロールのヘッダのソート記号 [#g161d5e1] リストビューのヘッダに対して以下の定数を設定する。 |HDF_SORTUP|昇順(▲)| |HDF_SORTDOWN|降順(▼)| ※この機能はXP以降の機能なので、VC6.0(1998年)時点では未対応。 #code(c){{ // CListCtrl m_LcTest リストビューのコントロール変数 // long lSubItem ヘッダの列インデックス HDITEM hdItem; // ヘッダ設定情報 CHeaderCtrl* pHrCtrl; // リストビューのヘッダ // 初期化 ::ZeroMemory(&hdItem, sizeof(HDITEM)); // メンバ変数 HDITEM::fmt を有効にする hdItem.mask = HDI_FORMAT; // ヘッダコントロールをリストから取得 pHrCtrl = m_LcTest.GetHeaderCtrl(); // ヘッダの指定列の現在の設定状況を取得 pHrCtrl->GetItem(lSubItem, &hdItem); // 例)昇順ソートマークを設定 hdItem.fmt &= ~HDF_SORTDOWN; // 降順マークを消す hdItem.fmt |= HDF_SORTUP; // 昇順マークを設定 // ヘッダに設定 pHrCtrl->SetItem(lSubItem, &hdItem); }} ***ツリービューの全展開 [#v4dc6172] ツリービューコントロールを全展開する関数 #code(c){{ // ツリー全展開 // CTreeCtrl *pTreeCtrl 展開対象のツリーコントロール // HTREEITEM hParent 展開対象のノード (呼び出し時はNULL指定する) 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); } } } }} ***スピンコントロールの値変更イベント [#m6c0c6c5] スピンコントロールの値が変更された場合のイベントハンドラは、バディされたエディットコントロールのEN_CHANGEイベントで行う。~ しかし、スピンコントロールとエディットコントロールを自動でバディする設定の場合は、OnInitDialog()よりも前に自動的にバディされたタイミングでEN_CHANGEイベントが発生してしまう。すると、まだ用意されていないコントロールにアクセスされてしまい、実行時エラーになってしまうことがある。~ 対処策としては、自分でエディットコントロールにバディするコードを書くか、フラグを用意してOnInitDialog()コール前ならEN_CHANGEイベントハンドラで何も処理しない設計にしておく。 ***リッチエディットコントロール使用前の準備 [#leee484e] リッチエディットコントロール(CRichEditCtrl)を使用する場合は、通常のエディットコントロール(CEdit)と違って予め準備をしておく必要がある。 -コントロール自体の初期化~ リッチエディットコントロールを使用する前に、アプリケーション内で1度だけMFCのグローバル関数 AfxInitRichEdit() を呼び出す必要がある。~ 呼び出すタイミングは、最初にリッチエディットコントロールを使用するダイアログの表示コードの直前 (ダイアログベースアプリケーションなら、CXxxApp::InitInstance())でよい。 -使用するイベントのマスク設定~ リッチエディットコントロールは、デフォルトでイベントマスクが ENM_NONE (イベント通知なし) に設定されており、ダイアログにイベントが通知されてこない。~ ダイアログのOnInitDialog()で、明示的に使用するイベントのマスク設定を行う必要がある。~ #code(c){{ // m_ReCtrl : リッチエディットコントロールのコントロール変数 // EN_CHANGEイベント(内容が変更されたときのイベント)を使用する場合のマスク設定 m_ReCtrl.SetEventMask(m_ReCtrl.GetEventMask() | ENM_CHANGE); // ENM_CHANGE : EN_CHANGEのイベントマスク }} ***エディット/リッチエディットコントロールのワード区切り [#ve95f167] 単語境界やワードラップ機能は、どのように文字列のワード区切りを判断しているかに依存する。~ ワード区切りの方法を変更するには、以下のキーワードを調べる。(まだ試していない) -EM_SETWORDBREAKPROC -EditWordBreakProc ***コントロールの「通知」の有無 [#zfb7d171] コントロールのプロパティの「通知」の有無の違いによって、イベントハンドラがコールされる順番を検証。~ (CWnd::Create()でコントロールを作成する場合は、「SS_NOTFY」フラグに該当する。) -通知OFFの場合 ++親ダイアログの PreTranslateMessage() ++親ダイアログの OnLButtonDown() -通知ONの場合 ++コントロールの PreTranslateMessage() ++親ダイアログの PreTranslateMessage() ++コントロールの OnLButtonDown() ++親ダイアログに追加したコントロールクリック時のイベントハンドラ OnXXXX() ***ActiveXコントロールの登録状況の確認 [#g8d5ab8f] ActiveXコントロールを使用したMFCアプリケーションを作成すると、デフォルトの動作ではActiveXコントロールが登録されていないと何の表示も行われずに終了してしまう。~ それだと、なぜアプリケーションが起動しなかったのかが分からないため、この方法を用いてエラーメッセージを表示するとよい。 -チェック関数 #code(c){{ // clsid : 確認対象のActiveXコントロールのクラスID BOOL IsControlRegister(CLSID clsid) { BOOL bRegistered = FALSE; // クラスIDを文字列に変換 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] ); // クラスIDを元にレジストリキーを作成 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; } }} -使用例 #code(c){{ // AppクラスのInitInstance()で以下のコードを記述。(dlg変数が宣言された直後くらいに) // m_xxxx : dlgダイアログで使用しているOCXコントロール // ウィザードによってGetClsid()メンバ関数が作成されているので、その戻り値を引数として渡す if(!IsControlRegister(dlg.m_xxxx.GetClsid())){ ::MessageBox(NULL, "XXXX ActiveX コントロールがインストールされていません", "エラー", MB_OK | MB_ICONERROR); } }} **コントロールのサブクラス化 [#b1ba8fa2] ***サブクラス化の方法 [#ude717d6] -動的サブクラス化 ~親ダイアログクラスの初期処理で、CWnd::SubclassDlgItem()を使用することで行う。 -静的サブクラス化 ~ClassWizardを使用して派生クラスの型のコントロール変数を作成することで行う。~ 派生クラスのヘッダやソースファイルがクラスウィザードに登録されていないと、画面上で変数の型として選択できないので注意。 その場合は、直接ヘッダファイル内の変数定義の型名を編集すればOK。 ※サブクラス化とは、C++の「クラスの継承」のことではない。~ 既存のウィンドウプロシージャを、独自のウィンドウプロシージャに差し替えることを指す。 ***サブクラス化に用いた派生クラス内での初期処理 [#va683d55] OnCreate()はコールされないため、初期処理を記述することはできない。~ 派生クラスでは、PreSubclassWindow()をオーバライドして、そこに初期処理を書く。~ 元のクラスが構築されて(OnCreate)、サブクラス化が行われるのであって、派生クラスが直接構築されるわけではない。 ***MFCを継承した自作クラスへのイベントハンドラの追加 [#k71c47ce] MFCを継承している場合は、クラスウィザードに派生クラスを認識させておく必要がある。~ (認識されているかどうかは、クラスウィザードの「クラス名」に表示されるかどうかで判断できる)~ 認識されていない場合は、以下の操作ができない。 -コントロールのメンバ変数追加時に、型の候補とする -クラスビューからWindowsメッセージハンドラを追加する プロジェクト内でクラスを作成した場合は自動的に認識されるが、外部から持ってきたコードを用いる場合は、 一度clwファイルを削除してクラスウィザードを作り直すときに、ファイルを含める必要がある。 ※ちなみに、MFC継承クラスをさらに継承した場合は上記作業を行っても不可。仕様上は問題なくイベントハンドラを実装できるはずなので、手動で行うしかない? **ウィンドウ [#b879af76] ***ウィンドウ操作関数一覧(一部) [#bc075974] |>|操作|>|CENTER:Win32API|>|CENTER:MFC|h |~|~|Set|Get|Set|Get|h |>|ウィンドウ情報|-|::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&br;CWnd::GetForegroundWindow| |~|~|::SetWindowPos|-|CWnd::SetWindowPos|-| |~|中央へ移動|-|-|CWnd::CenterWindow|-| |>|キャプション|::SetWindowText|::GetWindowText|CWnd::SetWindowText|CWnd::GetWindowText| |>|透明度|::SetLayeredWindowAttributes|::GetLayeredWindowAttributes|CWnd::&br;SetLayeredWindowAttributes|CWnd::&br;GetLayeredWindowAttributes| |>|フォント|-|-|CWnd::SetFont|CWnd::GetFont| |操作|閉じる|::DestroyWindow|-|CWnd::DestroyWindow|-| |~|最大化|::ShowWindow|::IsZoomed|CWnd::ShowWindow|CWnd::IsZoomed| |~|最小化|::ShowWindow&br;::CloseWindow|::IsIconic|CWnd::ShowWindow&br;CWnd::CloseWindow|CWnd::IsIconic| |~|メッセージ送信(同期)|::SendMessage|-|CWnd::SendMessage|-| |~|メッセージ送信(非同期)|::PostMessage|-|CWnd::PostMessage|-| |~|再描画|::UpdateWindow|-|CWnd::UpdateWindow|-| ***ウィンドウの中央表示 [#e4d8e618] -''Win32API'' ~ウィンドウの矩形を計算し、::MoveWindow() で移動する。 #code(c){{ // HWND hWnd 対象ウィンドウのハンドル RECT rectDesktop; // デスクトップの領域矩形 RECT rectWnd; // ウィンドウの領域矩形 RECT rectCenter; // 中央表示の領域矩形 // デスクトップの領域を取得 SystemParametersInfo(SPI_GETWORKAREA, 0, &rectDesktop, 0); // ウィンドウの領域を取得 GetWindowRect(hWnd, &rectWnd); // 中央表示の領域を計算 rectCenter.top = rectDesktop.bottom / 2 - (rectWnd.bottom - rectWnd.top) / 2; rectCenter.left = rectDesktop.right / 2 - (rectWnd.right - rectWnd.left) / 2; rectCenter.right = rectCenter.left + rectWnd.right; rectCenter.bottom = rectCenter.top + rectWnd.bottom; // ウィンドウを移動 MoveWindow( hWnd, rectCenter.left, rectCenter.top, (rectCenter.right - rectCenter.left), (rectCenter.bottom - rectCenter.top), TRUE); }} -''MFC'' ~CWnd::CenterWindow() を使用する。 #code(c){{ // デスクトップを基準に中央に表示する CenterWindow(CWnd::GetDesktopWindow()); }} ***タイトルバーの高さの取得 [#ab66f67c] OSの設定(フォントやテーマ等)に依存する。 #code(c){{ ::GetSystemMetrics(SM_CYCAPTION) // タイトルバーの高さを取得 }} ***コンソールウィンドウの使用 [#w5bf5c30] ::AllocConsole(), ::FreeConsole()を用いる。~ コンソールへの入出力をするためには、標準入出力を割り当てるか専用の関数でハンドルを取得する。 #code(c){{ ::AllocConsole(); // コンソール作成 // ①標準入出力を割り当てる方法 // printf()などの標準関数を使う場合 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); // アプリケーション終了時?などに::FreeConsole()でコンソールを解放する }} ***ウィンドウの描画更新の抑制 [#q420663c] CWnd::LockWindowUpdate() と CWnd::UnlockWindowUpdate() で、処理を挟み込む。 #code(c){{ LockWindowUpdate() // 描画更新を抑制した状態で行う処理を記述 UnlockWindowUpdate() }} 大きなツリービューへのアイテムの追加など、時間がかかる処理は描画更新を抑制することで、画面のチラつきを防止できる。 ***小さいアイコン(16x16)の設定 [#haeb34ab] 大きいアイコン(32x32)と小さいアイコン(16x16)の両方を用意してあるのに、小さいアイコンがうまく適用されないことがある。~ その場合は、OnInitDialog() 等のアイコン設定処理で、小さいアイコン設定の処理をコメントアウトするとよい。 #code(c){{ SetIcon(m_hIcon, TRUE); // 大きいアイコンを設定 //SetIcon(m_hIcon, FALSE); // 小さいアイコンを設定 }} ※正式な修正方法ではないかも。 ***メニューの項目の取得 [#e1529ac9] 取得の手順。 +メニューを持つウィンドウを識別 +メニューバーを取得 +必要があればサブメニューを取得していき、目的の項目を持つサブメニューまで辿る +目的のメニュー項目の情報を取得(/設定)する メニューの項目は、0から始まるインデックスで位置を指定する~ サブメニュー内は、それぞれで0から始まるインデックスを持つ。 #br メニュー項目の取得は、MENUITEMINFO構造体を介して行う。~ MENUITEMINFO::cbSize に構造体のサイズを設定し、必要なメンバ変数に対応したフラグを MENUITEMINFO::fMask に設定する。 #br -''Win32API'' #code(c){{ // HWND hWnd メニューを持つウィンドウハンドル HMENU hMenu; // メニュー HMENU hSubMenu; // サブメニュー MENUITEMINFO menuItemInfo; // メニュー項目の情報 // メニューの取得 hMenu = ::GetMenu(hWnd); // サブメニューの取得 hSubMenu = ::GetSubMenu(hMenu, 0); // メモ帳でいう「ファイル」を取得 // MENUITEMINFO構造体を初期化 ::ZeroMemory(&menuItemInfo, sizeof(MENUITEMINFO)); menuItemInfo.cbSize = sizeof(MENUITEMINFO); // お約束 menuItemInfo.fMask = MIIM_ID | MIIM_STATE; // メニュー項目IDとメニュー項目状態を取得するマスクの例 // メニュー項目の情報を取得 ::GetMenuItemInfo( hSubMenu, // 対象のメニューハンドル 2, // メモ帳でいう「上書き保存」を取得 TRUE, // TRUEならメニューの位置、FALSEならメニュー項目IDを、第2引数に指定する &menuItemInfo); // MENUITEMINFO構造体 // menuItemInfo.wID にメニュー項目IDが取得された // menuItemInfo.fState にメニュー項目状態が取得された }} -''MFC'' #code(c){{ // CWnd *pWnd メニューを持つウィンドウクラス CMenu *pSubMenu; // サブメニュークラス MENUITEMINFO menuItemInfo; // メニュー項目の情報 // サブメニューを取得 pSubMenu = pWnd->GetMenu()->GetSubMenu(0); // メモ帳でいう「ファイル」を取得 // MENUITEMINFO構造体を初期化 ::ZeroMemory(&menuItemInfo, sizeof(MENUITEMINFO)); menuItemInfo.cbSize = sizeof(MENUITEMINFO); // お約束 menuItemInfo.fMask = MIIM_ID | MIIM_STATE; // メニュー項目IDとメニュー項目状態を取得するマスクの例 // メニュー項目の情報を取得 pSubMenu->GetMenuItemInfo( 2, // メモ帳でいう「上書き保存」を取得 &menuItemInfo, // MENUITEMINFO構造体 TRUE); // TRUEならメニューの位置、FALSEならメニュー項目IDを、第1引数に指定する // menuItemInfo.wID にメニュー項目IDが取得された // menuItemInfo.fState にメニュー項目状態が取得された }} ***メニューバーの描画更新 [#d24ac8ed] メニュー項目に変更を行っても自動的に描画更新されないため、明示的に描画更新する必要がある。 -''Win32API'' ~::DrawMenuBar() -''MFC'' ~CWnd::DrawMenuBar() ***指定ウィンドウの描画更新 [#l366c04f] ウィンドウの描画更新を促すには、WM_PAINTを発行する必要がある。ただし、ウィンドウに直接WM_PAINTを送るのではなく、下記の手順を踏む必要がある。 #code(c){{ // ウィンドウ全体を再描画する pWnd->InvalidateRect(NULL); // 更新リージョンにウィンドウ全体を追加する pWnd->UpdateWindow(); // 更新リージョンが空でなければ、WM_PAINTを発行する }} ウィンドウの一部だけを再描画させる場合は、CWnd::InvalidateRect()の第一引数にNULLではなく、RECT*型の領域を渡す。 ***メッセージボックスの簡易的な最前面化 [#o379d572] 最前面ウィンドウを親に持つメッセージボックスを作成すればよい。~ 親ウィンドウは非表示にしておけば、メッセージボックスのみ表示される。 #code(c){{ 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; }} ***ウィンドウの再表示 [#v91c4ad4] ウィンドウの再表示を行う際、::ShowWindow()の第2引数に「SW_SHOWNORMAL」や「SW_RESTORE」を渡すだけでは、ウィンドウの状態(最小化、最大化)によって動作が異なってしまう。(ヘルプを読むと「SW_SHOWNORMAL」でよさそうだが。。)~ 以下のように場合分けをすることで解決できる。 #code(c){{ int nShowCmd; // 表示コマンドを決定 if(::IsIconic(hWnd)){ // 最小化時:元の状態に戻す nShowCmd = SW_RESTORE; } else if(::IsZoomed(hWnd)){ // 最大化時:そのまま nShowCmd = SW_SHOWMAXIMIZED; } else { // それ以外は単純に再表示 nShowCmd = SW_SHOW; } // ウィンドウを表示 ::ShowWindow(hWnd, nShowCmd); }} ***タイトルバーを常にアクティブ色にする [#qa874a77] 再描画のたびにウィンドウメッセージでアクティブ通知を行う。 #code(c){{ void CXxxDlg::OnPaint() { : // キャプションを常にアクティブ色にする PostMessage(WM_NCACTIVATE, TRUE); : } }} ***メッセージ専用ウィンドウ [#i0849f40] ウィンドウメッセージの処理を行いたいがウィンドウの実体は必要無いというときは、メッセージ専用ウィンドウを利用することができる。~ (ウィンドウ列挙等の対象からも外れる) 使用する場合は、予め下記の定数を設定しておく必要がある。 #define WINVER 0x0500 // 0x0500以上ならOK -''Win32API'' #code(c){{ 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)); // 既にクラスが登録されていたらRegisterClassEx()をコールしない if(!::GetClassInfo(wcx.hInstance, wcx.lpszClassName, &wcx_dmy) && ::RegisterClassEx(&wcx)){ ::CreateWindowEx(0, szWndClassName, "", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); } }} -''MFC'' #code(c){{ // CWnd *pWnd; pWnd = new CWnd(); CString strWndClassName = ::AfxRegisterWndClass(NULL); pWnd->CreateEx(0, strWndClassName, "", 0, 0, 0, 0, 0, HWND_MESSAGE, 0); }} **ウィンドウメッセージ [#a73dc816] ***自作メッセージ [#n6691b3d] 自作のメッセージの定義は、以下の2通り。 -WM_USER + n~ プライベートなウィンドウに送る自作メッセージの定義。コモンダイアログなどは既に使用している場合があるので注意。 -WM_APP + n~ 自作アプリケーションから任意のウィンドウに送る自作メッセージの定義。 ***メッセージフック処理 [#x95361dc] -''Win32API'' ~自分でウィンドウプロシージャを定義する。 #code(c){{ 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); } }} -''MFC'' ~CWnd::PreTranslateMessage() をオーバーライドして、対応するメッセージフック処理を書く。~ MFCにデフォルトで定義されているメッセージ処理を行わせない場合は、TRUE を返す。~ #br ※PreTranslateMessage() は、Postされたメッセージしか処理できない?Sendされたメッセージは無理? ***アクティブウィンドウに対してキーメッセージを送信 [#ifdeb25b] #code(c){{ // VK_xx に送信したい仮想キーを書く keybd_event(VK_xx, 0, KEYEVENTF_EXTENDEDKEY, 0); // キー押下(WM_KEYDOWN) keybd_event(VK_xx, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); // キー解放(WM_KEYUP) }} 「ALT+F」などのメッセージも、キー押下と解放のタイミングと組み合わせで実現可能。「PrntScrn」や「NumLock」も可能。 ***Windowsアプリケーションが終了する仕組み [#e28c5dda] +ユーザが終了処理を行う(×を押すなど) ~↓ WM_CLOSE +::DestroyWindow() ~↓ WM_DESTROY +::PostQuitMessage() ~↓ WM_QUIT +WinMain()やスレッド内で用いられている::GetMessage() が FALSE を返すことにより、メッセージループを抜け、処理が終了する。 ***メッセージによるコマンドの実行 [#wab4d1b3] ウィンドウにコマンドIDを付与した WM_COMMAND メッセージを送ることで実行できる。~ #code(c){{ // HWND hWnd ウィンドウハンドル // UINT nID コマンドID // メニューコマンドを送信する場合 // ・第2引数のWPARAM // 上位ワード : 0x0000 0 // 下位ワード : nID メニュー項目ID ::PostMessage(hWnd, WM_COMMAND, (WPARAM)MAKELONG((WORD)nID, (WORD)0x0000), 0); // アクセラレータコマンドを送信する場合 // ・第2引数のWPARAM // 上位ワード : 0x0001 1 // 下位ワード : nID アクセラレータキーID ::PostMessage(hWnd, WM_COMMAND, (WPARAM)MAKELONG((WORD)nID, (WORD)0x0001), 0); }} ***アプリ間で使用する任意メッセージの受信 [#y9a866d6] +グローバル変数として、メッセージIDを保持しておく #code(c){{{ // ユニークなメッセージIDを取得 // ("TEST" はメッセージID登録用の任意の文字列。メッセージを送受信するアプリ間で決めておく) static const UINT uiWmTest = ::RegisterWindowMessage("TEST"); }}} +ウィンドウクラスのヘッダに以下のようにイベントハンドラを追加 #code(c){{{ // 生成されたメッセージ マップ関数 //{{AFX_MSG(CXxx) : //}}AFX_MSG afx_msg LRESULT OnTest(WPARAM=0, LPARAM=0); // イベントハンドラを追加 DECLARE_MESSAGE_MAP() }}} +ウィンドウクラスのソースに以下のようにメッセージマップとイベントハンドラの実装を追加 #code(c){{{ BEGIN_MESSAGE_MAP(CXxx, CXxxParent) //{{AFX_MSG_MAP(CXxx) : //}}AFX_MSG_MAP ON_REGISTERED_MESSAGE(uiWmTest, OnTest) // メッセージマップに追加 END_MESSAGE_MAP() }}} ~ #code(c){{{ // イベントハンドラを実装 LRESULT CXxx::OnTest(WPARAM wParam/*=0*/, LPARAM lParam/*=0*/){ // 任意の処理 MessageBox("TEST"); return 0; } }}} ***ウィンドウサイズの制限(WM_GETMINMAXINFO) [#l9313add] マウスドラッグやWin7のスナップ機能などによるウィンドウサイズの変更時に、ウィンドウサイズを制限する場合は、WM_GETMINMAXINFO をハンドルする。 **コモンダイアログ [#e36b4d9a] ***ファイル選択(保存)ダイアログとカレントディレクトリ [#h01c710f] -ファイル選択ダイアログを使用すると、カレントディレクトリが変更される。(OFN_NOCHANGEDIRフラグを使用すれば防げる) -OPENFILENAME::lpstrInitialDir (初期ディレクトリ) に無効な文字列を指定したときは、カレントディレクトリが使用される。 ***ファイル保存ダイアログの拡張子自動付与機能 [#dc09b3e1] -OPENFILENAME::lpstrDefExt (デフォルト拡張子) よりも、OPENFILENAME::lpstrFilter (フィルタ)に指定した文字列が優先される。 -OPENFILENAME::lpstrDefExt (デフォルト拡張子) にNULLを指定すると、拡張子自動付加は行われない。 ※デフォルト拡張子は「.」を含まない。 ***ファイル選択(保存)ダイアログ使用時にアプリケーションが落ちる現象 [#z9897604] ファイル選択ダイアログを使用していると、呼び出し元のアプリケーションごと強制終了してしまう現象が発生している。PCによって起きたり起きなかったりする。 -発生条件 ~AdobeReader7.0がインストールされていること。 -現象 ~現象は、あるアプリケーションでファイル選択ダイアログを立ち上げ(メモ帳の開くなど)、何かのファイル(フォルダでは起きない)にカーソルを合わせ、ツールチップを表示させる。~ 一度ファイル選択ダイアログを閉じて再度同じ操作をすると、ツールチップが表示される瞬間に強制終了される。 -対処法 ~根本的な解決策は分かっていない。AdobeReader7.0が原因という話もあるが、アンインストールしても改善しなかった。~ 自作のアプリケーションであれば、ファイル選択ダイアログを起動する前に、CoInitialize()を呼ぶと落ちなくなる。ただし、CoInitialize() を使用するのであれば、対応する CoUninitialize() を忘れないこと。~ 既存のアプリケーション(メモ帳など)では、フォルダオプションでツールチップ表示機能をOFFにするしかない。 -原因 ~本当に原因なのかは未確認だが、ネットに pdfshell.dll が原因ではないかという記述を見つけた。下記の何れかに存在する。~ → (AdobeReaderインストールフォルダ)\ActiveX~ → C:\Program Files\Common Files\Adobe\Acrobat\ActiveX **文字列処理 [#l9e19a41] ***マルチバイト文字とワイド文字のサンク機能 [#d510747d] Wwindowsプログラミングをする上で、文字列を「マルチバイト」と「ワイド文字」のどちらで扱うかに気をつける必要がある。 -マルチバイト文字(MBCS) ~char型に格納して扱う。Asciiコードなどの半角英数字は1バイトだが、日本語等の全角文字は2バイトなので、2要素に分割して格納される。 -ワイド文字(Unicode) ~wchar_t型に格納して扱う。1文字を常に2バイト(1つの数値)で扱う。~ wchar_t型は、C言語では unsigned short に typedef されており、C++では予約語になっている。~ #br ~Win32APIやMFCには、これらの文字セットの切り替えを容易にした仕組み(thunk)が用意されており、これに則ってプログラミングすることで、どちらの文字セットを使用するかをコンパイルスイッチで切り替えることができる。~ -全般 ||サンク|マルチバイト文字(MBCS)|ワイド文字(Unicode)|h |文字&br;文字列|_T('~')&br;_T("~")|'~'&br;"~"|L'~'&br;L"~"| |変数型|TCHAR|CHAR&br;char|WCHAR&br;wchar_t| |~|LPTSTR&br;TCHAR*|LPSTR&br;char*|LPWSTR&br;wchar_t*| |~|LPCTSTR&br;const TCHAR*|LPCSTR&br;const char*|LPCWSTR&br;const wchar_t*| |Win32API 関数(例)|MessageBox()|MessageBoxA()|MessageBoxW()| |Win32API 構造体(例)|OPENFILENAME|OPENFILENAMEA|OPENFILENAMEW| |MFC クラス(例)|CString/&br;CStringT(※VC6.0では未対応)|CStringA|CStringW| -C標準関数(一部) |分類|サンク|マルチバイト文字(MBCS)|ワイド文字(Unicode)|h |文字列|_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オブジェクトの変換 [#t25669ac] constな文字列型(LPCTSTR)への変換しか許されていない。~ それ以外は、変換関数が用意されていないため。 ***文字列のコンバート (VBのStrConv()相当) [#i5b5ee29] ::LCMapString()で、ひらがな/カタカナ、半角/全角、大文字/小文字の相互変換が可能。 ***ファイルパス長関係のマクロの意味 [#d479626e] これらは、「バイト数」ではなく「文字数」を定義したもの。~ マルチバイト文字を扱う場合は注意が必要。 |マクロ名|値|意味|h |_MAX_PATH|260|フルパス| |_MAX_DRIVE|3|ドライブ| |_MAX_DIR|256|ディレクトリ| |_MAX_FNAME|256|ファイル名(拡張子除く)| |_MAX_EXT|256|拡張子(「.」含む)| ↑ <stdlib.h> に定義されている ***各種文字列型の相互変換 [#n70c2ccd] |>||>|>|変換元 src|h |>|~|char []|(STL) string|CString|h |変換先&br;dst|char []||strcpy(dst, src.c_str());|strcpy(dst, src);| |~|(STL) string|dst = src;&br;dst = string(src);&br;string dst(src);||dst = src.c_str();&br;dst = CString(src.c_str());&br;CString dst(src.c_str());| |~|CString|dst = src;&br;dst = CString(src);&br;CString dst(src);|dst = src.c_str();&br;dst = CString(src.c_str());&br;CString dst(src.c_str());|| |~|BSTR|dst = ::SysAllocString(src);&br;:&br;::SysFreeString(dst);|dst = ::SysAllocString(src.c_str());&br;:&br;::SysFreeString(dst);|dst = src.AllocSysString();&br;:&br;::SysFreeString(dst);| |~|CComBSTR|dst = src;&br;dst = CComBSTR(src);&br;CComBSTR dst(src);|dst = src.c_str();&br;dst = CComBSTR(src.c_str());&br;CComBSTR dst(src.c_str());|dst.Attach(src.AllocSysString());| ~ |>||>|変換元 src|h |>|~|BSTR|CComBSTR|h |変換先&br;dst|char []|::WideCharToMultiByte(CP_ACP, 0, (OLECHAR*)src, -1, dst, sizeof(dst)-1, NULL, NULL);|直接変換する方法はない| |~|(STL) string|直接変換する方法はない|直接変換する方法はない| |~|CString|dst = src;&br;dst = CString(src);&br;CString dst(src);|dst = (BSTR)src;&br;dst = CString((BSTR)src);&br;CString dst((BSTR)src);| |~|BSTR||dst = src.Copy();&br;:&br;::SysFreeString(dst);| |~|CComBSTR|dst = src;&br;dst = CComBSTR(src);&br;CComBSTR dst(src);|| ***マルチバイト文字の判定 [#bccb9a6f] _ismbblead(),_ismbbtrail() を使用すると、「その文字がマルチバイト文字の第1バイト/第2バイトに相当するか」をチェック可能。~ しかし、マルチバイト文字の第1バイトと第2バイトの取り得る数値の範囲は重複しているため、この関数では「そのバイトが第1バイトと第2バイトのどちらか」という判定には使えない。 例)「り」(0x82E8) の第2バイトである「0xE8」は、第1バイト/第2バイトどちらでも使用される値である。 その場合は、_ismbslead(), _ismbstrail() を使用し、文脈から判定させることで解決できる。~ ただし、指定した文字列全体のチェックを行うため、パフォーマンスは低いことに注意。 **ファイル処理 [#m33fb060] ***ファイル(ドライブ、ディレクトリ)の存在確認 [#c834cbdc] -''Win32API'' ~ファイル属性を取得する ::GetFileAttributes() で確認可能。ファイルが存在しない場合は、0xFFFFFFFF を返す。~ または、PathFileExists() や PathIsDirectory()。 -''C標準ライブラリ'' ~access() や stat() 。 ***ファイルのプロパティダイアログの表示 [#a2069ff1] ファイルを右クリックして「プロパティ」を選択したときのダイアログのこと。::ShellExecuteEx() を使用する。 ***フォルダの属性変更 [#m8294ea4] なぜかできない。CFile::SetStatus() だと例外が発生し、::SetFileAttributes() だと何も起こらず変更もされない。 **デバイスコンテキスト/画像処理 [#of44cd7a] ***用途別のデバイスコンテキスト [#f9290d55] |MFCクラス|用途|備考|h |CPaintDC|更新領域のみに対して描画する場合に使用する。|・コンストラクタでBeginPaint()、デストラクタでEndPaint()が呼び出されているため、クリッピングリージョンが自動的に設定される。(WS_CLIPSIBLINGSスタイルで兄弟ウィンドウをクリップにしている場合など、自動的に適用される)&br;・OnPaint()の処理内でしか使用できない。OnDraw()の引数で渡されるpDcも元はこれ。(OnDraw()はOnPaint()から呼び出されている)| |CClientDC|ウィンドウのクライアント領域全体に対して描画する場合に使用する。|・コンストラクタでGetDC()、デストラクタでReleaseDC()が呼び出される。&br;・領域のクリッピング等は自前で行う必要がある。| |CWindowDC|ウィンドウの非クライアント領域(キャプションやメニューなど)も含めた領域全体に対して描画する場合に使用する。|・コンストラクタでGetWindowDC()、デストラクタでReleaseDC()が呼び出される。&br;・領域のクリッピング等は自前で行う必要がある。| ***ダブルバッファリング [#q6be4fa4] 1つ1つの描画処理を行うたびに画面が更新されてしまうことによってチラつきが発生してしまう。そこで、描画処理は裏画面に対して行っておき、描画処理が終わった時に画面更新を1度だけ行うことでチラつきを防ぐ手法がダブルバッファリング。以下はダブルバッファリングの実装例。 +ウィンドウ(ダイアログ)クラスのメンバ変数として以下を定義しておく。 #code(c){{ // CStatic m_PcCtrl ピクチャコントロール CDC m_memDC; // メモリデバイスコンテキスト(裏画面) CBitmap m_memBmp; // メモリデバイスコンテキストの実体ビットマップ CBitmap *m_pOldBmp; // メモリデバイスコンテキストの旧実体 CRect m_PcRect; // キャンバスの矩形領域 }} +アプリケーションの初期処理で、デバイスコンテキストと裏画面の設定を行う。 #code(c){{ 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); }} +アプリケーションの終了処理で、デバイスコンテキストと裏画面の解放を行う。 #code(c){{ // メモリデバイスコンテキストのビットマップを元に戻す m_memDC.SelectObject(m_pOldBmp); // 実体ビットマップを削除 m_memBmp.DeleteObject(); // メモリデバイスコンテキストを削除 m_memDC.DeleteDC(); }} +CWnd::OnPaint() 等の再描画処理で、裏画面からの転送処理を行う。 #code(c){{ CPaintDC dc(this); // device context for painting // TODO : ここにメッセージ ハンドラ コードを追加します。 // 描画メッセージで CStatic::OnPaint() を呼び出さないでください。 // ピクチャコントロールにメモリデバイスコンテキストの内容を転送 dc.BitBlt(0, 0, m_PcRect.Width(), m_PcRect.Height(), &m_memDC, 0, 0, SRCCOPY); }} +実際の描画処理。~ 描画は裏画面に対して行い、最後にウィンドウの再描画を促す。 #code(c){{ // m_memDC に対して描画処理を行う // 再描画 InvalidateRect(NULL, FALSE); // 領域を無効化 UpdateWindow(); // 領域を無効化して呼ぶことで、WM_PAINTを送信する }} ***ウィンドウキャプチャ(ハードコピー) [#je77318f] #code(c){{ // CWnd *pWnd キャプチャ対象のウィンドウクラス CDC capDC; // ウィンドウキャプチャのDC CBitmap capBmp; // ウィンドウキャプチャの実体ビットマップ CBitmap *pBmpOld; // 旧Bitmap CRect wndRect; // ウィンドウの矩形 CWindowDC wndDC(pWnd); // ウィンドウDC // ウィンドウの矩形を取得 pWnd->GetWindowRect(&wndRect); // ウィンドウキャプチャのDCの作成 capDC.CreateCompatibleDC(NULL); // 実体ビットマップの作成 capBmp.CreateBitmap(wndRect.Width(), wndRect.Height(), wndDC.GetDeviceCaps(PLANES), wndDC.GetDeviceCaps(BITSPIXEL), NULL); // ウィンドウキャプチャのDCに実体を関連付ける pBmpOld = capDC.SelectObject(&capBmp); capDC.SetMapMode(MM_TEXT); // マップモードは1ピクセル単位 // キャプチャ capDC.BitBlt(0, 0, wndRect.Width(), wndRect.Height(), &wndDC, 0, 0, SRCCOPY); // capBmp にキャプチャされた画像が保持されている // @TODO // メモリデバイスコンテキストの実体を戻す capDC.SelectObject(pBmpOld); // メモリデバイスコンテキストを削除 capDC.DeleteDC(); // ビットマップを削除 capBmp.DeleteObject(); }} ***ビットマップのコピー [#e1e1dce3] メモリ上のビットマップを複製する方法。 -''MFC'' #code(c){{ // CBitmap bmpSrc; // コピー元のビットマップ 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'' #code(c){{ // HBITMAP hBmpSrc; // コピー元のビットマップ 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); // Win32では使用不可? } delete[] bmpBits; } }} ※SetBitmapBits()の替わりにSetDIBits()を使う必要があるらしい。 ***DCの内容をビットマップファイルとして保存 [#jb197919] ビットマップファイルは次のような構造になっている。 +BITMAPFILEHEADER +BITMAPINFOHEADER +(カラーパレット) +ピクセルデータ 上記の順番でバイナリデータをファイルに保存していくことで、ビットマップファイルを作成できる。~ 以下に、24bitフルカラーでのビットマップファイルの保存処理の例を示す。~ ※フルカラーの場合、カラーパレットは格納しない。 #code(c){{ // LPCTSTR szFilePath 保存ファイルパス // HBITMAP hBmp 保存対象のBitmapハンドル // HDC hDC デバイスコンテキスト DWORD dwFileAttr; // ファイル属性 HANDLE hFile; // Bitmapファイルハンドル DWORD dwWritten; // ファイル書込み時の結果の値 DWORD dwInfoHdrSize; // BITMAPINFOHEADER(+カラーパレット)のサイズ DWORD dwScanDataSize; // ピクセルデータのサイズ BITMAPFILEHEADER bmpFileHeader; // BITMAPFILEHEADER BITMAPINFOHEADER *pBmpInfoHdr; // BITMAPINFOHEADER BYTE *pHeaderBuffer; // BITMAPINFOHEADER(+カラーパレット)の実体 BYTE *pScanDataBuffer; // ピクセルデータの実体 BITMAP bmp; // Bitmap実体 BOOL bRet; // 戻り値 // ハンドルからBitmapの実体を取得 GetObject(hBmp, sizeof(BITMAP), &bmp); //////// BITMAPINFOHEADER //////// // BITMAPINFOHEADERのサイズを決定 dwInfoHdrSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 0; // フルカラーの場合はカラーパレットを使用しない // BITMAPINFOHEADERの実体を作成 pHeaderBuffer = new BYTE[dwInfoHdrSize]; memset(pHeaderBuffer, 0, dwInfoHdrSize); pBmpInfoHdr = (BITMAPINFOHEADER*)pHeaderBuffer; // 情報を格納 pBmpInfoHdr->biSize = sizeof(BITMAPINFOHEADER); // BITMAPINFOHEADERのサイズ pBmpInfoHdr->biBitCount = 24; // 1ピクセルあたりのビット数(画像の色数) フルカラーは24bit pBmpInfoHdr->biWidth = bmp.bmWidth; // Bitmap画像の幅 [pixel] pBmpInfoHdr->biHeight = bmp.bmHeight; // Bitmap画像の高さ [pixel] pBmpInfoHdr->biPlanes = 1; // 1を格納する pBmpInfoHdr->biCompression = BI_RGB; // 圧縮状態 BI_RGB:非圧縮 pBmpInfoHdr->biSizeImage = 0; // 画像サイズ [Byte] 後で::GetDIBits()で取得する pBmpInfoHdr->biXPelsPerMeter = 0; // X方向の解像度 pBmpInfoHdr->biYPelsPerMeter = 0; // Y方向の解像度 pBmpInfoHdr->biClrUsed = 0; // 色の使用数 最大数使用の場合は0を格納 pBmpInfoHdr->biClrImportant = 0; // 色の必要数 全て必要な場合は0を格納 //////// ピクセルデータ //////// // pBmpInfoHdr->biSizeImageを取得する 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); //////// BITMAPFILEHEADER //////// bmpFileHeader.bfType = 0x4d42; // ファイル形式 文字列"BM"をリトルエンディアンで格納 bmpFileHeader.bfReserved1 = 0; // 予約メンバ 0を格納する bmpFileHeader.bfReserved2 = 0; // 予約メンバ 0を格納する bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + dwInfoHdrSize; // ピクセルデータの位置 [Byte] bmpFileHeader.bfSize = dwScanDataSize + bmpFileHeader.bfOffBits; // ファイルサイズ [Byte] //////// Bitmapファイル作成 //////// 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); // BITMAPFILEHEADER WriteFile(hFile, pHeaderBuffer, dwInfoHdrSize, &dwWritten, NULL); // BITMAPINFOHEADER (+カラーパレット) WriteFile(hFile, pScanDataBuffer, dwScanDataSize, &dwWritten, NULL); // ピクセルデータ CloseHandle(hFile); } // メモリ解放 delete[] pScanDataBuffer; delete[] pHeaderBuffer; }} ***描画時の兄弟ウィンドウのクリッピング [#d194e502] WS_CLIPSIBLINGSスタイルを指定すると、OnPaint()やOnDraw()に用意される標準のデバイスコンテキストは兄弟ウィンドウがクリップ(兄弟ウィンドウの上に描画処理を行わないようにリージョンを除外すること)された状態となる。理由は、BeginPaint() によって作られたDCのため。(WS_CLIPCHILDRENスタイルの場合も同様に、子ウィンドウがクリップされた状態になると思われる。)~ しかし、CClientDCでクライアント領域全体のデバイスコンテキストを取得してそれに対して描画を行うような場合は、常に領域全体が描画対象になっているため、必要に応じて自前でクリップ処理を作成するしかない。~ 自作コントロールの描画処理を行う場合などは注意が必要。~ 以下、兄弟ウィンドウをクリップする処理を自前で作成する場合の例。~ (※コントロール描画用のデバイスコンテキストとして CClientDC型 のメンバ変数 m_pDC が用意されているものとする。)~ -コントロールの描画処理 #code(c){{ // WS_CLIPSIBLINGS スタイルが設定されている if((this->GetStyle() & WS_CLIPSIBLINGS) != 0){ // 兄弟ウィンドウを列挙してクリップ ::EnumChildWindows(GetParent()->GetSafeHwnd(), ClipSiblingsProc, (LPARAM)this); } // 描画処理 // 元に戻す // WS_CLIPSIBLINGS スタイルが設定されている if((this->GetStyle() & WS_CLIPSIBLINGS) != 0){ m_pDC->SelectClipRgn(NULL); } }} -兄弟ウィンドウのクリッピング処理(static CALLBACK にしておくこと) #code(c){{ 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; } }} **フォント [#bf2344c3] ***フォントを変更する際の注意点 [#u289bbd2] フォントの設定にCFontクラスのオブジェクトを用いる場合、メンバ変数にしておく必要がある。~ ローカル変数だと、スコープを抜けたときに変数が自動的に破棄され、デストラクタの働きによってフォントの内容が削除されてしまうため、正しくフォントが反映されない。 ***単位系 [#ta347f7a] コンピュータの世界でよく用いられる長さに関する単位。 |単位|説明|換算|h |pixel|文字や画像を表示する際の最小要素。画素。&br;ディスプレイ上ではdotと同義。|-| |Twip|もとはVBでフォーム設計する際に用いられる長さの単位。&br;リッチエディットコントロール上ではこの単位がもちいられる。(要確認)&br;mmやinchよりも細かく指定できる。|1Twip = 1/20point = 1/1440inch| |point|フォントの大きさを表す|1point = 1/72inch| |inch|一般的に使われる長さ。コンピュータの世界ではmmより使われる頻度が高い?|-| |dpi|dot per inch。1inchあたりのdot数。&br;ディスプレイ上では1inchあたりのpixel数と同義。&br;PCの解像度は通常96dpi。|-| ***セル高さと文字高さ [#f80f2072] アクセント記号を含めた高さをセル高さ(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構造体によるフォントサイズの指定 [#r782dcb2] LOGFONT:lfHeightでフォントの高さ、LOGFONT:lfWidthでフォントの幅を指定する。~ -lfHeight~ 0を指定すると、そのフォントのデフォルトサイズを指定したことになる。~ 正数を指定すると、セル高さを指定したことになる。~ 負数を指定すると、その絶対値を文字高さとして指定したことになる。~ 実際には、そのフォントで使用できるサイズのうち、指定された値を超えない最大のサイズが使用される。~ ~ 指定する値の単位は論理単位(マッピングモード依存)。通常はMM_TEXTマッピングモードなので、論理単位=ピクセル数。~ フォントのpoint数を用いて指定する場合は、以下の変換式を用いる。~ #code(c){{ // pixel数 = (point数 * dpiY) / 72 ※dpiY : dot per inchのY方向の値 lfHeight = -::MulDiv(lPoint, GetDeviceCaps(hDC, LOGPIXELSY), 72); }} -lfWidth~ 0を指定すると、フォントの種類と lfHeight から適切な値が自動決定される。~ 必ずしも lfHeight/2 となるわけではないことに注意。~ lfWidth に 0 を指定することで実際に幅がいくつになるかは、CDC::GetTextMetrics() を使用して TEXTMETRIC::tmAveCharWidth を確認してみるのが良い。等幅フォントの場合は、全ての文字がこの幅に設定されることになる。 **DLL [#df7cb44a] ***リソースのみを含むDLL [#oe51089d] ソースコード(エントリポイント)を持たないDLLを作るには、プロジェクト設定の「リンク」タブの「一般」カテゴリで、「プロジェクトオプション」に "/NOENTRY" を追加する。 ***DLL内での実行ファイルのパス取得 [#v9685502] WIN32APIの ::GetModuleFileName() を使用することで、実行ファイルのパスを取得できる。~ DLL内部のコードから ::GetModuleFileName() を使用した場合、第1引数にDLLのインスタンスハンドルを渡せばDLLのパスが取得できるが、NULLを渡した場合は呼び出し元のEXEのパスの取得となる。 ***DLLからモードレスダイアログを表示 [#w560662f] DLLからモードレスダイアログを表示する場合、タブストップやキー入力等の処理がデフォルトで行われない。→[[参考:http://support.microsoft.com/kb/233263/ja]]~ モーダルダイアログのようにタブストップ等を処理させるには、メッセージをフックする必要がある。 +グローバルな変数に、対象となるダイアログクラスオブジェクトのアドレスを渡しておく。 #code(c){{ CXxxDlg *g_pDlg; }} +ダイアログクラスのヘッダファイルで、フックハンドルとフックプロシージャを定義する。 #code(c){{ public: // フックハンドル HHOOK m_hHook; private: // メッセージ監視フックプロシージャ static long CALLBACK GetMsgProc(int, WPARAM, LPARAM); }} +CXxxDlg::OnInitDialog() でフックプロシージャを登録する。 #code(c){{ // メッセージ監視フックプロシージャ m_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)CXxxDlg::GetMsgProc, NULL, ::GetCurrentThreadId()); }} +CXxxDlg::OnDestroy() でフックプロシージャを破棄する。 #code(c){{ // フックプロシージャの破棄 ::UnhookWindowsHookEx(m_hHook); }} +CXxxDlgクラスのソースで、フックプロシージャを実装する。 #code(c){{ // メッセージ監視フックプロシージャ 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)){ // 処理済みなら WM_NULL とする lpMsg->message = WM_NULL; lpMsg->lParam = 0; lpMsg->wParam = 0; } } } // 次のフックプロシージャへ return ::CallNextHookEx(g_pDlg->m_hHook, nCode, wParam, lParam); } }} ***DLL内でMFCリソースの使用 [#r709bcc8] DLL内でダイアログを作成して表示するコードを用意して使用する場合、exportする関数の先頭に以下のコードを記述する必要がある。(COMのDLLも同様) #code(c){{ AFX_MANAGE_STATE(AfxGetStaticModuleState()); }} EXEからexportした関数が呼び出された状態では、リソースハンドル等はEXE用のものを使用する状態になっているため、DLL内では正しい処理が行われない。~ 上記のコードによって、モジュール状態がDLL用のものに切り替わるため、正しい処理が行える。また、スコープを抜けるときに元の状態に戻してくれるため、EXE側に処理が帰った後も問題がない。 ***DLLのデバッグ方法 [#af846f08] 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 [#z4a969ed] ***libファイルのリンク [#ed45b4e8] プロジェクトにlibファイルをリンクするには以下の3つの方法がある。 +プロジェクトにlibファイルを追加する。コマンドラインでのビルドであれば、オブジェクトファイルと一緒にlibファイルを指定する。 +プロジェクトの設定でリンク指定する。 +プリプロセッサで指定する。 #code(c){{ #pragma comment(lib, "xxx.lib") }} ***自作のスタティックライブラリ [#v57b8948] 自作クラスなどの汎用的なコードは、スタティックライブラリ化しておくと再利用しやすい。 +スタティックライブラリ側のプロジェクト ++スタティックライブラリのプロジェクトを作り、ヘッダとソースを追加する。 ++ヘッダファイルの置き場所はあらかじめ決めておく。複数のライブラリのプロジェクトがあっても、ヘッダは一ヵ所にまとめる。 ++出力ファイルであるLibファイルもあらかじめ決めた場所に出力する。Debug版のLibファイルも、名前を変えて同じ場所に出力させる。 +アプリケーション側のプロジェクト(Libファイルを使用する側) ++プロジェクトの設定で、リンクするLIBファイルを設定する。(Debug版はDebug版のLibファイルを使用する) ++ワークスペースに使用するライブラリのプロジェクトを含め、さらに依存関係を設定しておくと常に最新のライブラリファイルをリンクできる。 +VC++の設定 ++ヘッダファイル、Libファイルを置いたパスをディレクトリ設定に追加する。 **リソーススクリプト [#v66277bf] -http://monado.dtiblog.com/blog-entry-52.html -http://msdn.microsoft.com/en-us/library/aa380599(VS.85).aspx **その他 [#w7ba403a] ***実行対象のWindowsバージョン [#y8998984] Win32API等のOSの機能を使用する場合、OSのバージョンによって利用できる機能と利用できない機能が存在するため、例えばWinXPから実装された機能を使用するアプリケーションは、Win2kでは動作が保証されないことになる。~ つまり、アプリケーションを作成する場合、実行対象のWindowsの最小バージョンを決めておき、それより後のバージョンで実装された機能を使用しないようにする必要がある。~ そこで、Windows用のヘッダファイルには以下のマクロが用意されており、定義された値によって機能が限定されるように作られている。アプリケーションを作る場合、使用する機能によってはこのマクロの値を適宜設定する必要がある。(stdafx.h内のWindows用のヘッダファイルのインクルードよりも前に定義する) -WINVER~ 実行対象のWindowsの最小バージョン -_WIN32_WINNT~ 実行対象のWindowsNTの最小バージョン -_WIN32_IE~ 実行対象の環境にインストールされているIEの最小バージョン #br -Windowsの最小バージョンの定義 |Windows|WINVER&br;_WIN32_WINNT|h |Windows 95|0x0400| |Windows 98|0x0410| |Windows Me|0x0500| |Windows NT 4.0|0x0400| |Windows 2000|0x0500| |Windows XP&br;Windows Server 2003|0x0501| |Windows XP SP2&br;Windows Server 2003 SP1|0x0502| |Windows Vista|0x0600| |Windows 7|0x0601| |Windows 8|0x0602| -IEの最小バージョンの定義 |IE|_WIN32_IE|h |Windows 95&br;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>http://msdn.microsoft.com/ja-jp/library/aa383745.aspx]] ***コマンドライン引数 [#zc6d6927] -''Win32API'' ~::GetCommandLine()で取得可能。~ また、WinMain()で起動された場合は、__argv/__argc マクロを使用することで引数リストを受け取れる。main()の引数 argv/argc のように扱えるので便利。 -''MFC'' ~CWinApp::m_lpCmdLine に格納されている。 ***二重起動防止 [#ud8cf73a] CWinApp 派生クラスの InitInstance() で、ウィンドウ起動処理に以下を追加。 #code(c){{ 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>をインクルードする必要がある。~ ※ ミューテックス作成時に渡す文字列は大文字小文字を区別することに注意。 ***処理中の砂時計カーソル [#e2177ffb] -''Win32API'' ~::LoadIcon() であらかじめ用意されているカーソルから砂時計カーソルを取得して ::SetCursor() で使用する。 #code(c){{ // HINSTANCE hInst アプリケーションのインスタンスハンドル // 砂時計カーソル ::SetCursor(LoadCursor(NULL, IDC_WAIT)); // 任意の処理 // カーソルを元に戻す ::SetCursor(LoadCursor(NULL, IDC_ARROW)); }} -''MFC'' ~任意の処理の先頭で CWaitCursor型の変数を定義するのみ。~ CWaitCursorクラスのコンストラクタが砂時計カーソルを設定し、デストラクタが元に戻す。 #code(c){{ // 砂時計カーソル CWaitCursor waitCursor; // 任意の処理 // 何もしなくても、スコープを抜ければカーソルが元に戻る }} ***カーソルの変更 [#n85c65d2] -''MFC'' ~CWnd::OnSetCursor() を実装する。~ 引数の nHitTest でカーソル位置、message でマウス状態を判断することができる。nHitTest に用いられるマウス列挙子は、MSDNの CWnd::OnNcHitTest() の項を参照。message は、マウス関連のウィンドウメッセージが格納される。~ 実際に設定するにはカーソルハンドルを指定する必要がある。OSに用意されているストックオブジェクトを用いる場合、::LoadCursor() の第一引数にNULLを渡し、第二引数にリソースIDを指定する。ストックオブジェクトのIDは、MSDNの::LoadCursor() の項を参照。~ #br 例) #code(c){{ BOOL CXxxDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { // TODO: この位置にメッセージ ハンドラ用のコードを追加するかまたはデフォルトの処理を呼び出してください HCURSOR hCursor; // カーソルハンドル // カーソルがタイトルバーの上 if(nHitTest == HTCAPTION){ // 左クリック押下中 if(message == WM_LBUTTONDOWN){ // 全方向矢印 hCursor = ::LoadCursor(NULL, IDC_SIZEALL); // カーソルを設定 SetCursor(hCursor); // 独自にカーソルを設定した場合は、TRUEを返して終了する return TRUE; } } // カーソルを変更しない場合は親クラスに任せる return CDialog::OnSetCursor(pWnd, nHitTest, message); } }} ***外部プログラムの起動 [#i8452370] -::CreateProcess()~ 実行可能モジュールを新しいプロセスで起動する関数。~ 基本的に第1引数は NULL にしておき、第2引数の文字列の1つ目のトークンとして実行コマンド名も指定する方が無難。~ 理由は、第1引数に指定したコマンドは、フルパス指定かカレントディレクトリに実体を持つ必要がある。第2引数であれば、コマンドプロンプトと同様、パスが通った場所も検索されるため。 -::ShellExecute()~ シェルにコマンドを実行させる関数。用途は実行ファイルの起動に限らない。~ 関連付けられたアプリケーションでファイルを開くことも簡単にできる。 ***Win32APIやMFCのメンバ関数の戻り値 [#i3976e5f] -BOOL型 (int型) --エラーの詳細を知るためには、::GetLastError() を使用する必要がある。 --戻り値を TRUE(1)と比較してはいけない。条件が「真」であることの定義は「0ではない」ことなので、関数が成功した場合に「1」を返している保障はないことに注意。 -HRESULT型 (long型) --成否の判定には、SUCCEEDED() / FAILED() マクロを用いる。~ (値の正負を判断しているだけ。つまり、HRESULT型は最上位ビットが成否を表している。) --戻り値からエラーの詳細を知ることができる。 --HRESULT型には、S_FALSE という、「エラーではないが失敗」という微妙なニュアンスを表す値が存在する。上記マクロを使用した場合、「真」と判断されるため注意。 ***クリップボードの読み書き [#k28e4563] -サンプルコード --テキストの読み込み #code(c){{ // char *pStr 読み込む文字列 // int nMaxCount 読み込む最大文字数 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(); }} --テキストの書き込み #code(c){{ // char *pStr 書き込む文字列 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(); // ※クリップボードへの書込みに成功したら、hMemは解放してはいけない } else { // メモリの解放 ::GlobalFree(hMem); } }} -クリップボードを閉じた後でのデータの変更~ クリップボードにはハンドル(グローバルな領域のアドレスを指す)が設定されているだけであり、ビットマップや文字列のデータそのものがコピーされているわけではない。~ そのため、クリップボードを閉じた後であっても、データを変更するとクリップボードの指す先の内容を変更したことになってしまう。 #code(c){{ // HBITMAP hBmpは、ビットマップデータのハンドル // クリップボードを開く if(::OpenClipboard(hWnd)){ // クリップボードをクリアして所有権を得る if(::EmptyClipboard()){ // ビットマップをクリップボードに設定 ::SetClipboardData(CF_BITMAP, hBmp); } // クリップボードを閉じる ::CloseClipboard(); } // ここでhBmpの画像データを変更すると、クリップボードに設定された画像データも変更されることになる }} ***アクセラレータ [#t171c798] アプリケーションのショートカットキーを実現するアクセラレータの使用方法。 +リソースにアクセラレータテーブルを追加する。(例:IDR_ACCELERATOR) +アクセラレータを関連付けるウィンドウ(ダイアログ)クラスの CWnd::PreTranslateMessage() をオーバライドし、以下のコードを追加する。 #code(c){{ BOOL CxxxxDlg::PreTranslateMessage(MSG* pMsg) { // TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください HACCEL hAccKey; // アクセラレータテーブルハンドル // アクセラレータテーブルハンドルを取得 hAccKey = ::LoadAccelerators(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_ACCELERATOR)); if (hAccKey != NULL){ // アクセラレータ処理 if (::TranslateAccelerator(this->m_hWnd, hAccKey, pMsg)){ // アクセラレータとして処理された場合は、TRUEを返して終了 return TRUE; } } return CDialog::PreTranslateMessage(pMsg); } }} +アクセラレータテーブルにショートカットキーを登録する。(例:「Ctrl+C」→ ID_ACCEL_CTRL_C) +クラスウィザードで、アクセラレータをウィンドウ(ダイアログ)クラスに関連付け、COMMANDメッセージのイベントハンドラを作成する。 ***タスクトレイ常駐 [#ga41bb66] タスクトレイに常駐するアプリケーションの基本的な作成方法。 +リソースに、タスクトレイに表示するアイコンと、ポップアップ用のメニューを追加する。 ++アイコン~ アイコンを切り替える場合は、複数用意しておく。(例:IDR_MAINFRAME, IDR_SUBICON) ++ポップアップメニュー~ 通常のメニューと同様に追加する。ポップアップ用メニューはサブメニューとして定義する必要がある。(例:IDR_POPUPMENU) +ポップアップメニューの項目のイベントハンドラを実装しておく。 +ウィンドウ(ダイアログ)クラスのメンバ変数として以下を定義しておく。 #code(c){{ NOTIFYICONDATA m_notifyIcon; // タスクトレイのアイコンデータ }} +タスクトレイのアイコンがクリックされた場合のユーザ定義メッセージを定義する。 #code(c){{ #define WM_USER_ACTION (WM_USER + 100) }} +アプリケーションの初期処理で、メインウィンドウを非表示し、タスクトレイにアイコンを設定する。 #code(c){{ // ウィンドウの表示を消す 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; // メンバの hIcon, uCallbackMessage, szTip を有効にする 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() 等のウィンドウプロシージャにユーザ定義メッセージのハンドラを追加する。 #code(c){{ POINT pntCur; // マウスカーソルの位置 CMenu mainMenu; // メインメニュー CMenu *pPopupMenu; // ポップアップメニュー // メッセージ処理 switch(message){ // タスクトレイアイコンのユーザ定義メッセージ case WM_USER_ACTION: // wParam : m_notifyIcon.uIDの値 // lParam : マウスメッセージ // カーソルの現在位置を取得 ::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){ //////// 以降、バルーンチップ関連 (※VC++6.0では非対応) //////// // バルーンが表示されたとき } else if(lParam == NIN_BALLOONSHOW){ // バルーンがタイムアウトで消えたとき } else if(lParam == NIN_BALLOONTIMEOUT){ // バルーンが通知領域のクリックにより消えたとき } else if(lParam == NIN_BALLOONUSERCLICK){ // バルーンが×ボタンクリックにより消えたとき } else if(lParam == NIN_BALLOONHIDE){ } break; } }} +アプリケーションの終了処理で、タスクトレイからアイコンを削除する。 #code(c){{ // タスクトレイのアイコンを削除 ::Shell_NotifyIcon(NIM_DELETE, &m_notifyIcon); }} +タスクトレイのアイコンに対する処理の例。 #code(c){{ //////// バルーンチップの例 (※VC++6.0では非対応) //////// m_notifyIcon.uFlags |= NIF_INFO; // バルーンチップ用のメンバを有効にする m_notifyIcon.uTimeout = 3000; // 表示時間 3000ms 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); }} -注意 ::Shell_NotifyIcon()は、時間がかかりすぎるとタイムアウトで失敗するとのこと。~ その場合、タスクトレイにアイコンが表示されなくなるので注意。 参考:http://support.microsoft.com/kb/418138/JA/ ***プロセス間共有メモリ [#e8896b6a] 通常、異なるアプリケーション間では別々のメモリ空間が使用されるため、メモリを共有することができない。(アプリAのメモリアドレス0x00001111は、アプリBのメモリアドレス0x00001111とは別の領域である)~ そこで、OS側に仮想的なメモリ空間を作成してもらい、そのメモリ空間をそれぞれのアプリケーションが自プロセスのメモリ空間にマッピングして使用することで、あたかも同じメモリを操作しているように振る舞うことができる。~ アプリAが共有メモリの内容を変更すると、それをマッピングしている全てのプロセスの共有メモリが同じように変更される。~ 変更が即時反映されるファイルを複数プロセスで同時にオープンしているイメージ。共有メモリは実体がない(正確にはOSが管理するメモリ空間に実体を持つらしい)ため、一意な名前をつけて識別する必要がある。 +共有ファイル作成に使用する変数等の定義 #code(c){{ #define FMAP_KEY "TEST_FMAP" // 共有メモリの識別名 #define FMAP_SIZE (sizeof(char) * 100) // サイズ:100バイト HANDLE hFileMap; // 共有メモリのハンドル char *pData; // 共有メモリのマッピング先アドレス }} +共有ファイルの使用開始 #code(c){{ // 共有メモリの実体を作成する // ※既に実体が作成されている場合はオープンとなる。 // 明示的にオープンする場合は::OpenFileMapping()を使用する。 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); }} +共有ファイルの使用終了 #code(c){{ // 共有メモリのビューのマッピングを終了 ::UnmapViewOfFile(pData); pData = NULL; // 共有メモリのハンドルを閉じる ::CloseHandle(hFileMap); hFileMap = NULL; }} ***アプリケーションのアイコン設定 [#b9dd2934] -エクスプローラ上で表示されるexeファイルのアイコン~ アプリケーションのリソースファイル(*.rc)内で定義されているアイコンの内、IDが最小のものが使用される。~ コンソールアプリケーションのアイコンを変更したい場合も、アイコンを定義したリソースファイルとともにビルドすることで実現できる。 -ウィンドウ左上のアイコン~ コード中で明示的にアイコンを設定する必要がある。~ コンソールアプリケーションの場合は変更不可。 --''Win32API''~ ::RegisterClassEx() に指定するウィンドウクラス(WNDCLASS)に、アイコンハンドルを登録する。 --''MFC''~ CWnd::SetIcon() を使用してアイコンを設定する。 ***アプリケーションのバージョン情報の取得 [#zeaf5ee0] 対象の実行ファイルのパスから、バージョン情報を取得できる。 #code(c){{ // char szPath[] モジュールのパス // バージョン情報リソースのサイズを取得 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; // 以下の形式で取得するバージョン情報を指定する // 「\StringFileInfo\[①言語識別子&コードページ識別子(16進文字列)]\[②定義済みバージョン情報識別名]」 // ①は、日本語の場合"041104b0"となる // FileVersionを取得 CString strFileVersion; ::VerQueryValue(pVerInfo, "\\StringFileInfo\\041104b0\\FileVersion", &pvVersion, &VersionLen); strFileVersion = (LPCTSTR)pvVersion; delete[] pVerInfo; return TRUE; }} ***CreateProcess()を使用する場合のリダイレクトの方法 [#if12709f] 外部コマンドを起動して標準出力をリダイレクトする方法。(test.exe > stdou.txt) #code(c){{ // HANDLE hFile // リダイレクト先のハンドル(テキストファイルなど) BOOL bRet; DWORD dwEndCode; PROCESS_INFORMATION pi; STARTUPINFO si; // 起動情報 ZeroMemory(&si, sizeof (si)); si.cb = sizeof(si); si.dwFlags |= STARTF_USESTDHANDLES; // hStdOutputの使用フラグ si.hStdOutput = (HANDLE)hTxtFile; // リダイレクト先に指定する // プロセス起動 (第5引数をTRUEにする) 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)~ 同様の手順で、標準入力や標準エラー出力もリダイレクト可能。~ ***ファイルのアイコン表示(エクスプローラと同じ表示) [#l2b40658] -アイコンを単体で取得する場合 #code(c){{ SHFILEINFO sfi // 小さいアイコン(16x16) HICON hIconSmall; hIconSmall = ::SHGetFileInfo(ファイルパス, 0, &sfi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_SMALLICON); // 大きいアイコン(32x32) HICON hIconLarge; hIconLarge = ::SHGetFileInfo(ファイルパス, 0, &sfi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_LARGEICON); }} -アイコンをイメージリストとして取得する場合(システムイメージリストの取得) #code(c){{ SHFILEINFO sfi // 小さいアイコン(16x16) 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); }} #code(c){{ // アイコンを使用するときのインデックス SHFILEINFO sfi SHGetFileInfo(ファイルパス, 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX); // sfi.iIcon にイメージリストのインデックスが取得される }} リストビュー、ツリービュー、タブなどのコントロールにアイコンを表示する場合は、イメージリストを使用した方法を使用する必要がある。~ あらかじめ、CListCtrl::SetImageList()、ListView_SetImageList() などでイメージリストをコントロールに登録しておき、CListCtrl::InsertItem()、ListView_InsertItem() でアイテムを挿入する際にアイコンのインデックスを指定することで表示される。~ ※コントロールにシステムイメージリストを使用する場合は、ウィンドウスタイルに LVS_SHAREIMAGELISTS (リストビューコントロールの場合) を設定しておくこと。 ***TCPサーバーの通信切断検知 [#mc9fe05c] TCP通信を行っている際、LANケーブルが抜けるなどで通信切断されてしまってもサーバー側はしばらく復帰を待ってしまいなかなか通信切断を検知してくれない。~ この動作は、Keep-Aliveパケットを投げて通信が復帰しないことを確認して諦めるまでの時間に依存し、下記3つのパラメータによって時間が決定される。 |パラメータ|説明|デフォルト値(Linux)|h |TCP_KEEPIDLE|Keep-Aliveを投げ始めるまでの待機時間(秒)|7200| |TCP_KEEPINTVL|Keep-Aliveを投げる間隔(秒)|75| |TCP_KEEPCNT|Keep-Aliveを投げる回数|9| デフォルトだと、7200 + 75 * 9 = 7875秒 待機してしまうことになるので、もっと早く切断を検知してほしい場合は下記のようにパラメータを変更しておく必要がある。 #code(c){{ // sock : ソケットオブジェクト int option = 1; // trueの意味 setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &option, sizeof(option)); // 5 + 1 * 5 = 10秒 で切断検知する例 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 ***同期処理/排他制御 [#da47c13b] |>||セマフォ|ミューテックス|クリティカルセクション|h |>|同時に所有できる数|上限を設定可能|1つまで|1つまで| |>|使用可能な範囲|プロセスをまたいで使用可能|プロセスをまたいで使用可能|同一プロセス内でのみ使用可能| |Win32API|オブジェクトの作成|CreateSemaphore|CreateMutex|InitializeCriticalSection| |~|所有権の取得|>|WaitForSingleObject に代表される待機関数|EnterCriticalSection| |~|所有権の破棄|ReleaseSemaphore|ReleaseMutex|LeaveCriticalSection| *C/C++ネイティブ [#u9b2e1bf] **C/C++共通 [#ccfc5367] ***定義済みプリプロセッサマクロ [#a736dd8d] |カテゴリ|マクロ名|意味|h |ANSI C&br;(C90)|__STDC__|標準規格のC言語であることを示す&br;K&Rとの区別に用いる| |~|__FILE__|ソースファイルパス| |~|__LINE__|ソースの行番号| |~|__TIMESTAMP__|コンパイル日時| |~|__DATE__|コンパイル日| |~|__TIME__|コンパイル時刻| |C99|__func__|関数名| |C++|__cplusplus|C++であることを示す&br;値が1とは限らない| |文字セット&br;(どちらか一方)|_MBCS|マルチバイト文字セットであることを示す| |~|_UNICODE|ワイド文字(Unicode)セットであることを示す| |ビルド構成&br;(どちらか一方)|_DEBUG|Debug版| |~|NDEBUG|Release版| ***const修飾子の効力 [#g654f90b] ある関数Aでconst修飾された引数を、さらに関数Bの非constな引数として渡すことができてしまう。もちろん関数B内で値の書き換えも可能。~ コンパイラの警告レベルによっては、警告は出力される。 ***CALLBACK関数の定義 [#oaa2d339] CALLBACK関数は、グローバル関数やstaticメンバ関数などの静的な関数として定義する必要がある。~ その他、関数ポインタを使用する仕組みも同様。~ クラスのメンバ関数はインスタンスが生成されるまでアドレス空間のどの位置に配置されるかが決まっていないため、ポインタにアドレスを格納できない。 ***ワイルドカード(*, ?)を用いた文字列検索 [#i6ddb4a0] 参考:http://katsura-kotonoha.sakura.ne.jp/prog/c/tip00003.shtml #code(c){{ // ワイルドカードを使用した文字列検索 // ※BOOLはintで代用可 BOOL strmatch(const char *ptn, const char *str) { BOOL bmatch = FALSE; switch(*ptn){ // パターン文字列の末尾 case '\0': // 検索対象文字列も末尾なら真 bmatch = (*str == '\0'); break; // * : 0文字以上の任意の文字列 case '*': // '*'が0文字とマッチしたと仮定して検索を継続 if(!(bmatch = strmatch(ptn+1, str))){ // '*'が1文字以上にマッチしていると仮定して検索を継続 if(*str != '\0'){ bmatch = strmatch(ptn, str+1); } } break; // ? : 任意の1文字 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; } }} ***関数シグニチャのマングルについて [#g5c5cfeb] 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~ ***構造化例外のキャッチ [#hdd334cf] 例えば、メモリアクセス違反(アクセスバイオレーション)などが構造化例外。~ C++のtry-catch構文でキャッチするためにはコンパイルオプション「/EHa」が必要。(VC6の場合は設定画面に項目がないため、自分で追記する必要がある)~ ただし、発生する例外はCException型ではないことに注意。以下のように全ての例外をキャッチすれば捕まるが、詳細情報は取れない。~ #code(c){{ try{ // アクセスバイオレーション発生 } catch(...) { printf("例外発生\n"); } }} Visual Studio を使用している場合は、MS独自仕様の __try-__except構文で構造化例外をキャッチできる。~ これはC言語のソースでも可能で、コンパイルオプションも不要。~ #code(c){{ #include <excpt.h> : __try{ // アクセスバイオレーション発生 } __except(EXCEPTION_EXECUTE_HANDLER) { printf("例外発生\n"); } }} **C++ [#pda30790] ***static関数/変数 [#lc012adb] -staticなメンバ変数 ~作成されるすべてのオブジェクト、派生クラスのオブジェクトでひとつの静的変数として値をやりとりできる。 -メンバ関数内のstaticなローカル変数 ~staticなメンバ変数と同じ。(スコープはその関数内のみという点が異なる) -staticなメンバ関数 ~クラスのインスタンス生成無しで関数を呼び出せる。 #code(c){{ // 例 CSampleClass::sampleStaticMethod(); }} ***CPPファイルからCファイルに定義された関数を呼び出す [#x9411c03] 関数のシグネチャがCとCPPで異なるため、単純なextern宣言では呼び出せない。(リンクエラーになる) #code(c){{ // グローバル関数 or グローバル変数 extern "C" ~~ // 複数のグローバル関数 extern "C"{ ~~ ~~ } }} ***メンバ初期化子(:) [#hb253078] クラスのメンバ変数を初期化する際、コンストラクタで初期値を代入することもできるが、これは厳密な意味での「初期化」ではなく、値の「代入」である。~ 厳密に初期化を行う場合は、メンバ初期化子「:」を用いる。コンストラクタ初期化子とも呼ばれる。 -コンストラクタでの「代入」 #code(c){{ class CChild { private: int m_count; int m_num; // コンストラクタ CChild() { // 代入 m_count = 0; m_num = 100; } } }} -メンバ初期化子による「初期化」 #code(c){{ class CChild { private: int m_count; int m_num; // コンストラクタ CChild() : m_count(0), m_num(100) { } } }} また、メンバ初期化子を用いることで基底クラスの初期化(と引数の指定)も可能。 #code(c){{ class CChild : public CParent { private: // コンストラクタ CChild() : CParent(1) // メンバ初期化子による基底クラスのコンストラクタの明示呼び出し { } } }} ※メンバ初期化子で複数のメンバを初期化する際に実行される順序は、初期化子に書いた順番ではなくメンバ変数として宣言した順番。 ***実行時型情報(RTTI) [#z042e38a] typeid演算子によって、オブジェクトが何のクラスのインスタンスなのかを実行時に知ることができる。~ 使用するには、プロジェクトの設定で「ランタイムタイプ情報(RTTI)を有効にする」にチェックを付けることでコンパイルオプション「/GR」を指定しなければならない。 ***メンバアクセス演算子でprivateメンバにアクセス [#pba36e4f] 自身のクラス型のオブジェクトのprivateメンバは、たとえインスタンスが異なっていたとしても「.」「->」でアクセス可能。~ 例) #code(c, menu){{ class CSample { private: int num; void Test0() { printf("Test\n"); } public: void Test1(CSample &obj) { printf("%d\n", obj.num); // OK } void Test2(CSample *pObj) { pObj->Test0(); // OK } }; }} *コーディングの慣例 [#k28c86ea] **MFC派生クラスのヘッダファイル [#ye4679d4] VC6で生成されるMFC派生クラスのヘッダファイルの例 #code(c){{{ // MFC派生クラス class CSample : public CBase { DECLARE_DYNAMIC(CSample) // ① // コンストラクション public: // ② // アトリビュート public: // ③ // オペレーション public: // ④ // オーバーライド // ClassWizard は仮想関数のオーバーライドを生成します。 //{{AFX_VIRTUAL(CAbc) // ⑤ //}}AFX_VIRTUAL // インプリメンテーション public: // ⑥ protected: // ⑦ //{{AFX_MSG(CAbc) // ⑧ //}}AFX_MSG private: // ⑨ }; }}} ''説明''~ ||概要|説明|追加|h |①|CObjectクラスの&br;機能の使用宣言 |全てのMFCクラスの基底クラスであるCObjectの機能を使用する場合は、hファイルに DECLARE_DYNAMIC マクロを追加する必要がある。&br;また、その場合はcppファイルにも対応する IMPLEMENT_DYNAMIC マクロを追加する必要がある。&br;VC6では、自動的に追加される訳ではないので注意。|任意| |②|コンストラクション |コンストラクタやCWnd::Create()のような、そのクラスのオブジェクトを使用する前に必ずコールする必要のあるメンバ関数を定義する。|任意| |③|アトリビュート |そのクラスの属性(プロパティ)を制御する公開メンバ変数や、Set/Getメンバ関数を定義する。&br;Getメンバ関数はconstにすべきである。|任意| |④|オペレーション |そのクラスのオブジェクトに対して、何らかの操作を行わせるための公開メンバ関数を定義する。|任意| |⑤|オーバーライド |MFC基底クラスの仮想関数のオーバーライドを定義する。|ウィザード| |⑥|インプリメンテーション&br;(公開メンバ) |そのクラスの実装のための公開メンバ(通常、クラスの使用者が知る必要のないメンバ)を定義する。|ウィザード| |⑦|インプリメンテーション&br;(被保護メンバ) |そのクラスの実装のための被保護メンバ(通常、クラスの使用者が知る必要のないメンバ)を定義する。|ウィザード| |⑧|Window&br;メッセージハンドラ |Windowメッセージハンドラ(メンバ関数)を定義する。|ウィザード| |⑨|インプリメンテーション&br;(非公開メンバ) |そのクラスの実装のための非公開メンバ(通常、クラスの使用者が知る必要のないメンバ)を定義する。|ウィザード|