# ListView

## 目录

- 入门和简单示例
- 选项参数的选项和样式
- 视图模式: 报告(默认), 图标, 平铺, 小图标和列表.
- ListView 的内置函数:
- G-标签通知
- 图像列表(把图标添加到 ListView 的方法)
- 备注
- 示例

## 入门和简单示例

List-View 是由操作系统提供的最精心设计的控件之一. 在其最容易识别的形式中, 它显示一个多行多列组成的表格视图, 最常见的例子是资源管理器的文件和文件夹列表(详细信息视图).

ListView 通常是这样的:

尽管它可能是精细的, 但 ListView 的基本功能也易于使用. 创建 ListView 的语法为:

    Gui, Add, ListView, Options, ColumnTitle1|ColumnTitle2|...

这是一个可运行脚本, 它创建并显示一个 ListView 其中包含用户的 "我的文档" 目录中的文件列表:

    ; 创建含名称和大小两列的 ListView:
    Gui, Add, ListView, r20 w700 gMyListView, Name|Size (KB)

    ; 从文件夹中获取文件名列表并把它们放入 ListView:
    Loop, %A_MyDocuments%\*.*
        LV_Add("", A_LoopFileName, A_LoopFileSizeKB)

    LV_ModifyCol()  ; 根据内容自动调整每列的大小.
    LV_ModifyCol(2, "Integer")  ; 为了进行排序, 指出列 2 是整数.

    ; 显示窗口并返回. 每当用户点击一行时脚本会发出通知.
    Gui, Show
    return

    MyListView:
    if (A_GuiEvent = "DoubleClick")
    {
        LV_GetText(RowText, A_EventInfo)  ; 从行的第一个字段中获取文本.
        ToolTip You double-clicked row number %A_EventInfo%. Text: "%RowText%"
    }
    return

    GuiClose:  ; 表示当窗口关闭时脚本应自动退出.
    ExitApp

## 选项参数的选项和样式

AltSubmit: 通知脚本有比正常更多的 ListView 事件类型. 换句话说, g-标签会运行地更频繁. 请参阅 ListView 通知了解详情.

Background: 指定单词 Background 后面紧跟着颜色名称(请参阅颜色图表) 或 RGB 值(0x 前缀可以省略). 例如: `BackgroundSilver`, `BackgroundFFDD99`. 如果此选项不存在, 则 ListView 初始默认的背景颜色由 Gui Color 的最后一个参数设置(如果没有, 则使用系统默认的背景颜色). 指定 `BackgroundDefault` 来应用系统的默认背景颜色(通常为白色). 例如, 使用 `GuiControl, +BackgroundDefault, MyListView` 可以把 ListView 恢复为默认的颜色.

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

Checked: 在每行的左侧提供一个复选框. 当添加行时, 在其选项中指定单词 Check 来让复选框初始为选中而不是未选中状态. 用户可以点击复选框或按下空格键来选中或取消选中一行.

Count: 指定单词 Count 后面跟着 ListView 最后会包含的总行数. 这不是限制: 超过此数目后仍然可以添加行. 相反, 此选项给控件一个暗示, 允许它一次性的而不是每次添加一行时分配内存, 这样极大地提高了添加行时的性能(这也可以提高排序性能). 要提升更多的性能, 请在添加大量行之前使用 `GuiControl, -Redraw, MyListView`. 随后, 使用 `GuiControl, +Redraw, MyListView` 来重新启用重绘(这样也重绘了控件).

Grid: 提供水平的和垂直的线以形象地显示出行与行和列与列之间的边界.

Hdr: 指定 `-Hdr`(负 Hdr) 以省略标题(包含列标题的特殊顶行). 后面要让它显示, 请使用 `GuiControl, +Hdr, MyListView`.

LV: 指定字符串 LV 后面紧跟着扩展 ListView 样式的数字. 这些样式完全独立于通用扩展样式. 例如, 指定 `-E0x200` 会移除通用扩展样式 WS_EX_CLIENTEDGE, 这样将会移除控件的默认边框. 与之相比, 指定 `-LV0x20` 会移除 LVS_EX_FULLROWSELECT 选择整行效果.

LV0x10: 指定 `-LV0x10` 阻止用户通过拖动列标题到左边或右边来对它们重新排序. 然而, 通常没必要这么做, 因为列的实际重排不会影响脚本看到的列顺序. 例如, 从脚本的角度看第一列总是 column 1, 即使用户实际已经把它移动到其他列的右边.

LV0x20: 指定 `-LV0x20` 来要求点击一行的第一个字段才可以进行选择(正常情况下, 点击 任意 字段都可以选择). 这样的好处是用户可以更方便地拖动矩形包围一组行来选择它们.

Multi: 指定 `-Multi`(负 Multi) 来禁止用户一次选择多行.

NoSortHdr: 禁止点击标题. 此时会显示平坦的外观而不是正常的类似按钮的外观. 与其他大多数 ListView 样式不同, 此样式在 ListView 创建后无法改变.

NoSort: 关闭当用户点击标题时发生的自动排序. 然而, 标题的外观看起来仍像按钮(除非指定了 NoSortHdr). 此外, g-标签仍会接收到 ColClick 通知, 可以用自定义排序或其他动作对它进行响应.

ReadOnly: 指定 `-ReadOnly`(负 ReadOnly) 来允许编辑每行首列的文本. 要编辑某行, 请在选择后按下 `F2`(请参阅下面的 WantF2 选项). 或者, 您可以对一行点击一次来选择它, 至少等待半秒钟, 然后再次点击同一行进行编辑.

