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

Posted infoworld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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[ATL/WTL]_[初级]_[解决自定义按钮禁用时没有绘制自定义样式-显示黑色矩形框的问题]

[ATL/WTL]_[初级]_[解决自定义按钮禁用时没有绘制自定义样式-显示黑色矩形框的问题]

[ATL/WTL]_[初级]_[解决自定义按钮禁用时没有绘制自定义样式-显示黑色矩形框的问题]

[ATL/WTL]_[0基础]_[CBitmap复制图片-截取图片-平铺图片]

[ATL/WTL]_[中级]_[保存CBitmap到文件-保存屏幕内容到文件]

STL,ATL,WTL之间的联系和区别