您的位置:首页 > 移动开发

WTL Application Develop Use CListViewCtrl Control With LVS_OWNERDATA Style (virtual ListCtrl)

2009-11-21 21:08 537 查看
Visual Studio2005 + Win CE 6.0 + WTL 8.1

WTL Application Develop Use CListViewCtrl Control With LVS_OWNERDATA Style (virtual ListCtrl)

For next wentao

Include: COwnerDraw, CCustomDraw, CImageList, Base On Dialog, WM_MEASUREITEM

Summary:开发一个车载系统的SD卡,USB中文件的查看应用模块,其中对图像,音乐,视频文件进行过滤并在ListViewCtrl控件中显示。
根据需求,整套系统要与WinCE 6.0系统默认风格区别的统一风格,所以所有文件的图标和显示内容都需要自己来Draw。
其中音乐和视频文件以LVS_REPORT方式显示,利用ImageList根据不同的文件画出不同的ICON,再显示文件名。Item被选中时,Item的背景色与文件名颜色改变。
图像文件的显示比较复杂,用LVS_ICON方式显式,需要自己画出一定大小的图像缩略图(不同格式的需自行解码为bmp格式)。选择时用一颜色框圈中图片。
由于风格的问题,还需要自己实现一个ScrollBar,并把系统默认的隐藏。

考虑到硬件发展的速度,外存设备的大小成指数上升,所以文件个数成百上千已经不足为奇,
特别是图片文件,为了实现快速查找文件并显示出来,所以用virtual list(也就是在创建
ListViewCtrl的时候添加LVS­_OWNERDATA Style),因为virtual list不保存数据。
Virtual list的用法,建议认真看一遍下面的这篇Article:
http://www.codeproject.com/KB/list/virtuallist.aspx

这篇文章包函的知识点非常多,而且非常实用,虽然些没有用到,就像
LVN_ODFINDITEM消息的处理,还有check boxes的使用,等等。
不过作者对LVN_ODCACHEHINT处理讲得比较简单,其实这条消息是非常重要的,只是作者实现的功能不需要对这个消息进行处理而已。
用ListViewCtrl控件通常会与ImageList一同使用,用以显示Item的图标,我在开发中以LVS_REPORT Style过滤视频和音乐文件的时候就用到了下面这条图片List来输出。

用CImageList来输出一系列固定的图片,只需先判断文件的属性,然后指定一个Index就可以把图标Draw出来。
而我对图片文件就过滤就不这么简单,因为图片是以LVS_ICON Style显示的,每找到一张图片都把它的缩略图显示出来,这就要动态创建一张ImageList。
而我在前面讲过,virtual ListCtrl是不保存数据的,当它需要数据的时候才向我们要,我们就把准备好的数据给它,这样,我们就要处理LVN_ODCACHEHINT消息了,在这里我们可以向ListViewCtrl提供从第几条到第几条需要用到的数据。

在这次的开发中,CImageList我也是第一次用到,不难,不过用的好的话真的非常方便。我以前试过设置一个定时器,在一系列图片当中,自己计算每次截取的部分显示出来,实现一个动画闪光就效果,虽然也不会很难,但显然使用CImageList来画比较规范。

CImageList的在上面的例子中也有用到,你可以上那个网站,注册一个会员,就可以下载到源码,可以参考一下如何使用,或者看MSDN。
http://www.codeproject.com/KB/list/virtuallist.aspx
(codeproject这个网站提供非常丰富的源码下载,值得注册一个会员)

如果不用CImageList先保存Icon,直接找一个画一个,也可以,不过还是有必要创建一个CImageList与控件关联,并加载一张你将要显示的Icon大小的图,用以CListViewCtrl确定Icon的大小。这一点很重要,花了很多时间在这里,因为就算SetIconSpacing()还是无法取到Icon的大小。
在OnCreate()中,初始化CImageList:
m_ImageList.Create(IDB_FOLDER, ICON_WIDTH, 1, RGB(0, 0, 0));
SetImageList(m_ImageList, LVSIL_NORMAL);
还有一点非常重要,一定不能在OnCreate()中简单地SetMsgHandled(FALSE);一定要在一开始先初始化基类:
DefWindowProc(m_pCurrentMsg->message, m_pCurrentMsg->wParam , m_pCurrentMsg->lParam);
否则所有在OnCreate()中的对ListViewCtrl的初始化都没有一点作用!!!
要实现自己对ListViewCtrl中的Item进行绘制,无非三种方法:1.继承COwnerDraw,响应DrawItem消息,有OnDrawItem()中进行绘制。2.继承CCustomDraw,在OnPrePaint()中进行绘制。用CustomDraw来处理,要自己控制步骤,对消息的处理要十分小心,特别是返回值。3.就是在OnPaint()中老老实实地一条一条画。

