Windowsプログラミング Edit

ダイアログボックス Edit

ダイアログの閉じ方と対応するイベントハンドラ Edit

操作イベントハンドラ
EnterOnOK()
EscOnCancel()
Alt+F4

タブオーダの保存形式 Edit

リソースファイル(*.rc)内でコントロールが定義される順番が、そのままタブオーダとして解釈される。

他のコントロールと連携が必要な場合の例 Edit

異なるコントロールを連携させて使用する場合がある。連携させるには、タブオーダを連続させる必要がある。

  • ラジオボタンのグループ化

    グループ化したいラジオボタンのタブオーダを連続させ、先頭のラジオボタンのプロパディでグループをTRUEにする。

  • スピンコントロールのバディコントロール

    エディットボックスのタブオーダをスピンコントロールの直前の値にすることで、そのエディットボックスをスピンコントロールのバディコントロールとして設定することができる。
    この場合、値の取得やコントロールの操作は全てCSpinButtonCtrl変数に対して行い、CEditは使用する必要がない。

コントロールへのドラッグ&ドロップの優先順位 Edit

複数のコントロールが重なっている場合、タブオーダの小さいコントロールが優先。
グループボックスコントロールを使用する際などは注意。

クラスウィザードに表示されていないメッセージ Edit

クラスウィザードに目的のメッセージが表示されていない場合、表示内容がフィルタリングされている可能性がある。 「詳細設定オプション」で「メッセージフィルタ」を変更することで、目的に合ったメッセージを表示できる。

例)ダイアログクラスで初期状態ではフィルタリングされているメッセージ。

  • WM_DROPFILES

    ファイルのドラッグ&ドロップ

  • WM_ACTIVATE

    ウィンドウのアクティブ/非アクティブ

コントロールのフォーカス移動 Edit

CWnd::SetFocus() ではなく、CDialog::GotoDlgCtrl() を用いる。
CWnd::SetFocus()を用いてもフォーカスは移動するが、画面描画上は移動していないように見えてしまうことがある。

  1
  2
  3
    // ボタンのコントロール変数が m_BtCtrl の場合
    m_BtCtrl.SetFocus();    // ←×
    GotoDlgCtrl(&m_BtCtrl); // ←○

ダイアログ表示直後に指定コントロールへフォーカス設定するには、OnInitDialog() で GotoDlgCtrl() を呼び出した後、OnInitDialog() の戻り値を FALSE にする必要がある。

リソースエディタ上でコントロールのフォントを指定した際の文字化け Edit

コード上でフォントの設定をすると直ることがある。

ツールバーを使用するには Edit

通常、ツールバーはSDIかMDIのアプリケーションにしか用意されないが、以下の手順でダイアログにも使用できる。
※ダイアログに配置するコントロールの位置は、ツールバーの範囲を考慮する必要がある。また、ドッキング可能にすることはできない。

<ツールバーの作成>

  1. ツールバーのリソースを用意する。
  2. ダイアログクラスのメンバ変数として以下を定義しておく。
      1
    
        CToolBar    m_wndToolBar;
  3. 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);
     
        // 作成時は大きさが0なので設定する必要がある
        m_wndToolBar.MoveWindow(0, 0, rcClient.Width(), sizeToolBar.cy);
    <ツールバーのボタン押下時のイベントハンドラ定義>

    クラスウィザードは対応していないため、自分で定義する必要がある。(以下、ボタンのIDを ID_XXXとする)

  4. ダイアログクラスのヘッダのメッセージマップ部分に関数のプロトタイプを作る。
      1
      2
      3
      4
      5
      6
      7
      8
    
        // 生成されたメッセージ マップ関数
        //{{AFX_MSG(CMStepEditorDlg)
        virtual BOOL OnInitDialog();
        afx_msg void OnPaint();
        afx_msg HCURSOR OnQueryDragIcon();
        //}}AFX_MSG
        afx_msg void OnXxx();        // @TODO:ここに追加
        DECLARE_MESSAGE_MAP()
  5. ダイアログクラスのソースに関数 OnXxx() を定義する。
  6. ダイアログクラスのソースのメッセージマップ部分に定義した関数を登録する。
      1
      2
      3
      4
      5
      6
      7
    
        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()

イベントハンドラ活用 Edit

  • 複数の操作から同じイベントハンドラを呼び出す場合

    1つのイベントハンドラと対応する1つのリソースIDを用意し、そのIDを以下のような複数のリソースで共用することができる。同一IDを持っている処理は同じイベントハンドラが呼び出される。

    • メニューの項目のID
    • ツールバーのボタンのID
    • アクセラレータのID
    • ストリングテーブルのID
  • 別のIDを持つ操作から同じイベントハンドラを呼び出す場合

    1つのイベントハンドラに対して複数のIDをメッセージマップに登録することができる。
    例えば、2つのラジオボタンのどちらをクリックしたときも同じ処理を行いたい場合は、両方のクリックイベントに対して同じイベントハンドラ(関数)を指定する。

F1押下時のヘルプの無効化 Edit

アプリケーションクラスのメッセージ処理を修正することで、無効化できる。

  1
  2
  3
  4
  5
  6
  7
BEGIN_MESSAGE_MAP(CXxxApp, CWinApp)
    //{{AFX_MSG_MAP(CXxxApp)
        // メモ - ClassWizard はこの位置にマッピング用のマクロを追加または削除します。
        //        この位置に生成されるコードを編集しないでください。
    //}}AFX_MSG
//    ON_COMMAND(ID_HELP, CWinApp::OnHelp)        // ※ここをコメントアウト
END_MESSAGE_MAP()

ダイアログベースアプリケーションの終了コード Edit

アプリケーションのCWinAppを継承したクラスのExitInstance()をオーバーライドし、戻り値を設定すればよい。

WM_INITDIALOGの戻り値 Edit

通常、ウィンドウプロシージャやダイアログプロシージャの戻り値は、メッセージを処理したか否かをBOOL型で返すが、WM_INITDIALOGだけは戻り値に特別な意味がある。
TRUE を返すと、タブオーダーが一番若いコントロールに自動的にフォーカスが当てられる。
FALSE を返すと、何も行われない。(自分でフォーカスを当てるコードを書く必要がある。)

ダイアログ生成時に明示的にあるコントロールにフォーカスを当てたい場合や、ダイアログを非表示で生成しておき後で表示するような場合は、FALSEを返す必要がある。(後者のケースは、ダイアログ生成時にフォーカスが奪われてしまうのを防ぐため)

CDialogBar派生クラスでOnInitDialog()を実装する方法 Edit

CDialogBarクラスにはOnInitDialog()が用意されていないため、自分でON_MESSAGE()でWM_INITDIALOGをハンドルする必要がある。

参考:http://support.microsoft.com/kb/185672/ja

MFCによりダイアログが自動的に中央に移動する Edit

MFCで作られたダイアログは、自動的に中央に移動する機能が組み込まれている。
通常、OnInitDialog()でMoveWindow()等を呼び出し、明示的に表示位置を移動している場合は中央に移動することはないが、ある条件を満たすと中央に移動させられてしまう。
例として、(0, 0)の位置に移動しようとしたときに現象が発生する。

以下の条件のいずれかを満たしている場合は中央移動は行われない(一部抜粋)

  • 画面の表示位置を(0, 0)以外にしている
  • ダイアログテンプレート(リソース)のスタイル設定に DS_CENTER, DS_CENTERMOUSE, DS_ABSALIGN のいずれかのスタイルが設定されている
  • ダイアログテンプレート(リソース)のXY位置設定が(0, 0)ではない


詳細は関連するMFCの関数を参照。

  • ::_AfxPostInitDialog()
  • CDialog::CheckAutoCenter()

コントロール Edit

エディットボックス内の改行の注意点 Edit

  • 複数行を許可していなくても、Ctrl+Enterで改行することができてしまう。
  • 文字列取得の際、改行コードは"\r\n"として取得されるため、取得した文字列をテキストモードで開いたファイルに書き込む場合は注意。('\n'が"\r\n"に展開されるため、"\r\r\n"となってしまう)

コンボボックスのドロップダウンリストのサイズ変更 Edit

