# ListView

## 目录

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

## 入门和简单示例

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

ListView 通常是这样的:

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

    LV := GuiObj.Add("ListView", Options, ["ColumnTitle1","ColumnTitle2","..."])

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

    ; 创建窗口:
    MyGui := Gui()

    ; 创建含名称和大小两列的 ListView:
    LV := MyGui.Add("ListView", "r20 w700", ["Name","Size (KB)"])

    ; 每当用户双击某行时, 通知脚本:
    LV.OnEvent("DoubleClick", LV_DoubleClick)

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

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

    ; 显示窗口:
    MyGui.Show

    LV_DoubleClick(LV, RowNumber)
    {
        RowText := LV.GetText(RowNumber)  ; 从行的第一个字段中获取文本.
        ToolTip("You double-clicked row number " RowNumber ". Text: '" RowText "'")
    }

## Options 参数的选项和样式

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

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

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

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

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

Hdr: 指定 `-Hdr`(负 Hdr) 来隐藏标题(包含列标题的顶行). 后面要让它显示, 请使用 `LV.Opt("+Hdr")`.

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

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

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

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

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

NoSort: 关闭当用户点击标题时发生的自动排序. 然而, 标题的外观看起来仍像按钮(除非指定了 NoSortHdr). 此外, ColClick 事件仍然会被触发, 所以脚本可以用自定义的排序或其他操作来响应.

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

R: 行高(创建时). 指定字母 R 后面紧跟着要在控件中留出空间的行数. 例如, `R10` 会设置控件为 10 行的高度. 如果 ListView 是用报表视图以外的视图模式创建的, 那么控件的大小将适合于图标行而不是文本行. 注意: 在 ListView 的行中添加图标将增加每行的高度, 这将使该选项不准确.

Sort: 控件根据第一列的内容按字母顺序进行排序.

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

WantF2: 指定 `-WantF2`(负 WantF2) 阻止 `F2` 键编辑当前聚焦的行. 这个设置会被忽略, 除非 `-ReadOnly` 也有效.

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

## 视图模式

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

Icon: 显示大图标视图. 在该视图和除 Report 之外的所有其他视图中, 除第一列之外的其他列中的文本是不可见的. 要在这种模式下显示图标, 必须有一个大图标 ImageList 分配给它.

Tile: 显示大图标视图, 但具有人体工学差异, 例如每项的文本显示在图标的右边而不是下面. Checkbox 在此视图中不起作用.

IconSmall: 显示小图标视图.

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

Report: 切换回报告视图, 这是最初的默认模式. 例如: `LV.Opt("+Report")`.

## ListViews 的内置方法

除了 GUI 控件的默认方法/属性外, ListView 控件还有以下方法.

当在此页面使用短语 "行号" 时, 它指的是行在 ListView 中的当前位置. 最上面的行是 1, 第二行是 2, 以此类推. 当行被添加后, 它的行号往往会因为其他行的排序, 删除和插入而改变. 因此, 如果要根据行的内容找到特定的行, 通常最好在循环中使用 GetText 方法.

行的方法:

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

列的方法:

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

检索方法:

- GetCount: 返回控件中行或列的总数.
- GetNext: 返回下一个选择, 选中或具有焦点的行的行号.
- GetText: 检索指定行号和列号的文本.

其他方法:

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

### Add

添加新行到列表底部, 并返回新的行号, 如果 ListView 具有 Sort 或 SortDesc 样式, 则不一定是最后一行.

    NewRowNumber := LV.Add(Options, Col1, Col2, ...)

Options

  类型: 字符串

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

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

  Col: 指定单词 Col 后紧跟着列号, 从此列开始往后应用参数 Col1 及以后. 此选项通常和 Modify 方法一起使用来修改行中的个别字段, 而不会影响其左侧的字段.

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

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

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

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

Col1, Col2, ...

  类型: 字符串

  新行的列, 可以是文本或数字(包括数字表达式的结果). 要使任何字段成为空, 请指定 "" 或等价物. 如果字段太少无法填满所有的列, 那么末尾的列将被留空. 如果字段太多, 末尾的字段将被完全忽略.

### Insert

在指定行之前插入一个新行, 并返回新的行号.

    NewRowNumber := LV.Insert(RowNumber , Options, Col1, Col2, ...)