R: 行高(创建时). 指定字母 R 后面紧跟着要在控件中留出空间的行数. 例如, `R10` 会设置控件为 10 行的高度. 如果使用报告视图外的其他视图模式创建 ListView, 则控件会调整大小以适应含图标的行而不是文本行. 注: 添加图标到 ListView 的行中会增加每行的高度, 这会让此选项不准确.

Sort: 让控件根据首列的内容保持按字母顺序排列.

SortDesc: 和上面一样, 不过这里是降序排列.

WantF2 [v1.0.44+]: 指定 `-WantF2`(负 WantF2) 来阻止来自编辑当前焦点行的 `F2` 按键. 仅当 `-ReadOnly` 也有效时此设置才不会被忽略. 不论此设置如何, g-标签仍会接收到 `F2` 通告.

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

## 视图模式

ListView 有五种视图模式, 其中最常见的是报告视图(这是默认的). 要使用其他某种视图, 请在选项列表中指定它的名称. 还可以在控件创建后改变视图模式; 例如: `GuiControl, +IconSmall, MyListView`.

Icon: 显示大图标视图. 在此视图和除 Report 外的其他所有视图中, 只有首列中的文本才可见. 要在此模式中显示图标, ListView 必须分配有含大图标的图像列表.

Tile: 显示大图标视图, 但具有人体工学差异, 例如每项的文本显示在图标的右边而不是下面. Checkbox 不能使用于此视图中. 此外, 在比 Windows XP 早的操作系统中显示此视图没有效果.

IconSmall: 显示小图标视图.

List: 以列表格式显示小图标视图, 其中图标显示在列中. 列的数目取决于控件的宽度和控件中最宽文本项目的高度.

Report: 切换回报告视图, 这是最初的默认模式. 例如: `GuiControl, +Report, MyListView`.

## ListView 中的内置函数

所有的 ListView 函数操作于当前线程的默认 GUI 窗口(这可以使用 `Gui, 2:Default` 进行改变). 如果默认窗口不存在或不含有 ListView 控件, 则所有函数返回零来表明此问题.

如果窗口含有多个 ListView 控件, 默认情况下函数操作于最近添加的那个. 要改变这种情况, 请指定 `Gui, ListView, ListViewName`, 其中 ListViewName 为 ListView 的关联变量的名称或 Window Spy 中显示的其 ClassNN 或在 [v1.1.04+] 其 HWND. 一旦改变后, 所有现有和将来的线程将使用指定的 ListView. [v1.1.23+]: A_DefaultListView 包含了当前的设置.

当在此页面使用短语 "row number(行号)", 它指的是 ListView 中行的当前位置. 最上面一行为 1, 第二行为 2, 依此类推. 添加一行后, 其行号会因为排序, 删除和插入其他行而发生变化. 因此, 要根据内容查找特定的一行或多行, 通常最好在循环中使用 LV_GetText().

行函数

- LV_Add: 添加新行到列表底部.
- LV_Insert: 在指定的行号插入一个新行.
- LV_Modify: 修改行的属性和/或文本.
- LV_Delete: 删除指定行或所有行.

列函数

- LV_ModifyCol: 修改指定列和标题的属性和/或文本.
- LV_InsertCol: 在指定列号插入一个新列.
- LV_DeleteCol: 删除指定的列及其中所有内容.

检索函数

- LV_GetCount: 返回行或列的总数, 或只返回选定行数.
- LV_GetNext: 返回下一个选择的, 选中的或焦点行的行号.
- LV_GetText: 检索指定行和列上的文本.

其他函数:

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

### LV_Add

添加新行到列表底部.

    LV_Add(Options, Field1, Field2, ...)

参数 Field1 和其后的字段是新行的列内容, 可以是文本或数字(包括数值的表达式结果). 要让任何字段为空, 请指定 "" 或等价物. 如果参数中太少字段而无法填满所有列, 则留空后面剩下的列. 如果太多字段, 则忽略后面多出的字段.

失败时, LV_Add() 返回 0. 成功时它返回新行号, 如果 ListView 含有 Sort 或 SortDesc 样式则此行不一定是最后一行.

#### 行选项

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

Check: 在行中显示选中标记(需要 ListView 含有复选框). 以后要取消选中它, 请使用 `LV_Modify(RowNumber, "-Check")`.

ColN: 指定单词 Col 后紧跟着列号, 从此列开始往后应用参数 Col1 和其后参数的内容. 此选项通常和 LV_Modify() 一起使用来修改行中的个别字段, 而不会影响其他字段.

Focus: 设置键盘焦点到此行(通常和 Select 一起使用). 以后要取消它的焦点, 请使用 `LV_Modify(RowNumber, "-Focus")`.

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

Select: 选择行. 以后要取消选择, 请使用 `LV_Modify(RowNumber, "-Select")`. 当选择行时, 通常最好确保至少有一行总含有焦点属性, 因为这样允许 Apps 键在焦点行附近显示其上下文菜单(如果有). 单词 Select 后可以紧跟着 0 或 1 来表示初始状态. 换句话说, `"Select"` 和 `"Select" . VarContainingOne` 是一样的(此处的句点是连接运算符). 这种技术也适用于上面的 Focus 和 Check.

Vis [v1.0.44+]: 必要时通过滚动 ListView 来确保指定行完全可见. 此参数仅对 LV_Modify() 有效; 例如: `LV_Modify(RowNumber, "Vis")`.

### LV_Insert

在指定行号插入一个新行.

    LV_Insert(RowNumber , Options, Col1, Col2, ...)