ダイアログエディタ上で、コンボボックスの右端の▼マークをクリックすると、ドロップダウンリストのサイズ変更状態になる。

リストビューコントロールのヘッダのソート記号 Edit

リストビューのヘッダに対して以下の定数を設定する。

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
    // 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);

ツリービューの全展開 Edit

ツリービューコントロールを全展開する関数

  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
    // ツリー全展開
    //  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);
            }
        }
    }

スピンコントロールの値変更イベント Edit

スピンコントロールの値が変更された場合のイベントハンドラは、バディされたエディットコントロールのEN_CHANGEイベントで行う。
しかし、スピンコントロールとエディットコントロールを自動でバディする設定の場合は、OnInitDialog()よりも前に自動的にバディされたタイミングでEN_CHANGEイベントが発生してしまう。すると、まだ用意されていないコントロールにアクセスされてしまい、実行時エラーになってしまうことがある。
対処策としては、自分でエディットコントロールにバディするコードを書くか、フラグを用意してOnInitDialog()コール前ならEN_CHANGEイベントハンドラで何も処理しない設計にしておく。

リッチエディットコントロール使用前の準備 Edit

リッチエディットコントロール(CRichEditCtrl)を使用する場合は、通常のエディットコントロール(CEdit)と違って予め準備をしておく必要がある。

  • コントロール自体の初期化
    リッチエディットコントロールを使用する前に、アプリケーション内で1度だけMFCのグローバル関数 AfxInitRichEdit() を呼び出す必要がある。
    呼び出すタイミングは、最初にリッチエディットコントロールを使用するダイアログの表示コードの直前 (ダイアログベースアプリケーションなら、CXxxApp::InitInstance())でよい。
  • 使用するイベントのマスク設定
    リッチエディットコントロールは、デフォルトでイベントマスクが ENM_NONE (イベント通知なし) に設定されており、ダイアログにイベントが通知されてこない。
    ダイアログのOnInitDialog()で、明示的に使用するイベントのマスク設定を行う必要がある。
  1
  2
  3
  4
    // m_ReCtrl : リッチエディットコントロールのコントロール変数
 
    // EN_CHANGEイベント(内容が変更されたときのイベント)を使用する場合のマスク設定
    m_ReCtrl.SetEventMask(m_ReCtrl.GetEventMask() | ENM_CHANGE);    // ENM_CHANGE : EN_CHANGEのイベントマスク

エディット/リッチエディットコントロールのワード区切り Edit

単語境界やワードラップ機能は、どのように文字列のワード区切りを判断しているかに依存する。
ワード区切りの方法を変更するには、以下のキーワードを調べる。(まだ試していない)

  • EM_SETWORDBREAKPROC
  • EditWordBreakProc

コントロールの「通知」の有無 Edit

コントロールのプロパティの「通知」の有無の違いによって、イベントハンドラがコールされる順番を検証。
(CWnd::Create()でコントロールを作成する場合は、「SS_NOTFY」フラグに該当する。)

  • 通知OFFの場合
    1. 親ダイアログの PreTranslateMessage()
    2. 親ダイアログの OnLButtonDown()
  • 通知ONの場合
    1. コントロールの PreTranslateMessage()
    2. 親ダイアログの PreTranslateMessage()
    3. コントロールの OnLButtonDown()
    4. 親ダイアログに追加したコントロールクリック時のイベントハンドラ OnXXXX()

ActiveXコントロールの登録状況の確認 Edit

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
    
    // 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;
    }
  • 使用例
      1
      2
      3
      4
      5
      6
      7
    
        // AppクラスのInitInstance()で以下のコードを記述。(dlg変数が宣言された直後くらいに)
     
        // m_xxxx : dlgダイアログで使用しているOCXコントロール
        // ウィザードによってGetClsid()メンバ関数が作成されているので、その戻り値を引数として渡す
        if(!IsControlRegister(dlg.m_xxxx.GetClsid())){
            ::MessageBox(NULL, "XXXX ActiveX コントロールがインストールされていません", "エラー", MB_OK | MB_ICONERROR);
        }

コントロールのサブクラス化 Edit

サブクラス化の方法 Edit

  • 動的サブクラス化

    親ダイアログクラスの初期処理で、CWnd::SubclassDlgItem()を使用することで行う。

  • 静的サブクラス化

    ClassWizardを使用して派生クラスの型のコントロール変数を作成することで行う。
    派生クラスのヘッダやソースファイルがクラスウィザードに登録されていないと、画面上で変数の型として選択できないので注意。 その場合は、直接ヘッダファイル内の変数定義の型名を編集すればOK。

※サブクラス化とは、C++の「クラスの継承」のことではない。
既存のウィンドウプロシージャを、独自のウィンドウプロシージャに差し替えることを指す。

サブクラス化に用いた派生クラス内での初期処理 Edit

OnCreate()はコールされないため、初期処理を記述することはできない。
派生クラスでは、PreSubclassWindow()をオーバライドして、そこに初期処理を書く。
元のクラスが構築されて(OnCreate)、サブクラス化が行われるのであって、派生クラスが直接構築されるわけではない。

MFCを継承した自作クラスへのイベントハンドラの追加 Edit

MFCを継承している場合は、クラスウィザードに派生クラスを認識させておく必要がある。
(認識されているかどうかは、クラスウィザードの「クラス名」に表示されるかどうかで判断できる)
認識されていない場合は、以下の操作ができない。

  • コントロールのメンバ変数追加時に、型の候補とする
  • クラスビューからWindowsメッセージハンドラを追加する

プロジェクト内でクラスを作成した場合は自動的に認識されるが、外部から持ってきたコードを用いる場合は、 一度clwファイルを削除してクラスウィザードを作り直すときに、ファイルを含める必要がある。

※ちなみに、MFC継承クラスをさらに継承した場合は上記作業を行っても不可。仕様上は問題なくイベントハンドラを実装できるはずなので、手動で行うしかない?

ウィンドウ Edit

ウィンドウ操作関数一覧(一部) Edit

操作Win32APIMFC
SetGetSetGet
ウィンドウ情報-::GetWindowInfo-CWnd::GetWindowInfo
親ウィンドウ-::GetParent-CWnd::GetParent
関連ウィンドウ-::GetWindow-CWnd::GetWindow
スタイル::SetWindowLong::GetWindowLongCWnd::ModifyStyleCWnd::GetStyle
拡張スタイルCWnd::ModifyStyleExCWnd::GetExStyle
インスタンスハンドル--
ウィンドウプロシージャ--
(特記)使用可否::EnableWindow::IsWindowEnableCWnd::EnableWindowCWnd::IsWindowEnable
表示状態::ShowWindow::IsWindowVisibleCWnd::ShowWindowCWnd::IsWindowVisible
ウィンドウクラス登録::RegisterClassEx---
ウィンドウクラス登録解除::UnregisterClass---
コントロールID-::GetDlgCtrlIDCWnd::SetDlgCtrlIDCWnd::GetDlgCtrlID
ドラッグ&ドロップ許可::DragAcceptFiles-CWnd::DragAcceptFiles-
アクティブ::SetActiveWindow::GetActiveWindowCWnd::SetActiveWindowstatic CWnd::GetActiveWindow
位置スクリーン座標::MoveWindow::GetWindowRectCWnd::MoveWindowCWnd::GetWindowRect
クライアント座標-::GetClientRect-CWnd::GetClientRect
位置データ::SetWindowPlacement::GetWindowPlacementCWnd::SetWindowPlacementCWnd::GetWindowPlacement
Zレベル::SetForegroundWindow::GetForegroundWindowCWnd::SetForegroundWindowstatic
CWnd::GetForegroundWindow
::SetWindowPos-CWnd::SetWindowPos-
中央へ移動--CWnd::CenterWindow-
キャプション::SetWindowText::GetWindowTextCWnd::SetWindowTextCWnd::GetWindowText
透明度::SetLayeredWindowAttributes::GetLayeredWindowAttributesCWnd::
SetLayeredWindowAttributes
CWnd::
GetLayeredWindowAttributes
フォント--CWnd::SetFontCWnd::GetFont
操作閉じる::DestroyWindow-CWnd::DestroyWindow-
最大化::ShowWindow::IsZoomedCWnd::ShowWindowCWnd::IsZoomed
最小化::ShowWindow
::CloseWindow
::IsIconicCWnd::ShowWindow
CWnd::CloseWindow
CWnd::IsIconic
メッセージ送信(同期)::SendMessage-CWnd::SendMessage-
メッセージ送信(非同期)::PostMessage-CWnd::PostMessage-
再描画::UpdateWindow-CWnd::UpdateWindow-

