作成日: 2007/08/07

メモ: Windows プログラミングの覚え書き

概要

この前、ちょっとしたツールを作成した際にネットを探したり、作ったりした事柄を自身への覚え書きとして整理しました。 ネットを探せば同じ内容のもっと高度で詳細な記事がありますので、念のため。
処理系は VC++ 6 & MFC で行っていますが、VC++ 2005 でも利用できると思います (たぶん)。 嘘やらバグやらあったら、ごめんなさい。責任は持てません。。。(^^;)

ダイアログのベースフォントを変更する

ダイアログリソースをリソースエディタで作成し、クラスウィザードで対応するダイアログクラスを作成します。

モーダルダイアログを作成する場合、ダイアログクラスの DoModal() をオーバーライドします。

int CModalDlg::DoModal()
{
    LPCTSTR lpszTemplateName = m_lpszTemplateName;

    HINSTANCE hInst = AfxFindResourceHandle(lpszTemplateName, RT_DLGINIT);
    HRSRC hResource = FindResource(hInst, lpszTemplateName, RT_DLGINIT);
    HGLOBAL hDialogTemplate = LoadResource(hInst, hResource);
    void* lpDialogInit = LockResource(hDialogTemplate);

    //
    // ダイアログのリソースをロードしてフォントを設定する。
    //
    CDialogTemplate dlgTempl;
    dlgTempl.Load(lpszTemplateName);

    CString fontName = _T("MS UI Gothic");    // フォント名
    int fontSize = 15;                        // フォントサイズ

    dlgTempl.SetFont(fontName, fontSize);
    LPCDLGTEMPLATE pTemplate = (LPCDLGTEMPLATE) LockResource(dlgTempl.m_hTemplate);

    //
    // ダイアログを表示する。
    //
    m_lpszTemplateName = 0;
    InitModalIndirect(pTemplate, m_pParentWnd, lpDialogInit);
    return CDialog::DoModal();
}

モーダルダイアログの表示は、表示したい時にモーダルダイアログクラスの変数を作成して DoModal() を呼び出します。

CModalDlg modalDlg;
modalDlg.DoModal();

モーダレスダイアログを作成する場合、ダイアログクラスに Create() を追加します。

BOOL CModelessDlg::Create()
{
    LPCTSTR lpszTemplateName = m_lpszTemplateName;

    HINSTANCE hInst = AfxFindResourceHandle(lpszTemplateName, RT_DLGINIT);
    HRSRC hResource = FindResource(hInst, lpszTemplateName, RT_DLGINIT);
    HGLOBAL hDialogTemplate = LoadResource(hInst, hResource);
    void* lpDialogInit = LockResource(hDialogTemplate);

    //
    // ダイアログのリソースをロードしてフォントを設定する。
    //
    CDialogTemplate dlgTempl;
    dlgTempl.Load(lpszTemplateName);

    CString fontName = _T("MS UI Gothic");    // フォント名
    int fontSize = 15;                        // フォントサイズ

    dlgTempl.SetFont(fontName, fontSize);
    LPCDLGTEMPLATE pTemplate = (LPCDLGTEMPLATE) LockResource(dlgTempl.m_hTemplate);

    //
    // ダイアログを作成する。
    //
    m_lpszTemplateName = 0;
    return CDialog::CreateIndirect(pTemplate, m_pParentWnd, lpDialogInit);
}

モーダレスダイアログの表示は、あらかじめモーダレスダイアログの変数を設けておき、 初期化ルーチンなどで作成した後、表示したい時に ShowWindow() で表示します。

// モーダレスダイアログ
CModelessDlg m_ModelessDlg;
// モーダレスダイアログを作成しておく。
BOOL rc = m_ModelessDlg.Create();
ASSERT(rc);
m_ModelessDlg.ShowWindow(SW_SHOW);

なお、動的にダイアログのフォントおよびサイズを変更した場合、モーダル/モーダレスに関係なく、 作成したダイアログのサイズもフォントの大きさにあわせて変更されます。

また、DoModal()、Create() ともに CDialogTemplate を使うために afxpriv.h をインクルードする必要があります。

#include <afxpriv.h>

サンプルソース (ChangeDlgBaseFont.zip) (17.0 KB)

ダイアログにステータスバーを付加する

ダイアログリソースをリソースエディタで作成し、クラスウィザードで対応するダイアログクラスを作成します。