除了它的不同的第一个参数(它为新插入的行指定行号) 之外, 它的行为与 LV_Add() 相同. 在 RowNumber 下或下面的任何行都向下移动, 为新行腾出空间. 如果 RowNumber 大于列表中行的数目(甚至可以高至 2147483647), 则新行被添加到列表的底部. 要了解 Options, 请参阅行选项.

### LV_Modify

修改行的属性和/或文本.

    LV_Modify(RowNumber , Options, NewCol1, NewCol2, ...)

成功时返回 1, 而失败时返回 0. 如果 RowNumber 为 0, 则修改控件中所有行(此时如果操作完全成功时返回 1 而部分失败时返回 0). 如果只存在前两个参数, 则仅修改行的属性而不改变其文本. 同样, 如果参数太少导致无法覆盖所有列, 则后面剩下的列保持不变. 列选项可用于更新特定的列, 而不影响其他列. 有关其他选项, 请参阅行选项.

### LV_Delete

删除指定行或所有行.

    LV_Delete(RowNumber)

如果省略此参数, 则删除 ListView 中所有行. 否则, 仅删除指定 RowNumber 所在的行. 成功时返回 1 而失败时返回 0.

### LV_ModifyCol

修改指定列和标题的属性和/或文本.

    LV_ModifyCol(ColumnNumber, Options, ColumnTitle)

首列编号为 1(不是 0). 如果省略所有参数, 则调整所有列的宽度以适应行的内容. 如果仅存在首个参数, 则仅调整指定列的大小. 只有在报告(详细信息) 视图中自动调整大小才有效果. 此函数成功时返回 1 而失败时返回 0.

#### 列选项

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

#### 列选项: 常规

N: 指定 N 为列的新宽度, 单位为像素. 如果这是唯一的选项, 则此数字可以不加引号. 例如, 后面两种形式都是有效的: `LV_ModifyCol(1, 50)` 和 `LV_ModifyCol(1, "50 Integer")`.

Auto: 调整列宽来适应其内容. 只有在报告(详细信息) 视图中此选项才有效果.

AutoHdr: 调整列宽来适应其内容和列标题的文本, 以其中较宽的为准. 当应用于最后一列时, 它将被设置为至少和 ListView 中剩余空间的宽度一样. 通常最好仅在行已经添加后才应用此设置, 因为这样允许当调整最后一列的大小时考虑到可能新增加的垂直滚动栏. 只有在报告(详细信息) 视图中此选项才有效果.

Icon: 指定单词 Icon 后紧跟着图像列表中要显示在列标题文本后的图标的编号. 指定 `-Icon`(负 icon) 来移除任何现有的图标.

IconRight: 把图标放在列的右边而不是左边.

#### 列选项: 数据类型

Float: 为了进行排序, 指明此列包含浮点数(不支持十六进制格式). 浮点数和文本列的排序性能最多可能比整数列的慢 25 倍.

Integer: 为了进行排序, 指明此列包含整数. 要正确排序, 每个整数必须是 32 位的; 即在范围 -2147483648 到 2147483647 内. 任何非整数的值在排序时将被视为零(不过如果这些值以一个数字开头, 则使用这个数字). 数字格式可以为十进制或十六进制(例如 `0xF9E0`).

Text: 更改列返回到文本模式进行排序, 这是每列初始默认的排序方式. 在排序时只有文本开始的 8190 个字符是有意义的(如果如果使用了 Logical 选项, 此时限制为 4094).

#### 列选项: 对齐 / 字距调整

Center: 让文本居于列中央. 要让 Integer 或 Float 列居中, 请把单词 Center 加到 Integer 或 Float 后.

Left: 让列文本左对齐, 这是每列初始默认的对齐方式. 在较旧的操作系统中, 首列可能会强制进行左对齐.

Right: 让列文本右对齐. 对于 Integer 和 Float 数列不需要指定此属性, 因为它们默认是右对齐的. 通过指定例如 `"Integer Left"` 或 `"Float Center"` 的字符串可以覆盖此默认的对齐方式.

#### 列选项: 排序

Case: 让列排序区分大小写(仅影响文本列). 如果 Case, CaseLocale 以及 Logical 选项都省略, 则在排序中大写字母 A-Z 被视为等同于它们相应的小写形式.

CaseLocale [v1.0.43.03+]: 让列排序基于当前用户的区域设置且不区分大小写(仅影响文本列). 例如, 大多数英语和西欧地区把字母 A-Z 和 ANSI 字母例如 Ä 和 Ü 等同于它们的小写形式. 这种方法也使用 "单词排序", 它把单词中的连字符和撇号例如 "coop" 和 "co-op" 保持在一起.

Desc: 降序排列. 用户首次对此列排序时使用下降的顺序.

Logical [v1.0.44.12+]: 与 CaseLocale 相同, 除了把文本中的任何数字位序列看成真正的数字而不仅仅字符. 例如, 字符串 "T33" 将被视为大于 "T4". Logical 需要 Windows XP 或更高版本(在较旧的操作系统中, 会自动使用 CaseLocale 代替). 此外, Logical 和 Case 是互斥的: 只有最近指定的那个才有效.

NoSort: 避免用户点击此列对自动排序产生的影响. 要禁用所有列的排序而不仅是其中的子集, 请在 ListView 的选项中包含 NoSort. 如果 ListView 含有 g-标签, 则当用户点击 no-sort 的列时仍会接收到 ColClick 通知.

Sort: 立即对列按升序排列(即使它含有 Desc 选项).

SortDesc: 立即对列按降序排列.

Uni: 单向排序. 此选项防止在相同列进行第二次点击来反转排序方向.

### LV_InsertCol

