# TreeView

## 目录

- 入门和简单示例
- 选项参数的选项和样式
- TreeView 的内置方法
- 事件
- 备注
- 示例

## 入门和简单示例

Tree-View 通过缩进父项目下的子项目来显示出层级关系. 最常见的例子是资源管理器的驱动器和文件夹树.

TreeView 通常看起来像这样:

创建 TreeView 的语法为:

    TV := GuiObj.Add("TreeView", Options)

这是一个创建和显示简单项目层次结构的可运行脚本:

    MyGui := Gui()
    TV := MyGui.Add("TreeView")
    P1 := TV.Add("First parent")
    P1C1 := TV.Add("Parent 1's first child", P1)  ; 指定 P1 为此项目的父项目.
    P2 := TV.Add("Second parent")
    P2C1 := TV.Add("Parent 2's first child", P2)
    P2C2 := TV.Add("Parent 2's second child", P2)
    P2C2C1 := TV.Add("Child 2's first child", P2C2)

    MyGui.Show  ; 显示窗口和 TreeView.

## Options 参数的选项和样式

Background: 指定单词 Background 后面紧跟着颜色名称(请参阅颜色图表) 或 RGB 值(0x 前缀可以省略). 例如: `BackgroundSilver`, `BackgroundFFDD99`. 如果此选项不存在, 则 TreeView 初始的背景颜色默认为系统默认的背景颜色. 指定 `BackgroundDefault` 或 `-Background` 来应用系统的默认背景颜色(通常为白色). 例如, 使用 `TV.Opt("+BackgroundDefault")` 可以把 TreeView 恢复为默认的颜色.

Buttons: 指定 `-Buttons`(负 Buttons) 来避免在每个含有子项目的项目左边显示加号或减号按钮.

C: 文本颜色. 指定字母 C 后面紧跟着颜色名称(请参阅颜色图表) 或 RGB 值(0x 前缀可以省略). 例如: `cRed`, `cFF2211`, `c0xFF2211`, `cDefault`.

Checked: 在每项的左边提供一个复选框. 当添加项目时, 在其选项中指定单词 Check 来让复选框初始为选中而不是未选中状态. 用户可以点击复选框或按下空格键来选中或取消选中项目. 要找出 TreeView 中当前选中了哪些项目, 请调用 GetNext 方法或 Get 方法.

HScroll: 指定 `-HScroll`(负 HScroll) 来禁用控件中的水平滚动(而且控件将不显示任何水平滚动条).

ImageList: 这是把图标添加到 TreeView 的方法. 指定单词 ImageList 后紧跟着由之前调用 IL_Create. 返回的 ImageListID. 此选项仅在创建 TreeView 时才有效果(但是, SetImageList 方法没有此限制). 这里是一个可运行示例:

    MyGui := Gui()
    ImageListID := IL_Create(10)  ; 创建初始容量为 10 个图标的图像列表.
    Loop 10  ; 加载一些标准系统图标到图像列表中.
        IL_Add(ImageListID, "shell32.dll", A_Index)
    TV := MyGui.Add("TreeView", "ImageList" . ImageListID)
    TV.Add("Name of Item", 0, "Icon4")  ; 添加项目到 TreeView 并给它分配文件夹图标.
    MyGui.Show

Lines: 指定 `-Lines`(负 Lines) 来避免显示连接父项目和它们的子项目之间的网状线. 但是, 移除这些线也阻止了顶级项目坐标加号/减号按钮的显示.

ReadOnly: 指定 `-ReadOnly`(负 ReadOnly) 来允许编辑每项的文本/名称. 要编辑某项, 请选择它接着按下 `F2`(请参阅下面的 WantF2 选项). 或者, 您可以对某个项目点击一次来选择它, 至少等半秒, 然后再次点击同一项目进行编辑. 编辑后, 可以对一个项目在其同级项目之间按字母顺序进行重新定位, 请参考下面的例子:

    TV := MyGui.Add("TreeView", "-ReadOnly")
    TV.OnEvent("ItemEdit", MyTree_Edit)  ; 每当用户完成一个项目的编辑后, 调用 MyTree_Edit.
    ; ...
    MyTree_Edit(TV, Item)
    {
        TV.Modify(TV.GetParent(Item), "Sort")  ; 即使该项目没有父项目, 该功能也可以使用.
    }

R: 行高(创建时). 指定字母 R 后面紧跟着要在控件中留出空间的行数. 例如, `R10` 会设置控件为 10 个项目的高度.

WantF2: 指定 `-WantF2`(负 WantF2) 来禁止使用 `F2` 来编辑当前选择的项目. 仅当 -ReadOnly 也有效时, 此设置才不会被忽略.

(未命名的数值样式): 由于上述以外的其他样式很少使用, 所以没有为它们命名. 请参阅 TreeView 样式表了解这些样式.

## TreeViews 的内置方法

除了GUI 控件的默认方法/属性外, TreeView 还拥有以下方法(在 Gui.TreeView 类中定义).

项目方法:

- Add: 添加新的项目到 TreeView.
- Modify: 修改项目的属性和/或名称.
- Delete: 删除指定项目或所有项目.

检索方法:

- GetSelection: 返回选择项目的 ID 号.
- GetCount: 返回控件中项目的总数.
- GetParent: 返回指定项目的父项目的 ID.
- GetChild: 返回指定项目的第一个/最上面的子项目的 ID.
- GetPrev: 返回指定项目上面一个的同级项目的 ID 号.
- GetNext: 返回指定项目下面一个项目的 ID 号.
- GetText: 检索指定项目的文本/名称.
- Get: 如果指定项目含有指定的属性, 则返回一个非零值(项目 ID).

其他方法:

- SetImageList: 设置或替换用于显示图标的 ImageList.

### Add

添加新的项目到 TreeView, 并返回其唯一的项目 ID 号.

    UniqueID := TV.Add(Name, ParentItemID, Options)

Name

  类型: 字符串

  项目显示的文本, 它可以为文本或数值(包括数值表达式的结果).

ParentItemID

  类型: 整数

  新项目的父项的 ID(省略它或指定 0 来添加顶级项目).

Options

  类型: 字符串

  包含下表中零个或多个单词的字符串(不区分大小写). 在单词间使用空格或 tab 分隔. 要移除某个选项, 请在该选项前加上负号. 要添加选项, 选项前允许使用正号, 但不是必须的(可以省略).

  Bold: 用粗体显示项目的名称. 以后要取消项目名称的粗体显示, 请使用 `TV.Modify(ItemID, "-Bold")`. 单词 Bold 后可以可选地紧跟着 0 或 1 以指示起始状态.

  Check: 在项目的坐标显示一个复选标记(需要 TreeView 含有复选框). 以后要取消选中, 请使用 `TV.Modify(ItemID, "-Check")`. 在单词 Check 后可以紧跟着 0 或 1 来表示初始状态. 换句话说, `"Check"` 和 `"Check" . VarContainingOne` 是等同的(这里中间使用的是连接运算符).

  Expand: 展开此项以让其子项目显示出来(如果有). 以后要折叠项目, 请使用 `TV.Modify(ItemID, "-Expand")`. 如果没有子项目, 那么 Modify 方法返回 0 而不是它的项目 ID. 与之相比, Add 方法标记项目为展开, 以备以后添加子项目时使用. 与下面的 "Select" 不同, 展开一个项目不会自动展开其父项目. 最后, 在单词 Expand 后可以紧跟着 0 或 1 来表示初始状态. 换句话说, `"Expand"` 和 `"Expand" . VarContainingOne` 是等同的.

  First | Sort | N: 这些选项仅适用于 Add 方法. 它们指定新项目相对于 其同级项目的位置(同级项目 是同一级别的其他任何项目). 如果这些选项都不存在, 则新项目被添加到同级项目的最后/底部. 否则, 请指定 First 来添加项目到同级项目的第一个/顶部, 或指定 Sort 来按字母顺序插入新项目到同级项目中间. 如果指定一个纯整数(N), 则假定它为同级项目的 ID 号, 新项目会被插入到它的后面(如果整数 N 是唯一使用的选项, 则它不需要括在引号中).

  Icon: 指定单词 Icon 后紧跟着此项目图标的编号, 项目图标显示在项目名称的左边. 如果此选项不存在, 则使用图像列表中的首个图标. 要显示空白图标, 请指定一个大于图像列表中图标数目的数字. 如果控件没有图像列表, 则既不显示图标也不为图标保留空间.

  Select: 选择项目. 因为一次只能选择一个项目, 此时任何原来选择的项目会自动取消选择. 此外, 如果有必要此选项会展开其父项目以显示新选择的项目. 要找出当前选择的项目, 请调用 GetSelection 方法.

  Sort: 对于 Modify 方法, 此选项按字母顺序排列指定项目的子项目. 要对所有顶级项目进行排序, 请使用 `TV.Modify(0, "Sort")`. 如果不含子项目, 则返回 0 而不是所修改项目的 ID.

  Vis: 在需要时, 通过滚动 TreeView 和/或展开其父项目来确保此项目完全可见.

  VisFirst: 和上面一样, 不过在可行时它还会滚动 TreeView 使得此项显示在顶部. 此选项与 Modify 方法比与 Add 方法一起使用通常更有效.

