有时候我们需要将数据分为很多个分支来展示,那么使用列表视图很显然是不合适的,所以我们就可以使用树形结构的视图。今天用树视图来实现一个膳食宝塔,当点击某个分支时将其详细数据显示到窗口上。
目录
1.消息映射
我们不自绘只需要处理两个消息WM_INITDIALOG和WM_NOTIFY。我们在WM_INITDIALOG消息中对树视图初始化,在WM_NOTIFY消息中处理展开分支的消息。如果需要自绘,那么需要映射NM_CUSTOMDRAW消息。
public:
BEGIN_MSG_MAP(CTreeDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
END_MSG_MAP()
public:
LRESULT OnInitDialog(UINT msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnNotify(UINT msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
2.初始化图像列表
可以通过图像列表为每个分支的项绑定两个图像,用于项被选中和未被选中时显示。如果不需要则无需初始化。
2.1创建图像列表
通过ImageList_Create()函数创建图像列表。
HIMAGELIST himl;
//创建图标列表
himl = ImageList_Create(40, 40, ILC_COLORDDB, 6, 0);
if (himl == NULL)
return FALSE;
注意:如果绘制的图标颜色不对(透明图标绘制出来是黑色的背景),那么可能是图标位深不够,可以将ILC_COLORDDB标志改为ILC_COLOR32。
2.2添加图像
先加载我们需要使用到的图标,使用LoadIcon()方法或者LoadIamge()方法都可以。
icon = LoadIcon(CNWSModule::Get().GetModuleInstance(), MAKEINTRESOURCE(IDI_TREEROOT));
//icon = (HICON)LoadImage(NULL, L"..\\..\\..\\DeliDoc\\图标\\树根目录图标.ico", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE | LR_VGACOLOR);
ImageList_AddIcon(himl, icon);
DestroyIcon(icon);
然后将图标添加到图像列表。本次添加的是图标,那么就使用ImageList_ReplaceIcon()函数,也可以使用ImageList_AddIcon宏。如果需要添加图像,可以使用ImageList_Add()函数来添加图像。
因为图标是加载的并不是创建的,所以不要求必须手动销毁它。当不再需要图标资源时,系统会自动释放该资源。手动销毁提前释放内存也没有问题。
ImageList_AddIcon(himl, icon);
DestroyIcon(icon);
2.3绑定图像列表
向树视图发送TVM_SETIMAGELIST消息来绑定图像列表。如果图像列表句柄为空,那么则是从树视图删除绑定的图像列表。
//将图标列表绑定到树视图
SendMessage(hwndTV, TVM_SETIMAGELIST, TVSIL_NORMAL, (LPARAM)himl);
3.初始化树视图
3.1设置项数据
树视图项是一个TVINSERTSTRUCT结构体。其中hParent成员指定该分支的父分支,item成员指定该分支的属性,分支的属性是一个TVITEM结构体。
//树视图项的信息
TVITEM tvi = { 0 };
HTREEITEM hPrev = TVI_FIRST;
tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
tvi.pszText = (LPWSTR)lpszItem;
tvi.cchTextMax = lstrlen(lpszItem) + 1;
tvi.iImage = iconIndex;
tvi.iSelectedImage = iconIndex;
//树视图项
TVINSERTSTRUCT treeItem = { 0 };
treeItem.hParent = itemParent;
treeItem.hInsertAfter = TVI_LAST;
treeItem.item = tvi;
3.2添加项
向树视图发送TVM_INSERTITEM消息来插入指定的项。如果插入成功该消息会返回该分支的句柄。我们可以通过返回的分支句柄来添加该分支的子分支。
HTREEITEM hPrev = (HTREEITEM)SendMessage(hwndTV, TVM_INSERTITEM, 0, (LPARAM)&treeItem);
为了方便我们初始化树视图,我们一般会将其封装为一个函数:
HTREEITEM AddItemToTree(HWND hwndTV, LPCWSTR lpszItem, HTREEITEM itemParent, int iconIndex)
{
if (hwndTV == NULL)
return NULL;
//树视图项的信息
TVITEM tvi = { 0 };
HTREEITEM hPrev = TVI_FIRST;
tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
tvi.pszText = (LPWSTR)lpszItem;
tvi.cchTextMax = lstrlen(lpszItem) + 1;
//图像列表如果已经初始化,可以指定项被选中和未被选中的图像索引。
tvi.iImage = iconIndex; //未选中
tvi.iSelectedImage = iconIndex; //选中
//树视图项
TVINSERTSTRUCT treeItem = { 0 };
treeItem.hParent = itemParent;
treeItem.hInsertAfter = TVI_LAST;
treeItem.item = tvi;
//插入树视图项
hPrev = (HTREEITEM)SendMessage(hwndTV, TVM_INSERTITEM, 0, (LPARAM)&treeItem);
return hPrev;
}
3.3初始化
1)添加树视图的根部,其他分支都是在根部的基础上进行延展。
HTREEITEM hPrev = NULL, firstTree = NULL, secondTree = NULL;
hPrev = AddItemToTree(treeView, L"膳食宝塔", TVI_ROOT, i);
2)添加分支
firstTree = AddItemToTree(treeView, L"第三层", hPrev, i);
secondTree = AddItemToTree(treeView, L"动物性肉", firstTree, i);
AddItemToTree(treeView, L"禽类", secondTree, i);
AddItemToTree(treeView, L"畜类", secondTree, i);
AddItemToTree(treeView, L"水产类", secondTree, i);
3)图像列表有无的区别
带图像列表效果图:
不带:
4.响应选中分支
当我们选中分支会收到TVN_SELCHANGED通知,该通知会以WM_NOTIFY消息发送到父窗口。
LRESULT CTreeDlg::OnNotify(UINT msg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
LPNMTREEVIEW lpHdr = (LPNMTREEVIEW)lParam;
if (lpHdr->hdr.code == TVN_SELCHANGED)
{
WCHAR deptName[50] = { 0 };
//获取当前选中分支名
TVITEM vTtem = { 0 };
vTtem.mask = TVIF_TEXT;
vTtem.hItem = lpHdr->itemNew.hItem;
vTtem.cchTextMax = 50;
vTtem.pszText = deptName;
SendDlgItemMessage(IDC_TESTTREE, TVM_GETITEM, 0, (LPARAM)&vTtem);
HWND groupHwnd = GetDlgItem(IDC_DETAILGROUP);
HWND treeHwnd = GetDlgItem(IDC_TESTTREE);
auto i = m_towerMap.find(deptName);
if (i != m_towerMap.end())
{
::ShowWindow(groupHwnd, SW_SHOW);
DrawItemText(groupHwnd, treeHwnd, deptName);
}
return TRUE;
}
return FALSE;
}
效果图: