[WTL/ATL]_[中级]_[自定义ListView]

场景

  1. 表格是我们常用的管理数据控件之一, Win32的标准表格控件默认只显示文本数据,如何在单元格里显示图片? 如何显示复选框,可以够选整行?

  2. 表格是的特点是带分割线,以列和行显示数据的控件,我们是否可以通过响应WM_PAINT消息来全部绘制?为什么说响应
    WM_PAINT消息绘制表格不容易实现?

  3. 表格的表头能加上一个复选框的按钮吗? 这样可以实现全选的功能.

说明

  1. WTL封装了Win32的ListView的类叫CListViewCtrl, 所以我们需要增加额外的图片绘制或功能需要继承CListViewCtrl, 并且如果需要定制某行某列的内容,需要在在创建CListViewCtrl增加LVS_REPORT|LVS_OWNERDRAWFIXED两种样式. 这样才会响应WM_DRAWITEM消息.

  2. 如果要自己从头绘制整个表格,会比较复杂,特别拉动表格头改变列宽时,或者有滚动条时的处理会很麻烦, 还有点击行时显示选中效果,判断是第几行等都不是一个小的工作量。所以一般不会考虑重新实现WM_PAINT.

  3. 表格头要加一个复选框的图片并且还能响应点击效果,一种是方案是在表格头上加一个复选框的按钮,另一种方案就是在表格头上绘制复选框图片,并通过响应WM_LBUTTONDOWN消息来处理点击复选框图片切换图片达到效果.

  4. 我们自定义的子类需要继承COwnerDraw来绘制单元格, 这个类会在WM_DRAWITEM的处理函数OnDrawItem里调用
    DrawItem((LPDRAWITEMSTRUCT)lParam), 所以我们子类需要重载这个函数。在这个函数里我们能判断该单元格是否选中,是否需要绘制图片等,这个单元格里有我们想知道的所有属性LPDRAWITEMSTRUCT.

void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
  1. 至于表格头,我们也需要通过继承类CHeaderCtrl,并且继承CCustomDraw来实现自定义绘制. 由于如果只需要在原来的表格头上加东西,所以只需要重载CCustomDraw的函数OnPostPaintOnPrePaint,而在
    OnPrePaint里需要返回CDRF_NOTIFYPOSTPAINTOnPostPaint方法才会调用.

  2. 由于WTL其实就是对Win32的薄封装, CListViewCtrlMFC也有对应的CListView, 还有就是消息映射也是大同小异, 所以移植到MFC也容易. 同理,MFC的自定义控件移植到WTL也可行.

例子

bas_list_view_header.h



#ifndef __BAS_LIST_VIEW_HEADER
#define __BAS_LIST_VIEW_HEADER

#include <atlbase.h>
#include <atlapp.h>

#include <atlwin.h>
#include "atlframe.h"
#include "atlctrls.h"
#include "atlmisc.h"
#include <utility>
#include <GdiPlus.h>

using namespace std;

enum HeaderMessage
{
	AA_LISTVIEW_HEADER_CHG = WM_USER+1000
};

enum
{
	kBASListHeaderCheckButtonId = 5000
};

class BASListViewHeader:public CHeaderCtrl
{
public:
	~BASListViewHeader();
	BASListViewHeader(HWND hwnd = NULL);

	void SetCheckAll(int bcheck);
	BOOL GetIfChecked();
	void SetCheckBoxImage(Gdiplus::Bitmap* m_checkbox_yes,
		Gdiplus::Bitmap* m_checkbox_no);
	void RefreshCheckRect();

	DWORD OnPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCustomDraw);
	DWORD OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCustomDraw);

	void Attach(HWND hwnd);
	LRESULT OnCustomDraw(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);

protected:
	LRESULT OnLButtonDown(UINT umsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled );
	

private:
	typedef pair<LONG_PTR,LONG_PTR> ProcAndSelf;
	static LRESULT MyWNDPROC(HWND, UINT, WPARAM, LPARAM);
	ProcAndSelf procAndSelf_;

private:

	Gdiplus::Bitmap* m_checkbox_yes_;
	Gdiplus::Bitmap* m_checkbox_no_;
	bool checked_;

	DWORD bkg_color_;
	int m_nHeadHight;
	int m_nSortColumn;
	BOOL m_bAscend;

	int m_iSpacing;
	BOOL m_bDividerLines;
};

#endif

bas_list_view_header.cpp


#include "stdafx.h"
#include "bas_list_view_header.h"

#include <string>
#include <iostream>
#include <assert.h>
#include "utils.h"

