# Dropdown

> ComboBox —— 带箭头的按钮，点击后展开列表以进行单选。

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

# Dropdown

`Dropdown` 是一个 **ComboBox** 类型的组件：一个带文本 + 箭头（`expand_more`）的按钮，点击后会打开一个 `ListViewWindow` 显示条目列表，并根据可用空间定位在上方或下方。选择完成后，按钮文本会显示所选条目。在未选择任何内容时，显示**占位符（placeholder）**。

> 🟡 · **包：** `dev.crossfly.ui` · **继承：** `Button` · **样式：** `DropdownStyle`

## 何时使用
- 在中等数量的选项列表中进行**单选**，节省屏幕空间。
- 当所选值应在选择后保持可见时。

## 何时不要使用
- 对于始终可见的 2–3 个选项 → 使用 [`group`](/docs/ui/widgets/group) 中的 [`option`](/docs/ui/widgets/option)。

## 外观
文本在左侧；箭头的位置由 `DropdownStyle.iconPosition` 决定：`START`（贴紧左边界）、`CENTER`（在文本旁边，`Button` 的默认布局 `TEXT_AT_LEFT`）或 `END`（贴紧右边界）。弹出层以圆角白色窗口的形式打开，点击外部时关闭（`canMove(false).canHide(true)`）。

## 完整示例（复制、粘贴并运行）
```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());

      Label saida = new Label("Nada selecionado");
      add(LAY_AUTO, saida);

      String[] itens = {"One", "Two", "Three", "Four", "Five"};
      Dropdown cb = new Dropdown("Selecione...", itens);
      // onSelect 在弹窗中确认选择后触发
      cb.onSelect(e -> saida.setText("Escolhido: " + cb.getSelectedText()
                                     + " (idx " + cb.getSelectedIndex() + ")"));
      add(LAY_AUTO, cb);
   }
}
```

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

## 基本用法
```java
String[] itens = {"One", "Two", "Three", "Four", "Five"};
Dropdown cb = new Dropdown("Selecione...", itens);
cb.onSelect(e -> {
   String escolhido = cb.getSelectedText();
   System.out.println("escolhido: " + escolhido);
});
container.add(.95f, cb);
```

## 构造函数
| 构造函数 | 描述 |
|---|---|
| `Dropdown(String placeholder, String[] items)` | 占位符 + 条目。 |
| `Dropdown(String[] items)` | 条目，使用默认占位符 `"Select..."`。 |

## 属性与自定义
| 属性 / 方法 | 类型 | 默认值 | 作用 |
|---|---|---|---|
| `onSelect(Action)` | `Dropdown`（链式） | — | 确认选择时的回调。 |
| `popupHeightRatio(float)` | `Dropdown`（链式） | `0.4` | 弹出层高度（屏幕的 0..1），当理想高度未被测量时使用。 |
| `popupWidthRatio(float)` | `Dropdown`（链式） | `-1` | 弹出层宽度（屏幕的 0..1）；负值 = 使用 Dropdown 自身的宽度。 |
| `getSelectedIndex()` | `int` | `-1` | 当前条目的索引（或 `-1`）。 |
| `getSelectedText()` | `String` | `null` | 当前条目的文本（或 `null`）。 |
| `setSelectedIndex(int)` | `Dropdown`（链式） | — | 以编程方式选择；无效索引（如 `-1`）→ 回到占位符。 |

## 回调与事件
| 回调 | 何时触发 |
|---|---|
| `onSelect(Action)` | 在弹出层中确认选择**之后**。在其中使用 `getSelectedText()`/`getSelectedIndex()`。 |

`onTouchUp` 继承自 `Button`，并通过 `onUI(...)` 调度 `openPopup()`。

## 状态
- **placeholder**：未选择任何条目（`selectedIndex == -1`）；显示占位符文本。
- **已选择**：`selectedIndex >= 0`；显示条目文本。
- **pressed**：继承自 `Button`（按下淡入淡出效果）。

### 编程式选择与清空回到占位符
```java
String[] numeros = {"One", "Two", "Three"};
Dropdown cb = new Dropdown("", numeros);
cb.onSelect(e -> System.out.println("idx: " + cb.getSelectedIndex()));
container.add(.95f, cb);

cb.setSelectedIndex(1);   // 以编程方式选中 "Two"
cb.setSelectedIndex(-1);  // 清空：回到占位符
```

## 最佳实践
- 在 `onSelect` 内通过 `getSelectedText()`/`getSelectedIndex()` 读取选择结果。
- 使用 `setSelectedIndex(-1)` 将字段"重置"回占位符（例如在"清空"按钮中）。

## 常见错误
- 传入 `null` 的 `items`：构造函数会处理它（`new String[0]`），但如果列表为空，弹出层**不会打开**。
- 期望多选：`Dropdown` 是单选；如需多选，请使用 `ListView` 的 `MULTIPLE`。

## 另请参阅
- 领域指南：[`ui/`](/docs/ui) · 食谱：[`cookbook/`](/docs/cookbook)
- 概念（窗口与弹出层）：[`concepts/`](/docs/concepts)
- 相关组件：[`option`](/docs/ui/widgets/option) · [`group`](/docs/ui/widgets/group)
