[ATL/WTL]_[初级]_[自定义多列TreeView]
Posted infoworld
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[ATL/WTL]_[初级]_[自定义多列TreeView]相关的知识,希望对你有一定的参考价值。
场景
- 在开发
Win32
,WTL
,MFC
程序时,经常会用到ListView
这个表格控件,ListView
的数据是按照行来显示的,行与行之间没有并没有什么关系。但是如果行之间有父子关系,比如像树形控件TreeView
可以收起展开呢,如何实现?
图1
说明
-
这种具有列名的
TreeView
可以称为多列TreeView(ColumnTreeView)
, 也可以称为TreeListView
. 这种控件它的特点就是TreeView
有一个表头,拖动表头的分割线可以同步拖动TreeView
的对应的列数据。 -
在
WTL
开发的时候,当对窗口消息和通知不熟练的时候,我们实现自定义控件可以借助MFC
的自定义控件例子,改为WTL
的实现方式也很容易,因为MFC
的大多数控件本质上还是对Win32
控件的封装,操作的还是它的窗口句柄和消息。 -
在我的 使用WTL进行Windows桌面应用开发-第一部 里已经讲过自定义的
ListView
控件,知道它可以自定义一个表头, 也就是扩展类CHeaderCtrl
的子类,增加一个复选框。 要实现多列TreeView
,原理就是增加一个容器窗口来管理一个CHeaderCtrl
和CTreeCtrl
, 在拖动CHeaderCtrl
时CTreeCtrl
能响应表头的拖动操作,根据表头每列的横坐标变化来绘制CTreeCtrl
的文本。 -
我参考[4]了
Multi-Column Tree View
的MFC
实现,改为依赖WTL
库. 有两个关键的通知:- hdn-itemchanged:
CHeaderCtrl
的父窗口通知,当表头项属性改变时(如宽度变化)响应处理函数。 - NM_CUSTOMDRAW:
CTreeCtrl
在父窗口里实现TreeView
的自定义HTREEITEM
项绘制,这样可以根据表头进行列数据的坐标调整。
- hdn-itemchanged:
例子
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复制图片-截取图片-平铺图片]