BASListViewHeader::BASListViewHeader(HWND hwnd):CHeaderCtrl(hwnd)
{
	m_nHeadHight = 25;
	m_nSortColumn = -1;
	bkg_color_ = RGB(242,242,242);
	checked_ = false;
	m_checkbox_yes_ = NULL;
	m_checkbox_no_ = NULL;
}

BASListViewHeader::~BASListViewHeader()
{
	Detach();
}

void BASListViewHeader::SetCheckBoxImage(Gdiplus::Bitmap* m_checkbox_yes,
	Gdiplus::Bitmap* m_checkbox_no)
{
	m_checkbox_yes_ = m_checkbox_yes;
	m_checkbox_no_ = m_checkbox_no;
}

void BASListViewHeader::RefreshCheckRect()
{
	CRect rect_client;
	GetClientRect(&rect_client);

	assert(m_checkbox_yes_);
	CRect rect;
	rect.left = 0;
	rect.top = 0;
	rect.right = rect.left+m_checkbox_yes_->GetWidth()+4;
	rect.bottom = rect_client.bottom;
	InvalidateRect(rect,0);
}

LRESULT BASListViewHeader::MyWNDPROC(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	auto pp = (BASListViewHeader::ProcAndSelf*)::GetWindowLongPtr(hwnd, GWLP_USERDATA);
	if (msg == WM_LBUTTONDOWN && wParam == MK_LBUTTON){
		auto self = (BASListViewHeader*)pp->second;
		BOOL bHandled = FALSE;
		return self->OnLButtonDown(msg,wParam,lParam,bHandled);
	}

	return CallWindowProc((WNDPROC)pp->first, hwnd, msg, wParam, lParam);
}

LRESULT BASListViewHeader::OnCustomDraw(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
	LPNMCUSTOMDRAW lpNMCustomDraw = (LPNMCUSTOMDRAW)pnmh;
	DWORD dwRet = CDRF_DODEFAULT;
	bHandled = TRUE;
	switch(lpNMCustomDraw->dwDrawStage)
	{
	case CDDS_PREPAINT:
		dwRet = OnPrePaint(idCtrl, lpNMCustomDraw);
		break;
	case CDDS_POSTPAINT:
		dwRet = OnPostPaint(idCtrl, lpNMCustomDraw);
		break;
	default:
		bHandled = FALSE;
		break;
	}
	return dwRet;
}

void BASListViewHeader::Attach(HWND hwnd)
{
	CHeaderCtrl::Attach(hwnd);
	auto oldProc = SetWindowLongPtr(GWLP_WNDPROC,(LONG_PTR)MyWNDPROC);
	procAndSelf_.first = oldProc;
	procAndSelf_.second = (LONG_PTR)this;

	SetWindowLongPtr(GWLP_USERDATA, (LONG_PTR)&procAndSelf_);
}

LRESULT BASListViewHeader::OnLButtonDown(UINT umsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled )
{
	if(!m_checkbox_yes_)
		return 1;

	POINT pt={LOWORD(lparam),HIWORD(lparam)};
	HDHITTESTINFO hdht;
	hdht.pt=pt;
	int nItem=HitTest(&hdht);
	if (nItem!=-1 && (hdht.flags & HHT_ONHEADER) && !(hdht.flags & HHT_ONDIVIDER) ){
		//5: 是checkbox的x位置,时间关系暂时先固定.
		if( 5 <= pt.x &&  pt.x <= 5+m_checkbox_yes_->GetWidth()){
			CWindow parent = GetParent();
			checked_ = !checked_;
			parent.SendMessage(AA_LISTVIEW_HEADER_CHG,checked_,0);
			RefreshCheckRect();
		}
	}
	bHandled = FALSE;
	return 0;
}

DWORD BASListViewHeader::OnPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCustomDraw)
{
	Gdiplus::Graphics graphics(lpNMCustomDraw->hdc);
	if(m_checkbox_yes_){
		if(checked_){
			Utils::DrawImage(graphics,m_checkbox_yes_,5,4);
		}else{
			Utils::DrawImage(graphics,m_checkbox_no_,5,4);
		}
	}
	return CDRF_DODEFAULT;
}

DWORD BASListViewHeader::OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCustomDraw)
{
	//return CDRF_NOTIFYPOSTPAINT; CDRF_NOTIFYITEMDRAW CDRF_NEWFONT
	return CDRF_NOTIFYPOSTPAINT;
}


void BASListViewHeader::SetCheckAll(int bcheck)
{
	checked_ = bcheck;
}

BOOL BASListViewHeader::GetIfChecked()
{
	return checked_;
}

bas_list_view.h


#ifndef __BAS_LIST_VIEW
#define __BAS_LIST_VIEW

#include <Windows.h>
#include <WinUser.h>
#include <atlbase.h>
#include <atlapp.h>