RowNumber

  类型: 整数

  新插入的行的行号. 任何位于 RowNumber 或其下方的行都会被向下移动, 以便为新行腾出空间. 如果 RowNumber 大于列表中的行数(甚至大于 2147483647), 新的行将被添加到列表的最后.

Options

  类型: 字符串

  请参阅 row 选项.

Col1, Col2, ...

  类型: 字符串

  新行的列, 可以是文本或数字(包括数字表达式的结果). 要使任何字段成为空, 请指定 "" 或等价物. 如果字段太少无法填满所有的列, 那么末尾的列将被留空. 如果字段太多, 末尾的字段将被完全忽略.

### Modify

修改行的属性和/或文本, 成功后返回 1, 失败后返回 0.

    LV.Modify(RowNumber , Options, NewCol1, NewCol2, ...)

注意: 如果只存在前两个参数, 则仅修改行的属性而不改变其文本.

RowNumber

  类型: 整数

  要修改的行. 如果 RowNumber 为 0, 那么控件中的 所有 的行都会被修改(在这种情况下, 如果操作完全成功, 返回值为 1, 如果操作的任何部分失败, 返回值为 0).

Options

  类型: 字符串

  ColN 选项可用于更新特定的列而不影响其他列. 有关其他选项, 请参阅 row 选项.

NewCol1, NewCol2, ...

  类型: 字符串

  指定行的新列, 可以是文本或数字(包括数字表达式的结果). 要使任何字段成为空, 请指定 "" 或等价物. 如果字段太少无法填满所有的列, 那么末尾的列将被留空. 如果字段太多, 末尾的字段将被完全忽略.

### Delete

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

    LV.Delete(RowNumber)

RowNumber

  类型: 整数

  要删除的行. 如果省略此参数, 则删除 ListView 中 所有的 行.

### ModifyCol

修改指定列和标题的属性和/或文本, 成功后返回 1, 失败后返回 0.

    LV.ModifyCol(ColumnNumber, Options, ColumnTitle)

注意: 如果省略所有参数, 则调整所有列的宽度以适应行的内容. 如果仅存在首个参数, 则仅调整指定列的大小. 只有在报告(详细信息) 视图中自动调整大小才有效果.

ColumnNumber

  类型: 整数

  要修改的列. 第一列编号为 1(不是 0).