在指定列号插入一个新列.

    LV_InsertCol(ColumnNumber , Options, ColumnTitle)

创建新列, 插入到指定的 ColumnNumber(移动其他的列到右边来腾出空间). 首列编号为 1(不是 0). 如果 ColumnNumber 大于当前控件中列的数目, 则新列被添加到列表的末尾(最右边). 新插入的列初始内容为空, 然而如果它是首列则会继承原来首列的内容, 同时原来首列的内容变为空的. 新列的属性(例如是否使用整数排序) 开始时总为它们的默认值, 不过可以使用选项改变. 此函数返回新列的位置编号(失败时为 0). ListView 中的最大列数为 200.

### LV_DeleteCol

删除指定的列及其中所有内容.

    LV_DeleteCol(ColumnNumber)

成功时返回 1 而失败时返回 0. 当一个列被删除后, 在此列右边的任何列的编号减小 1. 因此, 调用 `LV_DeleteCol(2)` 两次将删除第二列和第三列. 在比 Windows XP 旧的操作系统中, 试图删除原始的首列可能会失败并返回 0.

### LV_GetCount

返回行或列的总数, 或只返回选定行数.

    LV_GetCount(Mode)

省略参数时, 函数返回控件中的总行数. 当参数为 "S" 或 "Selected" 时, 计数仅包括选择的/高亮的行. 当参数为 "Col" 或 "Column", 函数返回控件中的列数. 此函数总是即时的, 因为控件会跟踪这些计数.

此函数常用在一个循环的顶行, 此时将仅调用一次函数(在首次循环前). 例如:

    Loop % LV_GetCount()
    {
        LV_GetText(RetrievedText, A_Index)
        if InStr(RetrievedText, "some filter text")
            LV_Modify(A_Index, "Select")  ; 选择首个字段包含过滤文本的所有行.
    }

要获取 ListView 的列宽(用于例如保存它们到 INI 文件以便在会话期间保持一致), 请参照此例:

    Gui +LastFound
    Loop % LV_GetCount("Column")
    {
        SendMessage, 0x101D, A_Index - 1, 0, SysListView321  ; 0x101D 为 LVM_GETCOLUMNWIDTH.
        MsgBox Column %A_Index%'s width is %ErrorLevel%.
    }

### LV_GetNext

返回下一个选择的, 选中的或焦点行的行号.

    LV_GetNext(StartingRowNumber, RowType)

如果没有找到, 则返回零. 如果 StartingRowNumber 省略或小于 1, 则从列表的顶部开始搜索. 否则, 从 StartingRowNumber 行后开始搜索. 如果省略 RowType, 则函数搜索下一个选择的/高亮的行. 否则, 指定 "C" 或 "Checked" 来寻找下一个选中的行; 或指定 "F" 或 "Focused" 来寻找焦点行(在整个列表中不可能有多个焦点行, 且有时甚至没有). 下面的例子, 报告 ListView 中所有选择的行:

    RowNumber := 0  ; 这样使得首次循环从列表的顶部开始搜索.
    Loop
    {
        RowNumber := LV_GetNext(RowNumber)  ; 在前一次找到的位置后继续搜索.
        if not RowNumber  ; 上面返回零, 所以选择的行已经都找到了.
            break
        LV_GetText(Text, RowNumber)
        MsgBox The next selected row is #%RowNumber%, whose first field is "%Text%".
    }

判断一个特定的行是否被选中的另一种方法如下:

    Gui +LastFound
    SendMessage, 0x102C, RowNumber - 1, 0xF000, SysListView321  ; 0x102C 为 LVM_GETITEMSTATE. 0xF000 为 LVIS_STATEIMAGEMASK.
    IsChecked := (ErrorLevel >> 12) - 1  ; 如果 RowNumber 为选中的则设置 IsChecked 为真, 否则为假.

### LV_GetText

检索指定 RowNumber 和 ColumnNumber 上的文本并保存到 OutputVar.

    LV_GetText(OutputVar, RowNumber , ColumnNumber)

如果省略 ColumnNumber, 则它默认为 1(首列的文本). 如果 RowNumber 为 0, 则获取列标题的文本. 如果文本长度超过 8191, 则仅获取前 8191 个字符. 此函数成功时返回 1 而失败时返回 0. 失败时, OutputVar 也被置空.

脚本中看到的列编号不会因为用户所做的拖放而改变. 例如, 即使用户已经把它拖到其他列的右边, 最初首列的编号仍是 1.

### LV_SetImageList

设置或替换用于图标显示的 ImageList(图像列表).

    LV_SetImageList(ImageListID , IconType)

此函数一般在 ListView 添加行之前调用. 它设置 ImageList, 其中的图标会显示在 ListView 的行中(可选的, 以及列). ImageListID 为前面调用 IL_Create() 返回的数字. 如果省略 IconType, 则自动检测图像列表中的图标为大图标还是小图标. 否则, 对于大图标指定 0, 对于小图标指定 1, 而状态图标则为 2(还没有直接支持状态图标, 不过可以通过 SendMessage 使用).

一个 ListView 最多可以关联两个图像列表: 小图标和/或大图标. 当脚本允许用户在大图标视图和其他视之间切换的时候, 这是很有用的. 要再关联一个图像列表到 ListView, 请再次调用 LV_SetImageList(), 其中指定第二个列表的 ImageListID. 同时关联有大图标和小图标图像列表的 ListView 应该确保两个列表中包含图标的顺序相同. 这是因为使用一个指定图标的大小版本时, 会引用相同的 ID 编号.