#include <atlwin.h>
#include "atlframe.h"
#include "atlctrls.h"
#include "atlmisc.h"
#include "atlcrack.h"
#include <GdiPlus.h>
#include <string>
#include <sstream>
#include "bas_list_view_header.h"

using namespace std;

enum ListViewMessage
{
	AA_LISTVIEWCHECKCHG = AA_LISTVIEW_HEADER_CHG+1
};

class BASListView;
typedef CWindowImpl<BASListView,CListViewCtrl> BASListViewBase;

typedef Gdiplus::Image* (*GetLogoImage)(BASListView *list_view,int nItem);
typedef void (*RenderItem)(CDC& hdcmem,BASListView *list_view,int nItem);

typedef enum BASListSeqOrder1
{
	BASListSeqOrderAscend,
	BASListSeqOrderDescend
}BASListSeqOrder;

class BASListView:public BASListViewBase,public COwnerDraw<BASListView>
{
public:
	BASListView();
	~BASListView();

	BEGIN_MSG_MAP_EX(BASListView)
		MSG_WM_ERASEBKGND(OnEraseBkgnd)
		MESSAGE_HANDLER(WM_LBUTTONDOWN,OnLButtonDown)
		MESSAGE_HANDLER(AA_LISTVIEW_HEADER_CHG,OnListViewHeaderCheck)
		MESSAGE_HANDLER(WM_CREATE,OnCreate)
		MESSAGE_HANDLER(WM_MOUSEMOVE,OnMouseMove)
		NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
		CHAIN_MSG_MAP_ALT(COwnerDraw<BASListView>,1)
	END_MSG_MAP()


	LRESULT OnHeaderLeftDown(int wParam, LPNMHDR lParam, BOOL& bHandled)
	{
		bHandled = FALSE;
		return 0;
	}

	BOOL CreateListView(HWND hWndParent, ATL::_U_RECT rect = NULL,
			ATL::_U_MENUorID MenuOrID = 0U,
			BASListSeqOrder order = BASListSeqOrderAscend,
			bool paint_cross_bg_color = false,bool visible = true);

	int InsertItemTexts(int index,...);
	int AppendItemTexts(LPCTSTR pszText,...);
	void SetItemHeight(int nh);
	void SetCheckBoxImage(Gdiplus::Bitmap* m_checkbox_yes,Gdiplus::Bitmap* m_checkbox_no);

	static int CALLBACK CompareFunc(LPARAM lparam1,LPARAM lparam2,LPARAM lparamSort);

	void SetDefaultPhoto(Gdiplus::Image* m_default_photo);

	inline void SetPhotoGetFunc(GetLogoImage func)
	{
		photo_func_ = func;
	}

	inline void SetRenderFunc(RenderItem func)
	{
		render_func_ = func;
	}

	void SetHeaderCheck(int bcheck)
	{
		m_listHead.SetCheckAll(bcheck);
	}
	BOOL GetHeaderChecked()
	{
		return m_listHead.GetIfChecked();
	}

	void RefreshHeaderCheck();

	void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
	void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
	void RemoveItem(int nItem)
	{
		CListViewCtrl::DeleteItem(nItem);
	}
	void DeleteItem(LPDELETEITEMSTRUCT lpDeleteItemStruct)
	{
	}
	LRESULT CompareItem(LPCOMPAREITEMSTRUCT lpCompareItemStruct)
	{
		return 1;
	}

	void RefreshColumn(int nCol);

	void RefreshCell(int nCol,int nRow);

	void SetSelectedChecked();

	BOOL SelectItem(int nIndex);


	virtual void Invalidate(BOOL bErase = TRUE);

	LRESULT OnCustomDraw(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);

protected:

	LRESULT OnListViewHeaderCheck(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

	LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);

	LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
	LRESULT OnMouseWheel(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);
	LRESULT OnVScroll(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);

	LRESULT OnNcCalSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		ShowScrollBar(SB_BOTH,0);
		bHandled=FALSE;
		return 0;
	}

	LRESULT OnLvnColClick(int iID,LPNMHDR pNMListView,BOOL& bHandled);

	LRESULT OnCreate(UINT umsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled );
	
	BOOL OnEraseBkgnd(CDCHandle dc);

	virtual void DrawPhotoColumn(HDC hdcmem,Gdiplus::Graphics& graphics,
		LPDRAWITEMSTRUCT lpDrawItemStruct,CRect& rctext,
	int nCol);

	virtual void DrawEveryColumnItem(HDC hdcmem,Gdiplus::Graphics& graphics,
		LPDRAWITEMSTRUCT lpDrawItemStruc);

	Gdiplus::Bitmap* m_checkbox_yes_;
	Gdiplus::Bitmap* m_checkbox_no_;

	GetLogoImage photo_func_;
	RenderItem   render_func_;
	Gdiplus::Image* m_default_photo_;

	COLORREF m_crEven,m_crOdd;
	int m_h_offset,m_h_size;

