# 如何应用 CPF、CEP 或日期掩码

> 使用掩码自动格式化 CPF、CEP 和日期，并读取纯净的数字。

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

# 如何应用 CPF、CEP 或日期掩码

**用途。** 在用户只输入数字时自动使用分隔符（点、破折号、斜杠）格式化输入，显示为 `123.456.789-00`，之后再取回纯净的值（`12345678900`）。

## 用到的 API
- `campo.<TextFieldStyle>getStyle().mask("999.999.999-99".toCharArray())` — 定义掩码模式。`9` = 数字位置；其他任何字符都是自动插入的字面量（`.`、`-`、`/`）。
- `.mode(EditMode.NUMERIC, true)` — 同时开启数字模式和 `maskedEdit`（第二个参数 `true`）。没有 `maskedEdit`，掩码不会被应用。顺序很重要：请一起调用 `mask(...)` 和 `mode(...)`。
- `TextField.getText()` — 返回带掩码的文本（`"123.456.789-00"`），适合以格式化形式显示/保存。
- `TextField.getTextWithoutMask()` — 只返回 `9` 位置的数字（`"12345678900"`），适合验证并发送到后端。

> 对于**日期**，优先使用 `setMode(EditMode.DATE, true)`：它会根据 locale 自动推导掩码（`LocaleFormats.dateSeparator` 和 `LocaleFormats.dateFormat`，例如 `dd/MM/yyyy` 或 `yyyy-MM-dd`），并且还会打开 DatePicker。参见配方[日期和时间](/docs/cookbook/forms/data-e-hora)。

> 注意 `mode(EditMode.NUMERIC, true)` 中的顺序：`NUMERIC`/`NORMAL` 模式仅在掩码已定义时才会根据掩码重新计算 `maxLength`。请**先**定义 `mask(...)`，或在同一条流式链中定义。

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

      // CPF：123.456.789-00
      TextField cpf = new TextField("");
      cpf.caption("CPF");
      cpf.<TextFieldStyle>getStyle()
         .mask("999.999.999-99".toCharArray())
         .mode(EditMode.NUMERIC, true);   // NUMERIC + maskedEdit

      // CEP：12345-678
      TextField cep = new TextField("");
      cep.caption("CEP");
      cep.<TextFieldStyle>getStyle()
         .mask("99999-999".toCharArray())
         .mode(EditMode.NUMERIC, true);

      Label resultado = new Label("");

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

      add(LAY_FILL, cpf);
      add(LAY_FILL, cep);
      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.EditMode;

// CPF：123.456.789-00
TextField cpf = new TextField("");
cpf.caption("CPF");
cpf.<TextFieldStyle>getStyle()
   .mask("999.999.999-99".toCharArray())
   .mode(EditMode.NUMERIC, true);   // NUMERIC + maskedEdit

// CEP：12345-678
TextField cep = new TextField("");
cep.caption("CEP");
cep.<TextFieldStyle>getStyle()
   .mask("99999-999".toCharArray())
   .mode(EditMode.NUMERIC, true);

Button enviar = new Button("Enviar");
enviar.onAction(o ->
{
   String cpfFormatado = cpf.getText();            // "123.456.789-00"
   String cpfLimpo     = cpf.getTextWithoutMask(); // "12345678900"
   // 校验 cpfLimpo.length() == 11 后提交
});
```

## 深入了解
- [掩码与键盘](/docs/ui/forms-and-input/masks-and-keyboards) · [文本字段](/docs/ui/forms-and-input/text-fields)
- 姊妹配方：[验证字段](/docs/cookbook/forms/validar-campo) · [货币掩码](/docs/cookbook/forms/mascara-moeda) · [日期和时间](/docs/cookbook/forms/data-e-hora)
- 权威来源：第 14 章 — 基础控件 · [枚举 (UIConstants)](/docs/reference/enums-uiconstants)