ウィンドウの中央表示 Edit

  • Win32API

    ウィンドウの矩形を計算し、::MoveWindow() で移動する。

      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
    
        // 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() を使用する。

      1
      2
    
        // デスクトップを基準に中央に表示する
        CenterWindow(CWnd::GetDesktopWindow());

タイトルバーの高さの取得 Edit

OSの設定(フォントやテーマ等)に依存する。

  1
    ::GetSystemMetrics(SM_CYCAPTION)    // タイトルバーの高さを取得

コンソールウィンドウの使用 Edit

::AllocConsole(), ::FreeConsole()を用いる。
コンソールへの入出力をするためには、標準入出力を割り当てるか専用の関数でハンドルを取得する。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
    ::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()でコンソールを解放する

ウィンドウの描画更新の抑制 Edit

CWnd::LockWindowUpdate() と CWnd::UnlockWindowUpdate() で、処理を挟み込む。

  1
  2
  3
  4
  5
    LockWindowUpdate()
 
    // 描画更新を抑制した状態で行う処理を記述
 
    UnlockWindowUpdate()

大きなツリービューへのアイテムの追加など、時間がかかる処理は描画更新を抑制することで、画面のチラつきを防止できる。

小さいアイコン(16x16)の設定 Edit

大きいアイコン(32x32)と小さいアイコン(16x16)の両方を用意してあるのに、小さいアイコンがうまく適用されないことがある。
その場合は、OnInitDialog() 等のアイコン設定処理で、小さいアイコン設定の処理をコメントアウトするとよい。

  1
  2
    SetIcon(m_hIcon, TRUE);    // 大きいアイコンを設定
    //SetIcon(m_hIcon, FALSE); // 小さいアイコンを設定

※正式な修正方法ではないかも。

メニューの項目の取得 Edit

取得の手順。

  1. メニューを持つウィンドウを識別
  2. メニューバーを取得
  3. 必要があればサブメニューを取得していき、目的の項目を持つサブメニューまで辿る
  4. 目的のメニュー項目の情報を取得(/設定)する

メニューの項目は、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
    
        // 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
      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
    
        // 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 にメニュー項目状態が取得された
    

メニューバーの描画更新 Edit

メニュー項目に変更を行っても自動的に描画更新されないため、明示的に描画更新する必要がある。

  • Win32API

    ::DrawMenuBar()

  • MFC

    CWnd::DrawMenuBar()

指定ウィンドウの描画更新 Edit

ウィンドウの描画更新を促すには、WM_PAINTを発行する必要がある。ただし、ウィンドウに直接WM_PAINTを送るのではなく、下記の手順を踏む必要がある。

  1
  2
  3
    // ウィンドウ全体を再描画する
    pWnd->InvalidateRect(NULL);        // 更新リージョンにウィンドウ全体を追加する
    pWnd->UpdateWindow();            // 更新リージョンが空でなければ、WM_PAINTを発行する

ウィンドウの一部だけを再描画させる場合は、CWnd::InvalidateRect()の第一引数にNULLではなく、RECT*型の領域を渡す。

メッセージボックスの簡易的な最前面化 Edit

最前面ウィンドウを親に持つメッセージボックスを作成すればよい。
親ウィンドウは非表示にしておけば、メッセージボックスのみ表示される。

  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;

ウィンドウの再表示 Edit

ウィンドウの再表示を行う際、::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);

タイトルバーを常にアクティブ色にする Edit

再描画のたびにウィンドウメッセージでアクティブ通知を行う。

  1
  2
  3
  4
  5
  6
  7
void CXxxDlg::OnPaint() 
{
    :
    // キャプションを常にアクティブ色にする
    PostMessage(WM_NCACTIVATE, TRUE);
    :
}

メッセージ専用ウィンドウ Edit

ウィンドウメッセージの処理を行いたいがウィンドウの実体は必要無いというときは、メッセージ専用ウィンドウを利用することができる。
(ウィンドウ列挙等の対象からも外れる)

使用する場合は、予め下記の定数を設定しておく必要がある。

#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));
     
        // 既にクラスが登録されていたら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
      1
      2
      3
      4
      5
    
        // CWnd *pWnd;
     
        pWnd = new CWnd();
        CString strWndClassName = ::AfxRegisterWndClass(NULL);
        pWnd->CreateEx(0, strWndClassName, "", 0, 0, 0, 0, 0, HWND_MESSAGE, 0);

ウィンドウメッセージ Edit

自作メッセージ Edit

自作のメッセージの定義は、以下の2通り。

  • WM_USER + n
    プライベートなウィンドウに送る自作メッセージの定義。コモンダイアログなどは既に使用している場合があるので注意。
  • WM_APP + n
    自作アプリケーションから任意のウィンドウに送る自作メッセージの定義。

メッセージフック処理 Edit

  • 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);
        }
  • MFC

    CWnd::PreTranslateMessage() をオーバーライドして、対応するメッセージフック処理を書く。
    MFCにデフォルトで定義されているメッセージ処理を行わせない場合は、TRUE を返す。

     
    ※PreTranslateMessage() は、Postされたメッセージしか処理できない?Sendされたメッセージは無理?

アクティブウィンドウに対してキーメッセージを送信 Edit

  1
  2
  3
    // 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アプリケーションが終了する仕組み Edit

  1. ユーザが終了処理を行う(×を押すなど)

    ↓ WM_CLOSE

  2. ::DestroyWindow()

    ↓ WM_DESTROY

  3. ::PostQuitMessage()

    ↓ WM_QUIT

  4. WinMain()やスレッド内で用いられている::GetMessage() が FALSE を返すことにより、メッセージループを抜け、処理が終了する。

メッセージによるコマンドの実行 Edit

ウィンドウにコマンドIDを付与した WM_COMMAND メッセージを送ることで実行できる。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
    // 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);

アプリ間で使用する任意メッセージの受信 Edit

  1. グローバル変数として、メッセージIDを保持しておく
      1
      2
      3
    
    // ユニークなメッセージIDを取得
    // ("TEST" はメッセージID登録用の任意の文字列。メッセージを送受信するアプリ間で決めておく)
    static const UINT uiWmTest = ::RegisterWindowMessage("TEST");
  2. ウィンドウクラスのヘッダに以下のようにイベントハンドラを追加
      1
      2
      3
      4
      5
      6
    
        // 生成されたメッセージ マップ関数
        //{{AFX_MSG(CXxx)
    //}}AFX_MSG
        afx_msg LRESULT OnTest(WPARAM=0, LPARAM=0);        // イベントハンドラを追加
        DECLARE_MESSAGE_MAP()
  3. ウィンドウクラスのソースに以下のようにメッセージマップとイベントハンドラの実装を追加
      1
      2
      3
      4
      5
      6
    
    BEGIN_MESSAGE_MAP(CXxx, CXxxParent)
        //{{AFX_MSG_MAP(CXxx)
    //}}AFX_MSG_MAP
        ON_REGISTERED_MESSAGE(uiWmTest, OnTest)            // メッセージマップに追加
    END_MESSAGE_MAP()

      1
      2
      3
      4
      5
      6
    
    // イベントハンドラを実装
    LRESULT CXxx::OnTest(WPARAM wParam/*=0*/, LPARAM lParam/*=0*/){
        // 任意の処理
        MessageBox("TEST");
        return 0;
    }

ウィンドウサイズの制限(WM_GETMINMAXINFO) Edit

マウスドラッグやWin7のスナップ機能などによるウィンドウサイズの変更時に、ウィンドウサイズを制限する場合は、WM_GETMINMAXINFO をハンドルする。

コモンダイアログ Edit

ファイル選択(保存)ダイアログとカレントディレクトリ Edit

  • ファイル選択ダイアログを使用すると、カレントディレクトリが変更される。(OFN_NOCHANGEDIRフラグを使用すれば防げる)
  • OPENFILENAME::lpstrInitialDir (初期ディレクトリ) に無効な文字列を指定したときは、カレントディレクトリが使用される。

ファイル保存ダイアログの拡張子自動付与機能 Edit

  • OPENFILENAME::lpstrDefExt (デフォルト拡張子) よりも、OPENFILENAME::lpstrFilter (フィルタ)に指定した文字列が優先される。
  • OPENFILENAME::lpstrDefExt (デフォルト拡張子) にNULLを指定すると、拡張子自動付加は行われない。

※デフォルト拡張子は「.」を含まない。

ファイル選択(保存)ダイアログ使用時にアプリケーションが落ちる現象 Edit

ファイル選択ダイアログを使用していると、呼び出し元のアプリケーションごと強制終了してしまう現象が発生している。PCによって起きたり起きなかったりする。

  • 発生条件

    AdobeReader7.0がインストールされていること。

  • 現象

    現象は、あるアプリケーションでファイル選択ダイアログを立ち上げ(メモ帳の開くなど)、何かのファイル(フォルダでは起きない)にカーソルを合わせ、ツールチップを表示させる。
    一度ファイル選択ダイアログを閉じて再度同じ操作をすると、ツールチップが表示される瞬間に強制終了される。

  • 対処法

    根本的な解決策は分かっていない。AdobeReader7.0が原因という話もあるが、アンインストールしても改善しなかった。
    自作のアプリケーションであれば、ファイル選択ダイアログを起動する前に、CoInitialize()を呼ぶと落ちなくなる。ただし、CoInitialize() を使用するのであれば、対応する CoUninitialize() を忘れないこと。
    既存のアプリケーション(メモ帳など)では、フォルダオプションでツールチップ表示機能をOFFにするしかない。

  • 原因

    本当に原因なのかは未確認だが、ネットに pdfshell.dll が原因ではないかという記述を見つけた。下記の何れかに存在する。
    → (AdobeReaderインストールフォルダ)\ActiveX
    → C:\Program Files\Common Files\Adobe\Acrobat\ActiveX

文字列処理 Edit

マルチバイト文字とワイド文字のサンク機能 Edit

Wwindowsプログラミングをする上で、文字列を「マルチバイト」と「ワイド文字」のどちらで扱うかに気をつける必要がある。

  • マルチバイト文字(MBCS)

    char型に格納して扱う。Asciiコードなどの半角英数字は1バイトだが、日本語等の全角文字は2バイトなので、2要素に分割して格納される。

  • ワイド文字(Unicode)

    wchar_t型に格納して扱う。1文字を常に2バイト(1つの数値)で扱う。
    wchar_t型は、C言語では unsigned short に typedef されており、C++では予約語になっている。

     

Win32APIやMFCには、これらの文字セットの切り替えを容易にした仕組み(thunk)が用意されており、これに則ってプログラミングすることで、どちらの文字セットを使用するかをコンパイルスイッチで切り替えることができる。

  • 全般
    サンクマルチバイト文字(MBCS)ワイド文字(Unicode)
    文字
    文字列
    _T('~')
    _T("~")
    '~'
    "~"
    L'~'
    L"~"
    変数型TCHARCHAR
    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 構造体(例)OPENFILENAMEOPENFILENAMEAOPENFILENAMEW
    MFC クラス(例)CString/
    CStringT(※VC6.0では未対応)
    CStringACStringW
  • C標準関数(一部)
    分類サンクマルチバイト文字(MBCS)ワイド文字(Unicode)
    文字列_tcscatstrcatwcscat
    _tcschrstrchrwcschr
    _tcscmpstrcmpwcscmp
    _tcscpystrcpywcscpy
    _tcscspnstrcspnwcscspn
    _tcslenstrlenwcslen
    _tcsncatstrncatwcsncat
    _tcsncmpstrncmpwcsncmp
    _tcsncpystrncpywcsncpy
    _tcspbrkstrpbrkwcspbrk
    _tcsrchrstrrchrwcsrchr
    _tcsspnstrspnwcsspn
    _tcsstrstrstrwcsstr
    _tcstodstrtodwcstod
    _tcstokstrtokwcstok
    _tcstolstrtolwcstol
    _tcstoulstrtoulwcstoul
    _tcsicmp_stricmp_wcsicmp
    _tcsnicmp_strnicmp_wcsnicmp
    _tcslwr_strlwr_wcslwr
    _tcsupr_strupr_wcsupr
    入出力_tprintfprintfwprintf
    _ftprintffprintffwprintf
    _stprintfsprintfswprintf
    _vtprintfvprintfvwprintf
    _vftprintfvfprintfvfwprintf
    _vstprintfvsprintfvswprintf
    _tscanfscanfwscanf
    _ftscanffscanffwscanf
    _stscanfsscanfswscanf
    _gettchargetchargetwchar
    _fgettcfgetcfgetwc
    _gettsgets_getws
    _fgettsfgetsfgetws
    _puttcharputcharputwchar
    _fputtcfputcfputwc
    _puttsputs_putws
    _fputtsfputsfputws
    変換_ttoiatoi_wtoi
    _ttolatol_wtol
    _ttofatof_wtof
    ファイル_tmkdir_mkdir_wmkdir
    _trmdir_rmdir_wrmdir
    _tremoveremove_wremove
    _trenamerename_wrename
    _tfopenfopen_wfopen
    _tpopen_popen_wpopen
    _tfullpath_fullpath_wfullpath
    _tmakepath_makepath_wmakepath
    _tsplitpath_splitpath_wsplitpath
    文字_istalnumisalnumiswalnum
    _istasciiisasciiiswascii
    _istcntrliscntrliswcntrl
    _istalphaisalphaiswalpha
    _istdigitisdigitiswdigit
    _istgraphisgraphiswgraph
    _istlowerisloweriswlower
    _istprintisprintiswprint
    _istpunctispunctiswpunct
    _istspaceisspaceiswspace
    _istupperisupperiswupper
    _totuppertouppertowupper
    _totlowertolowertowlower
    その他_tsystemsystem_wsystem

CStringオブジェクトの変換 Edit

constな文字列型(LPCTSTR)への変換しか許されていない。
それ以外は、変換関数が用意されていないため。

文字列のコンバート (VBのStrConv()相当) Edit

::LCMapString()で、ひらがな/カタカナ、半角/全角、大文字/小文字の相互変換が可能。

ファイルパス長関係のマクロの意味 Edit

これらは、「バイト数」ではなく「文字数」を定義したもの。
マルチバイト文字を扱う場合は注意が必要。

マクロ名意味
_MAX_PATH260フルパス
_MAX_DRIVE3ドライブ
_MAX_DIR256ディレクトリ
_MAX_FNAME256ファイル名(拡張子除く)
_MAX_EXT256拡張子(「.」含む)

↑ <stdlib.h> に定義されている

各種文字列型の相互変換 Edit

変換元 src
char [](STL) stringCString
変換先
dst
char []strcpy(dst, src.c_str());strcpy(dst, src);
(STL) stringdst = src;
dst = string(src);
string dst(src);
dst = src.c_str();
dst = CString(src.c_str());
CString dst(src.c_str());
CStringdst = src;
dst = CString(src);
CString dst(src);
dst = src.c_str();
dst = CString(src.c_str());
CString dst(src.c_str());
BSTRdst = ::SysAllocString(src);

::SysFreeString(dst);
dst = ::SysAllocString(src.c_str());

::SysFreeString(dst);
dst = src.AllocSysString();

::SysFreeString(dst);
CComBSTRdst = 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
BSTRCComBSTR
変換先
dst
char []::WideCharToMultiByte(CP_ACP, 0, (OLECHAR*)src, -1, dst, sizeof(dst)-1, NULL, NULL);直接変換する方法はない
(STL) string直接変換する方法はない直接変換する方法はない
CStringdst = src;
dst = CString(src);
CString dst(src);
dst = (BSTR)src;
dst = CString((BSTR)src);
CString dst((BSTR)src);
BSTRdst = src.Copy();

::SysFreeString(dst);
CComBSTRdst = src;
dst = CComBSTR(src);
CComBSTR dst(src);

マルチバイト文字の判定 Edit