private:
	BASListViewHeader m_listHead;
	int m_nItemHeight;
	CFont font_normal_;

	BASListSeqOrder order_;
	bool paint_cross_bg_color_;
	int nitem_hover_;
	int nitem_leave_;

};

#endif

bas_list_view.cpp



#include "stdafx.h"
#include "bas_list_view.h"

#include <sstream>
#include <assert.h>
#include <iostream>
#include <map>
#include "utils.h"

BASListView::BASListView()
{
	m_default_photo_ = NULL;
	m_nItemHeight = 60;
	photo_func_ = NULL;
	render_func_ = NULL;
	m_crEven=(RGB(255,255,255));
	m_crOdd=(RGB(245,245,245));

	m_checkbox_yes_ = NULL;
	m_checkbox_no_ = NULL;
	nitem_hover_ = -1;
	nitem_leave_ = -1;
}

BASListView::~BASListView()
{
	Detach();
}

void BASListView::Invalidate(BOOL bErase)
{
	GetHeader().Invalidate(bErase);
	CListViewCtrl::Invalidate(bErase);
}

void BASListView::RefreshCell(int nCol,int nRow)
{
	CRect rcItem;
	int column_width = GetColumnWidth(nCol);
	GetSubItemRect(nRow,nCol,LVIR_SELECTBOUNDS,rcItem);
	rcItem.right = rcItem.left+column_width;
	InvalidateRect(rcItem);
}

void BASListView::RefreshColumn(int nCol)
{
	int col_width = GetColumnWidth(nCol);
	int width = col_width;
	for(int i = 0; i<nCol;++i)
	{
		width += GetColumnWidth(i);
	}

	CRect rect;
	GetClientRect(&rect);
	rect.left = width-col_width;
	rect.right = width;

	InvalidateRect(rect);
}

void BASListView::SetDefaultPhoto(Gdiplus::Image* m_default_photo)
{
	m_default_photo_ = m_default_photo;
}

void BASListView::DrawPhotoColumn(HDC hdcmem,Gdiplus::Graphics& graphics,
	LPDRAWITEMSTRUCT lpDrawItemStruct,CRect& rctext,
	int nCol)
{
	int const kTextGap = 10;
	CRect photo_rect = rctext;

	Gdiplus::Image* photo = NULL;
	if(photo_func_) photo = photo_func_(this,lpDrawItemStruct->itemID);
	if(!photo) photo = m_default_photo_;

	if(photo)
	{
		int height = photo->GetHeight();
		int width = photo->GetWidth();
		int maxHeight = photo_rect.Height()-4;
		if(height > maxHeight)
			height = maxHeight;

		if(width > maxHeight)
			width = maxHeight;

		Gdiplus::Rect logo_rect;
		logo_rect.X = photo_rect.left+2;
		logo_rect.Y = photo_rect.top+(photo_rect.Height()-height)/2;
		logo_rect.Width = width;
		logo_rect.Height = height;

		int offset = (photo_rect.Height()-height)/2;
		photo_rect.top+=offset;
		Gdiplus::Status gs = graphics.DrawImage(photo,logo_rect,0,0,
		photo->GetWidth(),photo->GetHeight(),Gdiplus::UnitPixel);

		photo_rect.left = logo_rect.X+logo_rect.Width+kTextGap;
	}
	

	// WTL10已经没有定制的WTL::CString,推荐使用std::wstring.
	wchar_t* str = NULL;
	GetItemText(lpDrawItemStruct->itemID,nCol,str);
	if(str && wcslen(str))
	{
		photo_rect.top = rctext.top;
		UINT uFormat=DT_LEFT|DT_VCENTER|DT_END_ELLIPSIS;
		CSize lpSize;
		::GetTextExtentPoint32(hdcmem, str, wcslen(str), &lpSize);
		photo_rect.top+=(photo_rect.Height()-lpSize.cy)/2;
		DrawText(hdcmem,str,wcslen(str),photo_rect,uFormat);
	}
}