注意: 当添加大量项目时, 可以在添加项目之前使用 `TV.Opt("-Redraw")`, 之后使用 `TV.Opt("+Redraw")` 来提高性能.

### Modify

修改项目的属性和/或名称, 并返回项目自己的 ID.

    CurrentItemID := TV.Modify(ItemID , Options, NewName)

ItemID

  类型: 整数

  要修改的项目. 如果只使用此参数, 指定的项目是被选择的项目.

Options

  类型: 字符串

  请参阅上面的列表.

NewName

  类型: 字符串

  项目的新名称. 如果省略, 则保持当前名称不变.

### Delete

删除指定项目或所有项目, 成功后返回 1, 失败后返回 0.

    TV.Delete(ItemID)

ItemID

  类型: 整数

  要删除的项目. 如果省略, TreeView 中 所有的 项目会被删除.

### 从 TreeView 中获取数据

### GetSelection

返回选择项目的 ID 号.

    SelectedItemID := TV.GetSelection()

### GetCount

返回控件中项目的总数.

    CountNumber := TV.GetCount()

注意: 此返回值总是即时检索的, 因为控件会一直跟踪此计数.

### GetParent

返回指定项目的父项目的 ID.

    ParentItemID := TV.GetParent(ItemID)

ItemID

  类型: 整数

  要检查的项目. 顶层的项目没有父级, 因此返回 0.

### GetChild

返回指定项目的第一个/最上面的子项目的 ID(如果没有则为 0).

    TopChildID := TV.GetChild(ParentItemID)

ParentItemID

  类型: 整数

  要检查的父项目.

### GetPrev

返回指定项目上面一个的同级项目的 ID 号(如果没有则为 0).

    PrevItemID := TV.GetPrev(ItemID)

ItemID

  类型: 整数

  要检查的项目.

### GetNext

返回指定项目下面一个项目的 ID 号(如果没有则为 0).

    NextItemID := TV.GetNext(ItemID, ItemType)

ItemID

  类型: 整数

  要检查的项目. 如果该参数为 0 或省略, 则返回 TreeView 中第一个/顶部项目的 ID 号.

ItemType

  类型: 字符串

  如果省略, 则检索指定项目下面的伙伴控件的 ID 号. 否则指定 "Full" 或 "F" 检索下一个项目, 而不考虑其与指定项目的关系; 或指定 "Check", "Checked", 或 "C" 只检索下一个带有复选标记的项目.

下面的例子逐项遍历整个树:

    ItemID := 0  ; 这样使得首次循环从树的顶部开始搜索.
    Loop
    {
        ItemID := TV.GetNext(ItemID, "Full")  ; 把 "Full" 替换为 "Checked" 来找出所有含复选标记的项目.
        if not ItemID  ; 没有更多项目了.
            break
        ItemText := TV.GetText(ItemID)
        MsgBox('The next Item is ' ItemID ', whose text is "' ItemText '".')
    }

### GetText

检索指定项目的文本/名称.

    RetrievedText := TV.GetText(ItemID)

ItemID

  类型: 整数

  要检索文本的项目的 ID 号. RetrievedText 的最大长度为 8191.

### Get

如果指定项目含有指定的属性, 则返回一个非零值(项目 ID).

    CurrentItemID := TV.Get(ItemID, Attribute)

ItemID

  类型: 整数

  选中项目.

Attribute

  类型: 字符串

  指定 "E", "Expand" 或 "Expanded" 来判断此项当前是否是展开的(即它的子项目是显示的); 指定 "C", "Check" 或 "Checked" 来判断此项是否含有复选标记; 或指定 "B" 或 "Bold" 来判断此项当前是否为粗体.

