# Picker（基类）

> 所有 picker 的基类 —— getAnswer() + show(onAnswer) 模式。

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

# Picker（基类）

`Picker` 是 CrossFly 所有选择窗口（日期、时间、文件、照片、颜色、条形码、坐标）的通用基类。每个 picker 打开后，让用户进行交互，并在**确认**时自行关闭，通过回调将结果异步地交付给打开它的代码。

理解本页的模式，就等于一次性理解了**所有** picker。

> 🟡 · **包：** `dev.crossfly.ui.picker` · **继承：** `Window` · **过渡：** 默认 `FADE`

## 核心模式：`getAnswer()` + `show(onAnswer)`

- **`Picker.getAnswer()`** 产生所选的值（每个子类都会重写它以返回具体类型：`String`、`Integer`、`LatLon`……）。在基类中返回 `null`。
- **`Window.show(RunnableWith<Object> onAnswer)`** 在**窗口关闭时**消费这个值。

两者之间的纽带位于 `Window.hide()`：

```java
public void hide() {
   ...
   Object answer = this instanceof Picker ? ((Picker) this).getAnswer() : null;
   if (onAnswer != null && answer != null)
      onAnswer.run(answer);
   ...
}
```

实际后果（均已在代码中确认）：

- **回调 `onAnswer` 仅在 `hide()` 时触发，且仅当 `getAnswer() != null` 时。** 在**取消**时，子类会让 `getAnswer()` 返回 `null` —— 因此 `onAnswer` **不会**被调用。需要对取消作出反应的代码应重写 `onHide()`/`onRemove()` 或使用子类特定的回调。
- `show(onAnswer)` 是**线程安全的**：如果在 paint 线程之外调用，它会被重新调度并阻塞直到完成。
- 交付的值是 `Object`；消费者需将其**强制转换**为该 picker 的具体类型。

> 这与 [`Alert`](/docs/ui/widgets/alert)/[`ActionSheet`](/docs/ui/widgets/actionsheet) 形成对比，它们继承自 `Window` 但**不**继承 `Picker` —— 在它们中 `show(onAnswer)` 不会触发，响应通过按钮回调返回。

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

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

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

      Button abrir = new Button("Abrir picker");
      abrir.onAction(c ->
      {
         // 任何 Picker 的子类都遵循相同的 show(onAnswer) 模式
         DatePicker dp = new DatePicker();
         dp.show(answer ->
         {
            // 'answer' 是 getAnswer() 返回的 Object；只有在 != null 时才会到达这里
            String data = (String) answer;
            Hint.show("Selecionado: " + data, 2000);
         });
      });
      add(LAY_AUTO, abrir);
   }
}
```

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

## 基本用法（任何 picker 的通用形式）
```java
DatePicker dp = new DatePicker();
dp.show(answer ->
{
   // 'answer' 是 getAnswer() 返回的 Object；只有在 != null 时才会到达这里
   String data = (String) answer;
   Debug.log("Selecionado: " + data);
});
```

## 公共方法
| 方法 | 返回 | 描述 |
|---|---|---|
| `getAnswer()` | `Object` | picker 的结果，若取消则为 `null`。由每个子类重写。 |
| `show(RunnableWith<Object> onAnswer)` | `void` | 打开 picker；关闭时通过回调交付值（继承自 `Window`）。 |

## 各子类返回的类型
| Picker | `getAnswer()` 返回 |
|---|---|
| [`DatePicker`](/docs/ui/widgets/datepicker) | `String`（日期，遵循 `LocaleFormats.dateFormat`/`dateSeparator`） |
| [`TimePicker`](/docs/ui/widgets/timepicker) | `String`（`HH:MM`，24 小时制） |
| [`FilePicker`](/docs/ui/widgets/filepicker) | —（空骨架；从基类继承 `null`） |
| [`PhotoPicker`](/docs/ui/widgets/photopicker) | `String`（路径**或** `"ERROR: ..."`） |
| [`ColorPicker`](/docs/ui/widgets/colorpicker) | `Integer`（不含 alpha 的 RGB） |
| [`BarcodePicker`](/docs/ui/widgets/barcodepicker) | `String`（读取的载荷） |
| [`LocationPicker`](/docs/ui/widgets/locationpicker) | `LatLon` |

## 最佳实践
- 始终将 `Object` **强制转换**为上表中的具体类型。
- 要对取消作出反应，不要依赖 `onAnswer` —— 使用特定回调（`onSelect`/`onCapture`）或重写 `onHide()`。
- 硬件 picker（`PhotoPicker`、`BarcodePicker`、`LocationPicker`）在关闭时释放资源；不要在 `hide()` 之后重用同一个实例。

## 常见错误
- **在取消时期待 `onAnswer`** —— 它不会触发，因为 `getAnswer()` 返回 `null`。
- **忘记强制转换** —— 回调接收的是 `Object`。

## 另见
- 领域指南：[`ui/`](/docs/ui) · 食谱：[`cookbook/`](/docs/cookbook)
- 子类：[`DatePicker`](/docs/ui/widgets/datepicker) · [`TimePicker`](/docs/ui/widgets/timepicker) · [`FilePicker`](/docs/ui/widgets/filepicker) · [`PhotoPicker`](/docs/ui/widgets/photopicker) · [`ColorPicker`](/docs/ui/widgets/colorpicker) · [`BarcodePicker`](/docs/ui/widgets/barcodepicker) · [`LocationPicker`](/docs/ui/widgets/locationpicker)
