带列的树控件






4.86/5 (58投票s)
带列的树控件,

引言
CColumnTreeCtrl
控件可用于 MFC 项目,当需要树形和列表混合控件时(参见上图)。
此代码基于 Michal Mecinski 在 Multi-Column Tree View 文章中描述的控件,该文章可在 CodeGuru.com 上找到。此控件具有一些 改进 和 bug 修复,但基本思想相同 - 使用标准的 CTreeCtrl
作为多列树的主体,以及 CHeaderCtrl
作为其表头。
我添加了所有者绘制代码(感谢 VividTree - A Colorful and Picturesque Owner Drawn CTreeCtrl 文章,来自 CodeProject.com),因此您可以控制 CColumnTreeCtrl
外观的每个方面(树按钮、线条、背景等)。如果您想使用标准绘制代码 - 只需 禁用所有者绘制。
您可以执行标准 CTreeCtrl
可以执行的所有操作,以及一些与 CListCtrl
相关的操作。有关更多信息,请参见 使用代码。
改进
Michal Mecinski 文章中描述的控件已经具备了您需要的一切,但存在一些 bug 和一些(在我看来)缺失的方法,所以我尝试对 Michal 的控件进行了一些改进。
以下是改进列表
- 原始控件继承自
CView
。此控件继承自CStatic
,允许在基于对话框的应用程序中使用。 - 此控件具有改进的滚动条。如果您注意到,原始控件的滚动条显示不正确。
- 此控件支持列格式设置,而原始控件不支持。
- 此控件允许定义第一列的最小尺寸(如果您使用所有者绘制,可以忽略此设置,但如果不是,则必须定义第一列的最小宽度以避免外观问题);
- 此控件提供了方便的添加列和设置列文本的方法,而原始控件没有。我尽量不通过添加太多方法来增加控件的负担,只添加了几种真正需要的方法。
- 原始控件使用了不正确的树控件选中项颜色,而我们的控件使用默认系统颜色。
- 此控件支持所有者绘制,允许您控制其外观的每个方面。
控件内部
CColumnTreeCtrl
控件实现为一个容器,包含以下子控件(参见下图)
- 主表头 (
CHeaderCtrl
)。它包含您添加到控件的列。 - 附加表头 (
CHeaderCtrl
)。它仅用于填充子树控件(3)显示垂直滚动条时的空白空间。 - 自定义子树控件 (
CTreeCtrl
)。此子控件包含您添加的项,并提供垂直滚动条。 - 水平滚动条 (
CScrollBar
)。用于在水平方向滚动控件。
Michal Mecinsky 提出的想法非常有趣 - 使用标准的 CTreeCtrl
来存储项和子项,并使用自定义绘制来正确显示它们。例如,上图中第一个项被存储为一个单独的 string
"3.5\" Floppy (A:)\tRemovable\tFAT12\t1.44Mb"
制表符 '\t
' 用作子项分隔符。
当 CTreeCtrl
处理 WM_PAINT
消息时,它不会自行绘制项,而是向父容器发送自定义绘制通知,因此有可能按我们意愿绘制子项。
Using the Code
链接到您的 MFC 项目
您可以在 Visual C++ 2003 及更高版本中使用此控件。我曾尝试在 Visual C++ 6.0 下编译它,但似乎它的 SDK 太旧,不支持通用控件库版本 6(随 IE6 分发)。
请按照以下简单步骤将 CColumnTreeCtrl
添加到您的 MFC 对话框中
- 将 ColumnTreeCtrl.h 和 ColumnTreeCtrl.cpp 文件复制到您的项目文件夹。如果您将这些文件放在其他地方,可能会遇到资源链接问题,所以请不要这样做。
- 将 TREEBTNS.bmp 文件复制到您的 res 文件夹。此位图将用于在所有者绘制模式下绘制树控件的按钮。
- 将 TREEBTNS.bmp 添加到项目资源中,并将其命名为 IDB_TREEBTNS。
- 向您的对话框添加一个静态控件。
- 将其 ID 更改为,例如
IDC_COLUMNTREE
。 - 右键单击静态控件,然后在上下文菜单中选择“添加变量...”
- 在“变量名”字段中,输入“
m_columnTree
” - 在您的对话框的头文件中,添加“
#include "ColumnTreeCtrl.h"
”行; - 在您的对话框的头文件中,将“
CStatic m_columnTree;
”替换为“CColumnTreeCtrl m_columnTree;
”;
所有者绘制支持
所有者绘制是一种技术,其中所有绘制都由用户的绘制过程执行。尽管绘制整个控件相当困难,但这为您提供了按需更改其外观的机会。当禁用所有者绘制时,将使用标准的 CTreeCtrl
绘制代码加上额外的自定义绘制代码。
在不使用所有者绘制的情况下,您可以做什么?答案是任何事情。但是,所有者绘制有以下好处:
- 它允许使用背景图像。
- 它允许不为第一列设置最小宽度。相比之下,如果不使用它,您应该通过调用
CColumnTreeCtrl::SetFirstColumnMinWidth()
来设置第一列的最小宽度,否则控件外观会出现问题。 - 理论上,您可以更改控件外观的每个方面。当然,这意味着您需要为此目的理解并修改控件的代码。
默认情况下,所有者绘制是禁用的。要启用它,请在 CColumnTreeCtrl.h 中取消注释此行
//#define _OWNER_DRAWN_TREE // comment this line if you want to use
//standard drawing code
添加和删除列
您应该使用这些方法来添加/删除列
// Inserts new column
int
CColumnTreeCtrl::InsertColumn(
int nCol, // zero-based column index
LPCTSTR lpszColumnHeading, // column heading
int nFormat=0, // column formatting (as in CListViewCtrl)
int nWidth=-1, // column width
int nSubItem=-1 // not used
);
// Removes existing column
BOOL
CColumnTreeCtrl::DeleteColumn(
int nCol // zero-based column index
);
示例
// insert two columns
m_columnTree.InsertColumn(0, _T("Name"), LVCFMT_LEFT, 180);
m_columnTree.InsertColumn(1, _T("Type"), LVCFMT_LEFT, 80);
// remove last column
m_columnTree.DeleteColumn(1);
添加/删除项
示例
HTREEITEM hParent; // parent item
// add an item
HTREEITEM hItem = m_columnTree.GetTreeCtrl().InsertItem
( _T("3.5\" Floppy (A:)"), hParent );
// remove
m_columnTree.GetTreeCtrl().DeleteItem(hItem);
检索和设置项文本
您应该使用 CColumnTreeCtrl::GetItemText()
和 CColumnTreeCtrl::SetItemText()
方法来获取/设置项或子项文本。
// gets item text
CString
CColumnTreeCtrl::GetItemText(
HTREEITEM hItem, // item handle
int nSubItem // zero-based subitem index
);
// sets item text
void
CColumnTreeCtrl::SetItemText(
HTREEITEM hItem, // item handle
int nSubItem, // zero-based subitem index
LPCTSTR lpszText // pointer to text buffer
);
示例
// set text for the first and the second subitems
m_columnTree.SetItemText(hRoot, 1, _T("Removable"));
m_columnTree.SetItemText(hRoot, 2, _T("FAT12"));
设置第一列的最小宽度
// sets the minimum available width for the first column
void
CColumnTreeCtrl::SetFirstColumnMinWidth(
UINT uMinWidth // min. width
);
确定鼠标光标下的哪个项
您可以使用 CColumnTreeCtrl::HitTest()
方法来确定鼠标光标下的哪个项。
// CTVHITTESTINFO structure contains information used to determine the
// location of a point relative to a CColumnTreeCtrl control.
typedef struct _CTVHITTESTINFO {
POINT pt; // Client coordinates of the point to test.
UINT flags; // Standard TVHT -prefixed flags.
HTREEITEM hItem; // Handle to the item that occupies the point.
int iSubItem; // Zero-based subitem index.
} CTVHITTESTINFO;
// Call this function to determine the location of the specified point
// relative to the client area of a CColumnTreeCtrl control.
HTREEITEM
CColumnTreeCtrl::HitTest(
CPoint pt,
UINT* pFlags
) const;
HTREEITEM
CColumnTreeCtrl::HitTest(
CTVHITTESTINFO* pHitTestInfo
) const;
示例
// NM_RCLICK notification handler
void CMainDlg::OnRclickedColumntree(LPNMHDR pNMHDR, LRESULT* pResult)
{
CPoint pt;
::GetCursorPos(&pt);
m_columnTree.ScreenToClient(&pt);
CTVHITTESTINFO htinfo;
htinfo.pt = pt;
HTREEITEM hItem = m_columnTree.HitTest(&htinfo);
if(hItem)
{
CString szState;
if(htinfo.flags&TVHT_ONITEMBUTTON)
szState += _T("Clicked on item's button.");
// ... check other flags
CString szItemText = m_columnTree.GetItemText(hItem, htinfo.iSubItem);
MessageBox(szState + _T(" Item text: ") + szItemText);
}
}
访问子 CTreeCtrl 和 CHeaderCtrl
您可以像平常一样访问子树控件和表头控件并调用它们的方法
// returns reference to the child tree ctrl
CCustomTreeChildCtrl&
CColumnTreeCtrl::GetTreeCtrl();
// returns reference to the child header ctrl
CHeaderCtrl&
CColumnTreeCtrl::GetHeaderCtrl();
示例
// modify style of child tree control to enable lines
m_columnTree.GetTreeCtrl().ModifyStyle(TVS_HASLINES, 0);
设置背景位图
如果启用了所有者绘制,您可以指定要使用的背景位图。标准的 LVBKIMAGE
结构用于存储背景位图参数。
// gets background image
BOOL
CCustomTreeChildCtrl::GetBkImage(
LVBKIMAGE* plvbkImage // structure describing image and its position
) const;
// sets background image
BOOL
CCustomTreeChildCtrl::SetBkImage(
LVBKIMAGE* plvbkImage // structure describing image and its position
);
示例
LVBKIMAGE bk;
bk.xOffsetPercent = bk.yOffsetPercent = 70;
bk.hbm = LoadBitmap(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_BKGND));
m_columnTree.GetTreeCtrl().SetBkImage(&bk);
处理子树和表头通知
由于所有通知都会转发到父窗口,您可以轻松地在对话框的消息映射中处理来自子树和表头控件的通知。
示例
//.. fragment of the message map
ON_NOTIFY(NM_CLICK , IDC_COLUMNTREE, OnTreeClk)
//.. notify handler
void CMainDlg::OnTreeClk(NMHDR *pNMHDR, LRESULT *pResult)
{
NMTREEVIEW& nm = *(LPNMTREEVIEW)pNMHDR;
// which item was selected?
HTREEITEM hItem = m_columnTree.GetTreeCtrl().GetSelectedItem();
if(!hItem) return;
// the rest of processing code..
}
历史
- 2008 年 2 月 18 日 - v.1.0 提交
- 2008 年 3 月 24 日 - v.1.1 提交
- 添加了
CColumnTreeCtrl::HitTest()
方法
- 添加了
已知问题
到目前为止已报告以下问题
- 目前工具提示存在问题,因此为子树控件启用了
TVS_NOTOOLTIPS
样式。请勿禁用此样式,否则会显示无效提示。 - 在演示中,当您勾选/取消勾选某些选项导致样式修改时,控件外观可能会变得不正确。例如,垂直线可能会消失等。这在演示中是问题,但在您的项目中通常不会出现问题,因为您通常不会动态修改控件样式。
- 在 Windows Vista 下,垂直线绘制不均匀。这不是一个非常严重的问题。
- 调整控件大小时,水平滚动条有时会放置不正确。
报告 bug
我希望这个控件能有用,但如果您发现 bug,请告诉我。您可以在下方的讨论串中发帖,或者给我发电子邮件:olegkrivtsov@mail.ru 请不要忘记说明我如何重现控件的不正确行为,描述您在不正确行为发生时所做的操作,并指明您的 Windows 版本和当前的桌面主题。