[ATL/WTL]_[初级]_[自定义多列TreeView]

场景

  1. 在开发 Win32,WTL,MFC 程序时,经常会用到 ListView 这个表格控件,ListView 的数据是按照行来显示的,行与行之间没有并没有什么关系。但是如果行之间有父子关系,比如像树形控件 TreeView 可以收起展开呢,如何实现?

图1
在这里插入图片描述

说明

  1. 这种具有列名的 TreeView 可以称为多列 TreeView(ColumnTreeView), 也可以称为 TreeListView. 这种控件它的特点就是 TreeView 有一个表头,拖动表头的分割线可以同步拖动 TreeView的对应的列数据。

  2. WTL开发的时候,当对窗口消息和通知不熟练的时候,我们实现自定义控件可以借助MFC的自定义控件例子,改为 WTL 的实现方式也很容易,因为 MFC 的大多数控件本质上还是对 Win32 控件的封装,操作的还是它的窗口句柄和消息。

  3. 在我的 使用WTL进行Windows桌面应用开发-第一部 里已经讲过自定义的ListView控件,知道它可以自定义一个表头, 也就是扩展类CHeaderCtrl的子类,增加一个复选框。 要实现多列 TreeView,原理就是增加一个容器窗口来管理一个CHeaderCtrlCTreeCtrl, 在拖动 CHeaderCtrlCTreeCtrl能响应表头的拖动操作,根据表头每列的横坐标变化来绘制 CTreeCtrl 的文本。

  4. 我参考[4]Multi-Column Tree ViewMFC实现,改为依赖WTL库. 有两个关键的通知:

    • hdn-itemchanged: CHeaderCtrl的父窗口通知,当表头项属性改变时(如宽度变化)响应处理函数。
    • NM_CUSTOMDRAW: CTreeCtrl 在父窗口里实现 TreeView 的自定义HTREEITEM项绘制,这样可以根据表头进行列数据的坐标调整。

例子

ccolumn_tree_view.h

/*********************************************************************
* Multi-Column Tree View, version 1.4 (July 7, 2005)
* Copyright (C) 2003-2005 Michal Mecinski.
*
* You may freely use and modify this code, but don't remove
* this copyright note.
*
* THERE IS NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, FOR
* THIS CODE. THE AUTHOR DOES NOT TAKE THE RESPONSIBILITY
* FOR ANY DAMAGE RESULTING FROM THE USE OF IT.
*
* E-mail: mimec@mimec.org
* WWW: http://www.mimec.org
********************************************************************/

#pragma once

#include "ccolumn_tree_view_ctrl.h"
#include <Windows.h>
#include <atlbase.h>
#include <atlapp.h>
#include <vector>
#include <functional>
#include <utility>
#include <atlwin.h>
#include "atlframe.h"
#include "atlctrls.h"
#include "atlmisc.h"
#include "atlcrack.h"
#include <GdiPlus.h>
#include "ccolumn_tree_view_header.h"

class CColumnTreeView : public CWindowImpl<CColumnTreeView, CWindow>
{
public:
	DECLARE_WND_SUPERCLASS(NULL, CWindow::GetWndClassName())
	BOOL PreTranslateMessage(MSG* pMsg)
	{
		pMsg;
		return FALSE;
	}

	BEGIN_MSG_MAP_EX(CColumnTreeView)
		MSG_WM_PAINT(OnPaint)
		MSG_WM_ERASEBKGND(OnEraseBkgnd)
		MSG_WM_SIZE(OnSize)
		MSG_WM_CREATE(OnCreate)
		MSG_WM_HSCROLL(OnHScroll)
		MSG_WM_SETFONT(OnSetFont)
		MSG_WM_GETFONT(OnGetFont);
		COMMAND_ID_HANDLER(HeaderID,OnListViewHeaderCheck)
		NOTIFY_HANDLER_EX(HeaderID,HDN_ITEMCHANGED, OnHeaderItemChanged)
		NOTIFY_HANDLER_EX(HeaderID,HDN_DIVIDERDBLCLICK, OnHeaderDividerDblClick)
		NOTIFY_HANDLER_EX(TreeID,NM_CUSTOMDRAW, OnTreeCustomDraw)
		REFLECT_NOTIFICATIONS()
	END_MSG_MAP()

	CColumnTreeView():dpi_scale_(1.0),font_normal_(NULL){}

	enum ChildrenIDs { HeaderID = 1, TreeID = 2 };

	void UpdateColumns();
	void AdjustColumnWidth(int nColumn, BOOL bIgnoreCollapsed);

	CTreeViewCtrl& GetTreeCtrl() { return m_Tree; }
	CHeaderCtrl& GetHeaderCtrl() { return m_Header; }
	BOOL isHeaderChecked();

	void SetCheckImage(Gdiplus::Bitmap* checked,Gdiplus::Bitmap* uncheck);
	void SetButtonsImage(Gdiplus::Bitmap* folder,Gdiplus::Bitmap* file);

	// 设置DPI缩放因子
	void SetDpiScale(float dpi_scale);
	int DpiScale(int value);

	void ReadyView();

	void setFuncHeaderCheck(std::function<bool(bool)> func_check);
	void setAllItemCheck(bool check);
	void setHeaderCheck(bool check);
	void setFuncRootCheckListener(std::function<void(BOOL)> funcRootCheckListener);

private:
	std::function<bool(bool)> funcHeaderCheck_;

protected:
	LRESULT OnListViewHeaderCheck(WORD wNotify,WORD wID,HWND hCtrl, BOOL &bHandeld);
	int OnCreate(LPCREATESTRUCT lpCreateStruct);
	virtual void OnDraw(CDC* pDC) {}
	void OnSetFont(CFontHandle font, BOOL bRedraw);
	HFONT OnGetFont();

protected:
	void UpdateScroller();
	void RepositionControls();
	int GetMaxColumnWidth(HTREEITEM hItem, int nColumn, int nDepth, BOOL bIgnoreCollapsed);

protected:
	CColumnTreeViewCtrl m_Tree;
	CColumnTreeViewHeader m_Header;
	int m_cyHeader;
	int m_cxTotal;
	int m_xPos;
	int m_arrColWidths[16];
	int m_xOffset;
	Gdiplus::Bitmap* folder_;
	Gdiplus::Bitmap* file_;
	Gdiplus::Bitmap* image_checked_;
	Gdiplus::Bitmap* image_uncheck_;

protected:
	void OnPaint(CDCHandle dc);
	BOOL OnEraseBkgnd(CDCHandle dc);
	void OnSize(UINT nType, CSize size);
	void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar pScrollBar);

	LRESULT OnHeaderItemChanged(LPNMHDR pnmh);
	LRESULT OnHeaderDividerDblClick(LPNMHDR pnmh);
	LRESULT OnTreeCustomDraw(LPNMHDR pnmh);

	float dpi_scale_;
	HFONT font_normal_;

	typedef std::pair<Gdiplus::Image*,Gdiplus::Rect>  GpImageData;
	std::vector<GpImageData> gpDeferImageDataCache;
};

