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

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


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

#define WINVER 0x0420
#include <atlbase.h>
#if _ATL_VER == 0x900
#define _SECURE_ATL 1
#endif

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

#include <tpcshell.h>
#include <aygshell.h>
#pragma comment(lib, "aygshell.lib")

#include <atlcrack.h>
#include <atlmisc.h>
#include <atlframe.h>
#include <atlctrls.h>
#define _WTL_CE_NO_ZOOMSCROLL
#define _WTL_CE_NO_FULLSCREEN
#include <atlwince.h>
			

// SampleProjectDialog.h
#pragma once

class CSampleProjectDialog : 
    public CAppStdDialogImpl<CSampleProjectDialog>,
    public CUpdateUI<CSampleProjectDialog>,
    public CMessageFilter, public CIdleHandler, 
    public CCustomDraw<CSampleProjectDialog>
{
public:
    DECLARE_APP_DLG_CLASS(NULL, IDR_MAINFRAME, L"Software\\WTL")

    enum { IDD = IDD_MAINDLG };

    CListViewCtrl m_list_stripe;

    virtual BOOL PreTranslateMessage(MSG* pMsg){
        return CWindow::IsDialogMessage(pMsg);
    }

    virtual BOOL OnIdle(){
        return FALSE;
    }

    BEGIN_UPDATE_UI_MAP(CSampleProjectDialog)
    END_UPDATE_UI_MAP()

    BEGIN_MSG_MAP(CSampleProjectDialog)
        MSG_WM_INITDIALOG(OnInitDialog)
        MSG_WM_DESTROY(OnDestroy)
        CHAIN_MSG_MAP(CUpdateUI<CSampleProjectDialog>)
        CHAIN_MSG_MAP(CAppStdDialogImpl<CSampleProjectDialog>)
        CHAIN_MSG_MAP(CCustomDraw<CSampleProjectDialog>)    // 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){
        AtlCreateEmptyMenuBar(m_hWnd);

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

        m_list_stripe.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT);

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

        // アプリケーションがあるフォルダパスを取得
        TCHAR szAppRoot[_MAX_PATH];
        ::GetModuleFileName(NULL, szAppRoot, sizeof(szAppRoot) / sizeof(TCHAR));
        ::StringCbCopy(::_tcsrchr(szAppRoot, _T('\\')) + 1,
            sizeof(szAppRoot), _T("*.*"));

        CFindFile find;
        if(find.FindFile(szAppRoot)){
            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());
        }

        // メッセージループにメッセージフィルタとアイドルハンドラを追加
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        pLoop->AddMessageFilter(this);
        pLoop->AddIdleHandler(this);

        SetMsgHandled(false);

        return TRUE;
    }

    void OnDestroy(){
        // メッセージループからメッセージフィルタとアイドルハンドラを削除
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        pLoop->RemoveMessageFilter(this);
        pLoop->RemoveIdleHandler(this);
    }
};
			

// SampleProject.cpp
#include "stdafx.h"
#include "resourceppc.h"
#include "SampleProjectDialog.h"

CAppModule _Module;

int WINAPI _tWinMain(HINSTANCE hInstance,
    HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
    HRESULT hRes =
        CSampleProjectDialog::ActivatePreviousInstance(hInstance, lpstrCmdLine);

    if(FAILED(hRes) || S_FALSE == hRes){
        return hRes;
    }

    hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
    ATLASSERT(SUCCEEDED(hRes));

    AtlInitCommonControls(ICC_DATE_CLASSES);
    SHInitExtraControls();

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

    int nRet = CSampleProjectDialog::AppRun(lpstrCmdLine, nCmdShow);

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

    return nRet;
}
			

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

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

 次に、CSampleProjectDialogクラスでは、基底クラスに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の派生クラス(今回の例ではCSampleProjectDialogクラス)では、 これらのメンバ関数をオーバーライドすることによって、 コントロールに独自の外観を与えます。

今回の例では、まず、CSampleProjectDialogクラスで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

#define WINVER 0x0420
#include <atlbase.h>
#if _ATL_VER == 0x900
#define _SECURE_ATL 1
#endif

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

#include <tpcshell.h>
#include <aygshell.h>
#pragma comment(lib, "aygshell.lib")

#include <atlcrack.h>
#include <atlmisc.h>
#include <atlframe.h>
#include <atlctrls.h>
#define _WTL_CE_NO_ZOOMSCROLL
#define _WTL_CE_NO_FULLSCREEN
#include <atlwince.h>
			

// SampleProjectDialog.h
#pragma once

class CSampleProjectDialog : 
    public CAppStdDialogImpl<CSampleProjectDialog>,
    public CUpdateUI<CSampleProjectDialog>,
    public CMessageFilter, public CIdleHandler
{
public:
    DECLARE_APP_DLG_CLASS(NULL, IDR_MAINFRAME, L"Software\\WTL")

    enum { IDD = IDD_MAINDLG };

    CStripeListViewCtrl m_list_stripe;

    virtual BOOL PreTranslateMessage(MSG* pMsg){
        return CWindow::IsDialogMessage(pMsg);
    }

    virtual BOOL OnIdle(){
        return FALSE;
    }

    BEGIN_UPDATE_UI_MAP(CSampleProjectDialog)
    END_UPDATE_UI_MAP()

    BEGIN_MSG_MAP(CSampleProjectDialog)
        MSG_WM_INITDIALOG(OnInitDialog)
        MSG_WM_DESTROY(OnDestroy)
        CHAIN_MSG_MAP(CUpdateUI<CSampleProjectDialog>)
        CHAIN_MSG_MAP(CAppStdDialogImpl<CSampleProjectDialog>)
        REFLECT_NOTIFICATIONS()
    END_MSG_MAP()

    BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam){
        AtlCreateEmptyMenuBar(m_hWnd);

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

        m_list_stripe.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT);

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

        // アプリケーションがあるフォルダパスを取得
        TCHAR szAppRoot[_MAX_PATH];
        ::GetModuleFileName(NULL, szAppRoot, sizeof(szAppRoot) / sizeof(TCHAR));
        ::StringCbCopy(::_tcsrchr(szAppRoot, _T('\\')) + 1,
            sizeof(szAppRoot), _T("*.*"));

        CFindFile find;
        if(find.FindFile(szAppRoot)){
            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());
        }

        // メッセージループにメッセージフィルタとアイドルハンドラを追加
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        pLoop->AddMessageFilter(this);
        pLoop->AddIdleHandler(this);

        SetMsgHandled(false);

        return TRUE;
    }

    void OnDestroy(){
        // メッセージループからメッセージフィルタとアイドルハンドラを削除
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        pLoop->RemoveMessageFilter(this);
        pLoop->RemoveIdleHandler(this);
    }
};
			

// SampleProject.cpp
#include "stdafx.h"
#include "resourceppc.h"
#include "StripeListViewCtrl.h"
#include "SampleProjectDialog.h"

CAppModule _Module;

int WINAPI _tWinMain(HINSTANCE hInstance,
    HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
    HRESULT hRes =
        CSampleProjectDialog::ActivatePreviousInstance(hInstance, lpstrCmdLine);

    if(FAILED(hRes) || S_FALSE == hRes){
        return hRes;
    }

    hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
    ATLASSERT(SUCCEEDED(hRes));

    AtlInitCommonControls(ICC_DATE_CLASSES);
    SHInitExtraControls();

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

    int nRet = CSampleProjectDialog::AppRun(lpstrCmdLine, nCmdShow);

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

    return nRet;
}
			

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

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