# WebContainer

> 在 UI 中嵌入原生 webview（HTML/JS）—— 通过 IWebView 接口，依赖于平台。

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

# WebContainer

`WebContainer` 是一个在你的 CrossFly UI 内**嵌入原生 webview**（HTML/JavaScript 内容）的小部件。它是一个轻薄的*包装器*：自身不绘制任何内容 —— 一切都委托给平台通过 `PlatformUtils.instance.createWebView()` 提供的 `IWebView`。它用于像普通屏幕组件一样显示一个页面、一个 HTML 表单或一个 web 面板。

> ⚠️ **关于可用性的坦诚说明：** `WebContainer` **仅在存在原生 `IWebView` 的平台上有效**（Android、iOS、桌面）。**本代码库以及 Web/TeaVM 目标中都没有 `IWebView` 的具体实现** —— `createWebView()` 在不支持的平台上返回 `null`，此时 `WebContainer` 的构造函数会抛出 `UnsupportedOperationException`。在 Web/WebGL 构建中，请优先使用打开外部页面（`view(url)`）而不是嵌入 webview。

> 🔴 · **包：** `dev.crossfly.ui` · **继承：** `Component`（实现 `Runnable`）· **委托给：** `IWebView`

## 何时使用
- 在具有原生 webview 的平台上（Android/iOS/桌面），**在**应用内显示 web 内容（HTML/JS）。
- 集成一个 HTML 表单并通过 `setFormSubmitListener` 捕获提交。

## 何时不要使用
- 对于简单的静态内容（文本/图像）→ 使用原生小部件（[`chartview`](/docs/ui/widgets/chartview)、标签、图像），而不是加载一个页面。

## 外观
`WebContainer` 不绘制自身的像素：系统的原生 webview 覆盖在组件区域之上。可见性在 `onPaint` 中自动协调 —— webview 仅在父窗口位于最上层（`isTopMost()`）时出现，否则消失。

## 完整示例（复制、粘贴并运行）
`WebContainer` 仅在存在原生 `IWebView` 的平台上有效。此示例在创建容器**之前**通过 `PlatformUtils.instance.createWebView() != null` 检测支持情况（避免构造函数抛出 `UnsupportedOperationException`），并在不支持时（例如 Web/TeaVM 目标）降级为使用 `view(url)` 在外部打开页面。

```java
package com.exemplo;

import dev.crossfly.PlatformUtils;

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());

      String url = "https://example.com";

      // 仅当平台提供原生 IWebView 时才创建 WebContainer。
      if (PlatformUtils.instance.createWebView() != null)
      {
         WebContainer browser = new WebContainer();
         browser.show(url);
         add(LAY_FILL, browser);
      }
      else
      {
         // Web/TeaVM 目标（无内置 webview）：在外部打开页面。
         add(LAY_AUTO, new Label("Sem webview nativa — abrindo externamente…"));
         PlatformUtils.instance.view(url);
      }
   }
}
```

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

## 基本用法
```java
WebContainer browser = new WebContainer();   // 可能抛出 UnsupportedOperationException
browser.show("https://example.com");
container.add(LAY_FILL, browser);
```

## 构造函数
| 构造函数 | 描述 |
|---|---|
| `WebContainer()` | 通过 `PlatformUtils.instance.createWebView()` 获取原生 webview 并以不可见状态启动。如果平台不提供 `IWebView`，则**抛出 `UnsupportedOperationException`**。 |

## 属性与自定义
| 方法 | 类型 | 作用 |
|---|---|---|
| `show(String url)` | `void` | 在 webview 中加载并显示该 URL。 |
| `hide()` | `void` | 隐藏 webview 同时保留页面状态。 |
| `setVisible(boolean)` | `void` | `true` = `show(null)`（保留当前 URL/状态）；`false` = `hide()`。 |
| `isVisible()` | `boolean` | webview 是否可见。 |
| `evaluateJavaScript(String script)` | `void` | 在页面上执行 JavaScript。 |
| `setFormSubmitListener(IWebView.FormSubmitListener)` | `void` | 注册用于捕获 HTML 表单提交的回调。 |

> `IWebView` 要求操作发生在*渲染线程*（paint 线程）上。如果你从其他线程调用 `show`/`evaluateJavaScript`，请用 `Component.onUI(...)` 返回该线程。

## 回调与事件
| 回调 | 触发时机 |
|---|---|
| `IWebView.FormSubmitListener`（通过 `setFormSubmitListener`） | 在嵌入的页面内提交 HTML 表单时。 |

> `WebContainer` **不使用** `onAction`/`onClick` —— 交互发生在 web 页面内部。唯一的应用钩子是表单提交监听器。

## 状态
- **可见 / 隐藏**：由 `show`/`hide`/`setVisible` 管理，并由窗口位置自动管理（当父窗口不在最上层时 webview 消失）。
- **已移除**：在 `onRemove` 中，调用 `view.removeView()`，且 `IWebView` **无法被复用** —— 要重新显示，请创建一个新的 `WebContainer`。

### 带提交捕获的 HTML 表单（具备 webview 的平台）
```java
WebContainer form = new WebContainer();
form.setFormSubmitListener(dados -> processarEnvio(dados));  // IWebView.FormSubmitListener
form.show("https://meuservico/formulario.html");
container.add(LAY_FILL, form);
```

### 在 Web 目标上的安全降级
```java
// 在 Web/TeaVM 构建中，createWebView() 返回 null，构造函数抛出异常。
try {
   WebContainer c = new WebContainer();
   c.show("https://example.com");
   container.add(LAY_FILL, c);
} catch (UnsupportedOperationException e) {
   // 无内置 webview：在外部打开页面。
   PlatformUtils.instance.view("https://example.com");
}
```

## 最佳实践
- 将创建过程封装在 `UnsupportedOperationException` 的 `try/catch` 中，以在 Web 目标上良好降级。

## 常见错误
- **在不支持的平台上创建 `WebContainer`**：抛出 `UnsupportedOperationException`。请事先处理或检测。
- **在 `onRemove` 之后复用**：`IWebView` 已被丢弃（`removeView()`）；请创建一个新实例。
- **期望在 Web/TeaVM 目标上嵌入 web**：没有实现；嵌入 webview ≠ 成为一个 web 应用。请使用 `view(url)`。

## 另请参阅
- 领域指南：[`ui/`](/docs/ui) · 食谱：[`cookbook/`](/docs/cookbook)
- 概念（平台、UI 线程）：[`concepts/`](/docs/concepts)
- 相关组件：[`locationview`](/docs/ui/widgets/locationview)