尽管除了图标模式和平铺模式外, 传统的所有的视图模式都显示小图标, 但可以通过传递一个大图标列表到 LV_SetImageList() 并且在第二个参数中指定 1(小图标) 覆盖此行为. 这也增加了 ListView 中每行的高度以适合大图标.

如果成功, 则 LV_SetImageList() 返回之前与 ListView 关联的 ImageListID(如果没有则为 0). 任何分离的图像列表一般应通过 IL_Destroy(ImageListID) 进行销毁.

## G-标签通知(主要)

g-标签例如 `gMySubroutine` 可以使用在此控件的选项中. 这使得当用户在控件中执行动作时 MySubroutine 标签会自动运行. 此子程序中可引用内置变量 A_Gui 和 A_GuiControl 来找出产生事件的是哪个窗口和 ListView. 更重要的是, 它可以参考 A_GuiEvent, 其包含下列字符串或字母的其中一个(考虑到和未来版本的兼容性, 脚本不应假定这些字符串或字母是唯一可能的值):

DoubleClick: 用户在控件内双击了. 变量 A_EventInfo 包含焦点行号. LV_GetNext() 可用来获取首个 选中的 行号, 当用户在空位置双击时为 0.

R: 用户在控件内 双击了右键. 变量 A_EventInfo 包含焦点行号.

ColClick: 用户点击了列标题. 变量 A_EventInfo 包含列编号, 这是在列创建时分配的原始编号; 即它不会反映任何用户对列完成的拖放操作. 对列点击的一种可能的响应是通过一个包含排序友好格式(例如 YYYYMMDD 的整数日期) 数据的隐藏列(零宽度) 进行排序. 这样的隐藏列可以以更友好的格式(例如 MM/DD/YY) 显示与其他某列相同的数据. 例如, 脚本可以通过 `LV_ModifyCol(3, 0)` 隐藏 column 3, 接着通过 `LV_ModifyCol(2, "NoSort")` 关闭可见的 column 2 的自动排序. 然后为了响应 column 2 的 ColClick 通告, 脚本通过 `LV_ModifyCol(3, "Sort")` 使用隐藏的列对 ListView 进行排序.

D: 用户尝试开始拖动行或图标(目前还没有内置对拖动行或图标的支持). 变量 A_EventInfo 包含焦点行号. [v1.0.44+]: 即使不含 AltSubmit 此通知也会产生.

d(小写的 D): 和上面相同, 除了指右键拖动而不是左键.

e(小写的 E): 用户完成编辑一行的首个字段(只有当 ListView 选项中含有 `-ReadOnly` 时用户才可以进行编辑). 变量 A_EventInfo 包含目标行号.

## G-标签通知(次要)

如果 ListView 的选项中含有单词 AltSubmit, 则其 g-标签会运行的更频繁并且 A_GuiEvent 还可能包含下列值:

Normal: 用户左键单击了一行. 变量 A_EventInfo 包含焦点行号.

RightClick: 用户右键单击了一行. 变量 A_EventInfo 包含焦点行号. 在大多数情况下, 最好不要通过显示菜单来响应此通知. 而应使用 GuiContextMenu 标签, 因为它还能识别 Apps 键. 例如:

    GuiContextMenu:  ; 运行此标签来响应右键点击或按下 Apps 键.
    if (A_GuiControl != "MyListView")  ; 这个检查是可选的. 让它只为 ListView 中的点击显示菜单.
        return
    ; 在提供的坐标处显示菜单, A_GuiX 和 A_GuiY. 应该使用这些
    ; 因为即使用户按下 Apps 键它们也会提供正确的坐标:
    Menu, MyContextMenu, Show, %A_GuiX%, %A_GuiY%
    return

A: 激活了一行, 这种情况默认发生在双击这行的时候. 变量 A_EventInfo 包含目标行号.

C: ListView 释放了鼠标捕获.

E: 用户开始编辑一行的首个字段(只有在 ListView 选项中含有 `-ReadOnly` 时用户才可以进行编辑). 变量 A_EventInfo 包含目标行号.

F: ListView 接收到键盘焦点.

f(小写的 F): ListView 失去了键盘焦点.

I: 项目发生了变化. 通过变成选择的/未选择的, 选中的/未选中的等让一行改变了. 如果用户选择了一个新行, 则至少会接收到两个这样的通知: 一个是在取消之前的行时, 而另一个是在选择新行时. [v1.0.44+]: 变量 A_EventInfo 包含目标行号. [v1.0.46.10+]: ErrorLevel 包含零个或多个下列字母来表示项目发生的变化: S(选择) 或 s(取消选择), 和/或 F(成为焦点) 或 f(失去焦点), 和/或 C(选中) 或 c(取消选中). 例如, SF 表示此行被选择且成为了焦点. 要检查是否存在一个特定的字母, 请使用解析循环或带区分大小写选项的 InStr(); 例如: `InStr(ErrorLevel, "S", true)`. 注: 考虑到和未来版本的兼容性, 脚本不应假定 "SsFfCc" 包含了所有可能的字母或字母组合. 此外, 指定 Critical 作为 g-标签 子程序的首行来确保接收到所有的 "I" 通知(否则, 如果脚本无法跟上, 则它们其中一些可能会丢失).

K: 当 ListView 拥有焦点时用户按下了一个键. A_EventInfo 包含此键的虚拟按键代码, 这是介于 1 和 255 之间的数字. 然后可以通过 GetKeyName() 转换为按键名称或字符. 例如, `key := GetKeyName(Format("vk{:x}", A_EventInfo))`. 在大多数键盘布局中, A-Z 键都可以通过 `Chr(A_EventInfo)` 转换为相应的字符. `F2` 的按键动作总是被接收且不会被 WantF2 影响. 但是, `Enter` 不会被接收; 除非按照下面的方法设置为默认按钮.