要画出自己风络的Item,要解决三件事,获得当前Item的DC,RECT,ID。最重要还是ID,可以选的话,当然在DrawItem中画,来一条处理一条,在lpDrawItemStruct中,有你需要的所有信息。在CustomDraw中也差不多,而OnPaint中,它的绘图要求就一次性过来的,所以要自己计算ID。

先看一下COwnerDraw和CCustom的用法吧:
http://www.codeproject.com/KB/wtl/wtl4mfc5.aspx

如果是基于对话框的控件开发,还需要掌握DDX的知识:

http://www.codeproject.com/KB/wtl/wtl4mfc4.aspx

准备知识这样就差不多了,可以开始开发了。

先是用基于对话框的吧,用对话框作为父窗口, 这样有几个好处,首先就是资源可以统一管理,排版的工作全部都可以在OnSize()里面做,当然你也可以在资源中进行排版,不过这对于比较精细的排版有一定的困难,因为在资源中,不是以像素为单位的,而且经常会有一两个像素的误差。如果你在程序中要取客户区与屏幕的大小判断是否相等,这样就不能简单在用==来判断。

首先我们当然要创建两个类,一个是自己的ListViewCtrl,另一个是自己的ScrollBar.

class CFSListViewCtrl : public CWindowImpl<CFSListViewCtrl, CListViewCtrl>

DECLARE_WND_SUPERCLASS(L"CFSListViewCtrl", CListViewCtrl::GetWndClassName())

为什么不是这个继承呢?CFSListViewCtrl : public CListViewCtrl
因为CListViewCtrl只是一个简单的控件,在WTL中,其他控件也是一样,这样继承就没有了消息循环,没有什么作用。
在上面我们已经讲到,在CFSListViewCtrl的消息循环中我们要处理这两个消息,
REFLECTED_NOTIFY_CODE_HANDLER_EX(LVN_GETDISPINFO, OnGetdispinfo)
REFLECTED_NOTIFY_CODE_HANDLER_EX(LVN_ODCACHEHINT, OnOdcachehint)

这些消息都是发给父窗口的,所以在父窗要进行消息映射:
REFLECT_NOTIFICATIONS()
在父窗口还可以要对消息对进处理可以这样:
NOTIFY_HANDLER_EX(IDC_LISTVIEWCTRL, LVN_ITEMACTIVATE, OnItemactivate)

class CSmartScrollBar : public CWindowImpl<CSmartScrollBar>
而ScrollBar只是简单从一个CWindowImpl中派生出来。为什么要自己做一个ScrollBar控件?原因很简单,因为ScrollBar控件不提供添加背景位图和自定义风格的功能,所以在需要的情况下,可以把系统自带的隐藏掉,用一个自己设计的小窗口代替就行了,其实这个控件的开发还是比较简单的,想办法获取到当前页数和总页数就OK了!

因为是基于对话框的,所以我们还需要对两个控件DDX一把:

首先在父窗口中要继承DDX和添加消息循环:

class CFileSiteView : public CBaseView<IDD_FILESITEVIEW>, public CWinDataExchange<CFileSiteView>

BEGIN_DDX_MAP(CFileSiteView)
DDX_CONTROL(IDC_LISTVIEWCTRL, m_listViewCtrl)
END_DDX_MAP()

再在父窗口的OnInitDialog()理面添加消息处理和对控件初始化:

DoDataExchange(FALSE);
m_listViewCtrl.SetExtendedListViewStyle(LVS_EX_ONECLICKACTIVATE);
m_listViewCtrl.SetBkColor(RGB(0xff,0xff,0xff));
m_listViewCtrl.SetTextColor(RGB(0xff, 0x00, 0xff));
return FALSE;

你会发现CSmartScrollBar不是在这里DDX的,为什么呢?等一下我会讲到。

DDX不相当于把控件SubClass了一把。