_ismbblead(),_ismbbtrail() を使用すると、「その文字がマルチバイト文字の第1バイト/第2バイトに相当するか」をチェック可能。
しかし、マルチバイト文字の第1バイトと第2バイトの取り得る数値の範囲は重複しているため、この関数では「そのバイトが第1バイトと第2バイトのどちらか」という判定には使えない。

例)「り」(0x82E8) の第2バイトである「0xE8」は、第1バイト/第2バイトどちらでも使用される値である。

その場合は、_ismbslead(), _ismbstrail() を使用し、文脈から判定させることで解決できる。
ただし、指定した文字列全体のチェックを行うため、パフォーマンスは低いことに注意。

ファイル処理 Edit

ファイル(ドライブ、ディレクトリ)の存在確認 Edit

  • Win32API

    ファイル属性を取得する ::GetFileAttributes() で確認可能。ファイルが存在しない場合は、0xFFFFFFFF を返す。
    または、PathFileExists() や PathIsDirectory()。

  • C標準ライブラリ

    access() や stat() 。

ファイルのプロパティダイアログの表示 Edit

ファイルを右クリックして「プロパティ」を選択したときのダイアログのこと。::ShellExecuteEx() を使用する。

フォルダの属性変更 Edit

なぜかできない。CFile::SetStatus() だと例外が発生し、::SetFileAttributes() だと何も起こらず変更もされない。

デバイスコンテキスト/画像処理 Edit

用途別のデバイスコンテキスト Edit

MFCクラス用途備考
CPaintDC更新領域のみに対して描画する場合に使用する。・コンストラクタでBeginPaint()、デストラクタでEndPaint()が呼び出されているため、クリッピングリージョンが自動的に設定される。(WS_CLIPSIBLINGSスタイルで兄弟ウィンドウをクリップにしている場合など、自動的に適用される)
・OnPaint()の処理内でしか使用できない。OnDraw()の引数で渡されるpDcも元はこれ。(OnDraw()はOnPaint()から呼び出されている)
CClientDCウィンドウのクライアント領域全体に対して描画する場合に使用する。・コンストラクタでGetDC()、デストラクタでReleaseDC()が呼び出される。
・領域のクリッピング等は自前で行う必要がある。
CWindowDCウィンドウの非クライアント領域(キャプションやメニューなど)も含めた領域全体に対して描画する場合に使用する。・コンストラクタでGetWindowDC()、デストラクタでReleaseDC()が呼び出される。
・領域のクリッピング等は自前で行う必要がある。

ダブルバッファリング Edit

1つ1つの描画処理を行うたびに画面が更新されてしまうことによってチラつきが発生してしまう。そこで、描画処理は裏画面に対して行っておき、描画処理が終わった時に画面更新を1度だけ行うことでチラつきを防ぐ手法がダブルバッファリング。以下はダブルバッファリングの実装例。

  1. ウィンドウ(ダイアログ)クラスのメンバ変数として以下を定義しておく。
      1
      2
      3
      4
      5
      6
    
        // CStatic  m_PcCtrl        ピクチャコントロール
     
        CDC         m_memDC;            // メモリデバイスコンテキスト(裏画面)
        CBitmap     m_memBmp;           // メモリデバイスコンテキストの実体ビットマップ
        CBitmap     *m_pOldBmp;         // メモリデバイスコンテキストの旧実体
        CRect       m_PcRect;           // キャンバスの矩形領域
    
  2. アプリケーションの初期処理で、デバイスコンテキストと裏画面の設定を行う。
      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);
  3. アプリケーションの終了処理で、デバイスコンテキストと裏画面の解放を行う。
      1
      2
      3
      4
      5
      6
    
        // メモリデバイスコンテキストのビットマップを元に戻す
        m_memDC.SelectObject(m_pOldBmp);
        // 実体ビットマップを削除
        m_memBmp.DeleteObject();
        // メモリデバイスコンテキストを削除
        m_memDC.DeleteDC();
  4. CWnd::OnPaint() 等の再描画処理で、裏画面からの転送処理を行う。
      1
      2
      3
      4
      5
      6
    
        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);
  5. 実際の描画処理。
    描画は裏画面に対して行い、最後にウィンドウの再描画を促す。
      1
      2
      3
      4
      5
    
        // m_memDC に対して描画処理を行う
     
        // 再描画
        InvalidateRect(NULL, FALSE);    // 領域を無効化
        UpdateWindow();                 // 領域を無効化して呼ぶことで、WM_PAINTを送信する
    

ウィンドウキャプチャ(ハードコピー) Edit

  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
    // 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();

ビットマップのコピー Edit

メモリ上のビットマップを複製する方法。

  • MFC
      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
    
        // 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
      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
    
        // 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の内容をビットマップファイルとして保存 Edit

ビットマップファイルは次のような構造になっている。

  1. BITMAPFILEHEADER
  2. BITMAPINFOHEADER
  3. (カラーパレット)
  4. ピクセルデータ

上記の順番でバイナリデータをファイルに保存していくことで、ビットマップファイルを作成できる。
以下に、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
    // 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;

描画時の兄弟ウィンドウのクリッピング Edit

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
    
        // 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 にしておくこと)
      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;
    }

フォント Edit

フォントを変更する際の注意点 Edit

フォントの設定にCFontクラスのオブジェクトを用いる場合、メンバ変数にしておく必要がある。
ローカル変数だと、スコープを抜けたときに変数が自動的に破棄され、デストラクタの働きによってフォントの内容が削除されてしまうため、正しくフォントが反映されない。

単位系 Edit

コンピュータの世界でよく用いられる長さに関する単位。

単位説明換算
pixel文字や画像を表示する際の最小要素。画素。
ディスプレイ上ではdotと同義。
-
TwipもとはVBでフォーム設計する際に用いられる長さの単位。
リッチエディットコントロール上ではこの単位がもちいられる。(要確認)
mmやinchよりも細かく指定できる。
1Twip = 1/20point = 1/1440inch
pointフォントの大きさを表す1point = 1/72inch
inch一般的に使われる長さ。コンピュータの世界ではmmより使われる頻度が高い?-
dpidot per inch。1inchあたりのdot数。
ディスプレイ上では1inchあたりのpixel数と同義。
PCの解像度は通常96dpi。
-

セル高さと文字高さ Edit

アクセント記号を含めた高さをセル高さ(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構造体によるフォントサイズの指定 Edit

LOGFONT:lfHeightでフォントの高さ、LOGFONT:lfWidthでフォントの幅を指定する。

  • lfHeight
    0を指定すると、そのフォントのデフォルトサイズを指定したことになる。
    正数を指定すると、セル高さを指定したことになる。
    負数を指定すると、その絶対値を文字高さとして指定したことになる。
    実際には、そのフォントで使用できるサイズのうち、指定された値を超えない最大のサイズが使用される。

    指定する値の単位は論理単位(マッピングモード依存)。通常はMM_TEXTマッピングモードなので、論理単位=ピクセル数。
    フォントのpoint数を用いて指定する場合は、以下の変換式を用いる。
      1
      2
    
        // 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 Edit

リソースのみを含むDLL Edit

ソースコード(エントリポイント)を持たないDLLを作るには、プロジェクト設定の「リンク」タブの「一般」カテゴリで、「プロジェクトオプション」に "/NOENTRY" を追加する。

DLL内での実行ファイルのパス取得 Edit

WIN32APIの ::GetModuleFileName() を使用することで、実行ファイルのパスを取得できる。
DLL内部のコードから ::GetModuleFileName() を使用した場合、第1引数にDLLのインスタンスハンドルを渡せばDLLのパスが取得できるが、NULLを渡した場合は呼び出し元のEXEのパスの取得となる。

DLLからモードレスダイアログを表示 Edit

DLLからモードレスダイアログを表示する場合、タブストップやキー入力等の処理がデフォルトで行われない。→参考
モーダルダイアログのようにタブストップ等を処理させるには、メッセージをフックする必要がある。

  1. グローバルな変数に、対象となるダイアログクラスオブジェクトのアドレスを渡しておく。
      1
    
    CXxxDlg    *g_pDlg;
  2. ダイアログクラスのヘッダファイルで、フックハンドルとフックプロシージャを定義する。
      1
      2
      3
      4
      5
      6
      7
    
    public:
        // フックハンドル
        HHOOK    m_hHook;
        
    private:
        // メッセージ監視フックプロシージャ
        static long CALLBACK GetMsgProc(int, WPARAM, LPARAM);
  3. CXxxDlg::OnInitDialog() でフックプロシージャを登録する。
      1
      2
    
        // メッセージ監視フックプロシージャ
        m_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)CXxxDlg::GetMsgProc, NULL, ::GetCurrentThreadId());
  4. CXxxDlg::OnDestroy() でフックプロシージャを破棄する。
      1
      2
    
        // フックプロシージャの破棄
        ::UnhookWindowsHookEx(m_hHook);
  5. 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)){
     
                    // 処理済みなら WM_NULL とする
                    lpMsg->message = WM_NULL;
                    lpMsg->lParam  = 0;
                    lpMsg->wParam  = 0;
                }
            }
        }
        
        // 次のフックプロシージャへ
        return ::CallNextHookEx(g_pDlg->m_hHook, nCode, wParam, lParam);
    }