M: 选取框. 用户开始在一组行或图标周围拖动一个矩形选区.

S: 用户开始滚动 ListView.

s(小写的 S): 用户完成滚动 ListView.

## 图像列表

图像列表是保存在内存中的一组大小相同的图标. 创建时每个图像列表是空的. 脚本重复调用 IL_Add() 来添加图标到列表中, 且给每个图标分配一个序号, 序号从 1 开始. 脚本引用此编号在一行中或列标题中显示一个特定的图标. 这是个可运行示例, 其中演示了如何在 ListView 的行中显示图标:

    Gui, Add, ListView, h200 w180, Icon & Number|Description  ; 创建 ListView.
    ImageListID := IL_Create(10)  ; 创建加载 10 个小图标的图像列表.
    LV_SetImageList(ImageListID)  ; 把上面的图像列表指定给当前的 ListView.
    Loop 10  ; 把 DLL 中的一系列图标装入图像列表.
        IL_Add(ImageListID, "shell32.dll", A_Index)
    Loop 10  ; 在 ListView 中添加行(出于演示的目的, 每个图标一行).
        LV_Add("Icon" . A_Index, A_Index, "n/a")
    LV_ModifyCol("Hdr")  ; 自动调整列宽.
    Gui Show
    return

    GuiClose:  ; 当用户关闭 ListView 所在 GUI 窗口时退出脚本.
    ExitApp

### IL_Create

创建新的初始为空的图像列表, 并返回图像列表的唯一 ID(失败时为 0).

    IL_Create(InitialCount, GrowCount, LargeIcons)

InitialCount 为希望马上放入列表的图标数(如果省略, 则它默认为 2). GrowCount 为列表每次将增长时超出当前列表容量的图标数(如果省略, 则它默认为 5). LargeIcons 应该为数值: 如果不为零, 则图像列表将包含大图标. 如果为零, 则它包含小图标(这是省略时的默认情况). 会按比例对添加到列表中的图标自动进行缩放以符合系统中大图标和小图标的尺寸.

### IL_Add

添加图标或图片到指定的 ImageListID 并返回新图标的索引(首个图标索引为 1, 第二个为 2, 依此类推).

    IL_Add(ImageListID, Filename , IconNumber, ResizeNonIcon)

Filename 为图标(.ICO), 光标(.CUR) 或动画光标(.ANI) 文件的名称(动画光标在 ListView 中显示时实际将不会动). 图标的其他来源包含下列类型的文件: EXE, DLL, CPL, SCR, 以及包含图标资源的其他类型. 要使用文件中第一个以外的图标组, 请在 IconNumber 指定它的编号. 如果 IconNumber 为负数, 则假定其绝对值表示可执行文件中图标的资源 ID. 在下面的例子中, 将使用第二个图标组中的默认图标: `IL_Add(ImageListID, "C:\My Application.exe", 2)`.

还可以加载非图标图像, 例如 BMP, GIF 和 JPG. 然而, 此时应该指定最后两个参数以确保正确执行: IconNumber 应该为屏蔽的/透明的颜色编码(对于大多数图片 0xFFFFFF [白色] 可能是最佳的); 而 ResizeNonIcon 应该为非零值来缩放图片为单个图标, 或者为零来把图像分割为多个可以匹配实际宽度的图标.

在所有的操作系统中都支持 GIF, JPG, BMP, ICO, CUR 和 ANI 图像. 在 Windows XP 或更高版本中, 还支持其他图像格式, 例如 PNG, TIF, Exif, WMF 和 EMF. 比 XP 早的操作系统可以通过复制微软免费的 GDI+ DLL 到 AutoHotkey.exe 文件夹中来提供支持(但如果是已编译脚本, 则复制此 DLL 到脚本的文件夹). 要下载这个 DLL, 请在 www.microsoft.com 搜索下列短语: gdi redistributable

[v1.1.23+]: 位图或图标句柄可用于替代文件名称. 例如, `HBITMAP:%handle%`.

### IL_Destroy

删除指定的图像列表, 成功时返回 1, 而失败返回 0.

    IL_Destroy(ImageListID)

通常没必要销毁图像列表, 因为一旦关联到 ListView, 当 ListView 或其父窗口销毁时它们会被自动销毁. 但是, 如果一个 ListView 和其他 ListView 共享图像列表(通过在选项中包含 `0x40`), 在所有使用此图像列表的 ListView 销毁后脚本应该明确地销毁它. 同样地, 如果脚本使用一个新的图像列表代替 ListView 中原来的, 则应该明确销毁原来的图像列表.

## ListView 备注

Gui Submit 命令对 ListView 控件没有效果. 因此, 脚本可以使用 ListView 的关联变量(如果有) 来保存其他数据而不用担心它会被覆盖.

在对某个列进行排序后(通过用户点击其标题的方法或脚本调用 `LV_ModifyCol(1, "Sort")`), 任何随后添加的行将出现在列表的底部而不会遵循排序顺序. 例外情况是 Sort 和 SortDesc 样式, 它们会把新添加的行移动正确的位置.

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

    Gui, Add, Button, Hidden Default, OK
    ...
    ButtonOK:
    GuiControlGet, FocusedControl, FocusV
    if (FocusedControl != "MyListView")
        return
    MsgBox % "Enter was pressed. The focused row number is " . LV_GetNext(0, "Focused")
    return

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

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