在父窗口添加一下成员变员,然后在OnCreate()中SubClass如:

private:
CComButtonImpl m_wndbtn1;

int CStorageMainFrm::OnCreate(LPCREATESTRUCT lpstr)
{
m_wndbtn1.SubclassWindow(::GetDlgItem(m_dlg.m_hWnd,IDC_BUTTON1));
}

当然以上的工作都要在资源已经准备好之后,在对话框中添加LsitViewCtrl比较简单:
在工具箱中拖出来就行了 ,

中间白色的就是了。
它的属性设置中要注意的就是ID和下面的:

其中View中设置要根据自己需要设置,

可以先设为List Style,方便调试。
其他控件都可以这样来添加,但CScrollBar的有点不一样,因为它是从窗口直接派生的,没有对应的控件。这也是它为什么不能简单地DDX的原因。它的添加是一个Custom Control:

深色部门就是将来ScrollBar了。

ScrollBar属性设置除了要注意ID之外,最重要就是要指定对应的类名:

因为这个ScrollBar是与一个类相关联的,所以在创建SmartScrollBar之前就要先注册这个类。所以在类的声名中就要注册类名,还要声名一个注册类的成员方法,这个方法是用于父窗口在OnInitDialog()或在自己的构造方法中调用的。
class CSmartScrollBar : public CWindowImpl<CSmartScrollBar>
{
public:
DECLARE_WND_CLASS(TEXT("CSmartScrollBar"))
//……消息循环……
public:
BOOL RegisterWindowsClass();
};

void CSmartScrollBar::RegisterWindowsClass()
{
GetWndClassInfo().m_lpszOrigName = GetWndClassName();
GetWndClassInfo().Register(&m_pfnSuperWindowProc);
BOOL result;
ATLASSUME(m_hWnd == NULL);
// Allocate the thunk structure here, where we can fail gracefully.
result = m_thunk.Init(NULL,NULL);
if (result == FALSE) {
SetLastError(ERROR_OUTOFMEMORY);
return ;
}
_AtlWinModule.AddCreateWndData(&m_thunk.cd, this);
}

这个方法要根据你的实际情况而异,不能简单地向网上的介绍一样,
BOOL CSmartScrollBar::RegisterWindowsClass()
{
HINSTANCE hInstance = _Module.GetModuleInstance();
WNDCLASS wc;
wc.lpszClassName = _T("CSmartScrollBar");
wc.hInstance = hInstance;
wc.lpfnWndProc = ::DefWindowProc;
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hIcon = 0;
wc.lpszMenuName = NULL;
wc.hbrBackground = (HBRUSH) ::GetStockObject(LTGRAY_BRUSH);
wc.style = CS_GLOBALCLASS;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;

return (RegisterClass(&wc));
}

这样有可能会破坏你自己的消息循环机制,因为并不是每个人的缺省WndProc都是DefWindowProc。
wc.lpfnWndProc = ::DefWindowProc;

在构造方法中调用:

CSmartScrollBar::CSmartScrollBar()
{
RegisterWindowsClass();
}

这样,在一个对话框上,两件控件(FSListViewCtrl和SmartScrollBar)的架构就搭起来了,
接下来的工作就是具体的显示自己的东西了。

首先要做的,就是自定义ListViewCtrl中Item的大小。在创建控件的时候,控件会向父窗口发一个WM_MEASUREITEM消息,我们就可以通过对该消息的影应,设置Item的大小。相信大家还记得,我们的ListViewCtrl是通过DDX关联起来的,并不是Create出来的,所有这样就有一个问题,WM_MEASUREITEM消息是一个非常特殊的消息,它是在创建控件的时候发的,也就是说,我们通过DDX关联一个控件,在父窗口的OnInitDialog()处理之前,就已经发了这个消息,我们没有办法捕获到它并进行设置。

当然,网上也有办法可以强行通过MoveWindow()令控件再发一次WM_MEASUREITEM消息:
http://blog.sina.com.cn/s/blog_4ca086fc01008xpf.html
void ForceMeasureItemMessage()
{
// I just want to nudge it up a little.
CRect window_rect;
GetWindowRect(&window_rect);
CPoint pt = window_rect.TopLeft();
::ScreenToClient(GetParent(), &pt);
window_rect.right = pt.x + window_rect.Width();
window_rect.left = pt.x;
window_rect.bottom = pt.y + window_rect.Height();
window_rect.top = pt.y + 1;
MoveWindow(window_rect);
// Alright now move it back.
window_rect.top = pt.y;
MoveWindow(window_rect);
}