void BASListView::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	HDC hdc=lpDrawItemStruct->hDC;
	CRect rcItem=lpDrawItemStruct->rcItem;  //listview每项的rect,受滚动条位置影响
	int nItem = lpDrawItemStruct->itemID;
	CRect rcCient;
	GetClientRect(rcCient);  //固定listview的区域,宽度可小于rcitem

	SCROLLINFO si;
	si.cbSize=sizeof(SCROLLINFO);
	si.fMask=SIF_POS|SIF_RANGE;
	//偏移量很重要.
	if (GetScrollInfo(SB_HORZ,&si))
	{
		m_h_offset=-si.nPos;   //计算偏移 因为内存dc的rect和rcItem的原点不同
		m_h_size=max(si.nMax+1,rcCient.Width());
	}
	else
	{
		m_h_offset=rcCient.left;
		m_h_size=rcCient.Width();
	}
	CDC hdcmem;
	hdcmem.CreateCompatibleDC(hdc);
	CBitmap hbitmapmem;
	hbitmapmem.CreateCompatibleBitmap(hdc,rcItem.Width(),rcItem.Height());
	hdcmem.SelectBitmap(hbitmapmem);
	
	CBrush hbr;
	CPen hpen;
	hpen.CreatePen(PS_SOLID,1,RGB(220,220,220));
	hdcmem.SelectPen(hpen);
	hdcmem.SelectFont(font_normal_);

	if (lpDrawItemStruct->itemState & ODS_SELECTED  )  //先画背景
	{
		hbr.CreateSolidBrush(RGB(161,219,238));
	}
	else
	{
		if(nItem == nitem_leave_){
			hbr.CreateSolidBrush(m_crEven);
		}else if(nItem == nitem_hover_){
			hbr.CreateSolidBrush(RGB(205,236,245));
		}else{
			if(paint_cross_bg_color_){
				if(lpDrawItemStruct->itemID % 2){
					hbr.CreateSolidBrush(RGB(250,250,250));
				}else{
					hbr.CreateSolidBrush(m_crEven);
				}
			}else{
				hbr.CreateSolidBrush(m_crEven);
			}
		}
	}
	hdcmem.FillRect(CRect(0,1,rcItem.Width(),rcItem.Height()),hbr);

	//画item的左边
	hdcmem.MoveTo(0,0,NULL);
	hdcmem.LineTo(0,rcItem.Height());
	
	//画item的右边
	hdcmem.MoveTo(rcItem.Width()-1,0,NULL);
	hdcmem.LineTo(rcItem.Width()-1,rcItem.Height()-1);

	//画item的上边
	hdcmem.MoveTo(0,0,NULL);
	hdcmem.LineTo(rcItem.Width()-1,0);

	//画item的底边
	if(nItem == GetItemCount()-1){
		hdcmem.MoveTo(0,rcItem.Height()-1,NULL);
		hdcmem.LineTo(rcItem.Width()-1,rcItem.Height()-1);
	}
	
	hdcmem.SetBkMode(TRANSPARENT);
	if(render_func_)
	{
		render_func_(hdcmem,this,lpDrawItemStruct->itemID);
	}
	Gdiplus::Graphics graphics(hdcmem);
	DrawEveryColumnItem(hdcmem,graphics,lpDrawItemStruct);
	BitBlt(hdc,rcItem.left,rcItem.top,rcItem.Width(),rcItem.Height(),hdcmem,0,0,SRCCOPY);

}

