# 如何验证文本字段

> 限制可输入的字符与长度，并在提交前读到已清洗、可直接使用的值。

Source: https://crossfly.dev/zh/docs/cookbook/forms/validar-campo — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 如何验证文本字段

**用途。** 确保用户只能输入有效字符，限制字符数量，并在发送时获取不带掩码的值。在 CrossFly 中，输入验证是声明式的（在字段样式中完成），而业务验证在提交时进行。

## 用到的 API
- `campo.<TextFieldStyle>getStyle().validChars("0123456789")` — 过滤接受的字符。`null`（默认）接受所有字符。比较以大写进行：内部 `isCharValid(c)` 执行 `validChars.indexOf(Character.toUpperCase(c))`，因此即使字段接受小写字母，也请以大写形式提供字母。
- `campo.<TextFieldStyle>getStyle().maxLength(11)` — 限制字符总数（`0` = 无限制）。对输入和粘贴（`clipboardPaste`）均有效。
- `campo.<TextFieldStyle>getStyle().capitalise(Capitalise.UPPER)` — 强制大写/小写（`UPPER`、`LOWER`、`NORMAL`）。
- `TextField.getText()` — 显示的文本（若有掩码则带掩码）。
- `TextField.getTextWithoutMask()` — 只有数字/原始内容（例如 `"12345678900"` 而非 `"123.456.789-00"`）。发送时请使用这个。
- `TextInput.mapKeys(from, to)` — 重映射按键（例如用点替换逗号）。`from` 和 `to` 必须长度相同。

> CrossFly 中没有按键回调。不存在 `onTextChanged`——你在发送时（或在按钮的 `onAction` 内部）读取 `getText()`/`getTextWithoutMask()`。

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

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.style.*;   // TextFieldStyle（在样式中配置）

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

      TextField cpf = new TextField("");
      cpf.caption("CPF");
      cpf.<TextFieldStyle>getStyle()
         .validChars("0123456789")   // 仅数字
         .maxLength(11);             // 11 位数字

      TextField apelido = new TextField("");
      apelido.caption("Apelido");
      apelido.<TextFieldStyle>getStyle()
         .capitalise(Capitalise.LOWER)
         .maxLength(20);

      Label resultado = new Label("");

      Button enviar = new Button("Enviar");
      enviar.onAction(origin ->
      {
         String cpfLimpo = cpf.getTextWithoutMask();   // "12345678900"
         if (cpfLimpo.length() != 11)
         {
            Hint.show("CPF deve ter 11 dígitos", 2000);
            return;
         }
         resultado.setText("CPF: " + cpfLimpo + " · apelido: " + apelido.getText());
      });

      add(LAY_FILL, cpf);
      add(LAY_FILL, apelido);
      add(LAY_AUTO, enviar);
      add(LAY_FILL, resultado);
   }
}
```

> 下面的片段只展示**核心部分**。要运行它，请将其放入上面的 `MainWindow` 骨架中。

## 最小片段
```java
import dev.crossfly.ui.*;
import dev.crossfly.ui.style.*;
import static dev.crossfly.ui.UIConstants.Capitalise;

TextField cpf = new TextField("");
cpf.caption("CPF");
cpf.<TextFieldStyle>getStyle()
   .validChars("0123456789")   // 仅数字
   .maxLength(11);             // 11 位数字

TextField apelido = new TextField("");
apelido.caption("Apelido");
apelido.<TextFieldStyle>getStyle()
   .capitalise(Capitalise.LOWER)
   .maxLength(20);

Button enviar = new Button("Enviar");
enviar.onAction(origin ->
{
   String cpfLimpo = cpf.getTextWithoutMask();   // "12345678900"
   if (cpfLimpo.length() != 11)
   {
      new Alert("Erro", "CPF deve ter 11 dígitos").show();
      return;
   }
   // ... 继续提交
});
```

## 深入了解
- [掩码与键盘](/docs/ui/forms-and-input/masks-and-keyboards) · [焦点与验证](/docs/ui/forms-and-input/focus-and-validation)
- 姊妹配方：[CPF/CEP/日期掩码](/docs/cookbook/forms/mascara-cpf) · [密码字段](/docs/cookbook/forms/campo-de-senha)
- 权威来源：第 14 章 — 基础控件 · [枚举 (UIConstants)](/docs/reference/enums-uiconstants)