在父窗口的OnInitDialog()中调这个函数。
我也试过这种方式,当然是可行的,因为每改变一下子窗口(控件),它都会发这个消息,但是这样做非常投机取巧,非常别扭。
所有最终我决定放弃用基于对话框的架构开发,用回基于普通窗口的架构,这样,在父窗口Create这两个控件就可以顺利收到WM_MEASUREITEM消息,并进行设置。

int CFileSiteView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (!CreateListViewCtrl(lpCreateStruct))
return -1;
if(!CreateScrollBar(lpCreateStruct))
return -1;
m_listViewCtrl.SetFocus();
return 0;
}
void CFileSiteView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if (nIDCtl == IDC_LISTVIEWCTRL && m_type != IMAGE)
{
lpMeasureItemStruct->itemHeight = (m_rcLVC.Height()) / ONEPAGENUMBER;
lpMeasureItemStruct->itemWidth = m_rcLVC.Width();
}
else
{
DefWindowProc(m_pCurrentMsg->message, m_pCurrentMsg->wParam , m_pCurrentMsg->lParam);
}
}

在OnMeasureItem中if (nIDCtl == IDC_LISTVIEWCTRL && m_type != IMAGE)的在这里的意思是只对View为LVS_REPORT的ListViewCtrl进行大小的设置,其他是只是调了一个缺省的处理。也就是说,对于要以ICON显示的图片过滤,还没有设置,这并不是因为我不想在这里设置,而且在这里无法进行设置,就算设了,也取不对,原因我最后发现可能是因为View为LVS_ICON不支持LVS_OWNERDRAWFIXED属性。所以在FSListViewCtrl创建时要进行一个判断:
BOOL CFileSiteView::CreateListViewCtrl(LPCREATESTRUCT lpcs)
{
m_rcLVC.SetRect(0, 0, lpcs->cx, lpcs->cy);
DWORD style = WS_CHILD | WS_VISIBLE | LVS_OWNERDATA
| WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VSCROLL
| LVS_SINGLESEL | LVS_SHOWSELALWAYS ;
if (m_type == IMAGE)
{
style = style | LVS_ICON | LVS_AUTOARRANGE | LVS_NOLABELWRAP;
m_rcLVC.DeflateRect(80, 15, 80, 15);
}
else
{
style |= LVS_REPORT | LVS_NOCOLUMNHEADER | LVS_OWNERDRAWFIXED | LVS_SHAREIMAGELISTS;
int cy = ((lpcs->cy) / ONEPAGENUMBER) * ONEPAGENUMBER;
m_rcLVC.DeflateRect(0, (lpcs->cy - cy) / 2);
}
if(!m_listViewCtrl.Create(*this, m_rcLVC, _T("ListViewCtrl"), style, NULL, IDC_LISTVIEWCTRL))
return -1;
return TRUE;
}

那如果是LVS_ICON Style要去哪里设置Item的大小呢?
我是在FSListViewCtrl的OnCreate中进行设置的:
int CFSListViewCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
DefWindowProc(m_pCurrentMsg->message, m_pCurrentMsg->wParam , m_pCurrentMsg->lParam);
CRect rc;
GetClientRect(rc);
InitListCtrl(rc);
InitImageList(rc);
return 0;
}

一定要先处理完ListViewCtrl的创建工作才开始设置,也就是说一定要先调缺省的消息处于是函数,我这里是:
DefWindowProc(m_pCurrentMsg->message, m_pCurrentMsg->wParam , m_pCurrentMsg->lParam);
不然,你所有设置都是没有用的,白做工夫。
其实我们在OnCreate()和OnDeatroy()函数中都应该小心一点,要养成在OnCreate()k中先调缺省的处理,在OnDestroy()处理完自己的清理工作后再调一下缺省的处理的好习惯。

void CFSListViewCtrl::InitListCtrl(const CRect& rect)
{
if (GetStyle() & LVS_REPORT)
{
…… ……
}
else
{
SetIconSpacing(ICON_WIDTH + ICON_SPACEX, ICON_HEIGHT + ICON_SPACEY);
}
}
还要有一个地方,上面我也提到过,就是既使在LVS_ICON中,我们没有用到ImageList,也要加载一下我们要显示Icon大小的位图进去,让系统可以取到Icon的大小:

