ホーム ATL/WTL
カスタムドロー
ドキュメント種別 ATL/WTL に関する文書
最終更新日 2007/03/18
PR
 カスタムドローを利用すると、カスタムドローに対応したコントロールの外観を、 オーナードローよりも簡単にカスタマイズすることができます。 WTLはカスタムドローをサポートするためにCCustomDrawクラステンプレートを用意しています。

 次に示すのは、CCustomDrawクラステンプレートを使用する例です。 ストライプのリストビューを作成します。


プロジェクトファイル ダウンロード
// stdafx.h
#pragma once

#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>

#include <atlcrack.h>
#include <atlmisc.h>
#include <atlctrls.h>  // CCustomDrawを使用するため
			

// MainDlg.h
#pragma once

class CMainDlg : public CDialogImpl<CMainDlg>, public CCustomDraw<CMainDlg>
{
public:
    enum { IDD = IDD_MAINDLG };

    CListViewCtrl m_list_stripe;

    BEGIN_MSG_MAP(CMainDlg)
        MSG_WM_INITDIALOG(OnInitDialog)
        COMMAND_ID_HANDLER_EX(IDOK, OnOK)
        COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
        CHAIN_MSG_MAP(CCustomDraw<CMainDlg>)    // CCustomDrawへチェイン
    END_MSG_MAP()

    DWORD OnPrePaint(int nID, LPNMCUSTOMDRAW lpnmcd){
        if(lpnmcd->hdr.idFrom == IDC_LIST_STRIPE)
            return CDRF_NOTIFYITEMDRAW;
        else
            return CDRF_DODEFAULT;
    }

    DWORD OnItemPrePaint(int nID, LPNMCUSTOMDRAW lpnmcd){
        if(lpnmcd->hdr.idFrom == IDC_LIST_STRIPE){
            LPNMLVCUSTOMDRAW lpnmlv = (LPNMLVCUSTOMDRAW)lpnmcd;
            if(lpnmcd->dwItemSpec % 2){
                lpnmlv->clrText = RGB(255, 255, 255);
                lpnmlv->clrTextBk = RGB(128, 128, 128);
            }
        }
        return CDRF_DODEFAULT;
    }

    BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam){
        // スクリーンの中央に配置
        CenterWindow();

        // 大きいアイコン設定
        HICON hIcon = AtlLoadIconImage(IDR_MAINFRAME, LR_DEFAULTCOLOR,
            ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON));
        SetIcon(hIcon, TRUE);
        
        // 小さいアイコン設定
        HICON hIconSmall = AtlLoadIconImage(IDR_MAINFRAME, LR_DEFAULTCOLOR,
            ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON));
        SetIcon(hIconSmall, FALSE);

        // コントロール設定
        m_list_stripe = GetDlgItem(IDC_LIST_STRIPE);

        m_list_stripe.SetExtendedListViewStyle(LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT);

        CRect rcList;
        m_list_stripe.GetWindowRect(rcList);
        int nScrollWidth = GetSystemMetrics(SM_CXVSCROLL);
        int n3DEdge = GetSystemMetrics(SM_CXEDGE);
        m_list_stripe.InsertColumn(0, _T("名前"), LVCFMT_LEFT, 190, -1);
        m_list_stripe.InsertColumn(1, _T("種類"), LVCFMT_LEFT,
            rcList.Width() - 190 - nScrollWidth - n3DEdge * 2, -1);

        CFindFile find;
        if(find.FindFile()){
            do{
                if(!find.IsDots()){
                    int nIndex = m_list_stripe.GetItemCount();
                    m_list_stripe.AddItem(nIndex, 0, find.GetFileName());
                    m_list_stripe.AddItem(nIndex, 1,
                        find.IsDirectory() ? _T("フォルダ") : _T("ファイル"));
                }
            }while(find.FindNextFile());
        }

        return TRUE;
    }

    void OnOK(UINT uNotifyCode, int nID, CWindow wndCtl){
        EndDialog(nID);
    }

    void OnCancel(UINT uNotifyCode, int nID, CWindow wndCtl){
        EndDialog(nID);
    }
};
			

// SampleProject.cpp
#include "stdafx.h"
#include "resource.h"
#include "MainDlg.h"

CAppModule _Module;

int WINAPI _tWinMain(HINSTANCE hInstance, 
    HINSTANCE /*hPrevInstance*/, LPTSTR /*lpstrCmdLine*/, int /*nCmdShow*/)
{
    HRESULT hRes = ::CoInitialize(NULL);
    ATLASSERT(SUCCEEDED(hRes));

    ::DefWindowProc(NULL, 0, 0, 0L);

    AtlInitCommonControls(ICC_BAR_CLASSES);

    hRes = _Module.Init(NULL, hInstance);
    ATLASSERT(SUCCEEDED(hRes));

    CMainDlg dlg;
    int nRet = (int) dlg.DoModal();

    _Module.Term();
    ::CoUninitialize();

    return nRet;
}
			

 まず、リソースを作成します。ダイアログにリストビューコントロール配置し、 [ID]を次のように設定します。 なお、リストビューコントロールの「プロパティ」では[View]を[レポート]に設定します。

コントロール名 ID
リストビュー IDC_LIST_STRIPE

 次に、stdafx.hヘッダではCCustomDrawクラステンプレートを使用するためにatlctrls.hヘッダをインクルードします。

 CMainDlgクラスでは、基底クラスにCCustomDrawを追加し、 メッセージマップにCCustomDrawへチェインを追加します。 CCustomDrawは、内部に次のようなメッセージマップを用意しています。

// CCustomDrawのメッセージマップ
BEGIN_MSG_MAP(CCustomDraw< T >)
    NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
ALT_MSG_MAP(1)  // メッセージリフレクション用代替メッセージマップ
    REFLECTED_NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
END_MSG_MAP()
			

このメッセージマップでは、OnCustomDraw()メッセージハンドラを呼び出します。 このハンドラ関数では、NMCUSTOMDRAW構造体のdwDrawStageメンバの値によって、 次に示すオーバーライド可能なメンバ関数を呼び出します。

dwDrawStageの値 メンバ関数名
CDDS_PREPAINT OnPrePaint
CDDS_POSTPAINT OnPostPaint
CDDS_PREERASE OnPreErase
CDDS_POSTERASE OnPostErase
CDDS_ITEMPREPAINT OnItemPrePaint
CDDS_ITEMPOSTPAINT OnItemPostPaint
CDDS_ITEMPREERASE OnItemPreErase
CDDS_ITEMPOSTERASE OnItemPostErase
CDDS_ITEMPREPAINT | CDDS_SUBITEM OnSubItemPrePaint

CCustomDrawクラスへのチェインにより、 カスタムドローが必要なタイミングでメッセージがCCustomDrawのメッセージマップへ送られ、 これらのオーバーライド可能なメンバ関数が呼び出されます。 CCustomDrawの派生クラス(今回の例ではCMainDlgクラス)では、 これらのメンバ関数をオーバーライドすることによって、 コントロールに独自の外観を与えます。

今回の例では、まず、CMainDlgクラスでOnPrePaint()をオーバーライドし、 コントロールのIDがIDC_LIST_STRIPEの場合にはCDRF_NOTIFYITEMDRAWを返します。 これにより、リストビューコントロールのアイテムが描画されるタイミングでカスタムドロー用のメッセージが送られてきます。

次に、OnItemPrePaint()をオーバーライドし、 IDを確認してから、インデックスが偶数のアイテムの文字色と背景色を設定します。

 最後に、WM_INITDIALOGメッセージハンドラでリストビューコントロールにアイテムを設定します。 カラムを2つ追加し、カレントディレクトリのファイル名とフォルダ名をアイテムとしてリストビューコントロールに追加します。

 ところで、CCustomDrawのメッセージマップは、 メッセージリフレクション用の代替メッセージマップも用意しています。 次に示すのはメッセージリフレクションを使ったカスタムドローの方法です。

メッセージリフレクションによるカスタムドロー
 メッセージリフレクションを利用することによって、 コントロール自身にカスタムドローさせることができるため、 再使用可能なコントロールクラスを記述しやすくなります。 次に示すのは、CCustomDrawクラステンプレートとメッセージリフレクションを利用して、 アイテムをストライプ表示するリストビューコントロールを定義する例です。 プロジェクトにStripeListViewCtrl.hというヘッダファイルを追加し、 そこにCStripeListViewCtrlというクラスを定義します。

// StripeListViewCtrl.h
#pragma once

class CStripeListViewCtrl : public CWindowImpl<CStripeListViewCtrl, CListViewCtrl>,
    public CCustomDraw<CStripeListViewCtrl>
{
public:
    DECLARE_WND_SUPERCLASS(_T("StripeListViewCtrl"), GetWndClassName())

    BEGIN_MSG_MAP(CStripeListViewCtrl)
        CHAIN_MSG_MAP_ALT(CCustomDraw<CStripeListViewCtrl>, 1)
        DEFAULT_REFLECTION_HANDLER()
    END_MSG_MAP()

    DWORD OnPrePaint(int nID, LPNMCUSTOMDRAW lpnmcd){
        return CDRF_NOTIFYITEMDRAW;
    }

    DWORD OnItemPrePaint(int nID, LPNMCUSTOMDRAW lpnmcd){
        LPNMLVCUSTOMDRAW lpnmlv = (LPNMLVCUSTOMDRAW)lpnmcd;
        if(lpnmcd->dwItemSpec % 2){
            lpnmlv->clrText = RGB(255, 255, 255);
            lpnmlv->clrTextBk = RGB(128, 128, 128);
        }

        return CDRF_DODEFAULT;
    }
};
			