ダイアログクラスにステータスバー用のクラス CStatusBar と ステータスバー作成時に引数として渡すステータスバーインジケーター用の配列 CArray<UINT, UINT> のメンバ変数を追加します。

    // ステータスバー
    CStatusBar m_wndStatusBar;
    // ステータスバーインジケーター
    CArray<UINT, UINT> m_statusBarIndicators;

ダイアログクラスのコンストラクタでインジケータ用の配列 m_statusBarIndicators に値を設定します。 以下ではセパレータのみ追加しています。

    // セパレーターのみ
    m_statusBarIndicators.Add(ID_SEPARATOR);

クラスウィザードを使って WM_INITDIALOG のハンドラ OnInitDialog() を作成し、ステータスバーの作成を行います。

    //
    // ステータスバーを作成し、表示する。
    //
    if (0 < m_statusBarIndicators.GetSize()) {
        if (!m_wndStatusBar.Create(this)
         || !m_wndStatusBar.SetIndicators(m_statusBarIndicators.GetData(), m_statusBarIndicators.GetSize())) {
            // 失敗した
            return FALSE;
        }

        // 枠を上下につける。
        m_wndStatusBar.SetBarStyle(CBRS_BORDER_TOP | CBRS_BORDER_BOTTOM);
    }

ダイアログクラスにステータスバーを適正な位置に配置するための適当なメンバ関数を追加し、ステータスバーの位置を設定します。

// ステータスバーを配置する。
void CStatusBarDlg::LayoutControls()
{
    CRect rcClient;

    const int statusBarHeight = 24; // 適当な高さにする。

    CDialog::GetClientRect(&rcClient);

    //
    // ステータスバーを再配置する。
    //
    if (m_wndStatusBar.GetSafeHwnd()) {

        CRect rcStatusBar(
                rcClient.left,
                rcClient.bottom - statusBarHeight,
                rcClient.right,
                rcClient.bottom
            );

        m_wndStatusBar.MoveWindow(rcStatusBar);
    }
}

先に追加した OnInitDialog() から上記メンバ関数 LayoutControls() を呼び出します。

// WM_INITDIALOG ハンドラ
BOOL CStatusBarDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    //
    // ステータスバーを作成し、表示する。
    //
    if (0 < m_statusBarIndicators.GetSize()) {
        if (!m_wndStatusBar.Create(this)
         || !m_wndStatusBar.SetIndicators(m_statusBarIndicators.GetData(), m_statusBarIndicators.GetSize())) {
            // 失敗した
            return FALSE;
        }

        // 枠を上下につける。
        m_wndStatusBar.SetBarStyle(CBRS_BORDER_TOP | CBRS_BORDER_BOTTOM);
    }

    // ステータスバーを再配置する
    LayoutControls();

    return TRUE;
}

ダイアログをサイズ変更枠にする場合は、さらにクラスウィザードを使って WM_SIZE のハンドラ OnSize() を作成し、 LayoutControls() を呼び出します。

// WM_SIZE ハンドラ
void CStatusBarDlg::OnSize(UINT nType, int cx, int cy)
{
    CDialog::OnSize(nType, cx, cy);

    // ステータスバーを再配置する
    LayoutControls();
}

なお、上記は CArray を使うために afxtempl.h をインクルードする必要があります。 ただし、CArray をわざわざ使う理由はないので、CArray の代わりに UINT の配列を使い、 コンストラクタで値を設定し、ステータスバー作成の際の引数としても構いません。

#include <afxtempl.h>

サンプルソース (AddStatusBar2Dlg.zip) (15.2 KB)

UI スレッドからワーカースレッドへのイベント付きのキューを使う

UI スレッドからワーカースレッドに必要な値を渡して通知し処理をさせたいときに少しだけ便利かもしれないクラスです。

使い方

  1. 使いたい型を使ってキューの typedef 定義をします。サンプルでは int を使っています。
    ※変数定義で書いてもいいのですが、めんどうなので、typedef しています。

    // イベント付きのキュー
    typedef CEventQue<int> CThreadEventQue;
    // キューの値
    typedef CArray<int, int> QueValue;
    
  2. typedef した型を使って変数を定義します。サンプルではダイアログクラスのメンバにしました。

        // イベント付きのキュー
        CThreadEventQue m_Que;
    
  3. ワーカースレッドを生成して、キューの入力待ちに入ります。

            // キューの入力を待つ
            QueValue& queValue = pThreadEventQue->Get();
    
  4. UI スレッドからワーカースレッドに値を渡して通知する場合は、キューに値を追加します。 これによりワーカースレッドはキューの入力待ちから抜けますので、キューの値を使って必要な処理をします。

        int number = _ttoi(m_Number);
        m_Que.Add(number);
    
  5. ワーカースレッドを終了させるなどするためにキューの入力待ちから抜けさせるためには、 UI スレッド側からイベントを発生させます。なお、サンプルでは SetEvent() 呼び出しは使わずに、 キューに負の値が追加されたらワーカースレッドが終了するコードになっています (深い意味はありません)。

    m_Que.SetEvent();
    

サンプルソース (WorkerThreadQue.zip) (14.9 KB)

ダイアログのタイトルバーではなくクライアント領域のマウス操作でウィンドウを移動する

ダイアログリソースをリソースエディタで作成し、クラスウィザードで対応するダイアログクラスを作成します。
クラスウィザードで WM_LBUTTONDOWN のハンドラ OnLButtonDown() を作成し、ハンドラ内で WM_NCLBUTTONDOWN を送ります。

// WM_LBUTTONDOWN ハンドラ
void CMoveDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
    // ダイアログを移動する。
    SendMessage(WM_NCLBUTTONDOWN, HTCAPTION, 0);

    CDialog::OnLButtonDown(nFlags, point);
}

サンプルソース (MoveClientWindow.zip) (36.7 KB)

INI ファイルの読み書き

読み書きの実装が少しだけ楽になる(かも?な)クラスです。なお、整数値(int)、文字列、浮動小数点値(double) のみ対応しています。

使い方

  1. CProfileManager クラスを継承したクラスを書き、INI から読み書きする値を保持する変数をメンバとして定義します。
    サンプルでは MySection1 に int の Key1、CString の Key2、double の Key3、 また MySection2 にも int の Key1、CString の Key2、double の Key3 を定義しました。 なお、構造体にする必要は全くないのですが、コードを書いているときにわかりやすいかな、 と思い構造体でセクションを表し、個々の値はそのメンバとなるよう定義しました。

    class CMyProfileReadWrite : public CProfileManager {
    protected:
        // INI 情報を初期化する
        virtual void Init();
        // INI 情報を読み書きする
        virtual void ProfileRW();
    
    public:
        struct MySection1_ {
            int Key1;
            CString Key2;
            double Key3;
        } MySection1;
        struct MySection2_ {
            int Key1;
            CString Key2;
            double Key3;
        } MySection2;
    };
    
  2. 純粋仮想関数である Init() と ProfileRW() を実装します。
    Init() には上で定義した変数に初期値を与えます。 INI ファイル上に値が設定されていないときは、代わりにこの値が使われます。
    ProfileRW() には INI ファイルからの読み書きのためのコードを書きます。ItemRW() 関数の引数として、 前から順に、セクション名文字列、キー名文字列、上で定義した変数、を渡します。

    // INI 情報を初期化する
    void CMyProfileReadWrite::Init()
    {
        // デフォルト値を設定する
        // MySection1
        MySection1.Key1 = 11;
        MySection1.Key2 = _T("Value22");
        MySection1.Key3 = 33.3;
    
        // MySection2
        MySection2.Key1 = 111;
        MySection2.Key2 = _T("Value222");
        MySection2.Key3 = 333.33;
    }
    
    // INI 情報を読み書きする
    void CMyProfileReadWrite::ProfileRW()
    {
        // MySection1
        ItemRW(_T("MySection1"), _T("Key1"), MySection1.Key1);
        ItemRW(_T("MySection1"), _T("Key2"), MySection1.Key2);
        ItemRW(_T("MySection1"), _T("Key3"), MySection1.Key3);
    
        // MySection2
        ItemRW(_T("MySection2"), _T("Key1"), MySection2.Key1);
        ItemRW(_T("MySection2"), _T("Key2"), MySection2.Key2);
        ItemRW(_T("MySection2"), _T("Key3"), MySection2.Key3);
    }
    
  3. INI ファイルからの読み書きを行います。
    Load() で INI ファイルから値が読み込まれます。Save() で INI ファイルに書き込まれます。

            // INI ファイルのパスを得る。
            TCHAR modulePath[4096];
            DWORD length = GetModuleFileName(AfxGetInstanceHandle(), modulePath, sizeof(modulePath));
            modulePath[length] = _T('\0');
            TCHAR *wp = _tcsrchr(modulePath, _T('\\'));
            if (wp) {
                *wp = _T('\0');
            }
            CString filepath;
            filepath.Format(_T("%s\\%s"), modulePath, _T("MyProfile.ini"));
    
            // INI ファイルを読む。
            CMyProfileReadWrite myProfile;
            myProfile.Load(filepath);
    
            // INI ファイルの内容を表示する。
    
            // MySection1
            TRACE(_T("MySection1.Key1: [%d]\n"), myProfile.MySection1.Key1);
            TRACE(_T("MySection1.Key2: [%s]\n"), myProfile.MySection1.Key2);
            TRACE(_T("MySection1.Key3: [%f]\n"), myProfile.MySection1.Key3);
    
            // MySection2
            TRACE(_T("MySection2.Key1: [%d]\n"), myProfile.MySection2.Key1);
            TRACE(_T("MySection2.Key2: [%s]\n"), myProfile.MySection2.Key2);
            TRACE(_T("MySection2.Key3: [%f]\n"), myProfile.MySection2.Key3);
    
            // INI ファイルの値を設定する。
            ++myProfile.MySection1.Key1;
            myProfile.MySection1.Key2 = _T("ほげ");
            myProfile.MySection1.Key3 = 1.23;
    
            myProfile.MySection2.Key1 = 999;
            myProfile.MySection2.Key2 += _T("あいうえお");
            myProfile.MySection2.Key3 *= 3;
    
            // INI ファイルに書く。
            myProfile.Save();
    