void CFSListViewCtrl::InitImageList(const CRect &rect)
{
if ((GetStyle() & LVS_TYPEMASK) == LVS_ICON)
{
m_ImageList.Create(IDB_FOLDER, ICON_WIDTH, 1, RGB(0, 0, 0));
SetImageList(m_ImageList, LVSIL_NORMAL);
return;
}
m_ImageList.Create(IDB_FLIST, bmpInfo.bmWidth / 6, 6, col);
}
col是要过滤掉要颜色,要自己先求。
(这张就是LVS_ICON中文件夹Icon的32位位图)

终于到如何绘制图标了!当然最理想的办法是响应WM_DRAWITEM消息,在OnDrawItem()中一条一条的画。
如果View是LVS_REPORT, 再添加LVS_OWNERDRAWFIXED属性,我们的ListViewCtrl就会发DRAWITEM消息,当然,这个消息是发给父窗口的,我们要在控件的消息循环中处理,就要这样来接收:
MSG_OCM_DRAWITEM(OnDrawItem)
声明:
void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct);
处理:
void CFSListViewCtrl::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
if ((GetStyle() & LVS_TYPEMASK) != LVS_ICON)
{
int iItem = lpDrawItemStruct->itemID;
DWORD state = lpDrawItemStruct->itemState;
CString filename = (_tcsrchr(m_files[iItem], '//') + 1);
CDCHandle dc(lpDrawItemStruct->hDC);
CRect rc(lpDrawItemStruct->rcItem);
int fileIcon = ICON_OTHER;
if ((GetFileAttributes(m_files[iItem])) & FILE_ATTRIBUTE_DIRECTORY)
fileIcon = ICON_DIR;
else
{
if(MOVIE == m_type)
fileIcon = ICON_MOVIE;
if (MUSIC == m_type)
fileIcon = ICON_MUSIC;
}
dc.FillRect(rc, (HBRUSH)::GetStockObject(BLACK_BRUSH));
if ((state & CDIS_SELECTED) && (state & CDIS_FOCUS))
{
CBitmap bmp;
bmp.LoadBitmap(IDB_LISTSEL);
CDC dcMem;
dcMem.CreateCompatibleDC(dc);
CBitmap bmpOld = dcMem.SelectBitmap(bmp);
dc.BitBlt(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, dcMem, 0, 0, SRCCOPY);
dcMem.SelectBitmap(bmpOld);
}
ImageList_Draw(m_ImageList, fileIcon, dc, rc.left + HIGHLINECX, rc.top + ((rc.bottom - rc.top) - 48) / 2, ILD_TRANSPARENT);
rc.left += (HIGHLINECX*2 + 80);
rc.right -= HIGHLINECX;
dc.SetTextColor((state & CDIS_SELECTED) ? RGB(255,255, 0) : RGB(0xFF,0xFF,0xFF));
dc.SetBkMode(TRANSPARENT);
dc.DrawText(filename, -1, &rc, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
}
else
{
DefWindowProc(m_pCurrentMsg->message, m_pCurrentMsg->wParam , m_pCurrentMsg->lParam);
}
}
这里面还用到了ImageList,所以以Report画十分简单、轻松就解决了,效果图在后面会一同展示。

现在难点来了,就是以LVS_ICON来画图片的过滤。

非常可惜的是Icon View不支持LVS_OWNERDRAWFIXED属性,也就是它不会发DRAWITEM消息,所以用LVS_ICON的时候千万不要加LVS_OWNERDRAWFIXED Style,
不然会出很多错误,我就一直取不对Item的大小和DC。这样要特别小点。

既然不能用影响WM_DRAWITEM消息的办法,我们自己想到从COwnerDraw派生,COwnerDraw的用法大概是这样:

class CComButtonImpl : public CWindowImpl<CComButtonImpl, CButton>,
public COwnerDraw<CComButtonImpl>
{
public:
BEGIN_MSG_MAP_EX(CComButtonImpl)
CHAIN_MSG_MAP_ALT(COwnerDraw<CComButtonImpl>,1)
END_MSG_MAP()

public:
void DrawItem(LPDRAWITEMSTRUCT lpdis);
};

void CComButtonImpl::DrawItem(LPDRAWITEMSTRUCT lpdis)
{
……绘图……
}