DLL内でMFCリソースの使用 Edit

DLL内でダイアログを作成して表示するコードを用意して使用する場合、exportする関数の先頭に以下のコードを記述する必要がある。(COMのDLLも同様)

  1
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

EXEからexportした関数が呼び出された状態では、リソースハンドル等はEXE用のものを使用する状態になっているため、DLL内では正しい処理が行われない。
上記のコードによって、モジュール状態がDLL用のものに切り替わるため、正しい処理が行える。また、スコープを抜けるときに元の状態に戻してくれるため、EXE側に処理が帰った後も問題がない。

DLLのデバッグ方法 Edit

DLLは親となるプロセスにロードされるため、呼び出し側のEXEが必要になる。

[注意点]

  • DLLはDebug版で用意しておく必要がある。
  • DLLが動的リンクされている場合、::LoadLibrary()によってDLLが呼び出し側EXEにロードされた後でないと、DLL側のソースにブレークポイントが貼れない
  1. 呼び出し側EXEも含めてデバッグ
    呼び出し側EXEのソースが手元にある場合に有効。
    1. 呼び出し側EXEとデバッグ対象DLLのプロジェクトを1つのワークスペースにまとめておく。
    2. 呼び出し側EXEのデバッグを開始する。
    3. デバッグ対象DLL内のコードにブレークポイントを貼っておき、その位置まで実行する。
  2. 呼び出し側EXEを指定してデバッグ
    呼び出し側EXEが単純に起動できる場合に有効。
    1. デバッグ対象DLLのプロジェクトを開き、プロジェクト設定の「デバッグセッションの実行可能ファイル」に呼び出し側EXEのパスを指定する。
    2. デバッグ対象DLL内のコードにブレークポイントを貼っておき、デバッグを開始する。
  3. 呼び出し側EXEのプロセスにアタッチしてデバッグ
    呼び出し側EXEの起動タイミングを制御できない場合(常駐など)に有効
    1. 呼び出し側EXEが起動された状態にする。
    2. VisualStudioを新規に起動し、「プロセスにアタッチ」で呼び出し側EXEのプロセスを指定してデバッグを開始する。
    3. VisualStudioにデバッグ対象DLLのソースファイルをドラッグ&ドロップし、ブレークポイントを貼り、その位置まで実行する。

LIB Edit

libファイルのリンク Edit

プロジェクトにlibファイルをリンクするには以下の3つの方法がある。

  1. プロジェクトにlibファイルを追加する。コマンドラインでのビルドであれば、オブジェクトファイルと一緒にlibファイルを指定する。
  2. プロジェクトの設定でリンク指定する。
  3. プリプロセッサで指定する。
      1
    
        #pragma comment(lib, "xxx.lib")

自作のスタティックライブラリ Edit

自作クラスなどの汎用的なコードは、スタティックライブラリ化しておくと再利用しやすい。

  1. スタティックライブラリ側のプロジェクト
    1. スタティックライブラリのプロジェクトを作り、ヘッダとソースを追加する。
    2. ヘッダファイルの置き場所はあらかじめ決めておく。複数のライブラリのプロジェクトがあっても、ヘッダは一ヵ所にまとめる。
    3. 出力ファイルであるLibファイルもあらかじめ決めた場所に出力する。Debug版のLibファイルも、名前を変えて同じ場所に出力させる。
  2. アプリケーション側のプロジェクト(Libファイルを使用する側)
    1. プロジェクトの設定で、リンクするLIBファイルを設定する。(Debug版はDebug版のLibファイルを使用する)
    2. ワークスペースに使用するライブラリのプロジェクトを含め、さらに依存関係を設定しておくと常に最新のライブラリファイルをリンクできる。
  3. VC++の設定
    1. ヘッダファイル、Libファイルを置いたパスをディレクトリ設定に追加する。

リソーススクリプト Edit

その他 Edit

実行対象のWindowsバージョン Edit

Win32API等のOSの機能を使用する場合、OSのバージョンによって利用できる機能と利用できない機能が存在するため、例えばWinXPから実装された機能を使用するアプリケーションは、Win2kでは動作が保証されないことになる。
つまり、アプリケーションを作成する場合、実行対象のWindowsの最小バージョンを決めておき、それより後のバージョンで実装された機能を使用しないようにする必要がある。

そこで、Windows用のヘッダファイルには以下のマクロが用意されており、定義された値によって機能が限定されるように作られている。アプリケーションを作る場合、使用する機能によってはこのマクロの値を適宜設定する必要がある。(stdafx.h内のWindows用のヘッダファイルのインクルードよりも前に定義する)

  • WINVER
    実行対象のWindowsの最小バージョン
  • _WIN32_WINNT
    実行対象のWindowsNTの最小バージョン
  • _WIN32_IE
    実行対象の環境にインストールされているIEの最小バージョン
     
  • Windowsの最小バージョンの定義
    WindowsWINVER
    _WIN32_WINNT
    Windows 950x0400
    Windows 980x0410
    Windows Me0x0500
    Windows NT 4.00x0400
    Windows 20000x0500
    Windows XP
    Windows Server 2003
    0x0501
    Windows XP SP2
    Windows Server 2003 SP1
    0x0502
    Windows Vista0x0600
    Windows 70x0601
    Windows 80x0602
  • IEの最小バージョンの定義
    IE_WIN32_IE
    Windows 95
    Windows NT 4.0
    0x0200
    Internet Explorer 4.00x0400
    Internet Explorer 4.010x0401
    Internet Explorer 5.0, 5.0a, 5.0b0x0500
    Internet Explorer 5.010x0501
    Internet Explorer 5.50x0550
    Internet Explorer 6.00x0600
    Internet Explorer 6.0 SP10x0601
    Internet Explorer 6.0 SP20x0603
    Internet Explorer 7.00x0700
    Internet Explorer 8.00x0800
    Internet Explorer 9.00x0900
    Internet Explorer 10.00x0A00

参考URL

コマンドライン引数 Edit

  • Win32API

    ::GetCommandLine()で取得可能。
    また、WinMain()で起動された場合は、__argv/__argc マクロを使用することで引数リストを受け取れる。main()の引数 argv/argc のように扱えるので便利。

  • MFC

    CWinApp::m_lpCmdLine に格納されている。

二重起動防止 Edit

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>をインクルードする必要がある。
※ ミューテックス作成時に渡す文字列は大文字小文字を区別することに注意。

処理中の砂時計カーソル Edit

  • Win32API

    ::LoadIcon() であらかじめ用意されているカーソルから砂時計カーソルを取得して ::SetCursor() で使用する。

      1
      2
      3
      4
      5
      6
      7
      8
      9
    
        // HINSTANCE    hInst   アプリケーションのインスタンスハンドル
     
        // 砂時計カーソル
        ::SetCursor(LoadCursor(NULL, IDC_WAIT));
     
        // 任意の処理
     
        // カーソルを元に戻す
        ::SetCursor(LoadCursor(NULL, IDC_ARROW));
  • MFC

    任意の処理の先頭で CWaitCursor型の変数を定義するのみ。
    CWaitCursorクラスのコンストラクタが砂時計カーソルを設定し、デストラクタが元に戻す。

      1
      2
      3
      4
      5
      6
    
        // 砂時計カーソル
        CWaitCursor waitCursor;
     
        // 任意の処理
     
        // 何もしなくても、スコープを抜ければカーソルが元に戻る
    