提示: 因为在 IF 语句中把任何非零值视为 "true", `if TV.Get(ItemID, "Checked") = ItemID` 和 `if TV.Get(ItemID, "Checked")` 在功能上是相同的.

### SetImageList

设置或替换用于显示图标的 ImageList, 并返回之前与该控件关联的 ImageListID(如果没有, 则返回 0).

    PrevImageListID := TV.SetImageList(ImageListID , IconType)

ImageListID

  类型: 整数

  上次调用 IL_Create 后返回的数字.

IconType

  类型: 整数

  如果省略, 则默认为 0. 否则, 为状态图标指定 2(状态图标还没有被直接支持, 但可以通过 SendMessage 使用).

任何这样的分离的 ImageList 通常应该通过 IL_Destroy 来销毁.

## 事件

通过调用 OnEvent 来注册一个回调函数或方法, 可以检测到以下事件:

事件 | 当...触发
--- | ---
Click | 控件被点击.
DoubleClick | 控件被双击.
ContextMenu | 当控件拥有键盘焦点时, 用户右键单击控件或按下 `Menu` 或 `Shift`+`F10`.
Focus | 控件获得键盘焦点.
LoseFocus | 控件失去键盘焦点.
ItemCheck | 一个项目被选中或取消选中.
ItemEdit | 一个项目的标签被用户编辑.
ItemExpand | 一个项目被展开或折叠.
ItemSelect | 一个项目被选中或取消选中.

额外的(很少使用的) 通知可以通过使用 OnNotify 来检测. 这些通知在 MSDN 上都有文档. MSDN 没有显示每个通知代码的数值; 这些可以在 Windows SDK 中找到, 或者在互联网上搜索.

## 备注

当 TreeView 拥有焦点时如果要检测到用户按下的 `Enter` 键, 请使用默认按钮(如果需要可以隐藏它). 例如:

    MyGui.Add("Button", "Hidden Default", "OK").OnEvent("Click", ButtonOK)
    ...
    ButtonOK(*) {
        global
        if MyGui.FocusedCtrl != TV
            return
        MsgBox("Enter was pressed. The selected item ID is " TV.GetSelection())
    }

使用键盘除了在项与项之间导航外, 用户还可以通过输入一个项目名称的前几个字符来进行增量搜索. 这使得选择对象跳转到最近匹配的项目.

尽管 TreeView 中的每个项目可以存储任意长度的文本, 但仅显示开始的 260 个字符.

尽管理论上 TreeView 中的项目数可以多达 65536, 然而接近此数目时添加项的性能将显著降低. 通过使用 Add 方法中描述的重绘提示可以稍微减轻这种情况.

与 ListViews, 不同, 当 TreeView 销毁时它的图像列表不会自动被销毁. 因此, 如果 TreeView 中使用的图像列表以后不再用于其他地方, 则在销毁 TreeView 所在的窗口后脚本应调用 IL_Destroy 来销毁此图像列表. 然而, 如果脚本很快将退出, 这样做是没必要的, 因为那时所有的图像列表会自动被销毁.

脚本可以在每个窗口中创建多个 TreeView.

要执行一些操作, 例如调整大小, 隐藏或改变 TreeView 的字体, 请参阅 GuiControl 对象.

Tree View eXtension(TVX) 扩展了 TreeView 的功能, 增加对移动, 插入和删除的支持. 演示的例子请参阅 www.autohotkey.com/forum/topic19021.html

## 相关

ListView, 其他控件类型, Gui(), ContextMenu 事件, Gui 对象, GuiControl 对象, TreeView 样式表

## 示例