ccolumn_tree_view.cpp

/*********************************************************************
* Multi-Column Tree View, version 1.4 (July 7, 2005)
* Copyright (C) 2003-2005 Michal Mecinski.
*
* You may freely use and modify this code, but don't remove
* this copyright note.
*
* THERE IS NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, FOR
* THIS CODE. THE AUTHOR DOES NOT TAKE THE RESPONSIBILITY
* FOR ANY DAMAGE RESULTING FROM THE USE OF IT.
*
* E-mail: mimec@mimec.org
* WWW: http://www.mimec.org
********************************************************************/
#include "stdafx.h"
#include "ccolumn_tree_view.h"

#include <shlwapi.h>
#include <string>
#include <assert.h>
#include "utils.h"
#include <iostream>
#include "ccolumn_tree_view_ctrl.h"
#ifndef TVS_NOHSCROLL
#define TVS_NOHSCROLL 0x8000	// IE 5.0 or higher required
#endif

void CColumnTreeView::SetCheckImage(Gdiplus::Bitmap* checked,Gdiplus::Bitmap* uncheck){
	image_checked_ = checked;
	image_uncheck_ = uncheck;
}


void CColumnTreeView::SetButtonsImage(Gdiplus::Bitmap* folder,Gdiplus::Bitmap* file){
	folder_ = folder;
	file_ = file;
}

void CColumnTreeView::SetDpiScale(float dpi_scale)
{
	dpi_scale_ = dpi_scale;
}

int CColumnTreeView::DpiScale(int value)
{
	return dpi_scale_*value;
}

BOOL CColumnTreeView::isHeaderChecked()
{
	return m_Header.GetIfChecked();
}

int CColumnTreeView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (m_Tree.m_hWnd)
		return 0;

	assert(folder_ && file_ && image_checked_ && image_uncheck_);

	// create tree and header controls as children
	m_Tree.SetDpiScale(dpi_scale_);
	m_Tree.GetWndClassInfo().m_wc.hbrBackground = AtlGetStockBrush(WHITE_BRUSH);
	m_Tree.Create(m_hWnd, CRect(),0,WS_CHILD | WS_VISIBLE | TVS_NOHSCROLL | TVS_NOTOOLTIPS | TVS_HASBUTTONS | TVS_HASLINES 
		| TVS_LINESATROOT | TVS_FULLROWSELECT | TVS_DISABLEDRAGDROP,0,TreeID);
	
	m_Header.SetCheckBoxImage(image_checked_,image_uncheck_);
	m_Header.SetDpiScale(dpi_scale_);
	m_Header.Create(m_hWnd,CRect(),0,WS_CHILD | WS_VISIBLE | HDS_FULLDRAG,0, HeaderID);

	
	return 0;
}

void CColumnTreeView::setFuncRootCheckListener(std::function<void(BOOL)> funcRootCheckListener)
{
	m_Tree.setFuncRootCheckListener(funcRootCheckListener);
}

HFONT CColumnTreeView::OnGetFont()
{
	return font_normal_;
}

void CColumnTreeView::OnSetFont(CFontHandle font, BOOL bRedraw)
{
	font_normal_ = font;
}

void CColumnTreeView::ReadyView()
{
	// set correct font for the header
	HFONT pFont = GetFont();
	m_Header.SetFont(pFont);
	m_Tree.SetFont(pFont);

	// check if the common controls library version 6.0 is available
	BOOL bIsComCtl6 = FALSE;

	HMODULE hComCtlDll = LoadLibrary(L"comctl32.dll");

	if (hComCtlDll)
	{
		typedef HRESULT (CALLBACK *PFNDLLGETVERSION)(DLLVERSIONINFO*);

		PFNDLLGETVERSION pfnDllGetVersion = (PFNDLLGETVERSION)GetProcAddress(hComCtlDll, "DllGetVersion");

		if (pfnDllGetVersion)
		{
			DLLVERSIONINFO dvi;
			ZeroMemory(&dvi, sizeof(dvi));
			dvi.cbSize = sizeof(dvi);

			HRESULT hRes = (*pfnDllGetVersion)(&dvi);

			if (SUCCEEDED(hRes) && dvi.dwMajorVersion >= 6)
				bIsComCtl6 = TRUE;
		}

		FreeLibrary(hComCtlDll);
	}

	// calculate correct header's height
	auto hdc =  GetDC();
	CDCHandle pDC(hdc);
	pDC.SelectFont(pFont);
	CSize szExt;
	const wchar_t* A = L"A";
	pDC.GetTextExtent(A,wcslen(L"A"),&szExt);
	m_cyHeader = szExt.cy + (bIsComCtl6 ? DpiScale(7) :DpiScale(4));

	// offset from column start to text start
	m_xOffset = bIsComCtl6 ? DpiScale(9) : DpiScale(6);

	m_xPos = 0;
	UpdateColumns();
	ReleaseDC(hdc);
}


void CColumnTreeView::OnPaint(CDCHandle dc1)
{
	// do nothing
	CPaintDC dc(m_hWnd);
}

BOOL CColumnTreeView::OnEraseBkgnd(CDCHandle dc)
{
	return TRUE;
}

void CColumnTreeView::OnSize(UINT nType, CSize size)
{
	DefWindowProc(WM_SIZE, (WPARAM)nType,MAKELPARAM(size.cx,size.cy));

	UpdateScroller();
	RepositionControls();
}

void CColumnTreeView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar pScrollBar)
{
	CRect rcClient;
	GetClientRect(&rcClient);
	int cx = rcClient.Width();

	int xLast = m_xPos;

	switch (nSBCode)
	{
	case SB_LINELEFT:
		m_xPos -= 15;
		break;
	case SB_LINERIGHT:
		m_xPos += 15;
		break;
	case SB_PAGELEFT:
		m_xPos -= cx;
		break;
	case SB_PAGERIGHT:
		m_xPos += cx;
		break;
	case SB_LEFT:
		m_xPos = 0;
		break;
	case SB_RIGHT:
		m_xPos = m_cxTotal - cx;
		break;
	case SB_THUMBTRACK:
		m_xPos = nPos;
		break;
	}

	if (m_xPos < 0)
		m_xPos = 0;
	else if (m_xPos > m_cxTotal - cx)
		m_xPos = m_cxTotal - cx;

	if (xLast == m_xPos)
		return;

	SetScrollPos(SB_HORZ, m_xPos);
	RepositionControls();
}


LRESULT CColumnTreeView::OnHeaderItemChanged(LPNMHDR pNMHDR)
{
	UpdateColumns();

	m_Tree.Invalidate();
	return 0;
}