カーソルの変更 Edit

  • MFC

    CWnd::OnSetCursor() を実装する。
    引数の nHitTest でカーソル位置、message でマウス状態を判断することができる。nHitTest に用いられるマウス列挙子は、MSDNの CWnd::OnNcHitTest() の項を参照。message は、マウス関連のウィンドウメッセージが格納される。
    実際に設定するにはカーソルハンドルを指定する必要がある。OSに用意されているストックオブジェクトを用いる場合、::LoadCursor() の第一引数にNULLを渡し、第二引数にリソースIDを指定する。ストックオブジェクトのIDは、MSDNの::LoadCursor() の項を参照。

     
    例)
      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
    
        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);
        }

外部プログラムの起動 Edit

  • ::CreateProcess()
    実行可能モジュールを新しいプロセスで起動する関数。
    基本的に第1引数は NULL にしておき、第2引数の文字列の1つ目のトークンとして実行コマンド名も指定する方が無難。
    理由は、第1引数に指定したコマンドは、フルパス指定かカレントディレクトリに実体を持つ必要がある。第2引数であれば、コマンドプロンプトと同様、パスが通った場所も検索されるため。
  • ::ShellExecute()
    シェルにコマンドを実行させる関数。用途は実行ファイルの起動に限らない。
    関連付けられたアプリケーションでファイルを開くことも簡単にできる。

Win32APIやMFCのメンバ関数の戻り値 Edit

  • BOOL型 (int型)
    • エラーの詳細を知るためには、::GetLastError() を使用する必要がある。
    • 戻り値を TRUE(1)と比較してはいけない。条件が「真」であることの定義は「0ではない」ことなので、関数が成功した場合に「1」を返している保障はないことに注意。
  • HRESULT型 (long型)
    • 成否の判定には、SUCCEEDED() / FAILED() マクロを用いる。
      (値の正負を判断しているだけ。つまり、HRESULT型は最上位ビットが成否を表している。)
    • 戻り値からエラーの詳細を知ることができる。
    • HRESULT型には、S_FALSE という、「エラーではないが失敗」という微妙なニュアンスを表す値が存在する。上記マクロを使用した場合、「真」と判断されるため注意。

クリップボードの読み書き Edit

  • サンプルコード
    • テキストの読み込み
        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
      
          // 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();
    • テキストの書き込み
        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
      
          // 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);
          }
  • クリップボードを閉じた後でのデータの変更
    クリップボードにはハンドル(グローバルな領域のアドレスを指す)が設定されているだけであり、ビットマップや文字列のデータそのものがコピーされているわけではない。
    そのため、クリップボードを閉じた後であっても、データを変更するとクリップボードの指す先の内容を変更したことになってしまう。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
    // HBITMAP hBmpは、ビットマップデータのハンドル
 
    // クリップボードを開く
    if(::OpenClipboard(hWnd)){
        // クリップボードをクリアして所有権を得る
        if(::EmptyClipboard()){
            // ビットマップをクリップボードに設定
            ::SetClipboardData(CF_BITMAP, hBmp);
        }
        // クリップボードを閉じる
        ::CloseClipboard();
    }
 
    // ここでhBmpの画像データを変更すると、クリップボードに設定された画像データも変更されることになる

アクセラレータ Edit

アプリケーションのショートカットキーを実現するアクセラレータの使用方法。

  1. リソースにアクセラレータテーブルを追加する。(例:IDR_ACCELERATOR)
  2. アクセラレータを関連付けるウィンドウ(ダイアログ)クラスの CWnd::PreTranslateMessage() をオーバライドし、以下のコードを追加する。
      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
    
        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);
        }
  3. アクセラレータテーブルにショートカットキーを登録する。(例:「Ctrl+C」→ ID_ACCEL_CTRL_C)
  4. クラスウィザードで、アクセラレータをウィンドウ(ダイアログ)クラスに関連付け、COMMANDメッセージのイベントハンドラを作成する。

タスクトレイ常駐 Edit

タスクトレイに常駐するアプリケーションの基本的な作成方法。

  1. リソースに、タスクトレイに表示するアイコンと、ポップアップ用のメニューを追加する。
    1. アイコン
      アイコンを切り替える場合は、複数用意しておく。(例:IDR_MAINFRAME, IDR_SUBICON)
    2. ポップアップメニュー
      通常のメニューと同様に追加する。ポップアップ用メニューはサブメニューとして定義する必要がある。(例:IDR_POPUPMENU)
  2. ポップアップメニューの項目のイベントハンドラを実装しておく。
  3. ウィンドウ(ダイアログ)クラスのメンバ変数として以下を定義しておく。
      1
    
        NOTIFYICONDATA      m_notifyIcon;   // タスクトレイのアイコンデータ
    
  4. タスクトレイのアイコンがクリックされた場合のユーザ定義メッセージを定義する。
      1
    
        #define     WM_USER_ACTION      (WM_USER + 100)
  5. アプリケーションの初期処理で、メインウィンドウを非表示し、タスクトレイにアイコンを設定する。
      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;     // メンバの 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);
  6. 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:
            // 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;
        }
  7. アプリケーションの終了処理で、タスクトレイからアイコンを削除する。
      1
      2
    
        // タスクトレイのアイコンを削除
        ::Shell_NotifyIcon(NIM_DELETE, &m_notifyIcon);
  8. タスクトレイのアイコンに対する処理の例。
      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
    
        //////// バルーンチップの例 (※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/

プロセス間共有メモリ Edit

通常、異なるアプリケーション間では別々のメモリ空間が使用されるため、メモリを共有することができない。(アプリAのメモリアドレス0x00001111は、アプリBのメモリアドレス0x00001111とは別の領域である)
そこで、OS側に仮想的なメモリ空間を作成してもらい、そのメモリ空間をそれぞれのアプリケーションが自プロセスのメモリ空間にマッピングして使用することで、あたかも同じメモリを操作しているように振る舞うことができる。
アプリAが共有メモリの内容を変更すると、それをマッピングしている全てのプロセスの共有メモリが同じように変更される。

変更が即時反映されるファイルを複数プロセスで同時にオープンしているイメージ。共有メモリは実体がない(正確にはOSが管理するメモリ空間に実体を持つらしい)ため、一意な名前をつけて識別する必要がある。

  1. 共有ファイル作成に使用する変数等の定義
      1
      2
      3
      4
      5
    
    #define    FMAP_KEY    "TEST_FMAP"                // 共有メモリの識別名
    #define    FMAP_SIZE    (sizeof(char) * 100)    // サイズ:100バイト
     
    HANDLE    hFileMap;        // 共有メモリのハンドル
    char    *pData;            // 共有メモリのマッピング先アドレス
    
  2. 共有ファイルの使用開始
      1
      2
      3
      4
      5
      6
      7
    
        // 共有メモリの実体を作成する
        // ※既に実体が作成されている場合はオープンとなる。
        //  明示的にオープンする場合は::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);
  3. 共有ファイルの使用終了
      1
      2
      3
      4
      5
      6
      7
    
        // 共有メモリのビューのマッピングを終了
        ::UnmapViewOfFile(pData);
        pData = NULL;
     
        // 共有メモリのハンドルを閉じる
        ::CloseHandle(hFileMap);
        hFileMap = NULL;
    

アプリケーションのアイコン設定 Edit

  • エクスプローラ上で表示されるexeファイルのアイコン
    アプリケーションのリソースファイル(*.rc)内で定義されているアイコンの内、IDが最小のものが使用される。
    コンソールアプリケーションのアイコンを変更したい場合も、アイコンを定義したリソースファイルとともにビルドすることで実現できる。
  • ウィンドウ左上のアイコン
    コード中で明示的にアイコンを設定する必要がある。
    コンソールアプリケーションの場合は変更不可。
    • Win32API
      ::RegisterClassEx() に指定するウィンドウクラス(WNDCLASS)に、アイコンハンドルを登録する。
    • MFC
      CWnd::SetIcon() を使用してアイコンを設定する。

