0%

MFC对话框:创建级联树形控件

  1. 前言
  2. 准备工作
  3. 初始化树形控件
    1. 导入图标
    2. 初始化树形控件
    3. 带图标初始化树形控件
  4. 选中节点时的事件
  5. 移动到节点上方时的事件
  6. 选中复选框的事件

前言

创建了一个基于对话框的MFC应用程序,中间放置了一个树形控件,可以实现复选框,并实现级联操作,即当子节点选满时,父节点自动选中,当选中父节点时,子节点自动全选

image-20210801020403176

准备工作

  • IDE: VS2019
  • 编译器:MSVC100

(VC100是项目需要,VS2019是更好的代码和用户界面)

在VS2010中创建MFC应用程序,选择应用程序类型为“基于对话框”,其他选项均为默认,点击“完成”。

image-20210801020734045

创建完成后,使用VS2019打开工程,选择保留v100工具集(不升级),然后找到“资源视图”,双击Dialog-IDD_工程名_DIALOG,进入对话框布局设置,找到“工具箱”,各拖一个Tree ControlStatic TextEdit Control到界面中,右键每一个控件分别设置属性:

  1. Tree Control: ID设为IDC_TREE1,复选框设置为True,具有按钮设置为True,具有行设置为True,信息提示设置为True,行在跟处设置为True,其余为默认选项;
  2. Static Text: 设置描述文字为“节点名称:”;
  3. Edit Control:ID设为IDC_ITEM_SEL_TEXT,只读设置为True

添加完控件按下F5调试按钮,不出意外会弹出一个基本对话框。

初始化树形控件

右键对话框中的树形控件,添加变量,名称为m_Tree

1
CTreeCtrl m_Tree;

导入图标

不需要显示图标可以跳过这一步

在树形控件变量所在的.h文件中添加图像列表,用于存放树形控件单项前面的图标

1
CImageList m_ImageList;

将提前准备好的.ico文件拷贝到工程目录下的res文件夹中,打开资源视图,右键Icon,添加资源,导入,选择res目录下的.ico文件,导入,可以为其设置ID。

在对话框类的OnInitDialog()函数中使用以下代码导入图标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
BOOL CTestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();

// 将“关于...”菜单项添加到系统菜单中。

// 省略...
// TODO: 在此添加额外的初始化代码
HICON hIcon[3]; // 图标句柄数组

// 加载图标
hIcon[0] = theApp.LoadIconW(IDI_ICON1);
hIcon[1] = theApp.LoadIconW(IDI_ICON2);
hIcon[2] = theApp.LoadIconW(IDI_ICON3);

// 创建图像序列,添加图标到其中
m_ImageList.Create(16, 16, ILC_COLOR32, 3, 3);
for (int i = 0; i < 3; i++)
{
m_ImageList.Add(hIcon[i]);
}

// 省略...
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}

其中,m_ImageList.Create前两个参数为图标指定了宽高,这决定了其在树形控件中显示的大小。

初始化树形控件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
BOOL CTestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();

// 将“关于...”菜单项添加到系统菜单中。

// 省略...
// TODO: 在此添加额外的初始化代码

HTREEITEM hRoot; // 根节点句柄
HTREEITEM hFirst; // 第一节点句柄
HTREEITEM hSecond; // 第二节点句柄

// 插入根节点
hRoot = m_Tree.InsertItem(_T("根节点"));

// 在根节点下插入子节点
int count = 1;
CString strText;
for (int i = 1; i <= 5; i++)
{
strText.Format(_T("子节点%d"), i);
hFirst = m_Tree.InsertItem(strText, hRoot, TVI_LAST);
m_Tree.SetItemData(hFirst, count++);
for (int j = 1; j <= 5; j++)
{
strText.Format(_T("子子节点 %d"), j);
hSecond = m_Tree.InsertItem(strText, hFirst, TVI_LAST);
m_Tree.SetItemData(hSecond, count++);
}
m_Tree.Expand(hFirst, TVE_EXPAND);
}
m_Tree.Expand(hRoot, TVE_EXPAND);

return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}

带图标初始化树形控件

为树形控件设置图像序列:

1
m_Tree.SetImageList(&m_ImageList, TVSIL_NORMAL);

添加完图像序列后,若图像序列中有图像,那么默认情况下,每一个树形控件的节点都将使用第一张图像作为图标,也可以指定,只需要将插入节点的函数写为:

1
m_Tree.InsertItem(strText, 0, 0, hRoot, TVI_LAST);

其中区别在于增加了第二、第三个参数,数字含义是在图像列表中的序号。第二个参数指定了节点的图标,第三个参数指定了节点选中时的图标。

选中节点时的事件

主要目的是选中时将节点的文字显示到下方的文本框中

右键树形控件,为消息TVN_SELCHANGED添加响应函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void CTestDlg::OnTvnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
*pResult = 0;

CString strText; // 树节点的标签文本字符串

// 获取当前选中节点的句柄
HTREEITEM hItem = m_Tree.GetSelectedItem();
// 获取选中节点的标签文本字符串
strText = m_Tree.GetItemText(hItem);
// 将字符串显示到编辑框中
SetDlgItemText(IDC_ITEM_SEL_TEXT, strText);
}

移动到节点上方时的事件

展示了如何在鼠标移动到节点上方时,显示有关内容;

以及如何为节点附加32位数据;

右键树形控件,为消息TVN_GETINFOTIP添加响应函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void CTestDlg::OnTvnGetInfoTipTree1(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMTVGETINFOTIP pGetInfoTip = reinterpret_cast<LPNMTVGETINFOTIP>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
*pResult = 0;

// 将传入的pNMHDR转为NMTVGETINFOTIP指针类型
NMTVGETINFOTIP* pTVTipInfo = (NMTVGETINFOTIP*)pNMHDR;

// 根节点
HTREEITEM hRoot = m_Tree.GetRootItem();

// 提示信息字符串
CString strText;

if (pTVTipInfo->hItem == hRoot)
{
// 根节点: 不显示提示信息
strText = _T("");
}
else
{
// 非根节点: 将节点附加的32位数据格式化为字符串
strText.Format(_T("%d"), pTVTipInfo->lParam);
}

// 将strText字符串拷贝到pTVTipInfo结构体变量的pszText成员中, 完成显示
wcscpy_s(pTVTipInfo->pszText, strText.GetLength() + 1, strText);
}

选中复选框的事件

使用事件递归的方式来处理父节点和子节点的选中问题

右键树形控件,为消息NM_TVSTATEIMAGECHANGING添加响应函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void CTestDlg::OnNMTVStateImageChangingTree1(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: 在此添加控件通知处理程序代码
*pResult = 0;

// 获取点击点在树形控件中的位置
CPoint point;
UINT uFlag;
GetCursorPos(&point);
this->m_Tree.ScreenToClient(&point);

// 获取树形控件的子项
HTREEITEM hItem = m_Tree.HitTest(point, &uFlag);

if (hItem == NULL) return;

BOOL bCheck = m_Tree.GetCheck(hItem);
CString itemText = m_Tree.GetItemText(hItem);

// 输出列表项的信息
CString str;
str.Format(_T("点击的点: x=%d, y=%d, 项目文本为: %s, 点击时选中状态为: %d\n"), point.x, point.y, itemText, bCheck);
OutputDebugString(str);

bCheck = !bCheck;

// 设置自身状态
m_Tree.SelectItem(hItem);
m_Tree.SetCheck(hItem, bCheck);

// 分别设置父节点和子节点的选中状态
this->SetParentCheck(hItem, bCheck); // 设置父节点状态
this->SetChildenCheck(hItem, bCheck); // 设置子节点状态
}

处理父节点的选中状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void CTestDlg::SetParentCheck(HTREEITEM hItem, BOOL bCheck)
{
if (hItem == NULL) return;

HTREEITEM hParent = m_Tree.GetParentItem(hItem);
if (hParent == NULL) return;

// 如果当前节点未选中, 则其父节点也未选中
if (!bCheck)
{
m_Tree.SetCheck(hParent, bCheck);
}
else
{
HTREEITEM hSibling = m_Tree.GetNextSiblingItem(hItem);
BOOL nFlag = TRUE;

// 遍历后续节点
while (hSibling)
{
if (!m_Tree.GetCheck(hSibling))
{
nFlag = FALSE;
break;
}
hSibling = m_Tree.GetNextSiblingItem(hSibling);
}

// 遍历前节点
if (nFlag)
{
hSibling = m_Tree.GetPrevSiblingItem(hItem);
while (hSibling)
{
if (!m_Tree.GetCheck(hSibling))
{
nFlag = FALSE;
break;
}
hSibling = m_Tree.GetPrevSiblingItem(hSibling);
}
}

if (nFlag)
{
// 全部选中的话, 父节点也要选中
m_Tree.SetCheck(hParent, TRUE);
}
}

// 递归调用, 直到不存在父节点
this->SetParentCheck(hParent, m_Tree.GetCheck(hParent));
}

处理子节点的选中状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void CTestDlg::SetChildenCheck(HTREEITEM hItem, BOOL bCheck)
{
if (hItem == NULL) return;

// 获取第一个子节点
HTREEITEM hChild = m_Tree.GetChildItem(hItem);

// 如果有子节点, 则设置为一样的状态
while (hChild)
{
m_Tree.SetCheck(hChild, bCheck);

// 递归调用, 直到不存在子节点
this->SetChildenCheck(hChild, bCheck);

hChild = m_Tree.GetNextSiblingItem(hChild);
}
}