LRESULT CColumnTreeView::OnHeaderDividerDblClick(LPNMHDR pNMHDR)
{
	NMHEADER* pNMHeader = (NMHEADER*)pNMHDR;

	AdjustColumnWidth(pNMHeader->iItem, TRUE);
	return 0;
}

void CColumnTreeView::setFuncHeaderCheck(std::function<bool(bool)> func_check)
{
	funcHeaderCheck_ = func_check;
}

void CColumnTreeView::setAllItemCheck(bool check)
{
	auto hItem = m_Tree.GetRootItem();
	while(hItem){
		m_Tree.SetSelfCheckState(hItem,check);
		if(m_Tree.ItemHasChildren(hItem)){
			m_Tree.SetChildCheckState(hItem,check);
		}
		hItem = m_Tree.GetNextSiblingItem(hItem);
	}
}

void CColumnTreeView::setHeaderCheck(bool check)
{
	m_Header.SetCheckAll(check);
	m_Header.RefreshCheckRect();
}

LRESULT CColumnTreeView::OnListViewHeaderCheck(WORD wNotify,WORD wID,HWND hCtrl, BOOL &bHandeld)
{
	setAllItemCheck(wNotify);

	if(funcHeaderCheck_)
		funcHeaderCheck_(wNotify);

	return 0;
}

LRESULT CColumnTreeView::OnTreeCustomDraw(LPNMHDR pNMHDR)
{
	NMCUSTOMDRAW* pNMCustomDraw = (NMCUSTOMDRAW*)pNMHDR;
	NMTVCUSTOMDRAW* pNMTVCustomDraw = (NMTVCUSTOMDRAW*)pNMHDR;

	LRESULT result = 0;
	LRESULT* pResult = &result;

	switch (pNMCustomDraw->dwDrawStage)
	{
	case CDDS_PREPAINT:
		*pResult = CDRF_NOTIFYITEMDRAW;
		break;

	case CDDS_ITEMPREPAINT:
		*pResult = CDRF_DODEFAULT | CDRF_NOTIFYPOSTPAINT;
		break;

	case CDDS_ITEMPOSTPAINT:
		{
			HTREEITEM hItem = (HTREEITEM)pNMCustomDraw->dwItemSpec;
			CRect rcItem = pNMCustomDraw->rc;

			if (rcItem.IsRectEmpty())
			{
				// nothing to paint
				*pResult = CDRF_DODEFAULT;
				break;
			}

			CDCHandle dc(pNMCustomDraw->hdc);
			//CMemoryDC dc(pNMCustomDraw->hdc,rcItem);
			//dc.SetBkMode(TRANSPARENT);

			CRect rcLabel;
			m_Tree.GetItemRect(hItem, &rcLabel, TRUE);
			auto item_data = (CColumnTreeViewCtrlData*)m_Tree.GetItemData(hItem);
			BOOL is_folder = item_data->is_folder;

			COLORREF crTextBk = pNMTVCustomDraw->clrTextBk;
			COLORREF crText = pNMTVCustomDraw->clrText;
			//COLORREF crWnd = GetSysColor(COLOR_WINDOW);
			COLORREF crWnd = RGB(255,255,255);
			COLORREF color_selected = GetSysColor(COLOR_HIGHLIGHT);
			// clear the original label rectangle
			CRect rcClear = rcLabel;
			if (rcClear.left > m_arrColWidths[0] - DpiScale(1))
				rcClear.left = m_arrColWidths[0] - DpiScale(1);

			if ((pNMCustomDraw->uItemState & CDIS_SELECTED) == 0){
				dc.FillSolidRect(&rcClear, crWnd);
			}

			int nColsCnt = m_Header.GetItemCount();

			// draw horizontal lines...
			int xOffset = 0;
			for (int i=0; i<nColsCnt; i++)
			{
				xOffset += m_arrColWidths[i];
				rcItem.right = xOffset-DpiScale(1);
				//dc.DrawEdge(&rcItem, BDR_SUNKENINNER, BF_RIGHT);
			}
			// ...and the vertical ones
			dc.DrawEdge(&rcItem, BDR_SUNKENINNER, BF_BOTTOM);

			std::wstring strSub;
			TCHAR szBuf[MAX_PATH]={0};
			memset(szBuf,0,sizeof(szBuf));
			m_Tree.GetItemText(hItem,szBuf,MAX_PATH);
			auto pos = wcschr(szBuf,L'\t');
			if(pos)
				strSub.append(szBuf,pos-szBuf);
			else
				strSub = szBuf;

			// calculate main label's size
			CRect rcText(0,0,0,0);
			dc.DrawText(strSub.c_str(),strSub.size(), &rcText, DT_NOPREFIX | DT_CALCRECT);
			rcLabel.right = min(rcLabel.left + rcText.right + DpiScale(4), m_arrColWidths[0] - DpiScale(4));

			CRect rcBack = rcLabel;
			if (::GetWindowLong(m_Tree.m_hWnd, GWL_STYLE) & TVS_FULLROWSELECT)
			{
				int nWidth = 0;
				for (int i=0; i<nColsCnt; i++)
					nWidth += m_arrColWidths[i];
				rcBack.right = nWidth - DpiScale(1);
				if (rcBack.left > m_arrColWidths[0] - DpiScale(1))
					rcBack.left = m_arrColWidths[0] - DpiScale(1);
			}

			if (rcBack.Width() < 0)
				crTextBk = crWnd;

			// draw label's background
			// crTextBk != crWnd && 
			if ((pNMCustomDraw->uItemState & CDIS_SELECTED)){
				dc.FillSolidRect(&rcBack, color_selected); // crTextBk
			}

			// draw focus rectangle if necessary
			if (pNMCustomDraw->uItemState & CDIS_FOCUS){
				dc.DrawFocusRect(&rcBack);
			}

			// draw main label
			gpDeferImageDataCache.clear();
			xOffset = m_arrColWidths[0];

			rcText = rcLabel;
			rcText.DeflateRect(DpiScale(2), DpiScale(1));

			auto data = (CColumnTreeViewCtrlData*)m_Tree.GetItemData(hItem);
			auto imageCheck = data->checked?image_checked_:image_uncheck_;

			auto widthCheck = DpiScale(image_checked_->GetWidth());
			auto heightCheck = DpiScale(image_checked_->GetHeight());

			int image_checky = rcText.top+(rcText.Height()-heightCheck)/2;
			gpDeferImageDataCache.push_back(std::make_pair(imageCheck,
				Gdiplus::Rect(rcText.left,image_checky,widthCheck,heightCheck)));

			auto imageFile = (is_folder)?folder_:file_;
			auto widthFile = DpiScale(imageFile->GetWidth());
			auto heightFile = DpiScale(imageFile->GetHeight());

			int imagex = rcText.left+widthCheck+DpiScale(8);
			int imagey = rcText.top+(rcText.Height()-heightFile)/2;
			gpDeferImageDataCache.push_back(std::make_pair(imageFile,
				Gdiplus::Rect(imagex,imagey,widthFile,heightFile)));

			dc.SetBkMode(TRANSPARENT);
			CSize size_strSub;
			dc.GetTextExtent(strSub.c_str(),strSub.size(),&size_strSub);
			rcText.top = (rcText.Height()- size_strSub.cy)/2+rcText.top;
			rcText.bottom = rcText.top+size_strSub.cy;
			rcText.right = rcText.left+xOffset;
			rcText.left =  imagex+widthFile+DpiScale(4);

			dc.SetTextColor(crText); // crText
			dc.DrawText(strSub.c_str(),strSub.size(), rcText, DT_NOPREFIX | DT_END_ELLIPSIS);
			

			if (!(::GetWindowLong(m_Tree.m_hWnd, GWL_STYLE) & TVS_FULLROWSELECT))
				dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));

			// draw other columns text
			wchar_t* start = pos;
			for (int i=1; i<nColsCnt; i++)
			{
				if(start && (start+1)){
					start += 1;
					pos = wcschr(start,L'\t');
					if(pos){
						strSub.clear();
						strSub.append(start,pos-start);
						start = pos;
					}else{
						strSub.clear();
						strSub = start;
						start = pos;
					}
					rcText = rcLabel;
					rcText.left = xOffset;
					rcText.right = xOffset + m_arrColWidths[i];
					rcText.DeflateRect(m_xOffset, DpiScale(1),DpiScale(2), DpiScale(1));

					dc.GetTextExtent(strSub.c_str(),strSub.size(),&size_strSub);
					rcText.top = (rcText.Height()- size_strSub.cy)/2+rcText.top;
					rcText.bottom = rcText.top+size_strSub.cy;
					dc.DrawText(strSub.c_str(),strSub.size(), &rcText, DT_NOPREFIX | DT_END_ELLIPSIS);
				}
				xOffset += m_arrColWidths[i];
			}

			// 1. 最后再使用Graphics,避免Gdi和Gdi+混合调用.
			auto sizeCache = gpDeferImageDataCache.size();
			if(sizeCache){
				Gdiplus::Graphics graphics(dc);
				graphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
				for(auto i = 0; i<gpDeferImageDataCache.size();++i){
					auto& one = gpDeferImageDataCache[i];
					Utils::DrawImage(graphics,one.first,one.second);
				} 
			}
		}
		*pResult = CDRF_DODEFAULT;
		break;

	default:
		*pResult = CDRF_DODEFAULT;
	}

	return result;
}


void CColumnTreeView::UpdateColumns()
{
	m_cxTotal = 0;

	HDITEM hditem;
	hditem.mask = HDI_WIDTH;
	int nCnt = m_Header.GetItemCount();
	if (nCnt > 16)
		nCnt = 16;

	// get column widths from the header control
	for (int i=0; i<nCnt; i++)
	{
		if (m_Header.GetItem(i, &hditem))
		{
			m_cxTotal += m_arrColWidths[i] = hditem.cxy;
			if (i==0)
				m_Tree.m_cxFirstCol = hditem.cxy;
		}
	}
	m_Tree.m_cxTotal = m_cxTotal;

	UpdateScroller();
	RepositionControls();
}

void CColumnTreeView::UpdateScroller()
{
	CRect rcClient;
	GetClientRect(&rcClient);
	int cx = rcClient.Width();

	int lx = m_xPos;

	if (m_xPos > m_cxTotal - cx)
		m_xPos = m_cxTotal - cx;
	if (m_xPos < 0)
		m_xPos = 0;

	SCROLLINFO scrinfo;
	scrinfo.cbSize = sizeof(scrinfo);
	scrinfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
	scrinfo.nPage = cx;
	scrinfo.nMin = 0;
	scrinfo.nMax = m_cxTotal;
	scrinfo.nPos = m_xPos;
	SetScrollInfo(SB_HORZ, &scrinfo);
}

void CColumnTreeView::RepositionControls()
{
	// reposition child controls
	if (m_Tree.m_hWnd)
	{
		CRect rcClient;
		GetClientRect(&rcClient);
		int cx = rcClient.Width();
		int cy = rcClient.Height();

		// move to a negative offset if scrolled horizontally
		int x = 0;
		if (cx < m_cxTotal)
		{
			x = GetScrollPos(SB_HORZ);
			cx += x;
		}
		m_Header.MoveWindow(-x, 0, cx, m_cyHeader);
		m_Tree.MoveWindow(-x, m_cyHeader, cx, cy-m_cyHeader);
	}
}

void CColumnTreeView::AdjustColumnWidth(int nColumn, BOOL bIgnoreCollapsed)
{
	int nMaxWidth = GetMaxColumnWidth(m_Tree.GetRootItem(), nColumn, 0, bIgnoreCollapsed);

	HDITEM hditem;
	hditem.mask = HDI_WIDTH;
	m_Header.GetItem(nColumn, &hditem);
	hditem.cxy = nMaxWidth + 20;
	m_Header.SetItem(nColumn, &hditem);
}

int CColumnTreeView::GetMaxColumnWidth(HTREEITEM hItem, int nColumn, int nDepth, BOOL bIgnoreCollapsed)
{
	int nMaxWidth = 0;

	std::wstring strSub;
	TCHAR szBuf[MAX_PATH]={0};
	m_Tree.GetItemText(hItem,szBuf,MAX_PATH);
	auto pos = wcschr(szBuf,L'\t');

	if (pos)
	{
		strSub.append(szBuf,pos-szBuf);
		CDCHandle dc;
		dc.CreateCompatibleDC(NULL);
		HFONT pOldFont = dc.SelectFont(m_Tree.GetFont());

		// calculate text width
		CSize size_strSub;
		dc.GetTextExtent(strSub.c_str(),strSub.size(),&size_strSub);
		nMaxWidth = size_strSub.cx;
		dc.SelectFont(pOldFont);
	}

	// add indent and image space if first column
	if (nColumn == 0)
	{
		int nIndent = nDepth;

		if (::GetWindowLong(m_Tree.m_hWnd, GWL_STYLE) & TVS_LINESATROOT)
			nIndent++;

		int nImage, nSelImage;
		m_Tree.GetItemImage(hItem, nImage, nSelImage);
		if (nImage >= 0)
			nIndent++;

		nMaxWidth += nIndent * m_Tree.GetIndent();
	}

	if (!bIgnoreCollapsed || (m_Tree.GetItemState(hItem, TVIS_EXPANDED) & TVIS_EXPANDED))
	{
		// process child items recursively
		HTREEITEM hSubItem = m_Tree.GetChildItem(hItem);
		while (hSubItem)
		{
			int nSubWidth = GetMaxColumnWidth(hSubItem, nColumn, nDepth + 1, bIgnoreCollapsed);
			if (nSubWidth > nMaxWidth)
				nMaxWidth = nSubWidth;

			hSubItem = m_Tree.GetNextSiblingItem(hSubItem);
		}
	}

	return nMaxWidth;
}

ccolumn_tree_view_ctrl.h

// test-document-viewView.h : interface of the CColumnTreeViewCtrl class
//
/

#pragma once

#include <stdint.h>
#include <functional>
#include <Windows.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>

typedef struct CColumnTreeViewCtrlData1
{
	BOOL checked;
	void* userdata;
	BOOL is_folder;
	int totalChildren;
	int checkedChildren;
}CColumnTreeViewCtrlData;

class CColumnTreeViewCtrl : public CWindowImpl<CColumnTreeViewCtrl, CTreeViewCtrl>
{
public:
	DECLARE_WND_SUPERCLASS(NULL, CTreeViewCtrl::GetWndClassName())

	CColumnTreeViewCtrl():dpi_scale_(1.0){}

	// 设置DPI缩放因子
	void SetDpiScale(float dpi_scale);
	int DpiScale(int value);

	BOOL PreTranslateMessage(MSG* pMsg)
	{
		pMsg;
		return FALSE;
	}

	BEGIN_MSG_MAP_EX(CColumnTreeViewCtrl)
		MSG_WM_PAINT(OnPaint)
		MSG_WM_ERASEBKGND(OnEraseBkgnd)
		MSG_WM_LBUTTONDOWN(OnLButtonDown)
		MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk)
		DEFAULT_REFLECTION_HANDLER()
	END_MSG_MAP()

// Handler prototypes (uncomment arguments if needed):
//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)

protected:
	void HandleMouse(UINT message, UINT nFlags, CPoint point);

protected:
	int m_cxFirstCol;
	int m_cxTotal;
	void refreshCheck(HTREEITEM hItem);
	BOOL getRootCheckState();
	std::function<void(BOOL)> funcRootCheckListener_;

public:
	void setFuncRootCheckListener(std::function<void(BOOL)> funcRootCheckListener);

public:
	void SetParentCheckState(HTREEITEM hItem,BOOL checked);
	void SetSelfCheckState(HTREEITEM hItem,BOOL checked);
	void SetChildCheckState(HTREEITEM hItem,BOOL checked);
	void OnPaint(CDCHandle dc);
	BOOL OnEraseBkgnd(CDCHandle dc);
	void OnLButtonDown(UINT nFlags, CPoint point);
	void OnLButtonDblClk(UINT nFlags, CPoint point);
	friend class CColumnTreeView;

	float dpi_scale_;

};

ccolumn_tree_view_ctrl.cpp

/*********************************************************************
* Multi-Column Tree View, version 1.4 (July 7, 2005)
* Copyright (C) 2003-2005 Michal Mecinski.
*
* You may freely use and modify this code, but don't remove
* this copyright note.
*
* THERE IS NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, FOR
* THIS CODE. THE AUTHOR DOES NOT TAKE THE RESPONSIBILITY
* FOR ANY DAMAGE RESULTING FROM THE USE OF IT.
*
* E-mail: mimec@mimec.org
* WWW: http://www.mimec.org
********************************************************************/
#include "stdafx.h"
#include "ccolumn_tree_view_ctrl.h"
#include <string>

void CColumnTreeViewCtrl::SetDpiScale(float dpi_scale)
{
	dpi_scale_ = dpi_scale;
}

int CColumnTreeViewCtrl::DpiScale(int value)
{
	return dpi_scale_*value;
}

void CColumnTreeViewCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
	// mask left click if outside the real item's label
	HandleMouse(WM_LBUTTONDOWN, nFlags, point);
}


void CColumnTreeViewCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)
{
	HandleMouse(WM_LBUTTONDBLCLK, nFlags, point);
}

void CColumnTreeViewCtrl::OnPaint(CDCHandle dc1)
{
	CPaintDC dc(m_hWnd);

	CRect rcClient;
	GetClientRect(&rcClient);

	CDC dcMem;
	CBitmap bmpMem;

	// use temporary bitmap to avoid flickering
	dcMem.CreateCompatibleDC(dc);
	if (bmpMem.CreateCompatibleBitmap(dc, rcClient.Width(), rcClient.Height()))
	{
		auto pOldBmp = dcMem.SelectBitmap(bmpMem);

		// paint the window onto the memory bitmap
		DefWindowProc(WM_PAINT, (WPARAM)dcMem.m_hDC, 0);

		// copy it to the window's DC
		dc.BitBlt(0, 0, rcClient.right, rcClient.bottom, dcMem, 0, 0, SRCCOPY);

		dcMem.SelectBitmap(pOldBmp);

		bmpMem.DeleteObject();
	}
	dcMem.DeleteDC();
}

BOOL CColumnTreeViewCtrl::OnEraseBkgnd(CDCHandle dc)
{
	return TRUE;
}

void CColumnTreeViewCtrl::HandleMouse(UINT message, UINT nFlags, CPoint point)
{
	UINT fFlags;
	HTREEITEM hItem = HitTest(point, &fFlags);

	// verify the hit result
	if (fFlags & (TVHT_ONITEMLABEL | TVHT_ONITEMRIGHT))
	{
		CRect rcItem;
		GetItemRect(hItem, &rcItem, TRUE);

		if (::GetWindowLong(m_hWnd, GWL_STYLE) & TVS_FULLROWSELECT)
		{
			if (message == WM_LBUTTONDOWN)
				SetFocus();

			// ignore if outside all columns
			rcItem.right = m_cxTotal;
			if (!rcItem.PtInRect(point))
				return;

			// select or expand item
			if (message == WM_LBUTTONDOWN)
			{
				Select(hItem, TVGN_CARET);

				rcItem.right = rcItem.left+DpiScale(24);
				if(rcItem.PtInRect(point)){
					auto data = (CColumnTreeViewCtrlData*)GetItemData(hItem);
					data->checked = !data->checked;
					InvalidateRect(rcItem);

					if(ItemHasChildren(hItem)){
						SetChildCheckState(hItem,data->checked);
					}

					SetParentCheckState(hItem,data->checked);
				}	
			}
			else if (message == WM_LBUTTONDBLCLK)
			{
				// send the NM_DBLCLK notification
				NMHDR nmhdr;
				nmhdr.hwndFrom = m_hWnd;
				nmhdr.idFrom = GetDlgCtrlID();
				nmhdr.code = NM_DBLCLK;
				::SendMessage(GetParent(),WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr);
				Expand(hItem, TVE_TOGGLE);
			}

			return;
		}
		else
		{
			// ignore if outside the first column
			rcItem.right = m_cxFirstCol;
			if (!rcItem.PtInRect(point))
			{
				if (message == WM_LBUTTONDOWN)
					SetFocus();
				return;
			}

			std::wstring strSub;
			TCHAR szBuf[MAX_PATH]={0};
			GetItemText(hItem,szBuf,MAX_PATH);
			auto pos = wcschr(szBuf,L'\t');
			if(pos)
				strSub.append(szBuf,pos-szBuf);
			else
				strSub = szBuf;

			CClientDC pDC(m_hWnd);
			pDC.SelectFont(GetFont());
			CSize size_str_sub;
			pDC.GetTextExtent(strSub.c_str(),strSub.size(),&size_str_sub);
			rcItem.right = rcItem.left + size_str_sub.cx + DpiScale(6);
			ReleaseDC(pDC);

			// ignore if outside the label's rectangle
			if (!rcItem.PtInRect(point))
			{
				if (message == WM_LBUTTONDOWN)
					SetFocus();
				return;
			}
		}
	}
	else
	{
		// check if the button or icon is hidden
		if (point.x >= m_cxFirstCol)
		{
			if (message == WM_LBUTTONDOWN)
				SetFocus();

			// ignore if outside all columns
			if (point.x > m_cxTotal)
				return;

			// select or expand item
			if (message == WM_LBUTTONDOWN)
			{
				Select(hItem, TVGN_CARET);
			}
			else if (message == WM_LBUTTONDBLCLK)
			{
				// send the NM_DBLCLK notification
				NMHDR nmhdr;
				nmhdr.hwndFrom = m_hWnd;
				nmhdr.idFrom = GetDlgCtrlID();
				nmhdr.code = NM_DBLCLK;
				::SendMessage(GetParent(),WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr);

				Expand(hItem, TVE_TOGGLE);
			}

			return;
		}
	}

	// pass message to the default procedure
	DefWindowProc(message, nFlags, MAKELONG(point.x, point.y));
}

BOOL CColumnTreeViewCtrl::getRootCheckState()
{
	int checkedCount = 0;
	int count = 0;
	auto hItem = GetRootItem();
	while(hItem){
		auto itemData = (CColumnTreeViewCtrlData*)GetItemData(hItem);
		count++;
		if(!itemData->checked)
			break;
		
		checkedCount++;
		hItem = GetNextSiblingItem(hItem);
	}

	return (checkedCount == count);
}

void CColumnTreeViewCtrl::setFuncRootCheckListener(std::function<void(BOOL)> funcRootCheckListener)
{
	funcRootCheckListener_ = funcRootCheckListener;
}

void CColumnTreeViewCtrl::SetParentCheckState(HTREEITEM hItem,BOOL checked)
{
	if(!hItem) return;
		
	auto item_parent = GetParentItem(hItem);
	if(!item_parent){
		auto checkedRoot = getRootCheckState();
		if(funcRootCheckListener_)
			funcRootCheckListener_(checkedRoot);
		return;
	}
		
	auto data = (CColumnTreeViewCtrlData*)GetItemData(item_parent);
	if(checked){
		data->checkedChildren++;
		if(data->checkedChildren == data->totalChildren){
			data->checked = checked;
			refreshCheck(item_parent);
			SetParentCheckState(item_parent,checked);
		}else{
			;
		}
	}else{
		data->checkedChildren--;
		if(data->checked){
			data->checked = checked;
			refreshCheck(item_parent);
			SetParentCheckState(item_parent,checked);
		}else{
			;
		}
	}
}

void CColumnTreeViewCtrl::refreshCheck(HTREEITEM hItem)
{
	CRect rect_item;
	GetItemRect(hItem,rect_item,TRUE);

	rect_item.right = rect_item.left+DpiScale(24);
	InvalidateRect(rect_item);
}

void CColumnTreeViewCtrl::SetSelfCheckState(HTREEITEM hItem,BOOL checked){
	auto data = (CColumnTreeViewCtrlData*)GetItemData(hItem);
	data->checked = checked;
	CRect rect_item;
	GetItemRect(hItem,rect_item,TRUE);

	rect_item.right = rect_item.left+DpiScale(24);
	InvalidateRect(rect_item);
}

void CColumnTreeViewCtrl::SetChildCheckState(HTREEITEM hItem,BOOL checked){
	auto one = GetChildItem(hItem);
	while(one){
		auto dataParent = (CColumnTreeViewCtrlData*)GetItemData(hItem);
		dataParent->checkedChildren = (checked)?dataParent->totalChildren:0;

		if(ItemHasChildren(one))
			SetChildCheckState(one,checked);
	
		auto data = (CColumnTreeViewCtrlData*)GetItemData(one);
		data->checked = checked;
		CRect rect_item;
		GetItemRect(one,rect_item,TRUE);

		rect_item.right = rect_item.left+DpiScale(24);
		InvalidateRect(rect_item);
		one = GetNextSiblingItem(one);
	}
}

ccolumn_tree_view_header.h


#ifndef __CCOLUMN_TREE_VIEW_HEADER_H
#define __CCOLUMN_TREE_VIEW_HEADER_H

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

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

#include <GdiPlus.h>

class CColumnTreeViewHeader:public CWindowImpl<CColumnTreeViewHeader,CHeaderCtrl>,public CCustomDraw<CColumnTreeViewHeader>
{
public:
	CColumnTreeViewHeader();
	~CColumnTreeViewHeader();

	// 设置DPI缩放因子
	void SetDpiScale(float dpi_scale);
	int DpiScale(int value);

	BEGIN_MSG_MAP(CColumnTreeViewHeader)
		MESSAGE_HANDLER(WM_CREATE,OnCreate)
		MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDown)
		MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
		CHAIN_MSG_MAP_ALT(CCustomDraw<CColumnTreeViewHeader>,1)
	END_MSG_MAP()

	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);
protected:

	LRESULT OnCreate(UINT umsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled );
	LRESULT OnPaint(UINT umsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled );
	LRESULT OnLButtonDown(UINT umsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled );

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;

	COLORREF m_cr3DHighLight;
	COLORREF m_cr3DShadow;
	COLORREF m_cr3DFace;

	int m_iSpacing;
	BOOL m_bDividerLines;

	float dpi_scale_;
};

#endif

ccolumn_tree_view_header.cpp

#include "stdafx.h"
#include "ccolumn_tree_view_header.h"

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

CColumnTreeViewHeader::CColumnTreeViewHeader():
m_nSortColumn(-1)
{
	bkg_color_ = RGB(242,242,242);
	checked_ = false;
	m_checkbox_yes_ = NULL;
	m_checkbox_no_ = NULL;
	dpi_scale_ = 1.0;
	m_cr3DFace   = ::GetSysColor(COLOR_3DFACE);
}

LRESULT CColumnTreeViewHeader::OnCreate(UINT umsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled )
{
	m_nHeadHight = DpiScale(25);
	bHandled = FALSE;
	return 0;
}

void CColumnTreeViewHeader::SetDpiScale(float dpi_scale)
{
	dpi_scale_ = dpi_scale;
}

int CColumnTreeViewHeader::DpiScale(int value)
{
	return dpi_scale_*value;
}

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

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

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

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

DWORD CColumnTreeViewHeader::OnPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCustomDraw)
{
	Gdiplus::Graphics graphics(lpNMCustomDraw->hdc);
	if(m_checkbox_yes_){
		CRect rect;
		GetClientRect(&rect);
		int width_image = DpiScale(m_checkbox_yes_->GetWidth());
		int height_image = DpiScale(m_checkbox_yes_->GetHeight());

		rect.top+= (rect.Height()-height_image)/2;
		if(checked_){
			Utils::DrawImage(graphics,m_checkbox_yes_,DpiScale(5),rect.top,
				width_image,height_image);
		}else{
			Utils::DrawImage(graphics,m_checkbox_no_,DpiScale(5),rect.top,
				width_image,height_image);
		}
	}

	auto color1 = GetSysColor(COLOR_ACTIVEBORDER);
	Gdiplus::Color color;
	color.SetFromCOLORREF(color1);
	Gdiplus::Pen pen(color);

	CRect rect;
	GetClientRect(&rect);
	graphics.DrawLine(&pen,rect.left,rect.bottom-1,rect.right,rect.bottom-1);
	return CDRF_DODEFAULT;
}

DWORD CColumnTreeViewHeader::OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCustomDraw)
{
	return CDRF_NOTIFYPOSTPAINT;
}

LRESULT CColumnTreeViewHeader::OnLButtonDown(UINT umsg,WPARAM wparam,LPARAM lparam,BOOL& bHandled )
{
	if(!m_checkbox_yes_)
	{
		bHandled = FALSE;
		return 0;
	}

	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位置,时间关系暂时先固定.
		auto five5 = DpiScale(5);
		if( five5 <= pt.x &&  pt.x <= five5+DpiScale(m_checkbox_yes_->GetWidth()))
		{
			CWindow parent = GetParent();
			checked_ = !checked_;
			parent.SendMessage(WM_COMMAND,MAKEWPARAM(GetWindowLong(GWL_ID),checked_),(LPARAM)m_hWnd);
			Invalidate(FALSE);
			bHandled = TRUE;
			return 0;
		}
	}
	bHandled = FALSE;
	//增加判断识别点击了CheckBox坐标.
	return 0;
}

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

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



utils.h


#ifndef __UTILS_H
#define __UTILS_H

#include <Windows.h>
#include <GdiPlus.h>

class Utils
{
public:
	static void DrawImage(Gdiplus::Graphics& graphics,Gdiplus::Image* image,
		int x,int y);
	static void DrawImage(Gdiplus::Graphics& graphics,Gdiplus::Image* image,
		int x,int y,int dest_width,int dest_height);
	static void DrawImage(Gdiplus::Graphics& graphics,Gdiplus::Image* image,
		Gdiplus::Rect dest_rect);
	static BOOL Is64BitWindows();
};

#endif

utils.cpp


#include "stdafx.h"
#include "utils.h"

void Utils::DrawImage(Gdiplus::Graphics& graphics,Gdiplus::Image* image,
	int x,int y)
{
	DrawImage(graphics,image,x,y,image->GetWidth(),image->GetHeight());
}

void Utils::DrawImage(Gdiplus::Graphics& graphics,Gdiplus::Image* image,
	int x,int y,int dest_width,int dest_height)
{
	Gdiplus::Rect dest_rect(x,y,dest_width,dest_height);
	DrawImage(graphics,image,dest_rect);
}

void Utils::DrawImage(Gdiplus::Graphics& graphics,Gdiplus::Image* image,
	Gdiplus::Rect dest_rect)
{
	graphics.DrawImage(image,dest_rect,0,0,image->GetWidth(),image->GetHeight(),
		Gdiplus::UnitPixel);
}

typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);

static LPFN_ISWOW64PROCESS fnIsWow64Process = NULL;

//
//   FUNCTION: SafeIsWow64Process(HANDLE, PBOOL)
//
//   PURPOSE: This is a wrapper of the IsWow64Process API. It determines 
//   whether the specified process is running under WOW64. IsWow64Process 
//   does not exist prior to Windows XP with SP2 and Window Server 2003  with 
//   SP1. For compatibility with operating systems that do not support 
//   IsWow64Process, call GetProcAddress to detect whether IsWow64Process is 
///  implemented in Kernel32.dll. If GetProcAddress succeeds, it is safe to 
//   call IsWow64Process dynamically. Otherwise, WOW64 is not present.
//
//   PARAMETERS:
//   * hProcess - A handle to the process. 
//   * Wow64Process - A pointer to a value that is set to TRUE if the process 
//     is running under WOW64. If the process is running under 32-bit Windows, 
//     the value is set to FALSE. If the process is a 64-bit application 
//     running under 64-bit Windows, the value is also set to FALSE.
//
//   RETURN VALUE: If the function succeeds, the return value is TRUE.If 
//   IsWow64Process does not exist in kernel32.dll, or the function fails, 
//   the return value is FALSE. 
//
static BOOL WINAPI SafeIsWow64Process(HANDLE hProcess, PBOOL Wow64Process)
{
    if (fnIsWow64Process == NULL)
    {
        // IsWow64Process is not available on all supported versions of 
        // Windows. Use GetModuleHandle to get a handle to the DLL that 
        // contains the function, and GetProcAddress to get a pointer to the 
        // function if available.
        HMODULE hModule = GetModuleHandle(L"kernel32.dll");
        if (hModule == NULL)
        {
            return FALSE;
        }

        fnIsWow64Process = reinterpret_cast<LPFN_ISWOW64PROCESS>(
            GetProcAddress(hModule, "IsWow64Process"));
        if (fnIsWow64Process == NULL)
        {
            return FALSE;
        }
    }
    return fnIsWow64Process(hProcess, Wow64Process);
}

//
//   FUNCTION: Is64BitOS()
//
//   PURPOSE: The function determines whether the current operating system is 
//   a 64-bit operating system.
//
//   RETURN VALUE: The function returns TRUE if the operating system is 
//   64-bit; otherwise, it returns FALSE.
//
static BOOL Is64BitOS()
{
#if defined(_WIN64)
    return TRUE;   // 64-bit programs run only on Win64
#elif defined(_WIN32)
    // 32-bit programs run on both 32-bit and 64-bit Windows
    BOOL f64bitOS = FALSE;
    return (SafeIsWow64Process(GetCurrentProcess(), &f64bitOS) && f64bitOS);
#else
    return FALSE;  // 64-bit Windows does not support Win16
#endif
}

BOOL Utils::Is64BitWindows()
{
    return Is64BitOS();
}

MainFrm.h

// MainFrm.h : interface of the CMainFrame class
//
/

#pragma once

#include <math.h>
#include <algorithm>
#include <string>
#include <stdint.h>

static Gdiplus::Bitmap* GetBitmap(const wchar_t* path,int xdpi = 92,int ydpi = 92)
{
	Gdiplus::Bitmap* image = new Gdiplus::Bitmap(path);
	if(image->GetLastStatus()!=Gdiplus::Ok)
	{
		return NULL;
	}
	if (floor(image->GetHorizontalResolution()) != xdpi || 
		floor(image->GetVerticalResolution()) != ydpi)
	{
		image->SetResolution(xdpi,ydpi);
	}	
	return image;
}

class CMainFrame : 
	public CFrameWindowImpl<CMainFrame>, 
	public CUpdateUI<CMainFrame>,
	public CMessageFilter, public CIdleHandler
{
public:
	DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)

	CColumnTreeView* treeView_;
	//CImageList m_ImgList;

	virtual BOOL PreTranslateMessage(MSG* pMsg)
	{
		if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))
			return TRUE;

		return treeView_->PreTranslateMessage(pMsg);
	}

	virtual BOOL OnIdle()
	{
		return FALSE;
	}

	BEGIN_UPDATE_UI_MAP(CMainFrame)
	END_UPDATE_UI_MAP()

	BEGIN_MSG_MAP(CMainFrame)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
		COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
		COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
		CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
		CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
	END_MSG_MAP()

	// Handler prototypes (uncomment arguments if needed):
	//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
	//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)

	void AddBoolItemData(CTreeViewCtrl& tree,HTREEITEM item,BOOL check){
		auto data = (CColumnTreeViewCtrlData*)malloc(sizeof(CColumnTreeViewCtrlData));
		memset(data,0,sizeof(CColumnTreeViewCtrlData));
		data->checked = check;
		tree.SetItemData(item,(DWORD_PTR)data);
	}

	bool onHeaderCheckkAll(bool check)
	{
		return true;	
	}
	
	void onTreeCtrlCheckAll(bool check)
	{
		
	}

	void addOneTreeItem(const std::wstring& name,const std::wstring& url,HTREEITEM item_parent,
		bool include_children,int childrenCount)
	{
		auto& tree = treeView_->GetTreeCtrl();

		std::wstring value = name;
		std::replace(value.begin(),value.end(),L'\t',L' ');
		value.append(L"\t").append(url);

		HTREEITEM hItem = tree.InsertItem(value.c_str(), 0, 0, item_parent,TVI_LAST);
		addBoolItemData(tree,hItem,FALSE,NULL,include_children,childrenCount);

		if(!include_children)
			return;

		for(int i = 0; i< childrenCount; ++i){
			addOneTreeItem(name,url,hItem,false,0);
		}
	}

	void addBoolItemData(CTreeViewCtrl& tree,HTREEITEM item,BOOL check,
		void* userdata,bool isFolder,int childrenCount)
	{
		auto data = (CColumnTreeViewCtrlData*)malloc(sizeof(CColumnTreeViewCtrlData));
		memset(data,0,sizeof(CColumnTreeViewCtrlData));
		data->checked = check;
		data->userdata = userdata;
		data->is_folder = isFolder;
		data->totalChildren = childrenCount;
		tree.SetItemData(item,(DWORD_PTR)data);
	}

	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		auto folder_ = GetBitmap(L"res\\content-bookmarks-folder.png");
		auto file_ = GetBitmap(L"res\\content-bookmarks-file.png");

		auto image_checked_ = GetBitmap(L"res\\content-check-yes.png");
		auto image_uncheck_ = GetBitmap(L"res\\content-check-no.png");

		treeView_ = new CColumnTreeView();
		treeView_->SetButtonsImage(folder_,file_);
		treeView_->SetCheckImage(image_checked_,image_uncheck_);
		treeView_->Create(m_hWnd, CRect(CPoint(0,0),CSize(600,400)), NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,0);

		auto func = std::bind(&CMainFrame::onHeaderCheckkAll,this,std::placeholders::_1);
		treeView_->setFuncHeaderCheck(func);

		auto func2 = std::bind(&CMainFrame::onTreeCtrlCheckAll,this,std::placeholders::_1);
		treeView_->setFuncRootCheckListener(func2);
	
		CTreeViewCtrl& tree = treeView_->GetTreeCtrl();
		tree.SetBkColor(RGB(255,255,255));
		CHeaderCtrl& header = treeView_->GetHeaderCtrl();
		tree.SetItemHeight(32);
		treeView_->ReadyView();

		HDITEM hditem;
		hditem.mask = HDI_TEXT | HDI_WIDTH | HDI_FORMAT;
		hditem.fmt = HDF_CENTER | HDF_STRING;
		hditem.cxy = 300;
		hditem.pszText = (wchar_t*)L"name";
		header.InsertItem(0, &hditem);

		hditem.cxy = 300;
		hditem.pszText = (wchar_t*)L"url";
		header.InsertItem(1, &hditem);
		treeView_->UpdateColumns();

		addMockData();

		// register object for message filtering and idle updates
		CMessageLoop* pLoop = _Module.GetMessageLoop();
		ATLASSERT(pLoop != NULL);
		pLoop->AddMessageFilter(this);
		pLoop->AddIdleHandler(this);

		return 0;
	}

	void addMockData()
	{
		for(auto i  = 0;  i< 10; ++i){
			std::wstring name(L"Tobey-");
			name.append(std::to_wstring((int64_t)i));
			addOneTreeItem(name,L"https://blog.csdn.net/infoworld",NULL,true,4);
		}
	}

	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		// unregister message filtering and idle updates
		CMessageLoop* pLoop = _Module.GetMessageLoop();
		ATLASSERT(pLoop != NULL);
		pLoop->RemoveMessageFilter(this);
		pLoop->RemoveIdleHandler(this);

		bHandled = FALSE;
		return 1;
	}

	LRESULT OnFileExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
	{
		PostMessage(WM_CLOSE);
		return 0;
	}

	LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
	{
		// TODO: add code to initialize document

		return 0;
	}

	LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
	{
		CAboutDlg dlg;
		dlg.DoModal();
		return 0;
	}
};

资源图片

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

输出

图2:
在这里插入图片描述

下载

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

参考

  1. Header Control的表头项属性变化通知 hdn-itemchanged

  2. 自定义TreeView控件的绘制通知 nm-customdraw-tree-view

  3. 自定义ListView

  4. Multi-Column Tree View

  5. 使用WTL进行Windows桌面应用开发-1

  6. 使用WTL进行Windows桌面应用开发-2

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Peter(阿斯拉达)

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

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

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

打赏作者

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

抵扣说明:

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

余额充值