アプリケーションのバージョン情報の取得 Edit

対象の実行ファイルのパスから、バージョン情報を取得できる。

  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
    // 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()を使用する場合のリダイレクトの方法 Edit

外部コマンドを起動して標準出力をリダイレクトする方法。(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
    // 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)

同様の手順で、標準入力や標準エラー出力もリダイレクト可能。

ファイルのアイコン表示(エクスプローラと同じ表示) Edit

  • アイコンを単体で取得する場合
      1
      2
      3
      4
      5
      6
      7
      8
      9
    
        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);
  • アイコンをイメージリストとして取得する場合(システムイメージリストの取得)
      1
      2
      3
      4
      5
      6
      7
      8
      9
    
        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);
      1
      2
      3
      4
      5
    
        // アイコンを使用するときのインデックス
        SHFILEINFO    sfi
        SHGetFileInfo(ファイルパス, 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX);
     
        // sfi.iIcon にイメージリストのインデックスが取得される
    

リストビュー、ツリービュー、タブなどのコントロールにアイコンを表示する場合は、イメージリストを使用した方法を使用する必要がある。
あらかじめ、CListCtrl::SetImageList()、ListView_SetImageList() などでイメージリストをコントロールに登録しておき、CListCtrl::InsertItem()、ListView_InsertItem() でアイテムを挿入する際にアイコンのインデックスを指定することで表示される。

※コントロールにシステムイメージリストを使用する場合は、ウィンドウスタイルに LVS_SHAREIMAGELISTS (リストビューコントロールの場合) を設定しておくこと。

TCPサーバーの通信切断検知 Edit

TCP通信を行っている際、LANケーブルが抜けるなどで通信切断されてしまってもサーバー側はしばらく復帰を待ってしまいなかなか通信切断を検知してくれない。
この動作は、Keep-Aliveパケットを投げて通信が復帰しないことを確認して諦めるまでの時間に依存し、下記3つのパラメータによって時間が決定される。

パラメータ説明デフォルト値(Linux)
TCP_KEEPIDLEKeep-Aliveを投げ始めるまでの待機時間(秒)7200
TCP_KEEPINTVLKeep-Aliveを投げる間隔(秒)75
TCP_KEEPCNTKeep-Aliveを投げる回数9

デフォルトだと、7200 + 75 * 9 = 7875秒 待機してしまうことになるので、もっと早く切断を検知してほしい場合は下記のようにパラメータを変更しておく必要がある。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
    // 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

同期処理/排他制御 Edit

セマフォミューテックスクリティカルセクション
同時に所有できる数上限を設定可能1つまで1つまで
使用可能な範囲プロセスをまたいで使用可能プロセスをまたいで使用可能同一プロセス内でのみ使用可能
Win32APIオブジェクトの作成CreateSemaphoreCreateMutexInitializeCriticalSection
所有権の取得WaitForSingleObject に代表される待機関数EnterCriticalSection
所有権の破棄ReleaseSemaphoreReleaseMutexLeaveCriticalSection

C/C++ネイティブ Edit

C/C++共通 Edit

定義済みプリプロセッサマクロ Edit

カテゴリマクロ名意味
ANSI C
(C90)
__STDC__標準規格のC言語であることを示す
K&Rとの区別に用いる
__FILE__ソースファイルパス
__LINE__ソースの行番号
__TIMESTAMP__コンパイル日時
__DATE__コンパイル日
__TIME__コンパイル時刻
C99__func__関数名
C++__cplusplusC++であることを示す
値が1とは限らない
文字セット
(どちらか一方)
_MBCSマルチバイト文字セットであることを示す
_UNICODEワイド文字(Unicode)セットであることを示す
ビルド構成
(どちらか一方)
_DEBUGDebug版
NDEBUGRelease版

const修飾子の効力 Edit

ある関数Aでconst修飾された引数を、さらに関数Bの非constな引数として渡すことができてしまう。もちろん関数B内で値の書き換えも可能。
コンパイラの警告レベルによっては、警告は出力される。

CALLBACK関数の定義 Edit

CALLBACK関数は、グローバル関数やstaticメンバ関数などの静的な関数として定義する必要がある。
その他、関数ポインタを使用する仕組みも同様。
クラスのメンバ関数はインスタンスが生成されるまでアドレス空間のどの位置に配置されるかが決まっていないため、ポインタにアドレスを格納できない。

ワイルドカード(*, ?)を用いた文字列検索 Edit

参考: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は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;
}

関数シグニチャのマングルについて Edit

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

構造化例外のキャッチ Edit

例えば、メモリアクセス違反(アクセスバイオレーション)などが構造化例外。
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++ Edit

static関数/変数 Edit

  • staticなメンバ変数

    作成されるすべてのオブジェクト、派生クラスのオブジェクトでひとつの静的変数として値をやりとりできる。

  • メンバ関数内のstaticなローカル変数

    staticなメンバ変数と同じ。(スコープはその関数内のみという点が異なる)

  • staticなメンバ関数

    クラスのインスタンス生成無しで関数を呼び出せる。

      1
      2
    
        // 例
        CSampleClass::sampleStaticMethod();

CPPファイルからCファイルに定義された関数を呼び出す Edit

関数のシグネチャがCとCPPで異なるため、単純なextern宣言では呼び出せない。(リンクエラーになる)

  1
  2
  3
  4
  5
  6
  7
  8
    // グローバル関数 or グローバル変数
    extern "C" ~~
 
    // 複数のグローバル関数
    extern "C"{
        ~~
        ~~
    }

メンバ初期化子(:) Edit

クラスのメンバ変数を初期化する際、コンストラクタで初期値を代入することもできるが、これは厳密な意味での「初期化」ではなく、値の「代入」である。
厳密に初期化を行う場合は、メンバ初期化子「:」を用いる。コンストラクタ初期化子とも呼ばれる。

  • コンストラクタでの「代入」
      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) Edit

typeid演算子によって、オブジェクトが何のクラスのインスタンスなのかを実行時に知ることができる。
使用するには、プロジェクトの設定で「ランタイムタイプ情報(RTTI)を有効にする」にチェックを付けることでコンパイルオプション「/GR」を指定しなければならない。

メンバアクセス演算子でprivateメンバにアクセス Edit

自身のクラス型のオブジェクトの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);    // OK
    }
    void Test2(CSample *pObj)
    {
        pObj->Test0();            // OK
    }
};

コーディングの慣例 Edit

MFC派生クラスのヘッダファイル Edit

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
// 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:
    // ⑨
};

説明

概要説明追加
CObjectクラスの
機能の使用宣言
全てのMFCクラスの基底クラスであるCObjectの機能を使用する場合は、hファイルに DECLARE_DYNAMIC マクロを追加する必要がある。
また、その場合はcppファイルにも対応する IMPLEMENT_DYNAMIC マクロを追加する必要がある。
VC6では、自動的に追加される訳ではないので注意。
任意
コンストラクションコンストラクタやCWnd::Create()のような、そのクラスのオブジェクトを使用する前に必ずコールする必要のあるメンバ関数を定義する。任意
アトリビュートそのクラスの属性(プロパティ)を制御する公開メンバ変数や、Set/Getメンバ関数を定義する。
Getメンバ関数はconstにすべきである。
任意
オペレーションそのクラスのオブジェクトに対して、何らかの操作を行わせるための公開メンバ関数を定義する。任意
オーバーライドMFC基底クラスの仮想関数のオーバーライドを定義する。ウィザード
インプリメンテーション
(公開メンバ)
そのクラスの実装のための公開メンバ(通常、クラスの使用者が知る必要のないメンバ)を定義する。ウィザード
インプリメンテーション
(被保護メンバ)
そのクラスの実装のための被保護メンバ(通常、クラスの使用者が知る必要のないメンバ)を定義する。ウィザード
Window
メッセージハンドラ
Windowメッセージハンドラ(メンバ関数)を定義する。ウィザード
インプリメンテーション
(非公開メンバ)
そのクラスの実装のための非公開メンバ(通常、クラスの使用者が知る必要のないメンバ)を定義する。ウィザード

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-07-17 (水) 10:43:18 (1762d)