但是!!!偏偏CListViewCtrl不支持COwnerDraw!!!好像是因为有一个DeleteItem()函数发生了冲突。
不过它支持CCustomDraw,那我们就来看一下CCustomDraw的用法:

class CFSListViewCtrl : public CWindowImpl<CFSListViewCtrl, CListViewCtrl,>,public CCustomDraw<CFSListViewCtrl>
{
public:
DECLARE_WND_SUPERCLASS(L"CFSListViewCtrl", CListViewCtrl::GetWndClassName())
public:
BEGIN_MSG_MAP_EX(CFSListViewCtrl)
CHAIN_MSG_MAP_ALT(CCustomDraw<CFSListViewCtrl>,1)
END_MSG_MAP()

LRESULT OnItemchanging(LPNMHDR pNMHDR);
DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw);
DWORD OnPostPaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw);
DWORD OnPreErase(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw);
DWORD OnPostErase(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw);
DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw);
DWORD OnItemPostPaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw);
DWORD OnItemPreErase(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw);
DWORD OnItemPostErase(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw);
};

DWORD CFSListViewCtrl::OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
return CDRF_NOTIFYITEMDRAW;
}

DWORD CFSListViewCtrl::OnPostPaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
return CDRF_SKIPDEFAULT;
}

DWORD CFSListViewCtrl::OnPreErase(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
return CDRF_ SKIPDEFAULT;
}

DWORD CFSListViewCtrl::OnPostErase(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
return CDRF_ SKIPDEFAULT;
}

DWORD CFSListViewCtrl::OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
if (m_type == IMAGE)//(GetStyle() & LVS_ICON)
{
DrawIconMode((LPNMLVCUSTOMDRAW)lpNMCustomDraw);
}
else
{
DrawReportMode((LPNMLVCUSTOMDRAW)lpNMCustomDraw);
}
return CDRF_ SKIPDEFAULT;
}

DWORD CFSListViewCtrl::OnItemPostPaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
return CDRF_ SKIPDEFAULT;
}

DWORD CFSListViewCtrl::OnItemPreErase(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
return CDRF_ SKIPDEFAULT;
}

DWORD CFSListViewCtrl::OnItemPostErase(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
return CDRF_ SKIPDEFAULT;
}

我们要Overload这八个函数,其实用到的就OnPrePaint()和OnItemPrePaint()两个,特别要小心的是它们的返回值,因为我们要通过返回值来控制程序的运行流程,OnPrePaint()的返回值一定要是CDRF_NOTIFYITEMDRAW,这是通知系统我们要用CCustomDraw来画图,也只有这样,我们才可以走到OnItemPrePaint()中来,继续自己的工作。
其他函数的返回值都应该是CDRF_ SKIPDEFAULT,告诉系统,这些函数不再作处理。之前我有一些函数的返回值是CDRF_DODEFAULT,会出现什么后果呢!
在我画的图标中间系统会自动添出文件名,本来我自己画的文件名是写在图标下面的,这样非常不好看,找了很久,才发现是这里返回值搞得鬼。

在一开始我们就说到,我们用的是Virtual ListCtrl,它有几个非常有用和重要的消息处理(虽然我这里没有用到),例如:LVN_GETDISPINFO消息。

其实这时你会发现ListViewCtrl再也没有发这个消息,原因就是我们用了CCustomDraw,
如果我们到上面八个函数的返回值全部都设成CDRF_DODEFAULT,ListViewCtrl又会重新发这条消息。
原因也非常简单,之前我们也说过,Virtual ListCtrl是不保存数据的,LVN_GETDISPINFO之类的这些消息是用于系统Item用的,当我们用了CCustomDraw之后,如果返回值不是CDRF_DODEFAULT,说明我们自己对画图进行了处理,当然,系统也就不关心画Item了!
就不发LVN_GETDISPINFO这些消息了。
虽然道理非常简单,但当然搞清楚也是下了一番功夫!

也就是说,用CCustomDraw是可以实现我们的要求的,不过在WinCE下有一个奇怪的问题,它的Item不是用左上角一个一个往右下角画的,而是从下面往上面画的,再加上一些闪屏,效率优化方面的考虑,最后还是选择了LVS_ICON模式在OnPaint()里面去画,不用CCustomDraw了。