void BASListView::DrawEveryColumnItem(HDC hdcmem,Gdiplus::Graphics& graphics,
	LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	LVCOLUMN lvc;
	lvc.mask=LVCF_WIDTH|LVCF_FMT;

	TCHAR szBuf[MAX_PATH]={0};
	LVITEM lvi;
	lvi.mask=LVIF_TEXT;
	lvi.iItem=lpDrawItemStruct->itemID;
	lvi.pszText=szBuf;
	lvi.cchTextMax=MAX_PATH;

	static const int kCheckBoxColumn = 0;
	static const int kPhotoColumn = 1;
	int const kTextGap = 10;

	for (int nCol=0;GetColumn(nCol,&lvc);++nCol)
	{
		CRect rcSubItem;
		if(!GetSubItemRect(lpDrawItemStruct->itemID,nCol,LVIR_LABEL,rcSubItem))
		{	
			continue;
		}
		lvi.iSubItem=nCol;
		GetItem(&lvi);

		CRect rctext(rcSubItem.left-m_h_offset,0,rcSubItem.right-m_h_offset,rcSubItem.Height());  //相对于内存dc的item rect

		int checkbox_width = 0;
		int checkbox_height = 0;
		if(m_checkbox_yes_)
		{
			checkbox_width = m_checkbox_yes_->GetWidth();
			checkbox_height = m_checkbox_yes_->GetHeight();
		}

		if(nCol == kCheckBoxColumn)
		{
			rctext.left+=5+checkbox_width;

			int nleft=5;
			int ntop=(rcSubItem.Height()-checkbox_height)/2;

			BOOL bChecked=ListView_GetCheckState(m_hWnd,lpDrawItemStruct->itemID );

			if(m_checkbox_yes_)
			{
				Gdiplus::Rect dest_rect(nleft,ntop,checkbox_width,checkbox_height);
				if(bChecked){	
					graphics.DrawImage(m_checkbox_yes_,dest_rect,0,0,checkbox_width,checkbox_height,
						Gdiplus::UnitPixel);
				}else{
					graphics.DrawImage(m_checkbox_no_,dest_rect,0,0,checkbox_width,checkbox_height,
						Gdiplus::UnitPixel);
				}
			}

			std::wstringstream wss;
			int id = (order_ == BASListSeqOrderAscend)?lpDrawItemStruct->itemID+1:
				GetItemCount()-lpDrawItemStruct->itemID;
			wss << id;
			std::wstring str = wss.str();
			if(str.size())
			{
				CRect photo_rect = rctext;
				photo_rect.left = nleft + checkbox_width+kTextGap;
				photo_rect.top = rctext.top;
				UINT uFormat=DT_LEFT|DT_VCENTER|DT_END_ELLIPSIS;
				CSize lpSize;
				::GetTextExtentPoint32(hdcmem, str.c_str(), str.size(), &lpSize);
				photo_rect.top+=(photo_rect.Height()-lpSize.cy)/2;
				DrawText(hdcmem,str.c_str(),str.size(),photo_rect,uFormat);
			}
			//GetItemData(lpDrawItemStruct->itemID);
		}else if(nCol == kPhotoColumn)
		{
			DrawPhotoColumn(hdcmem,graphics,lpDrawItemStruct,rctext,nCol);
		}else
		{
			wchar_t* str = NULL;
			GetItemText(lpDrawItemStruct->itemID,nCol,str);
			if(str && wcslen(str))
			{
				CSize lpSize;
				::GetTextExtentPoint32(hdcmem, str, wcslen(str), &lpSize);
				CRect other_text_rect = rctext;
				other_text_rect.top+=(other_text_rect.Height()-lpSize.cy)/2;
				UINT uFormat=DT_LEFT|DT_VCENTER|DT_END_ELLIPSIS;
				other_text_rect.left += kTextGap;
				DrawText(hdcmem,str,wcslen(str),other_text_rect,uFormat);
			}
		}
	}
}

LRESULT BASListView::OnCustomDraw(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
	return m_listHead.OnCustomDraw(idCtrl,pnmh,bHandled);
}

LRESULT BASListView::OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
	if(!m_checkbox_yes_){
		bHandled=FALSE;
		return 1;
	}

	POINT pt={LOWORD(lParam),HIWORD(lParam)};
	UINT uflag=0;
	int nItem=HitTest(pt,&uflag);
	if (nItem!=-1 && (uflag & LVHT_ONITEM ))
	{
		CRect rcItem;
		int column_width = GetColumnWidth(0);
		GetSubItemRect(nItem,0,LVIR_SELECTBOUNDS,rcItem);
		rcItem.right = rcItem.left+column_width;
		if (PtInRect(rcItem,pt)){
			BOOL bCheckState = ListView_GetCheckState(m_hWnd, nItem) ;
			ListView_SetCheckState( m_hWnd, nItem, !bCheckState );

			SetItemState(nItem,(bCheckState)?0:LVNI_SELECTED,LVNI_SELECTED);
			int checked_item = nItem;
			if(bCheckState){
				SetHeaderCheck(BST_UNCHECKED);
				m_listHead.Invalidate();

				// 获取首个check item.
				int i=0;
				for (;i<GetItemCount();++i){
					BOOL bcheck_temp=ListView_GetCheckState(m_hWnd, i);
					if(bcheck_temp){
						checked_item = i;
						break;
					}
				}

				// 如果没有选中项, 默认第一个.
				if(checked_item == nItem)
					checked_item = 0;

			}else{
				int i=0;
				for (;i<GetItemCount();++i){
					BOOL bcheck_temp=ListView_GetCheckState(m_hWnd, i);
					if(!bcheck_temp)
						break;
				}
				if(i==GetItemCount()){
					SetHeaderCheck(BST_CHECKED);
					m_listHead.Invalidate();
				}
			}
			
			::SendMessage(GetParent(),AA_LISTVIEWCHECKCHG,MAKEWPARAM(GetWindowLong(GWL_ID),0),checked_item);
		}else{
			bHandled=FALSE;
			return 1;
		}
	}
	else
	{
		bHandled=FALSE;
		return 1;
	}
	return 0;
}

void BASListView::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
	lpMeasureItemStruct->itemHeight=m_nItemHeight;
}

