# ListViewWindow

> 将 ListView 包裹在 Window 中的模态弹窗，用于在浮动窗口中进行选择。

Source: https://crossfly.dev/zh/docs/ui/widgets/listviewwindow — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# ListViewWindow

`ListViewWindow` 将 [`ListView`](/docs/ui/widgets/listview) 包裹在一个 `Window` 中，以实现**模态弹窗**行为（居中的浮动选择器）。在 `SINGLE` 模式下，点击某项会选择并**关闭**窗口；在 `MULTIPLE` 模式下，“确认”按钮会关闭窗口。

大多数方法只是**委托**给内部的 `ListView`（配置与选择查询）。如果你想要嵌入式的 `ListView` 而不要弹窗，请使用 `getPanel()`。

> 🟡 · **包：** `dev.crossfly.ui` · **继承自：** `Window` · **样式：** `WindowStyle`（通过 `selectionWindowStyle()`）

## 何时使用
- 中断流程的模态选择（操作菜单、项选择器、选项确认）。
- 你希望自动居中并按屏幕比例设置尺寸，而无需手动构建 `Window`。

## 何时不要使用
- 屏幕内嵌入的列表（非模态）→ 直接使用 [`ListView`](/docs/ui/widgets/listview)（或 `getPanel()`）。
- 数千项 → 在你自己的 `Window` 中使用 [`VirtualListView`](/docs/ui/widgets/virtuallistview)。

## 外观
居中窗口，占屏幕的一部分（`widthRatio`/`heightRatio`，默认 `0.5`/`0.5`），内含 `ListView`（表头 + 可滚动项 + `MULTIPLE` 模式下的按钮）。背景会根据 `WindowStyle`/`Skin` 变暗。

## 完整示例（复制、粘贴并运行）
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

/** 可执行的主界面。将 launcher 指向此类（classname）并运行：gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      // ListViewWindow 是一个 Window（弹窗）：用 show() 打开，不进入布局。
      Button abrir = new Button("Escolher vibração");
      abrir.onAction(e -> {
         ListViewWindow pm = new ListViewWindow("Vibrar",
            new String[]{"Curto", "Longo", "Padrão"}, ListViewWindow.ItemType.SINGLE);
         pm.onConfirm(c -> System.out.println("idx = " + pm.getSelectedIndex()));
         pm.show();
      });

      add(.9f, abrir);   // 打开弹窗的按钮进入布局
   }
}
```

> 下面的片段**只展示核心部分**（放在 `onShow()` 里的内容）。要运行它们，请放入上面的 `MainWindow` 骨架中。

## 基本用法
```java
// 菜单弹窗 (SINGLE)：点击即关闭并报告索引
ListViewWindow pm = new ListViewWindow("Vibrar", new String[]{"Curto", "Longo", "Padrão"},
      ListViewWindow.ItemType.SINGLE);
pm.onConfirm(c -> System.out.println("idx = " + pm.getSelectedIndex()));
pm.show();
```

## 构造函数
| 构造函数 | 描述 |
|---|---|
| `ListViewWindow(String[] items, ItemType itemType)` | 无标题弹窗 |
| `ListViewWindow(String title, String[] items, ItemType itemType)` | 表头带标题的弹窗 |

`ListViewWindow.ItemType` 是一个**自有的**枚举（`SINGLE`、`MULTIPLE`），会在内部转换为 `ListView.ItemType`。

## 属性与自定义
| 属性 / 方法 | 类型 | 默认值 | 作用 |
|---|---|---|---|
| `size(float widthRatio, float heightRatio)` | `ListViewWindow` | `0.5, 0.5` | 弹窗尺寸，占屏幕的比例 |
| `getPanel()` | `ListView` | — | 访问内部的 `ListView` |
| `ItemFactory(ItemFactory)` | `ListViewWindow` | `null` | 自定义行工厂（注意首字母**大写**） |
| `computeIdealHeight(int)` | `int` | — | 委托给 `panel.computeIdealHeight` |
| `title/confirmText/itemStyle/confirmButtonStyle/headerStyle` | `ListViewWindow` | 主题 | 流式委托给 `ListView` |

### 选择查询（委托）
`getSelectedCount()`、`getSelectedIndex()`、`getSelectedText()`、`getSelectedIndices()`、`getSelectedTexts()`、`isSelected(int)`、`getSelectionState()`。

## 回调与事件
| 回调 | 触发时机 |
|---|---|
| `onConfirm(Component.Action)` | `SINGLE`：点击某项时（并关闭）。`MULTIPLE`：点击“确认”时（并关闭） |

## 状态
如同所有 `Window`：用 `show()` 显示，用 `hide()` 关闭。内部的 `ListView` 已将其 `closeAction` 指向 `this::hide`，因此选择会自动关闭弹窗。

### 带行工厂的弹窗（图片 + 标签）
```java
Button abrir = new Button("Lista de imagens");
abrir.onAction(e -> {
   ListViewWindow w = new ListViewWindow("Imagens", labels, ListViewWindow.ItemType.SINGLE);
   w.ItemFactory((text, index) -> {
      Image img = new Image(paths[index]);
      ImageView iv = new ImageView(img, UIConstants.ImageSize.RATIO);
      Component row = new Component().style(Styles.component()
         .pressable().ingap(Gap.ofFont(.3f)).autoHeight(Relative.ofFont(3f)));
      row.setLayout(new GridLayout(Position.START, Position.CENTER));
      row.add(.22f, .78f, iv, new Label(text));
      return row;
   });
   w.show();
});
```

### 带自定义尺寸的多选
```java
ListViewWindow w = new ListViewWindow("Tópicos",
      new String[]{"Java", "Kotlin", "Dart"}, ListViewWindow.ItemType.MULTIPLE)
   .size(.7f, .6f)
   .confirmText("OK");
w.onConfirm(c -> System.out.println(w.getSelectedCount() + " marcados"));
w.show();
```

## 最佳实践
- 根据内容调整 `size(...)` —— 项多时需要更大的 `heightRatio`，项少时则更小。
- 若要在无弹窗的情况下嵌入同一个列表，调用 `getPanel()` 并将返回的 `ListView` 添加到你的布局中。

## 常见错误
- **混淆 `ItemFactory(...)`（方法，`I` 大写）与 `ListView` 的 `itemFactory(...)`** —— 这里的流式方法按该类的约定以大写开头。
- **使用错误的枚举** —— 在 `ListViewWindow` 中请传入 `ListViewWindow.ItemType`，而非 `ListView.ItemType`。

## 另请参阅
- 领域指南：[`ui/`](/docs/ui) · 实用配方：[`cookbook/`](/docs/cookbook)
- 相关组件：[`ListView`](/docs/ui/widgets/listview) · [列表项](/docs/ui/widgets/list-items)