Options

  类型: 字符串

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

  常规选项

  N: 指定 N 为列的新宽度, 单位为像素. 如果是唯一的选择, 这个数字可以不加引号. 例如, 后面两种形式都是有效的: `LV.ModifyCol(1, 50)` and `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: 让列排序基于当前用户的区域设置且不区分大小写(仅影响文本列). 例如, 大多数英语和西欧地区把字母 A-Z 和 ANSI 字母例如 Ä 和 Ü 等同于它们的小写形式. 这种方法也使用 "单词排序", 它把单词中的连字符和撇号例如 "coop" 和 "co-op" 保持在一起.

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

  Logical: 与 CaseLocale 相同, 除了把文本中的任何数字位序列看成真正的数字而不仅仅字符. 例如, 字符串 "T33" 将被视为大于 "T4". Logical 和 Case 是互斥的: 只有最近指定的那个才有效.

  NoSort: 避免用户点击此列对自动排序产生的影响. 然而, ColClick 事件仍然会被触发, 所以脚本可以用自定义的排序或其他操作来响应. 要禁用所有列的排序而不仅是其中的子集, 请在 ListView 的选项中包含 NoSort.

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

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

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

ColumnTitle

  类型: 字符串

  新列的标题. 省略此参数, 使其保持不变.

### InsertCol

在指定列之前插入一个新列, 并返回新的列的位置编号.

    NewColumnNumber := LV.InsertCol(ColumnNumber , Options, ColumnTitle)

ColumnNumber

  类型: 整数

  新插入列的列号. 在 ColumnNumber 处和其右侧的任何列都会向右移动, 以便为新列腾出空间. 第一列的编号为 1(不是 0). ListView 中的最大列数是 200. 如果 ColumnNumber 大于当前控件中的列数, 则新列被添加到列表的末尾(最右边). 新插入的列初始内容为空, 然而如果它是首列则会继承原来首列的内容, 同时原来首列的内容变为空的.

Options

  类型: 字符串

  新列的属性 -- 如是否使用整数排序 -- 总是以默认值开始, 除非通过 选项 更改.

ColumnTitle

  类型: 字符串

  新列的标题.

### DeleteCol

删除指定的列及其中所有内容, 成功后返回 1, 失败后返回 0.

    LV.DeleteCol(ColumnNumber)

ColumnNumber

  类型: 整数

  要删除的列. 当一列被删除后, 位于其右侧的列号将减少 1. 因此, 调用 `LV.DeleteCol(2)` 两次将删除第二列和第三列.

### GetCount

返回控件中行或列的总数.

    CountNumber := LV.GetCount(Mode)

Mode

  类型: 字符串

  当此参数被省略时, 返回控件中的总行数. 当此参数为 "S" 或 "Selected" 时, 计数只包括选中/高亮的行. 当该参数为 "Col" 或 "Column" 时, 返回控件中的列数. 返回值将被即时检索, 因为控件会跟踪这些计数.

这个方法通常用于 Loop 的顶行, 在这种情况下, 这个方法只会被调用一次(在首次循环前). 例如:

    Loop LV.GetCount
    {
        RetrievedText := LV.GetText(A_Index)
        if InStr(RetrievedText, "some filter text")
            LV.Modify(A_Index, "Select")  ; 选择首个字段包含过滤文本的所有行.
    }

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

    Loop LV.GetCount("Column")
    {
        ColWidth := SendMessage(0x101D, A_Index - 1, 0, LV)  ; 0x101D 是 LVM_GETCOLUMNWIDTH.
        MsgBox("Column " A_Index "'s width is " ColWidth ".")
    }

### GetNext

返回下一个选择, 选中或具有焦点的行的行号, 否则为零.

    RowNumber := LV.GetNext(StartingRowNumber, RowType)

StartingRowNumber

  类型: 整数

  如果省略或小于 1, 则从列表的顶部开始搜索. 否则, 从 StartingRowNumber 行后开始搜索.

RowType

  类型: 字符串

  如果省略 , 则搜索下一个选择的/高亮的行. 否则, 指定 "C" 或 "Checked" 来寻找下一个选中的行; 或 "F" 或 "Focused" 来寻找焦点行(在整个列表中不可能有多个焦点行, 且有时甚至没有).

下面的例子, 报告 ListView 中所有选择的行:

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

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

    ItemState := SendMessage(0x102C, RowNumber - 1, 0xF000, LV)  ; 0x102C 是 LVM_GETITEMSTATE. 0xF000 是 LVIS_STATEIMAGEMASK.
    IsChecked := (ItemState >> 12) - 1  ; 如果 RowNumber 为选中的则设置 IsChecked 为真, 否则为假.

### GetText

检索指定行号和列号的文本.

    RetrievedText := LV.GetText(RowNumber , ColumnNumber)

RowNumber

  类型: 整数

  要检索文本的行的编号. 如果该参数为 0, 则获取列标题的文本. RetrievedText 的最大长度为 8191.

ColumnNumber

  类型: 整数

  列的编号, 指定行所在的位置. 如果省略, 默认为 1(第一列的文本). 脚本看到的列号不会因为用户拖放列而改变. 例如, 最初首列的编号仍是 1, 即使用户把它拖到其他列的右边.

### 设置图标

### SetImageList

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

    PrevImageListID := LV.SetImageList(ImageListID , IconType)

ImageListID

  类型: 整数

  从之前调用 IL_Create 返回的数字.

IconType

  类型: 整数

  如果省略第二个参数, 则自动检测 ImageList 中的图标类型为大图标或小图标. 否则, 指定 0 为大图标, 1 为小图标, 2 为状态图标(状态图标还不直接支持, 但可以通过 SendMessage 使用).

这个方法通常在向 ListView 添加任何行之前被调用.

一个 ListView 最多可以有两个图像列表: 小图标和/或大图标. 当脚本允许用户在大图标视图之间进行切换时, 这很有用. 要再关联一个图像列表到 ListView, 请再次调用 SetImageList 方法, 指定第二个列表的 ImageListID. 一个同时拥有大图标和小图标 ImageList 的 ListView 应该确保两个列表中的图标顺序相同. 这是因为相同的 ID 号被用来引用特定图标的大版本和小版本.

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

任何这种分离的图像列表通常都应该通过 IL_Destroy 来销毁.

## 事件

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

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

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

## ImageList(图像列表)

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

    MyGui := Gui()  ; 创建 MyGui 窗口.
    LV := MyGui.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")  ; 自动调整列宽.
    MyGui.Show

### IL_Create

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

    ImageListID := IL_Create(InitialCount := 2, GrowCount := 5, LargeIcons := false)

InitialCount

  类型: 整数

  你希望立即放入列表中的图标数量(如果省略, 默认为 2).

GrowCount

  类型: 整数

  列表的图标数量, 每次超过当前列表的容量时, 列表的图标数量将增长(如果省略, 默认为 5).

LargeIcons

  类型: 整数(布尔值)

  如果这个参数为 1(true), 则图像列表将包含大图标. 如果为 0(false), 则它包含小图标(这是省略时的默认情况). 会按比例对添加到列表中的图标自动进行缩放以符合系统中大图标和小图标的尺寸.

### IL_Add

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

    IconIndex := IL_Add(ImageListID, Filename , IconNumber, ResizeNonIcon)

ImageListID

  类型: 整数

  IL_Create 创建的图像列表的 ID.

Filename

  类型: 字符串

  图标(.ICO), 光标(.CUR) 或动画光标(.ANI) 文件的名称(动态光标在 ListView 中显示时实际将不会动), 或位图或图标句柄 , 如 `"HBITMAP:" handle`. 图标的其他来源包含下列类型的文件: EXE, DLL, CPL, SCR, 以及包含图标资源的其他类型.

IconNumber

  类型: 整数

  要使用文件中第一个以外的图标组, 请在 IconNumber 指定它的编号. 如果 IconNumber 为负数, 则假定其绝对值表示可执行文件中图标的资源 ID. 在下面的例子中, 将使用第二个图标组中的默认图标: `IL_Add(ImageListID, "C:\My Application.exe", 2)`.

ResizeNonIcon

  类型: 整数(布尔值)

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

  支持的图片类型包括 ANI, BMP, CUR, EMF, Exif, GIF, ICO, JPG, PNG, TIF 和 WMF.

### IL_Destroy

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

    Success := IL_Destroy(ImageListID)

ImageListID

  类型: 整数

  IL_Create 创建的图像列表的 ID.

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

## 备注

Gui.Submit 对 ListView 控件没有效果.

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

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

    MyGui.Add("Button", "Hidden Default", "OK").OnEvent("Click", LV_Enter)
    ...
    LV_Enter(*) {
        global
        if MyGui.FocusedCtrl != LV
            return
        MsgBox("Enter was pressed. The focused row number is " LV.GetNext(0, "Focused"))
    }

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

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

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

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

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

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

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

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

## 相关

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

## 示例

指定行号为 0 来选择或取消选择所有行.

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

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

    LV.ModifyCol  ; 此模式不需要参数.

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

    ; 创建 GUI 窗口:
    MyGui := Gui("+Resize")  ; 允许用户最大化窗口或拖动来改变窗口的大小.

    ; 创建一些按钮:
    B1 := MyGui.Add("Button", "Default", "Load a folder")
    B2 := MyGui.Add("Button", "x+20", "Clear List")
    B3 := MyGui.Add("Button", "x+20", "Switch View")

    ; 通过 MyGui.Add 创建 ListView 及其列:
    LV := MyGui.Add("ListView", "xm r20 w700", ["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)

    ; 应用控件事件:
    LV.OnEvent("DoubleClick", RunFile)
    LV.OnEvent("ContextMenu", ShowContextMenu)
    B1.OnEvent("Click", LoadFolder)
    B2.OnEvent("Click", (*) => LV.Delete())
    B3.OnEvent("Click", SwitchView)

    ; 应用窗口事件:
    MyGui.OnEvent("Size", Gui_Size)

    ; 显示窗口:
    MyGui.Show()

    LoadFolder(*)
    {
        static IconMap := Map()
        MyGui.Opt("+OwnDialogs") ; 强制用户在使用主窗口之前关闭下面的对话框.
        Folder := DirSelect(, 3, "Select a folder to read:")
        if not Folder  ; 用户取消对话框.
            return

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

        ; 计算 SHFILEINFO 结构需要的缓冲大小.
        sfi_size := A_PtrSize + 688
        sfi := Buffer(sfi_size)

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

            ; 建立唯一的扩展 ID 以避免变量名中的非法字符,
            ; 例如破折号. 这种使用唯一 ID 的方法也会执行地更好,
            ; 因为在数组中查找项目不需要进行搜索循环.
            SplitPath(FileName,,, &FileExt)  ; 获取文件扩展名.
            if FileExt ~= "^(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 | (Ord(ExtChar) << (8 * (A_Index - 1)))
                }
                ; 检查此文件扩展名的图标是否已经在图像列表中. 如果是,
                ; 可以避免多次调用并极大提高性能,
                ; 尤其对于包含数以百计文件的文件夹而言:
                IconNumber := IconMap.Has(ExtID) ? IconMap[ExtID] : 0
            }
            if not IconNumber  ; 此扩展名还没有相应的图标, 所以进行加载.
            {
                ; 取与此文件扩展名关联的高质量小图标:
                if not DllCall("Shell32\SHGetFileInfoW", "Str", FileName
                , "Uint", 0, "Ptr", sfi, "UInt", sfi_size, "UInt", 0x101)  ; 0x101 是 SHGFI_ICON+SHGFI_SMALLICON
                    IconNumber := 9999999  ; 把它设置到范围外来显示空图标.
                else ; 成功加载图标.
                {
                    ; 从结构中提取 hIcon 成员:
                    hIcon := NumGet(sfi, 0, "Ptr")
                    ; 直接添加 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)
                    ; 缓存图标来节省内存并提升加载性能:
                    IconMap[ExtID] := IconNumber
                }
            }

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

    SwitchView(*)
    {
        static IconView := false
        if not IconView
            LV.Opt("+Icon")        ; 切换到图标视图.
        else
            LV.Opt("+Report")      ; 切换回详细信息视图.
        IconView := not IconView   ; 进行反转以为下次做准备.
    }

    RunFile(LV, RowNumber)
    {
        FileName := LV.GetText(RowNumber, 1) ; 获取首个字段的文本.
        FileDir := LV.GetText(RowNumber, 2)  ; 获取第二个字段的文本.
        try Run(FileDir "\" FileName)
        if A_LastError
            MsgBox("Could not open " FileDir "\" FileName ".")
    }

    ShowContextMenu(LV, Item, IsRightClick, X, Y)  ; 响应右键或 Apps 键.
    {
        ; 创建弹出式菜单, 作为上下文菜单使用:
        static ContextMenu := Menu()
        ContextMenu.Add("Open", OpenOrProperties)
        ContextMenu.Add("Properties", OpenOrProperties)
        ContextMenu.Add("Clear from ListView", ClearRows)
        ContextMenu.Default := "Open"  ; 将 "Open(打开)" 改为粗体字, 以表明这是双击的默认动作.
        ; 在所提供的 X 和 Y 坐标处显示菜单.
        ; 这些应该被使用, 因为即使用户按了 Apps 键, 它们也能提供正确的坐标:
        ContextMenu.Show(X, Y)

        OpenOrProperties(ItemName, *)  ; 用户在上下文菜单中选择了 "Open" 或 "Properties".
        {
            FileName := LV.GetText(Item, 1) ; 获取首个字段的文本.
            FileDir := LV.GetText(Item, 2)  ; 获取第二个字段的文本.
            if (ItemName = "Open")  ; 用户在上下文菜单中选择了 "Open".
                try Run(FileDir "\" FileName)
            else
                try Run("properties " FileDir "\" FileName)
            if A_LastError
                MsgBox("Could not perform requested action on " FileDir "\" FileName ".")
        }

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

    Gui_Size(thisGui, MinMax, Width, Height)  ; 扩大或缩小 ListView 来响应用户对窗口大小的改变.
    {
        if MinMax = -1  ; 窗口被最小化了. 无需进行操作.
            return
        ; 否则, 窗口的大小被调整过或被最大化了. 调整 ListView 的大小来适应.
        LV.Move(,, Width - 20, Height - 40)
    }

dftg