下面是一个比页面顶部的脚本更复杂的可工作的脚本. 它创建并显示一个 TreeView, 其中包含所有用户开始菜单中的所有文件夹. 当用户选择一个文件夹时, 它的内容会显示在右边的 ListView 中(类似于 Windows 资源管理器). 此外, StatusBar 控件会显示当前选择文件夹的信息.

    ; 下面的文件夹为 TreeView 的根文件夹. 请注意, 如果指定整个驱动器例如 C:\
    ; 那么可能需要很长加载时间:
    TreeRoot := A_MyDocuments
    TreeViewWidth := 280
    ListViewWidth := A_ScreenWidth/2 - TreeViewWidth - 30

    ;  创建 Gui 窗口并在标题栏中显示源目录(TreeRoot):
    MyGui := Gui("+Resize", TreeRoot)  ; 让用户可以最大化或拖动调整窗口大小.

    ; 创建图像列表并在其中放入一些标准的系统图标:
    ImageListID := IL_Create(5)
    Loop 5
        IL_Add(ImageListID, "shell32.dll", A_Index)
    ; 创建 TreeView 和 ListView, 让它们像在 Windows 资源管理器中那样靠在一起:
    TV := MyGui.Add("TreeView", "r20 w" TreeViewWidth " ImageList" ImageListID)
    LV := MyGui.Add("ListView", "r20 w" ListViewWidth " x+10", ["Name","Modified"])

    ; 创建状态栏/, 显示文件夹数及其总大小的信息:
    SB := MyGui.Add("StatusBar")
    SB.SetParts(60, 85)  ; 在状态栏中创建三个部分(第三部分占用所有剩余宽度).

    ; 添加文件夹及其子文件夹到树中. 如果加载需要很长时间, 则显示提示信息:
    M := Gui("ToolWindow -SysMenu Disabled AlwaysOnTop", "Loading the tree..."), M.Show("w200 h0")
    DirList := AddSubFoldersToTree(TreeRoot, Map())
    M.Hide()

    ; 每当有新的项目被选中时, 调用 TV_ItemSelect:
    TV.OnEvent("ItemSelect", TV_ItemSelect)

    ; 每当窗口被调整大小时, 调用 Gui_Size:
    MyGui.OnEvent("Size", Gui_Size)

    ; 设置 ListView 的列宽(这是可选的):
    Col2Width := 70  ; 缩小到只显示 YYYYMMDD 部分.
    LV.ModifyCol(1, ListViewWidth - Col2Width - 30)  ; 允许垂直滚动条.
    LV.ModifyCol(2, Col2Width)

    ; 显示窗口并返回. 每当用户执行符合条件的动作时, 操作系统会通知脚本:
    MyGui.Show

    AddSubFoldersToTree(Folder, DirList, ParentItemID := 0)
    {
        ; 该函数将指定文件夹中的所有子文件夹添加到 TreeView 中,
        ; 并将它们与 ID 相关联的路径保存到一个对象中, 供以后使用.
        ; 它还可以递归地调用自己来收集任意深度的嵌套文件夹.
        Loop Files, Folder "\*.*", "D"  ; 获取所有文件夹的子文件夹.
        {
            ItemID := TV.Add(A_LoopFileName, ParentItemID, "Icon4")
            DirList[ItemID] := A_LoopFilePath
            DirList := AddSubFoldersToTree(A_LoopFilePath, DirList, ItemID)
        }
        return DirList
    }

    TV_ItemSelect(thisCtrl, Item)  ; 当选择一个新的项目时, 该函数被调用.
    {
        ; 将文件放入 ListView 中:
        LV.Delete  ; 清除所有行.
        LV.Opt("-Redraw")  ; 通过在加载过程中禁止重绘来提高性能.
        TotalSize := 0  ; 在下面循环之前初始化.
        Loop Files, DirList[Item] "\*.*"  ; 为了简化, 这里省略了文件夹, 所以只在 ListView 中显示文件.
        {
            LV.Add(, A_LoopFileName, A_LoopFileTimeModified)
            TotalSize += A_LoopFileSize
        }
        LV.Opt("+Redraw")

        ; 更新状态栏的三个部分, 让它们显示当前选择的文件夹的信息:
        SB.SetText(LV.GetCount() " files", 1)
        SB.SetText(Round(TotalSize / 1024, 1) " KB", 2)
        SB.SetText(DirList[Item], 3)
    }

    Gui_Size(thisGui, MinMax, Width, Height)  ; 当用户改变窗口大小时扩展/收缩 ListView 和 TreeView.
    {
        if MinMax = -1  ; 窗口被最小化了. 无需进行操作.
            return
        ; 否则, 窗口的大小被调整过或被最大化了. 调整控件大小以适应.
        TV.GetPos(,, &TV_W)
        TV.Move(,,, Height - 30)  ; -30 用于状态栏和边距.
        TV.Move(,, Width - TV_W - 30, Height - 30)
    }

dftg