场景

  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
#endifvoid 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 childrenm_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 headerHFONT pFont = GetFont();m_Header.SetFont(pFont);m_Tree.SetFont(pFont);// check if the common controls library version 6.0 is availableBOOL 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 heightauto 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 startm_xOffset = bIsComCtl6 ? DpiScale(9) : DpiScale(6);m_xPos = 0;UpdateColumns();ReleaseDC(hdc);
}void CColumnTreeView::OnPaint(CDCHandle dc1)
{// do nothingCPaintDC 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 rectangleCRect 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 onesdc.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);elsestrSub = szBuf;// calculate main label's sizeCRect 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 necessaryif (pNMCustomDraw->uItemState & CDIS_FOCUS){dc.DrawFocusRect(&rcBack);}// draw main labelgpDeferImageDataCache.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); // crTextdc.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 textwchar_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 controlfor (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 controlsif (m_Tree.m_hWnd){CRect rcClient;GetClientRect(&rcClient);int cx = rcClient.Width();int cy = rcClient.Height();// move to a negative offset if scrolled horizontallyint 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 widthCSize 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 columnif (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 recursivelyHTREEITEM 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 labelHandleMouse(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 flickeringdcMem.CreateCompatibleDC(dc);if (bmpMem.CreateCompatibleBitmap(dc, rcClient.Width(), rcClient.Height())){auto pOldBmp = dcMem.SelectBitmap(bmpMem);// paint the window onto the memory bitmapDefWindowProc(WM_PAINT, (WPARAM)dcMem.m_hDC, 0);// copy it to the window's DCdc.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 resultif (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 columnsrcItem.right = m_cxTotal;if (!rcItem.PtInRect(point))return;// select or expand itemif (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 notificationNMHDR 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 columnrcItem.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);elsestrSub = 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 rectangleif (!rcItem.PtInRect(point)){if (message == WM_LBUTTONDOWN)SetFocus();return;}}}else{// check if the button or icon is hiddenif (point.x >= m_cxFirstCol){if (message == WM_LBUTTONDOWN)SetFocus();// ignore if outside all columnsif (point.x > m_cxTotal)return;// select or expand itemif (message == WM_LBUTTONDOWN){Select(hItem, TVGN_CARET);}else if (message == WM_LBUTTONDBLCLK){// send the NM_DBLCLK notificationNMHDR 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 procedureDefWindowProc(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 WindowsBOOL f64bitOS = FALSE;return (SafeIsWow64Process(GetCurrentProcess(), &f64bitOS) && f64bitOS);
#elsereturn 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 updatesCMessageLoop* 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 updatesCMessageLoop* 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 documentreturn 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

[ATL/WTL]_[初级]_[自定义多列TreeView]相关推荐

  1. [WTL/ATL]_[初级]_[微调控件CUpDownCtrl的使用]

    场景 开发WTL/ATL应用时,有时候需要对某些值进行微调,比如0-100的百分比的微调,或者字号的微调,通过键盘操作微调控件,能实时查看某些界面呈现的效果.而WTL提供了CUpDownCtrl控件, ...

  2. [WTL/ATL]_[初级]_[TreeView控件如何显示ToolTip]

    场景 在开发界面程序时,CTreeViewCtrl(它实际内部封装的就是Win32的TreeView控件)一般会用来作为选择某些类型的树形菜单,点击某项的时候,右边能显示某些对应的数据.当这个控件的宽 ...

  3. [ATL/WTL]_[初级]_[窗口如何实现一个Tooltip]

    场景: 1.  WTL的控件默认不支持进入和移出的 WM_MOUSEHOVER ,WM_MOUSELEAVE 事件, 即使写了映射也没用, 必须要使用函数 _TrackMouseEvent  添加监听 ...

  4. [WTL/ATL]_[初级]_[设置超链接字体颜色]

    场景 1.在开发WTL(Win32)程序时,超链接控件是很常见的,比如需要跳转网页或者打开本地文件夹等,都需要用到.Win32本身就有提供系统自带的WC_LINK控件,而我们用WTL有相应的封装类CL ...

  5. [JavaScript]_[初级]_[使用HTMLElement.dataset快速读写自定义属性]

    场景 在开发前端页面时,一般会在某些元素上增加自定义的属性来存储额外的数据.比如图片元素<img>上增加big和small属性来存储大图片和小图片,当点击某个按钮时可以通过JavaScri ...

  6. [Java]_[初级]_[使用正则高效替换字符串的多个占位符为多个值]

    场景 在开发基于模板内容的Java程序时, 比如一个邮件内容模板,在内容里放置一些占位符$email,$name等来作为替换实际内容的符号.那么这时候如何做才可以少生成不必要的String字符串,从而 ...

  7. [JavaScript]_[初级]_[关于forof或者for...of循环语句的用法]

    场景 在开发网页时,经常需要枚举NodeList类型的数据,比如通过document.body.childNodes获取的, 通过DOM.querySelectorAll也能获取到NodeList对象 ...

  8. [Windows]_[初级]_[如何不编程判断进程是管理员模式启动(UAC)]

    场景 我们在开发Windows程序时, 安装程序后一般会以admin方式启动程序, 这时候程序的权限一般是admin权限.可如果重新打开程序时, 程序就是asInvoker的权限,即所属用户的Norm ...

  9. [Cocoa]_[初级]_[关于nib文件加载过程awakeFromNib]

    场景 在用xcode开发Cocoa程序时, 我们一般会使用IB来设计界面xib. 而在xib文件里我们往往会对用到的自定义NSView进行绑定以便引用使用. 但是这些xib里的object是什么时候进 ...

最新文章

  1. android sql 顺序执行问题,怎么才能执行第一个完毕,才能继续执行第二个
  2. NodeJS是用来做什么的
  3. 向git库提交代码出现”There are no staged files怎么办?
  4. QT学习笔记(2)----- QT安装教程
  5. 如何实现Punycode中文域名转码
  6. php扇形分布图,使用php绘制扇形分布图
  7. torch.cat() 函数用法
  8. java ajax返回数组_js对ajax返回数组的处理介绍
  9. CCF NOI1117 排序
  10. Theine for Mac(电脑休眠工具)
  11. 阿里研究院安筱鹏:数字化,转型与原生并行
  12. 设计模式之十三:适配器模式(Adapter)
  13. C语言求素数中flag的作用,c语言编程判断素数 c语言程序设计(判断素数)
  14. SPSS问卷信度分析
  15. 计算机视觉前沿技术探索
  16. JavaScript之Ajax Util
  17. 电脑可以上网,但是仍然显示无网络连接
  18. Excel图表坐标轴出现系列1、系列2的解决方案
  19. 安卓手机运行python程序
  20. 如何基于深度学习实现商品识别技术|图普科技

热门文章

  1. 《计算机是怎样跑起来的》矢泽久雄 读书笔记(关于数据库,数据加密,XML和CSV)
  2. 洛谷 P1772 [ZJOI2006]物流运输
  3. Android ART虚拟机执行引擎-Interpreter(八)
  4. 1张上海各阶层本科生真实工资表流出,戳穿了对年轻人最残忍的骗局
  5. 【C#/WPF】TextBlock/TextBox/Label编辑文字的问题
  6. C语言——线性表 (数据结构)
  7. 回到顶部丝滑效果/指定位置下拉丝滑
  8. 算法设计与分析实验课(2)
  9. 【词目】:勿谓言之不预也
  10. python 网页微信_python---web微信开发