尽管 ListView 中可以使用的最大行数仅受系统可用内存的限制, 但参照 Count 选项中描述的方法可以极大地提高添加行的性能.

可以使用图片作为 ListView 周围的背景(即作为 ListView 的框架). 要实现这个效果, 请在 ListView 后创建图片控件并且在图片控件的 选项 中包含 `0x4000000`(这是 WS_CLIPSIBLINGS).

脚本可以在每个窗口中创建多个 ListView. 要对非默认的 ListView 进行操作, 请参阅内置函数.

最好不要使用 SendMessage 直接插入或删除列. 这是因为程序为每个列维护一个排序参数集, 而这会让它们无法同步. 作为替代, 请使用内置列函数.

要对 ListView 执行一些操作(例如调整大小, 隐藏或改变字体), 请使用 GuiControl.

要从外部的 ListView(不属于脚本自身的那些) 中提取文本, 请使用 ControlGet List.

## 相关

TreeView, 其他控件类型, Gui, GuiContextMenu, GuiControl, GuiControlGet, ListView 样式表

## 示例

通过指定 0 作为行号来选择或取消选择所有的行.

    LV_Modify(0, "Select")   ; 选择所有.
    LV_Modify(0, "-Select")  ; 取消选择所有.
    LV_Modify(0, "-Check")  ; 取消选中所有复选框.

自动根据内容调整所有列的宽度.

    LV_ModifyCol()  ; 此时不需要参数.

下面是一个比页面顶部的脚本更复杂的可工作的脚本. 它显示用户选择的文件夹中的文件, 且每个文件分配一个与其类型关联的图标. 用户在一个文件上双击或在一个或多个文件上右击后, 会显示上下文菜单:

    ; 允许用户最大化窗口或拖动来改变窗口的大小.
    Gui +Resize

    ; 创建一些按钮:
    Gui, Add, Button, Default gButtonLoadFolder, Load a folder
    Gui, Add, Button, x+20 gButtonClear, Clear List
    Gui, Add, Button, x+20, Switch View

    ; 通过 Gui Add 创建 ListView 及其列:
    Gui, Add, ListView, xm r20 w700 vMyListView gMyListView, Name|In Folder|Size (KB)|Type
    LV_ModifyCol(3, "Integer")  ; 为了排序, 表示 Size 列中的内容是整数.

    ; 创建图像列表, 这样 ListView 才可以显示图标:
    ImageListID1 := IL_Create(10)
    ImageListID2 := IL_Create(10, 10, true)  ; 大图标列表和小图标列表.

    ; 关联图像列表到 ListView, 然而它就可以显示图标了:
    LV_SetImageList(ImageListID1)
    LV_SetImageList(ImageListID2)

    ; 创建作为上下文菜单的弹出菜单:
    Menu, MyContextMenu, Add, Open, ContextOpenFile
    Menu, MyContextMenu, Add, Properties, ContextProperties
    Menu, MyContextMenu, Add, Clear from ListView, ContextClearRows
    Menu, MyContextMenu, Default, Open  ; 让 "Open" 粗体显示表示双击时会执行相同的操作.

    ; 显示窗口并返回. 当用户执行预期的动作时
    ; 操作系统会通知脚本:
    Gui, Show
    return

    ButtonLoadFolder:
    Gui +OwnDialogs  ; 强制用户解除此对话框后才可以操作主窗口.
    FileSelectFolder, Folder,, 3, Select a folder to read:
    if not Folder  ; 用户取消了对话框.
        return

    ; 检查文件夹名称的最后一个字符是否为反斜杠, 对于根目录则会如此,
    ; 例如 C:\. 如果是, 则移除这个反斜杠以避免之后出现两个反斜杠.
    LastChar := SubStr(Folder, 0)
    if (LastChar = "\")
        Folder := SubStr(Folder, 1, -1)  ; 移除尾随的反斜杠.

    ; 计算 SHFILEINFO 结构需要的缓存大小.
    sfi_size := A_PtrSize + 8 + (A_IsUnicode ? 680 : 340)
    VarSetCapacity(sfi, sfi_size)

    ; 获取所选择文件夹中的文件名列表并添加到 ListView:
    GuiControl, -Redraw, MyListView  ; 在加载时禁用重绘来提升性能.
    Loop %Folder%\*.*
    {
        FileName := A_LoopFileFullPath  ; 必须保存到可写的变量中供后面使用.

        ; 建立唯一的扩展 ID 以避免变量名中的非法字符,
        ; 例如破折号. 这种使用唯一 ID 的方法也会执行地更好,
        ; 因为在数组中查找项目不需要进行搜索循环.
        SplitPath, FileName,,, FileExt  ; 获取文件扩展名.
        if FileExt in EXE,ICO,ANI,CUR
        {
            ExtID := FileExt  ; 特殊 ID 作为占位符.
            IconNumber := 0  ; 标记为未找到, 以便这些类型可以各自拥有一个唯一的图标.
        }
        else  ; 其他的扩展名/文件类型, 所以要计算它们的唯一 ID.
        {
            ExtID := 0  ; 进行初始化来处理为更短的扩展名.
            Loop 7     ; 限制扩展名为 7 个字符, 这样之后计算的结果才能存放到 64 位值.
            {
                ExtChar := SubStr(FileExt, A_Index, 1)
                if not ExtChar  ; 没有更多字符了.
                    break
                ; 通过给每个字符分配一个不同的比特位置, 来得到唯一 ID:
                ExtID := ExtID | (Asc(ExtChar) << (8 * (A_Index - 1)))
            }
            ; 检查此文件扩展名的图标是否已经在图像列表中. 如果是,
            ; 可以避免多次调用并极大提高性能,
            ; 尤其对于包含数以百计文件的文件夹而言:
            IconNumber := IconArray%ExtID%
        }
        if not IconNumber  ; 此扩展名还没有相应的图标, 所以进行加载.
        {
            ; 获取与此文件扩展名关联的高质量小图标:
            if not DllCall("Shell32\SHGetFileInfo" . (A_IsUnicode ? "W":"A"), "Str", FileName
                , "UInt", 0, "Ptr", &sfi, "UInt", sfi_size, "UInt", 0x101)  ; 0x101 为 SHGFI_ICON+SHGFI_SMALLICON
                IconNumber := 9999999  ; 把它设置到范围外来显示空图标.
            else ; 成功加载图标.
            {
                ; 从结构中提取 hIcon 成员:
                hIcon := NumGet(sfi, 0)
                ; 直接添加 HICON 到小图标和大图标列表.
                ; 下面加上 1 来把返回的索引从基于零转换到基于一:
                IconNumber := DllCall("ImageList_ReplaceIcon", "Ptr", ImageListID1, "Int", -1, "Ptr", hIcon) + 1
                DllCall("ImageList_ReplaceIcon", "Ptr", ImageListID2, "Int", -1, "Ptr", hIcon)
                ; 现在已经把它复制到图像列表, 所以应销毁原来的:
                DllCall("DestroyIcon", "Ptr", hIcon)
                ; 缓存图标来节省内存并提升加载性能:
                IconArray%ExtID% := IconNumber
            }
        }

        ; 在 ListView 中创建新行并把它和上面的图标编号进行关联:
        LV_Add("Icon" . IconNumber, A_LoopFileName, A_LoopFileDir, A_LoopFileSizeKB, FileExt)
    }
    GuiControl, +Redraw, MyListView  ; 重新启用重绘(上面把它禁用了).
    LV_ModifyCol()  ; 根据内容自动调整每列的大小.
    LV_ModifyCol(3, 60) ; 把 Size 列加宽一些以便显示出它的标题.
    return

    ButtonClear:
    LV_Delete()  ; 清理 ListView, 但为了简化保留了图标缓存.
    return

    ButtonSwitchView:
    if not IconView
        GuiControl, +Icon, MyListView    ; 切换到图标视图.
    else
        GuiControl, +Report, MyListView  ; 切换回详细信息视图.
    IconView := not IconView             ; 进行反转以为下次做准备.
    return

    MyListView:
    if (A_GuiEvent = "DoubleClick")  ; 脚本还可以检查许多其他的可能值.
    {
        LV_GetText(FileName, A_EventInfo, 1) ; 从首个字段中获取文本.
        LV_GetText(FileDir, A_EventInfo, 2)  ; 从第二个字段中获取文本.
        Run %FileDir%\%FileName%,, UseErrorLevel
        if ErrorLevel
            MsgBox Could not open "%FileDir%\%FileName%".
    }
    return

    GuiContextMenu:  ; 运行此标签来响应右键点击或按下 Apps 键.
    if (A_GuiControl != "MyListView")  ; 仅在 ListView 中点击时才显示菜单.
        return
    ; 在提供的坐标处显示菜单, A_GuiX 和 A_GuiY. 应该使用这些
    ; 因为即使用户按下 Apps 键, 它们也会提供正确的坐标:
    Menu, MyContextMenu, Show, %A_GuiX%, %A_GuiY%
    return

    ContextOpenFile:  ; 用户在上下文菜单中选择了 "Open".
    ContextProperties:  ; 用户在上下文菜单中选择了 "Properties".
    ; 为了简化, 仅对焦点行进行操作而不是所有选择的行:
    FocusedRowNumber := LV_GetNext(0, "F")  ; 查找焦点行.
    if not FocusedRowNumber  ; 没有焦点行.
        return
    LV_GetText(FileName, FocusedRowNumber, 1) ; 获取首个字段的文本.
    LV_GetText(FileDir, FocusedRowNumber, 2)  ; 获取第二个字段的文本.
    if InStr(A_ThisMenuItem, "Open")  ; 用户在上下文菜单中选择了 "Open".
        Run %FileDir%\%FileName%,, UseErrorLevel
    else  ; 用户在上下文菜单中选择了 "Properties".
        Run Properties "%FileDir%\%FileName%",, UseErrorLevel
    if ErrorLevel
        MsgBox Could not perform requested action on "%FileDir%\%FileName%".
    return

    ContextClearRows:  ; 用户在上下文菜单中选择了 "Clear".
    RowNumber := 0  ; 这会使得首次循环从顶部开始搜索.
    Loop
    {
        ; 由于删除了一行使得此行下面的所有行的行号都减小了,
        ; 所以把行号减 1, 这样搜索里包含的行号才会与之前找到的行号相一致
        ; (以防选择了相邻行):
        RowNumber := LV_GetNext(RowNumber - 1)
        if not RowNumber  ; 上面返回零, 所以没有更多选择的行了.
            break
        LV_Delete(RowNumber)  ; 从 ListView 中删除行.
    }
    return

    GuiSize:  ; 扩大或缩小 ListView 来响应用户对窗口大小的改变.
    if (A_EventInfo = 1)  ; 窗口被最小化了. 无需进行操作.
        return
    ; 否则, 窗口的大小被调整过或被最大化了. 调整 ListView 的大小来适应.
    GuiControl, Move, MyListView, % "W" . (A_GuiWidth - 20) . " H" . (A_GuiHeight - 40)
    return

    GuiClose:  ; 当窗口关闭时, 自动退出脚本:
    ExitApp

dftg