设为首页 收藏本站
查看: 2060|回复: 0

[经验分享] WTL Application Develop Use CListViewCtrl Control With LVS_OWNERDATA Style (virt

[复制链接]

尚未签到

发表于 2015-11-20 15:06:17 | 显示全部楼层 |阅读模式
  
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,要解决三件事,获得当前ItemDCRECTID。最重要还是ID,可以选的话,当然在DrawItem中画,来一条处理一条,在lpDrawItemStruct中,有你需要的所有信息。在CustomDraw中也差不多,而OnPaint中,它的绘图要求就一次性过来的,所以要自己计算ID

先看一下COwnerDrawCCustom的用法吧:
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();
}


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

首先要做的,就是自定义ListViewCtrlItem的大小。在创建控件的时候,控件会向父窗口发一个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);
       }
}

OnMeasureItemif (nIDCtl == IDC_LISTVIEWCTRL && m_type != IMAGE)的在这里的意思是只对ViewLVS_REPORTListViewCtrl进行大小的设置,其他是只是调了一个缺省的处理。也就是说,对于要以ICON显示的图片过滤,还没有设置,这并不是因为我不想在这里设置,而且在这里无法进行设置,就算设了,也取不对,原因我最后发现可能是因为ViewLVS_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的大小呢?
我是在FSListViewCtrlOnCreate中进行设置的:
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中文件夹Icon32位位图)


       终于到如何绘制图标了!当然最理想的办法是响应WM_DRAWITEM消息,在OnDrawItem()中一条一条的画。
       如果ViewLVS_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_DODEFAULTListViewCtrl又会重新发这条消息。
原因也非常简单,之前我们也说过,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方式显式:




(知识是属于自己的,可以与大家分享;源码是属于公司的,所以不提供下载。)
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-141583-1-1.html 上篇帖子: LVS源码分析(1) 下篇帖子: LVS的三种负载均衡技术,八大负载调度算法
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表