このCStripeListViewCtrlクラスは、 CWindowImplCCustomDrawから派生しており、 CWindowImplの第2テンプレート引数にはCListViewCtrlを指定します。 これによりCStripeListViewCtrlクラスはCListViewCtrlの派生クラスとなります。

CStripeListViewCtrlクラスは、返送されたカスタムドロー用メッセージを、 チェインによってCCustomDrawの代替メッセージマップへ送ります。 CCustomDrawには前述のようにオーバーライド可能なメンバ関数が用意されており、 CStripeListViewCtrlクラスはその中のOnPrePaint()OnItemPrePaint() をオーバーライドしてリストビューアイテムを描画します。

 次に示すのは、CStripeListViewCtrlクラスを使用する例です。

プロジェクトファイル ダウンロード
// stdafx.h
#pragma once

#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>

#include <atlcrack.h>
#include <atlmisc.h>
#include <atlctrls.h>  // CCustomDrawを使用するため
			

// MainDlg.h
#pragma once

class CMainDlg : public CDialogImpl<CMainDlg>
{
public:
    enum { IDD = IDD_MAINDLG };

    CStripeListViewCtrl m_list_stripe;

    BEGIN_MSG_MAP(CMainDlg)
        MSG_WM_INITDIALOG(OnInitDialog)
        COMMAND_ID_HANDLER_EX(IDOK, OnOK)
        COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
        REFLECT_NOTIFICATIONS()
    END_MSG_MAP()

    BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam){
        // スクリーンの中央に配置
        CenterWindow();

        // 大きいアイコン設定
        HICON hIcon = AtlLoadIconImage(IDR_MAINFRAME, LR_DEFAULTCOLOR,
            ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON));
        SetIcon(hIcon, TRUE);
        
        // 小さいアイコン設定
        HICON hIconSmall = AtlLoadIconImage(IDR_MAINFRAME, LR_DEFAULTCOLOR,
            ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON));
        SetIcon(hIconSmall, FALSE);

        // コントロール設定
        m_list_stripe.SubclassWindow(GetDlgItem(IDC_LIST_STRIPE));

        m_list_stripe.SetExtendedListViewStyle(LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT);

        CRect rcList;
        m_list_stripe.GetWindowRect(rcList);
        int nScrollWidth = GetSystemMetrics(SM_CXVSCROLL);
        int n3DEdge = GetSystemMetrics(SM_CXEDGE);
        m_list_stripe.InsertColumn(0, _T("名前"), LVCFMT_LEFT, 190, -1);
        m_list_stripe.InsertColumn(1, _T("種類"), LVCFMT_LEFT,
            rcList.Width() - 190 - nScrollWidth - n3DEdge * 2, -1);

        CFindFile find;
        if(find.FindFile()){
            do{
                if(!find.IsDots()){
                    int nIndex = m_list_stripe.GetItemCount();
                    m_list_stripe.AddItem(nIndex, 0, find.GetFileName());
                    m_list_stripe.AddItem(nIndex, 1,
                        find.IsDirectory() ? _T("フォルダ") : _T("ファイル"));
                }
            }while(find.FindNextFile());
        }

        return TRUE;
    }

    void OnOK(UINT uNotifyCode, int nID, CWindow wndCtl){
        EndDialog(nID);
    }

    void OnCancel(UINT uNotifyCode, int nID, CWindow wndCtl){
        EndDialog(nID);
    }
};
			

// SampleProject.cpp
#include "stdafx.h"
#include "resource.h"
#include "StripeListViewCtrl.h"
#include "MainDlg.h"

CAppModule _Module;

int WINAPI _tWinMain(HINSTANCE hInstance, 
    HINSTANCE /*hPrevInstance*/, LPTSTR /*lpstrCmdLine*/, int /*nCmdShow*/)
{
    HRESULT hRes = ::CoInitialize(NULL);
    ATLASSERT(SUCCEEDED(hRes));

    ::DefWindowProc(NULL, 0, 0, 0L);

    AtlInitCommonControls(ICC_BAR_CLASSES);

    hRes = _Module.Init(NULL, hInstance);
    ATLASSERT(SUCCEEDED(hRes));

    CMainDlg dlg;
    int nRet = (int) dlg.DoModal();

    _Module.Term();
    ::CoUninitialize();

    return nRet;
}
			

 まず、CMainDlgクラスにCStripeListViewCtrlクラスのインスタンスをメンバ変数として宣言します。 これを使うためには、WM_INITDIALOGメッセージハンドラでサブクラス化し、 リストビューにアイテムを設定します。 メッセージマップにはメッセージリフレクションを利用するためにREFLECT_NOTIFICATIONSマクロを追加します。

 最後に、SampleProject.cppファイルでMainDlg.hヘッダの前にStripeListViewCtrl.hヘッダをインクルードします。