サンプルソース (IniReadWrite.zip) (8.02 KB)

タスクトレイの操作

タスクトレイの実装が少しだけ楽になる(かも?な)クラスです。

使い方 (を書いてて思ったのですが、使いにくいです。。。すみません)。

  1. タスクトレイ操作用のクラス CTaskTray の変数を定義します。サンプルではダイアログクラスのメンバにしました。

        // タスクトレイ用のクラス
        CTaskTray m_TaskTray;
    
  2. タスクトレイのアイコンを操作したときに送られてくるウィンドウメッセージを定義します。

    // タスクトレイからの通知メッセージ用
    #define WM_NOTIFY_TRAY_ICON_MSG   (WM_APP + 100)
    
  3. タスクバー再起動時に通知されるメッセージを保持する UINT の静的変数を設けます。

        // タスクバー再起動時に通知されるメッセージ
        static UINT s_WmTaskbarCreated;
    
    // タスクバー再起動時に通知されるメッセージ
    UINT CTaskTraySampleDlg::s_WmTaskbarCreated = 0;
    
  4. タスクトレイのアイコンが操作されたときのメッセージを受け取るためのハンドラと、 タスクバーが再起動されたときのメッセージを受け取るためのハンドラを作成します。 サンプルではそれぞれ OnNotifyTrayIconMsg()、OnTaskbarCreated() という名前で ダイアログのメンバ関数として定義しています。

    ヘッダのメッセージマップ関数の宣言部分に定義します。

        afx_msg LRESULT OnNotifyTrayIconMsg(WPARAM wParam, LPARAM lParam);
        afx_msg LRESULT OnTaskbarCreated(WPARAM wParam, LPARAM lParam);
    

    ソースに定義したメッセージマップです。なお、タスクバー再起動時のメッセージは、 CTaskTray クラス内でタスクトレイに登録した後に取得し静的メンバ変数 s_WmTaskbarCreated に格納することにしますので、 メッセージマップには静的メンバ変数をそのまま書きます。

        ON_MESSAGE(WM_NOTIFY_TRAY_ICON_MSG, OnNotifyTrayIconMsg)
        ON_REGISTERED_MESSAGE(s_WmTaskbarCreated, OnTaskbarCreated)
    

    ハンドラ本体です。タスクバー再起動時は、CTaskTray の OnTaskbarCreated() をそのまま呼び出します。

    // トレイアイコンからのメッセージ用のハンドラ
    LRESULT CTaskTraySampleDlg::OnNotifyTrayIconMsg(WPARAM wParam, LPARAM lParam)
    {
        UINT uMsg = lParam;
    
        // とりあえずマウスで右クリック、左クリックされたら表示する。
        switch (uMsg) {
        case WM_LBUTTONDOWN:
            AfxMessageBox(_T("WM_LBUTTONDOWN"), MB_OK | MB_ICONINFORMATION);
            break;
    
        case WM_RBUTTONDOWN:
            AfxMessageBox(_T("WM_RBUTTONDOWN"), MB_OK | MB_ICONINFORMATION);
            break;
        }
    
        return 0;
    }
    
    // タスクバー再起動時のメッセージハンドラ
    LRESULT CTaskTraySampleDlg::OnTaskbarCreated(WPARAM wParam, LPARAM lParam)
    {
        return m_TaskTray.OnTaskbarCreated(wParam, lParam);
    }
    
  5. リソースにアイコンを追加し、追加したアイコンをロードしておきます。サンプルでは WM_INITDIALOG ハンドラでロードしています。

        // アイコンをロードする。
        m_hIconRed = (HICON) LoadImage(AfxGetInstanceHandle(),
                MAKEINTRESOURCE(IDI_RED),
                IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
        ASSERT(m_hIconRed);
    
  6. タスクトレイにアイコンを登録します。サンプルではアイコンと文字を変更できるようにしたため、 Add() の引数に変な関数呼び出しが並んでいますが、前から順に、トレイアイコンからのメッセージを受け取るウィンドウのハンドル、 タスクトレイに表示するアイコンのハンドル、タスクトレイに表示する文字列 (マウスをかざすと表示されるツールチップ文字列)、 トレイアイコンからのウィンドウメッセージの定義、となります。
    また、タスクトレイに登録した後で、タスクバー再起動時のメッセージを取得し、静的メンバ変数に設定します。

        m_TaskTray.Add(GetSafeHwnd(), GetColorIcon(), m_Text, WM_NOTIFY_TRAY_ICON_MSG);
        s_WmTaskbarCreated = m_TaskTray.GetWmTaskbarCreated();
    
  7. タスクトレイのアイコンおよびツールチップ文字列を変更する場合は、

        m_TaskTray.Modify(GetColorIcon(), m_Text);
    

    とします。これも変な関数呼び出しが引数に並んでいますが、前から順に、 タスクトレイに表示するアイコンのハンドル、タスクトレイに表示する文字列 (マウスをかざすと表示されるツールチップ文字列)、 となります。

  8. 最後にタスクトレイからアイコンを削除するには、

        m_TaskTray.Delete();
    

    とします。ちなみにタスクトレイのアイコンは削除しない限り、プログラムが終了しても残ってしまいますので、 プログラム終了時には忘れずにタスクトレイアイコンの削除する必要があります。

サンプルソース (TaskTraySample.zip) (16.8 KB)

ソケットの操作

ソケットの実装が少しだけ楽になる(かも?な)クラスです。といってもネットを探せば優秀なライブラリがたくさんありますので、 そちらを使われることをお勧めします。。。
なお、サンプルはエラー処理をまったくしておらず非常に乱暴なので、お気を付け下さい。

使い方

  1. サーバソケットは CreateServerSocket() で作成します。引数は前から順に、ポート番号、バックログ値、です。 Accept() して取得されたクライアントからの接続に対して読み書きします。

            BOOL rc;
            SAMPLE_DATA sample;
            int length;
    
            CSocketLib sd;
            rc = sd.CreateServerSocket(5000, 5);
            ASSERT(rc);
            for (;;) {
                CSocketLib cd;
                rc = sd.Accept(&cd);
                ASSERT(rc);
    
                // 受信
                length = cd.Recv((CHAR*) &sample, sizeof(sample));
                ASSERT(length == sizeof(sample));
                printf("sample.m1: [%.1s]\n", sample.m1);
    
                // 送信
                memset(sample.m1, 'S', sizeof(sample.m1));
                length = cd.Send((CHAR*) &sample, sizeof(sample));
                ASSERT(length == sizeof(sample));
    
                cd.Close();
            }
            sd.Close();
    
  2. クライアントソケットは CreateConnectedClientSocket() で作成します。引数は前から順に、ホスト名(あるいは IP アドレス)、 ポート番号です。

            BOOL rc;
            int length;
            SAMPLE_DATA sample;
    
            CSocketLib cd;
            rc = cd.CreateConnectedClientSocket("localhost", 5000);
            ASSERT(rc);
    
            // 送信
            memset(sample.m1, 'C', sizeof(sample.m1));
            length = cd.Send((CHAR*) &sample, sizeof(sample));
            ASSERT(length == sizeof(sample));
    
            // 受信
            length = cd.Recv((CHAR*) &sample, sizeof(sample));
            ASSERT(length == sizeof(sample));
            printf("sample.m1: [%.1s]\n", sample.m1);
    
            cd.Close();
    

サンプルソース (SocketSample.zip) (16.4 KB)