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

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


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

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

// maindlg.h内
class CMainDlg : public CDialogImpl<CMainDlg>, public CCustomDraw<CMainDlg>
{
public:
    enum { IDD = IDD_MAINDLG };

    CListViewCtrl m_list_stripe;

    // メッセージマップ
    BEGIN_MSG_MAP_EX(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;
    }

    LRESULT OnInitDialog(HWND hWnd, LPARAM lParam){
        // スクリーンの中央に配置
        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, HWND hWndCtl){
        EndDialog(nID);
    }

    void OnCancel(UINT uNotifyCode, int nID, HWND hWndCtl){
        EndDialog(nID);
    }
};
			

// Control.cpp内
#include "stdafx.h"

#include "resource.h"

#include "maindlg.h"

CAppModule _Module;

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

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

    AtlInitCommonControls(ICC_COOL_CLASSES | ICC_WIN95_CLASSES);

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

    int nRet = 0;
    // BLOCK: アプリケーション実行
    {
        CMainDlg dlgMain;
        nRet = dlgMain.DoModal();
    }

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

    return nRet;
}
			

 まず、リソースを作成します。ダイアログにリストビューコントロール配置し、 リソースIDを次のように指定します。 なお、リストビューコントロールの[スタイル]ではデフォルトに加え[表示]で[レポート]を選択します。

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

 次に、CCustomDrawクラスを使用するためにstdafx.h内で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を確認してから、インデックスが偶数のアイテムの文字色と背景色を設定します。

 最後に、OnInitDialog()でリストビューにアイテムを設定します。 カラムを2つ追加し、カレントディレクトリのファイルとフォルダをアイテムとしてリストビューに追加します。

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

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

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

    BEGIN_MSG_MAP_EX(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クラスは、 CWindowImplクラスとCCustomDrawクラスから派生していますが、 CWindowImplクラスの第2テンプレート引数にはCListViewCtrlを指定します。 これによりCStripeListViewCtrlクラスはCListViewCtrlの派生クラスとなります。

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

 次に、このCStripeListViewCtrlクラスを使って、先のストライプリストビューのプログラムを変更する例を示します。

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

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

// maindlg.h内
class CMainDlg : public CDialogImpl<CMainDlg>
{
public:
    enum { IDD = IDD_MAINDLG };

    CStripeListViewCtrl m_list_stripe;

    // メッセージマップ
    BEGIN_MSG_MAP_EX(CMainDlg)
        MSG_WM_INITDIALOG(OnInitDialog)
        COMMAND_ID_HANDLER_EX(IDOK, OnOK)
        COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
        REFLECT_NOTIFICATIONS()
    END_MSG_MAP()

    LRESULT OnInitDialog(HWND hWnd, LPARAM lParam){
        // スクリーンの中央に配置
        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, HWND hWndCtl){
        EndDialog(nID);
    }

    void OnCancel(UINT uNotifyCode, int nID, HWND hWndCtl){
        EndDialog(nID);
    }
};
			

// Control.cpp内
#include "stdafx.h"

#include "resource.h"

#include "StripeListViewCtrl.h"
#include "maindlg.h"

CAppModule _Module;

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

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

    AtlInitCommonControls(ICC_COOL_CLASSES | ICC_WIN95_CLASSES);

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

    int nRet = 0;
    // BLOCK: アプリケーション実行
    {
        CMainDlg dlgMain;
        nRet = dlgMain.DoModal();
    }

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

    return nRet;
}
			

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

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