TreeView 基础
TreeView 是侧边栏最常用的数据展示形式:项目管理器、资源树、依赖树、历史记录……它把层级数据组织成可折叠、可点击的树形视图,是插件「数据面板」的标准形态。
注册方式对比
TreeView 有两种注册方式:
| 方式 | 说明 | 适用 |
|---|---|---|
contributes.views | package.json 声明式注册 | 标准树视图 |
window.createTreeView | 代码动态创建 | 需要编程控制视图 |
声明式注册(推荐)
package.json:
json
{
"contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "myExt",
"title": "我的工具",
"icon": "media/icon.svg"
}
]
},
"views": {
"myExt": [
{
"id": "myExt.tree",
"name": "项目资源"
}
]
}
}
}extension.ts:
typescript
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
// 注册数据提供器
vscode.window.registerTreeDataProvider(
'myExt.tree',
new TreeProvider()
);
}编程式创建
typescript
const treeView = vscode.window.createTreeView(
'myExt.tree',
{
treeDataProvider: new TreeProvider(),
showCollapseAll: true
}
);
context.subscriptions.push(treeView);TreeDataProvider 接口
数据提供器负责把「数据模型」映射为「树节点」:
typescript
class TreeProvider implements vscode.TreeDataProvider<Node> {
// 变更事件:数据变化时通知视图刷新
private _onDidChangeTreeData =
new vscode.EventEmitter<Node | undefined | null>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
// 获取节点的树项展示
getTreeItem(node: Node): vscode.TreeItem {
return node;
}
// 获取子节点列表
getChildren(node?: Node): vscode.ProviderResult<Node[]> {
if (!node) {
return this.getRootNodes();
}
return this.getChildNodes(node);
}
// 刷新
refresh() {
this._onDidChangeTreeData.fire();
}
}接口三要素
| 方法 | 作用 |
|---|---|
getTreeItem(node) | 把数据节点转为 TreeItem 显示 |
getChildren(node?) | 返回子节点(node 为空返回根) |
onDidChangeTreeData | 数据变化通知(可选但重要) |
TreeItem 属性详解
TreeItem 是树的显示单元:
typescript
class Node extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly children?: Node[]
) {
super(label, collapsibleState);
// 附加属性
this.description = '描述文本';
this.iconPath = new vscode.ThemeIcon('file');
this.contextValue = 'file';
this.command = {
command: 'myExt.openNode',
title: '打开',
arguments: [this]
};
}
}核心属性
| 属性 | 作用 |
|---|---|
label | 节点显示文本 |
iconPath | 图标(ThemeIcon/文件路径) |
collapsibleState | 折叠状态 |
command | 点击节点执行命令 |
contextValue | 右键菜单条件标识 |
description | 次要描述文本 |
tooltip | 悬停提示 |
resourceUri | 关联文件 |
CollapsibleState 折叠状态
| 值 | 含义 |
|---|---|
CollapsibleState.None | 叶子节点(不可折叠) |
CollapsibleState.Collapsed | 有子节点,默认收起 |
CollapsibleState.Expanded | 有子节点,默认展开 |
typescript
// 叶子节点
new vscode.TreeItem('file.ts', vscode.TreeItemCollapsibleState.None);
// 文件夹(默认收起)
new vscode.TreeItem('src', vscode.TreeItemCollapsibleState.Collapsed);完整示例:文件结构树
typescript
import * as vscode from 'vscode';
import * as path from 'path';
export function activate(context: vscode.ExtensionContext) {
vscode.window.registerTreeDataProvider(
'myExt.files',
new FileTreeProvider()
);
}
// 数据节点
class FileNode extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly uri: vscode.Uri,
public readonly isDirectory: boolean
) {
super(label, collapsibleState);
this.iconPath = isDirectory
? new vscode.ThemeIcon('folder')
: new vscode.ThemeIcon('file');
this.resourceUri = uri;
this.contextValue = isDirectory ? 'directory' : 'file';
this.tooltip = uri.fsPath;
}
}
class FileTreeProvider implements vscode.TreeDataProvider<FileNode> {
private _onDidChangeTreeData =
new vscode.EventEmitter<FileNode | undefined | null>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
getTreeItem(element: FileNode): vscode.TreeItem {
return element;
}
async getChildren(element?: FileNode): Promise<FileNode[]> {
// 根节点:返回工作区文件夹
if (!element) {
const folders = vscode.workspace.workspaceFolders;
if (!folders) {
return [];
}
return folders.map((folder) =>
new FileNode(
folder.name,
vscode.TreeItemCollapsibleState.Collapsed,
folder.uri,
true
)
);
}
// 子节点:读取目录
const entries = await vscode.workspace.fs.readDirectory(element.uri);
const nodes = entries.map(([name, type]) => {
const isDir = type === vscode.FileType.Directory;
return new FileNode(
name,
isDir
? vscode.TreeItemCollapsibleState.Collapsed
: vscode.TreeItemCollapsibleState.None,
vscode.Uri.joinPath(element.uri, name),
isDir
);
});
return nodes.sort((a, b) => {
// 目录优先
if (a.isDirectory !== b.isDirectory) {
return a.isDirectory ? -1 : 1;
}
return a.label.localeCompare(b.label);
});
}
refresh() {
this._onDidChangeTreeData.fire();
}
}数据更新与刷新
全量刷新
typescript
this._onDidChangeTreeData.fire(undefined);局部刷新
传入指定节点,只刷新该节点子树:
typescript
this._onDidChangeTreeData.fire(specificNode);实际场景
typescript
// 文件变更时刷新
const watcher = vscode.workspace.createFileSystemWatcher('**/*');
watcher.onDidCreate(() => provider.refresh());
watcher.onDidDelete(() => provider.refresh());
watcher.onDidChange(() => provider.refresh());注册 TreeItem 点击命令
节点点击行为通过 command 属性绑定:
typescript
// 数据节点
node.command = {
command: 'myExt.openNode',
title: '打开节点',
arguments: [node]
};typescript
// 命令处理
context.subscriptions.push(
vscode.commands.registerCommand('myExt.openNode', (node: FileNode) => {
vscode.window.showInformationMessage(`打开 ${node.label}`);
vscode.commands.executeCommand(
'vscode.open',
node.uri
);
})
);常见问题
| 问题 | 处理 |
|---|---|
| 视图空白 | 检查 registerTreeDataProvider 的 ID 匹配 |
| 节点不显示 | getChildren 返回数组需 await 完整 |
| 数据不变更 | 调用 refresh() 触发 onDidChangeTreeData |
| 图标不显示 | 用 ThemeIcon 或正确资源路径 |
TreeView 基础打通了「数据 → 树 → 交互」的主链路,掌握后可构建各类结构化数据面板。