int BASListView::InsertItemTexts(int index,...)
{
	int nItemIndex = InsertItem(index,L"");
	int nColCount = GetHeader().GetItemCount();
	va_list list;
	va_start(list,index);

	for (int i=0;i<nColCount;++i )
	{
		LPCTSTR pszText=va_arg(list,LPCTSTR);
		SetItemText(nItemIndex,i,pszText);
	}
	va_end(list);

	return nItemIndex;
}

int BASListView::AppendItemTexts(LPCTSTR pszText,...)
{
	int nItemIndex = InsertItem(GetItemCount(),pszText);
	int nColCount = GetHeader().GetItemCount();
	va_list list;
	va_start(list,pszText);

	for (int i=1;i<nColCount;++i )
	{
		pszText=va_arg(list,LPCTSTR);
		SetItemText(nItemIndex,i,pszText);
	}
	va_end(list);

	return nItemIndex;
}

BOOL BASListView::OnEraseBkgnd(CDCHandle dc)
{
	CBrush  br;
	CRect   rcCli;
	CRect   rcItemsRect(0, 0, 0, 0);
	int     nHeadHeight = 0;
	int     nItems      = GetItemCount();

	GetClientRect(&rcCli);

	CHeaderCtrl pHeadCtrl = GetHeader();
	CRect  rcHead;
	pHeadCtrl.GetWindowRect(&rcHead);
	nHeadHeight = rcHead.Height();
	rcCli.top += nHeadHeight;

	CRect   rcItem;
	if (nItems > 0)
	{
		CPoint  ptItem;

		GetItemRect(nItems - 1, &rcItem, LVIR_BOUNDS);
		GetItemPosition(nItems - 1, &ptItem);

		rcItemsRect.top    = rcCli.top;
		rcItemsRect.left   = ptItem.x;
		rcItemsRect.right  = rcItem.right;
		rcItemsRect.bottom = rcItem.bottom;

		if (GetExStyle() & LVS_EX_CHECKBOXES)
			rcItemsRect.left -= GetSystemMetrics(SM_CXEDGE) + 16;
	}

	br.CreateSolidBrush(GetBkColor());

	if (rcItemsRect.IsRectEmpty())
		dc.FillRect(rcCli, br);
	else
	{
		if (rcItemsRect.left > rcCli.left)     // fill left rectangle
			dc.FillRect(
			CRect(0, rcItemsRect.bottom, rcItemsRect.left, rcCli.bottom), br);
		if (rcItemsRect.bottom < rcCli.bottom) // fill bottom rectangle
			dc.FillRect(
			CRect(0, rcItemsRect.bottom, rcCli.right, rcCli.bottom), br);
		if (rcItemsRect.right < rcCli.right) // fill right rectangle
			dc.FillRect(
			CRect(rcItemsRect.right, rcCli.top, rcCli.right, rcCli.bottom), br);
	}
	return TRUE;
}

LRESULT BASListView::OnListViewHeaderCheck(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	BOOL checked = (BOOL)wParam;
	if(!checked && nitem_hover_ != -1){
		nitem_leave_ = nitem_hover_;
		CRect rect;
		GetItemRect(nitem_leave_,rect,LVIR_BOUNDS);
		InvalidateRect(rect);
	}
	nitem_hover_ = -1;
	for (int i=0;i<GetItemCount();++i){
		ListView_SetCheckState( m_hWnd, i, checked);
		SetItemState(i,(checked)?LVNI_SELECTED:0,LVNI_SELECTED);
	}

	GetParent().SendMessage(AA_LISTVIEW_HEADER_CHG,checked,0);

	bHandled = TRUE;
	return 0;
}

void BASListView::SetSelectedChecked()
{
	int count = GetItemCount();
	int checked_count = 0;
	for(int i =0;i<count;++i){
		BOOL selected = GetItemState(i,LVNI_SELECTED);
		BOOL checked = GetCheckState(i);
		if(selected){
			if(!checked)
				SetCheckState(i,TRUE);
			
			++checked_count;
		}else{
			if(checked)
				SetCheckState(i,FALSE);
		}
	}

	bool checked = (count == checked_count);
	if(GetHeaderChecked() != checked){
		SetHeaderCheck(checked);
		RefreshHeaderCheck();
	}
}

BOOL BASListView::SelectItem(int nIndex)
{
	if(nIndex > -1)
		SetCheckState(nIndex,TRUE);
	return BASListViewBase::SelectItem(nIndex);
}

BOOL BASListView::CreateListView(HWND hWndParent, ATL::_U_RECT rect,ATL::_U_MENUorID MenuOrID,
	BASListSeqOrder order,bool paint_cross_bg_color,bool visible)
{
	//LVS_EDITLABELS
	auto flag = WS_BORDER|WS_CHILD | WS_CLIPCHILDREN|WS_TABSTOP |LVS_ALIGNLEFT|LVS_REPORT|LVS_OWNERDRAWFIXED;
	if(visible)
		flag |= WS_VISIBLE;

	m_hWnd = Create(hWndParent,rect,0,flag,0,MenuOrID);
	SetExtendedListViewStyle(LVS_EX_FULLROWSELECT|LVS_EX_CHECKBOXES);

	m_listHead.Attach((HWND)::SendMessage(this->m_hWnd, LVM_GETHEADER, 0, 0L));
	m_listHead.SetCheckBoxImage(m_checkbox_yes_,m_checkbox_no_);

	order_ = order;
	paint_cross_bg_color_ = paint_cross_bg_color;

	return 0;
}

LRESULT BASListView::OnCreate(UINT umsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled )
{
	//button_check.SetBkgColor(RGB(226,244,234));

	font_normal_ = Utils::GetHFONT(16);

	ShowScrollBar(SB_BOTH,0);
	bHandled=FALSE;
	return 0;
}

void BASListView::SetCheckBoxImage(Gdiplus::Bitmap* m_checkbox_yes,
	Gdiplus::Bitmap* m_checkbox_no)
{
	m_checkbox_yes_ = m_checkbox_yes;
	m_checkbox_no_ = m_checkbox_no;
}

int CALLBACK BASListView::CompareFunc(LPARAM lparam1,LPARAM lparam2,LPARAM lparamSort)
{
	return 0;
}


void BASListView::SetItemHeight(int nh)
{
	m_nItemHeight=nh;
    CRect rcWin;
    GetWindowRect(&rcWin);
    WINDOWPOS wp;
    wp.hwnd = m_hWnd;
    wp.cx = rcWin.Width();
    wp.cy = rcWin.Height();
    wp.flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER;
    SendMessage(WM_WINDOWPOSCHANGED, 0, (LPARAM)&wp);
}

void BASListView::RefreshHeaderCheck()
{
	m_listHead.RefreshCheckRect();
}

LRESULT BASListView::OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	SCROLLINFO si;
	si.cbSize=sizeof(SCROLLINFO);
	si.fMask=SIF_ALL;
	GetScrollInfo(SB_VERT,&si);
	int bottom_pos = si.nPage+si.nPos;
	if(LOWORD(wParam)!=SB_ENDSCROLL || bottom_pos < si.nMax)
	{
		bHandled = FALSE;
		return 0;
	}
	bHandled = FALSE;	
	return 0;
}

LRESULT BASListView::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	POINT pt={LOWORD(lParam),HIWORD(lParam)};

	UINT uflag=0;
	int nitem =HitTest(pt,&uflag);
	if(nitem == nitem_hover_)
		return 0;

	nitem_leave_ = nitem_hover_;
	nitem_hover_ = nitem;
	if (nitem!=-1 && (uflag & LVHT_ONITEM )){
		BOOL selected = GetItemState(nitem,LVNI_SELECTED);
		if(selected){
			CRect rect;
			GetItemRect(nitem_leave_,rect,LVIR_BOUNDS);
			InvalidateRect(rect);
		}else{
			CRect rect_hover;
			GetItemRect(nitem_hover_,rect_hover,LVIR_BOUNDS);

			CRect rect_leave;
			if(nitem_leave_ != -1){
				GetItemRect(nitem_leave_,rect_leave,LVIR_BOUNDS);
			}

			rect_hover.UnionRect(rect_leave,rect_hover);
			InvalidateRect(rect_hover);
		}
	}else{
		CRect rect;
		GetItemRect(nitem_leave_,rect,LVIR_BOUNDS);
		InvalidateRect(rect);
	}
	return 0;
}

LRESULT BASListView::OnMouseWheel(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	SCROLLINFO si;
	si.cbSize=sizeof(SCROLLINFO);
	si.fMask=SIF_ALL;
	GetScrollInfo(SB_VERT,&si);
	if((si.nPage+si.nPos) >= si.nMax)
	{
		//AutoLoadLeftData();
	}

	bHandled=FALSE;
	return 0;
}

图示

在这里插入图片描述

项目下载

注意: 项目源码以 博客内容为准。
[2022.4.26]

https://download.csdn.net/download/infoworld/11244213

作业

  1. 如何实现连续插入1000条数据而让界面不卡死呢?-

参考

List-View Window Styles

NM_CUSTOMDRAW notification code

Header Control

VListVW Sample

About List-View Controls

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter(阿斯拉达)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值