树形视图控件 (STreeView)¶
Warning
The current page still doesn't have a translation for this language.
You can read it through google translate.
树形视图是一个高性能的 MVC 模式控件,用于展示具有层级结构的数据。它支持展开/折叠操作,支持节点选择,并且可以通过自定义模板来实现复杂的节点显示。
基本信息¶
- 类名:
STreeView
- 控件标签:
treeview
- 基类:
SScrollView
- 功能:提供树形结构数据的展示和交互
属性说明¶
基本属性¶
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
indent | int | 16 | 子节点缩进宽度 |
itemHeight | int | 20 | 节点项高度 |
toggleSkin | string | - | 展开/折叠按钮皮肤 |
checkSkin | string | - | 复选框皮肤 |
enableCheck | bool | 0 | 是否启用节点复选功能 |
multiSelect | bool | 0 | 是否允许多选 |
lines | bool | 1 | 是否显示连接线 |
rootToggle | bool | 0 | 根节点是否显示展开/折叠按钮 |
外观属性¶
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
colorText | color | 0 | 文本颜色 |
colorSelText | color | 0 | 选中项文本颜色 |
colorItemBkgnd | color | RGBA(255,255,255,255) | 项目背景色 |
colorSelBkgnd | color | RGBA(0,0,255,255) | 选中项背景色 |
hasLines | bool | 1 | 是否显示连接线 |
lineColor | color | RGBA(0,0,0,255) | 连接线颜色 |
使用示例¶
基本树形视图¶
<treeview name="tvBasic"
pos="10,10,210,310"
indent="20"
itemHeight="25">
<template>
<window class="treeitem"
layout="hbox"
padding="4,0,4,0">
<toggle name="switch"/>
<text name="text"/>
</window>
</template>
</treeview>
带复选框的树形视图¶
<treeview name="tvCheck"
pos="220,10,420,310"
enableCheck="1"
checkSkin="checkbox">
<template>
<window class="treeitem"
layout="hbox"
padding="4,0,4,0">
<toggle name="switch"/>
<check name="check"/>
<text name="text"/>
</window>
</template>
</treeview>
自定义模板树形视图¶
<treeview name="tvCustom"
pos="430,10,630,310">
<template>
<window class="treeitem"
layout="hbox"
padding="4,0,4,0">
<toggle name="switch"/>
<img name="icon"
size="16,16"
margin="0,0,4,0"/>
<window size="0,-2"
weight="1"
layout="vbox"
gravity="center">
<text name="title"
font="bold:1"/>
<text name="desc"
font="size:12"
colorText="#666666"/>
</window>
</window>
</template>
</treeview>
带图标和操作按钮的树形视图¶
<treeview name="tvAction"
pos="10,320,-10,-10"
indent="24"
itemHeight="30">
<template>
<window class="treeitem"
layout="hbox"
padding="4,0,4,0"
gravity="center">
<toggle name="switch"
size="16,16"/>
<img name="icon"
size="20,20"
margin="4,0"/>
<text name="title"
size="0,0"
flex="1"/>
<button name="btn_action"
size="60,24"
text="操作"
margin="4,0"/>
</window>
</template>
</treeview>
数据模型¶
实现数据适配器¶
#include <helper/SAdapterBase.h>
class CTreeViewAdapter : public STreeViewAdapter
{
protected:
// 定义节点数据结构
struct ItemData {
SStringT title;
SStringT desc;
SStringT iconSkin;
bool hasChildren;
bool bEnabled;
};
SArray<ItemData> m_items;
public:
// 获取子节点数量,注意 WINAPI 调用约定
virtual int WINAPI getChildCount(HSTREEITEM hItem) {
return hItem ? 5 : 3; // 示例:每个节点有5个子节点
}
// 判断是否有子节点,注意 WINAPI 调用约定
virtual bool WINAPI hasChildren(HSTREEITEM hItem) {
return true; // 示例:所有节点都可以有子节点
}
// 获取显示文本,注意 WINAPI 调用约定
virtual void WINAPI getText(HSTREEITEM hItem,
int subIndex,
SStringT& strText) {
strText.Format(_T("Item %d"), subIndex);
}
// 获取节点状态,注意 WINAPI 调用约定
virtual int WINAPI getItemState(HSTREEITEM hItem) {
return WndState_Normal;
}
// 获取项目视图,注意 WINAPI 调用约定
virtual void WINAPI getView(HSTREEITEM hItem,
SItemPanel* pItem,
SXmlNode xmlTemplate)
{
if(pItem && pItem->GetChildrenCount() == 0)
{
pItem->InitFromXml(&xmlTemplate);
// 订阅操作按钮事件
SWindow* pBtn = pItem->FindChildByName(L"btn_action");
if(pBtn)
{
pBtn->GetEventSet()->subscribeEvent(
EventCmd::EventID,
Subscriber(&CTreeViewAdapter::OnActionClick, this));
}
}
// 更新项目内容
UpdateItemView(hItem, pItem);
}
// 更新项目视图
void UpdateItemView(HSTREEITEM hItem, SWindow* pItem)
{
if(!pItem) return;
int nIndex = GetItemIndex(hItem);
if(nIndex < 0 || nIndex >= m_items.GetCount()) return;
const ItemData& item = m_items[nIndex];
// 设置图标
SWindow* pIcon = pItem->FindChildByName(L"icon");
if(pIcon)
{
pIcon->SetAttribute(L"skin", item.iconSkin);
}
// 设置标题
SWindow* pTitle = pItem->FindChildByName(L"title");
if(pTitle)
{
pTitle->SetWindowText(item.title);
}
// 设置描述
SWindow* pDesc = pItem->FindChildByName(L"desc");
if(pDesc)
{
pDesc->SetWindowText(item.desc);
}
// 设置启用状态
pItem->EnableWindow(item.bEnabled);
}
// 处理操作按钮点击
bool OnActionClick(EventArgs* pEvt)
{
SWindow* pBtn = sobj_cast<SWindow>(pEvt->sender);
if(pBtn)
{
SItemPanel* pItem = sobj_cast<SItemPanel>(pBtn->GetParent());
if(pItem)
{
HSTREEITEM hItem = (HSTREEITEM)pItem->GetItemIndex();
// 处理操作按钮点击
HandleActionClick(hItem);
}
}
return true;
}
// 添加根节点
HSTREEITEM AddRootItem(const ItemData& item)
{
m_items.Add(item);
HSTREEITEM hItem = InsertItem(item, STVI_ROOT);
return hItem;
}
// 添加子节点
HSTREEITEM AddChildItem(HSTREEITEM hParent, const ItemData& item)
{
m_items.Add(item);
HSTREEITEM hItem = InsertItem(item, hParent);
return hItem;
}
};
绑定数据适配器¶
void BindTreeViewData()
{
STreeView *pTreeView =
FindChildByName2<STreeView>(L"tvDemo");
if(pTreeView) {
CTreeViewAdapter *pAdapter = new CTreeViewAdapter;
// 添加根节点
CTreeViewAdapter::ItemData rootItem;
rootItem.title = L"根节点";
rootItem.desc = L"这是根节点";
rootItem.iconSkin = L"skin_folder";
rootItem.hasChildren = true;
rootItem.bEnabled = true;
pAdapter->AddRootItem(rootItem);
// 添加子节点
CTreeViewAdapter::ItemData childItem;
childItem.title = L"子节点";
childItem.desc = L"这是子节点";
childItem.iconSkin = L"skin_file";
childItem.hasChildren = false;
childItem.bEnabled = true;
pAdapter->AddChildItem(/*hParent*/, childItem);
pTreeView->SetAdapter(pAdapter);
pAdapter->Release();
}
}
事件处理¶
树形视图控件支持以下事件:
事件名 | EventID | 说明 |
---|---|---|
EVT_TV_SELCHANGING | EventTVSelChanging::EventID | 选择改变前事件 |
EVT_TV_SELCHANGED | EventTVSelChanged::EventID | 选择改变事件 |
// 事件处理示例
EVENT_MAP_BEGIN()
EVENT_NAME_HANDLER(L"tvBasic", EventTVSelChanged::EventID, OnTVSelChanged)
EVENT_MAP_END()
void OnTVSelChanged(IEvtArgs *pEvt)
{
EventTVSelChanged *pRealEvt = sobj_cast<EventTVSelChanged>(pEvt);
int nOldSel = pRealEvt->nOldSel;
int nNewSel = pRealEvt->nNewSel;
// 处理选择改变事件
}
代码操作¶
// 查找树形视图控件
STreeView *pTreeView = FindChildByName2<STreeView>(L"tvBasic");
// 设置适配器
pTreeView->SetAdapter(pAdapter);
// 展开/折叠节点
pTreeView->Expand(hItem, TRUE); // 展开
pTreeView->Expand(hItem, FALSE); // 折叠
// 获取选中节点
HSTREEITEM hSelectedItem = pTreeView->GetSelectedItem();
// 选中指定节点
pTreeView->SelectItem(hItem);
// 获取节点文本
SStringT strText;
pTreeView->GetItemText(hItem, strText);
// 获取父节点
HSTREEITEM hParent = pTreeView->GetParentItem(hItem);
// 获取子节点
HSTREEITEM hChild = pTreeView->GetChildItem(hItem);
// 获取下一个兄弟节点
HSTREEITEM hNext = pTreeView->GetNextSiblingItem(hItem);
实现文件浏览器示例¶
#include <helper/SAdapterBase.h>
// 文件树节点数据
struct FileTreeNode
{
SStringT strName; // 节点名称
SStringT strFullPath; // 完整路径
bool bIsFolder; // 是否为文件夹
SArray<FileTreeNode*> children; // 子节点
};
// 文件树适配器
class CFileTreeAdapter : public STreeViewAdapter
{
private:
FileTreeNode* m_pRootNode;
public:
CFileTreeAdapter() : m_pRootNode(NULL)
{
// 初始化根节点
m_pRootNode = new FileTreeNode();
m_pRootNode->strName = L"计算机";
m_pRootNode->strFullPath = L"";
m_pRootNode->bIsFolder = true;
}
~CFileTreeAdapter()
{
DeleteNode(m_pRootNode);
}
// 递归删除节点
void DeleteNode(FileTreeNode* pNode)
{
if(!pNode) return;
for(int i = 0; i < pNode->children.GetCount(); i++)
{
DeleteNode(pNode->children[i]);
}
delete pNode;
}
// 获取子节点数量,注意 WINAPI 调用约定
virtual int WINAPI getChildCount(HSTREEITEM hItem)
{
FileTreeNode* pNode = (FileTreeNode*)GetItemData(hItem);
if(!pNode) return 0;
if(pNode->bIsFolder && pNode->children.GetCount() == 0)
{
// 动态加载子节点
LoadChildren(pNode);
}
return pNode->children.GetCount();
}
// 判断是否有子节点,注意 WINAPI 调用约定
virtual bool WINAPI hasChildren(HSTREEITEM hItem)
{
FileTreeNode* pNode = (FileTreeNode*)GetItemData(hItem);
if(!pNode) return false;
if(pNode->bIsFolder)
{
// 对于文件夹,检查是否包含子项
if(pNode->children.GetCount() == 0)
{
// 检查实际文件系统
return HasFileSystemChildren(pNode->strFullPath);
}
return pNode->children.GetCount() > 0;
}
return false;
}
// 获取显示文本,注意 WINAPI 调用约定
virtual void WINAPI getText(HSTREEITEM hItem, int subIndex, SStringT& strText)
{
FileTreeNode* pNode = (FileTreeNode*)GetItemData(hItem);
if(pNode)
{
strText = pNode->strName;
}
}
// 获取项目视图,注意 WINAPI 调用约定
virtual void WINAPI getView(HSTREEITEM hItem, SItemPanel* pItem, SXmlNode xmlTemplate)
{
if(pItem && pItem->GetChildrenCount() == 0)
{
pItem->InitFromXml(&xmlTemplate);
}
FileTreeNode* pNode = (FileTreeNode*)GetItemData(hItem);
if(pNode && pItem)
{
// 设置图标
SWindow* pIcon = pItem->FindChildByName(L"icon");
if(pIcon)
{
pIcon->SetAttribute(L"skin",
pNode->bIsFolder ? L"skin_folder" : L"skin_file");
}
// 设置标题
SWindow* pTitle = pItem->FindChildByName(L"title");
if(pTitle)
{
pTitle->SetWindowText(pNode->strName);
}
}
}
// 加载子节点
void LoadChildren(FileTreeNode* pParentNode)
{
if(!pParentNode || !pParentNode->bIsFolder) return;
// 这里应该实际读取文件系统目录
// 示例代码,实际应用中需要替换为真实的文件系统操作
/*
WIN32_FIND_DATA findData;
SStringT strSearchPath = pParentNode->strFullPath + L"\\*";
HANDLE hFind = FindFirstFile(strSearchPath, &findData);
if(hFind != INVALID_HANDLE_VALUE)
{
do
{
if(lstrcmp(findData.cFileName, L".") == 0 ||
lstrcmp(findData.cFileName, L"..") == 0)
{
continue;
}
FileTreeNode* pChildNode = new FileTreeNode();
pChildNode->strName = findData.cFileName;
pChildNode->strFullPath = pParentNode->strFullPath + L"\\" + findData.cFileName;
pChildNode->bIsFolder = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
pParentNode->children.Add(pChildNode);
} while(FindNextFile(hFind, &findData));
FindClose(hFind);
}
*/
}
// 检查文件系统中是否有子项
bool HasFileSystemChildren(const SStringT& strPath)
{
// 实际应用中实现检查目录是否包含文件或子目录
return true; // 示例返回true
}
// 添加根目录
void AddRootDirectory(const SStringT& strPath)
{
FileTreeNode* pNode = new FileTreeNode();
pNode->strName = GetFileNameFromPath(strPath);
pNode->strFullPath = strPath;
pNode->bIsFolder = true;
m_pRootNode->children.Add(pNode);
InsertItem(pNode, STVI_ROOT);
}
// 从路径获取文件名
SStringT GetFileNameFromPath(const SStringT& strPath)
{
int nPos = strPath.ReverseFind(L'\\');
if(nPos != -1)
{
return strPath.Mid(nPos + 1);
}
return strPath;
}
};
// 使用文件树
void InitFileTree()
{
STreeView* pTreeView = FindChildByName2<STreeView>(L"tvFileTree");
if(pTreeView)
{
CFileTreeAdapter* pAdapter = new CFileTreeAdapter();
// 添加驱动器
pAdapter->AddRootDirectory(L"C:");
pAdapter->AddRootDirectory(L"D:");
pTreeView->SetAdapter(pAdapter);
pAdapter->Release();
}
}
样式定制¶
基本样式¶
<style>
<class name="treeStyle"
colorText="#333333"
colorSelText="#FFFFFF"
colorItemBkgnd="#FFFFFF"
colorSelBkgnd="#1E90FF"
hasLines="1"
lineColor="#CCCCCC"/>
</style>
<treeview class="treeStyle"/>
自定义连接线¶
<treeview hasLines="1"
lineColor="#1E90FF"
indent="24">
<template>
<!-- 自定义模板 -->
</template>
</treeview>
不同样式节点¶
<treeview name="tvStyled">
<template>
<window class="treeitem">
<toggle name="switch"/>
<img name="icon"/>
<text name="text"/>
</window>
</template>
</treeview>
<style>
<!-- 普通节点样式 -->
<treeitem colorText="#333333"
colorBkgnd="#FFFFFF"/>
<!-- 选中节点样式 -->
<treeitem checked="1"
colorText="#FFFFFF"
colorBkgnd="#1E90FF"/>
<!-- 禁用节点样式 -->
<treeitem disabled="1"
colorText="#999999"/>
</style>
常见问题¶
Q: 数据修改后如何刷新视图?¶
A: 如果要刷新全部视图,通过调用 adapter->notifyDataSetChanged() 方法刷新;如果只需要刷新指定的项目,可以通过调用 adapter->notifyItemChanged(hItem) 方法;如果要刷新一个节点及子节点,可以通过调用 adapter->notifyBranchChanged(hItem) 方法。
相关控件¶
- 树控件(STreeCtrl) - 传统的树形控件
- 列表视图(SListView) - 单列数据列表视图
- 平铺视图(STileView) - 网格形式展示项目的视图