void CFSListViewCtrl::OnPaint(CDCHandle hdc)
{
if ((GetStyle() & LVS_TYPEMASK) == LVS_ICON)
{
CPaintDC paintDC(*this);
{
CRect rcPaint = paintDC.m_ps.rcPaint;
CRect rcClient;
GetClientRect(rcClient);
CMemoryDC dc(paintDC, rcClient);
dc.FillRect(rcClient, (HBRUSH)::GetStockObject(BLACK_BRUSH));
int num = GetItemCount();
int bgn = GetTopID();
if (bgn < 0) bgn = 0;
int end = num;
for (int i = bgn; i >= 0 && i < end; i ++)
{
CRect rcItem, rcTmp;
ListView_GetItemRect(*this, i, rcItem, LVIR_BOUNDS);
if (!::IntersectRect(&rcTmp, &rcClient, &rcItem))
break;
if (!::IntersectRect(&rcTmp, &rcPaint, &rcItem))
continue;
DrawIconMode(dc, i);
}
}
}
else
{
DefWindowProc(m_pCurrentMsg->message, m_pCurrentMsg->wParam , m_pCurrentMsg->lParam);
}
}

void CFSListViewCtrl::DrawIconMode(HDC dc, int iItem)
{
CRect rc;
ListView_GetItemRect(*this, iItem, &rc, LVIR_BOUNDS);
UINT state = ListView_GetItemState(*this, iItem, LVIS_FOCUSED | LVIS_SELECTED);
CString filename = (_tcsrchr(m_files[iItem], '//') + 1);
CMemoryDC mdc(dc, rc);
FillRect(mdc, rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
{
CSize size(ICON_WIDTH, ICON_HEIGHT);
CBitmapHandle hIcon = GetIconBmp(iItem, &size);
int x = ((rc.right - rc.left) - size.cx) / 2;
int y = (ICON_HEIGHT - size.cy) / 2;
if (!hIcon)
{
hIcon.LoadBitmap(IDB_FOLDER);
CDC dcMem;
BITMAP info;
hIcon.GetBitmap(&info);
dcMem.CreateCompatibleDC(mdc);
CBitmap bmpOld = dcMem.SelectBitmap(hIcon);
BLENDFUNCTION blendFunction;
blendFunction.AlphaFormat = AC_SRC_ALPHA;
blendFunction.BlendFlags = 0;
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.SourceConstantAlpha = 255;
AlphaBlend(mdc, 0, 0, info.bmWidth, info.bmHeight, dcMem, 0, 0, info.bmWidth, info.bmHeight, blendFunction);
dcMem.SelectBitmap(bmpOld);
}
else
{
CDC dcMem;
dcMem.CreateCompatibleDC(dc);
CBitmap bmpOld = dcMem.SelectBitmap(hIcon);
mdc.BitBlt(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, dcMem, 0, 0, SRCCOPY);
dcMem.SelectBitmap(bmpOld);
}
if (state & (LVIS_SELECTED | LVIS_FOCUSED))
{
CPen pen;
pen.CreatePen(PS_SOLID, 2, RGB(0, 0, 0xff));
CPen penOld = mdc.SelectPen(pen);
CBrush brushOld = mdc.SelectBrush((HBRUSH)::GetStockObject(NULL_BRUSH));
mdc.Rectangle(rc.left + 1, rc.top + y + 1, rc.left + size.cx -1, rc.top + y +size.cy - 1);
mdc.SelectPen(penOld);
mdc.SelectBrush(brushOld);
}
CRect rcText = rc;
rcText.top += ICON_HEIGHT;
rcText.left -= x*2;
mdc.SetBkMode(TRANSPARENT);
mdc.SetTextColor((state & (LVIS_SELECTED | LVIS_FOCUSED)) ? RGB(0xff, 0xff, 0) : RGB(0xff, 0xff, 0xff));
mdc.DrawText(filename, -1, rcText, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
}
}

下面我展示一下效果图:
在WinCE的模拟器下,我们用这个文件夹虚拟成U盘:

主界面:

对视频文件和文件夹进行过滤,以LVS_REPORT显示:

对音频文件和文件夹进行过滤,以LVS_REPORT方式显示:

对图片和文件夹进行过滤,以LVS_ICON方式显式:

(知识是属于自己的,可以与大家分享;源码是属于公司的,所以不提供下载。)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: