You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX中如何在TextArea搜索格式化文本及修复按钮逻辑问题

问题分析与解决方案

核心问题点

  1. 对话框按钮事件绑定时机错误:原代码在showAndWait()后绑定事件,而showAndWait()会阻塞到对话框关闭,此时按钮已销毁,事件永远不会触发。
  2. Optional 误用:showAndWait()返回的Optional是用户点击的ButtonType,不是输入的文本,输入内容需从对话框的编辑器组件获取。
  3. 文本匹配逻辑错误:匹配源用了用户输入的文本而非TextArea内容,且未处理正则特殊字符,也没有记录当前匹配位置,无法连续遍历结果。
  4. 样式设置方式错误:原生TextArea是纯文本组件,无法直接插入带样式的Text节点,需通过选区高亮或使用富文本组件实现。

修改后的完整代码

首先添加必要的成员变量:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.MatchResult;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TextInputDialog;

// 类成员变量,用于记录匹配状态
private int currentMatchIndex = -1;
private List<MatchResult> matches = new ArrayList<>();
private TextInputDialog findDialog;

修改onLocateText()方法:

@FXML
protected void onLocateText(){
    findDialog = new TextInputDialog();
    findDialog.setTitle("Find");
    findDialog.setHeaderText("Enter the search phrase");
    
    // 添加按钮类型
    ButtonType nextBtnType = new ButtonType("Next", ButtonBar.ButtonData.OK_DONE);
    ButtonType prevBtnType = new ButtonType("Previous", ButtonBar.ButtonData.CANCEL_CLOSE);
    ButtonType closeBtnType = new ButtonType("Close", ButtonBar.ButtonData.CLOSE);
    findDialog.getDialogPane().getButtonTypes().setAll(nextBtnType, prevBtnType, closeBtnType);
    
    // 获取按钮并提前绑定事件
    Button nextBtn = (Button) findDialog.getDialogPane().lookupButton(nextBtnType);
    Button prevBtn = (Button) findDialog.getDialogPane().lookupButton(prevBtnType);
    
    nextBtn.setOnAction(e -> handleNextClick());
    prevBtn.setOnAction(e -> handlePreviousClick());
    
    // 对话框关闭时重置状态
    findDialog.setOnHidden(e -> {
        currentMatchIndex = -1;
        matches.clear();
        findDialog = null;
    });
    
    // 用show()而非showAndWait(),保持对话框打开以便多次操作
    findDialog.show();
}

实现handleNextClick()方法:

private void handleNextClick() {
    if (findDialog == null) return;
    
    String searchText = findDialog.getEditor().getText().trim();
    if (searchText.isEmpty()) {
        textArea.selectRange(0, 0);
        currentMatchIndex = -1;
        matches.clear();
        return;
    }
    
    // 搜索词变更时重新获取所有匹配
    if (currentMatchIndex == -1 || !matches.isEmpty() && !matches.get(0).group().equals(searchText)) {
        Pattern pattern = Pattern.compile(Pattern.quote(searchText)); // 避免正则特殊字符干扰
        Matcher matcher = pattern.matcher(textArea.getText());
        matches.clear();
        while (matcher.find()) {
            matches.add(matcher.toMatchResult());
        }
        currentMatchIndex = -1;
    }
    
    // 遍历下一个匹配项
    if (!matches.isEmpty()) {
        currentMatchIndex = (currentMatchIndex + 1) % matches.size();
        MatchResult match = matches.get(currentMatchIndex);
        // 选中匹配文本并滚动到视图
        textArea.selectRange(match.start(), match.end());
        textArea.requestFocus();
        textArea.scrollTo(textArea.getParagraphIndex(match.start()));
    }
}

实现handlePreviousClick()方法:

private void handlePreviousClick() {
    if (findDialog == null) return;
    
    String searchText = findDialog.getEditor().getText().trim();
    if (searchText.isEmpty()) {
        textArea.selectRange(0, 0);
        currentMatchIndex = -1;
        matches.clear();
        return;
    }
    
    if (currentMatchIndex == -1 || !matches.isEmpty() && !matches.get(0).group().equals(searchText)) {
        Pattern pattern = Pattern.compile(Pattern.quote(searchText));
        Matcher matcher = pattern.matcher(textArea.getText());
        matches.clear();
        while (matcher.find()) {
            matches.add(matcher.toMatchResult());
        }
        currentMatchIndex = matches.size();
    }
    
    // 遍历上一个匹配项
    if (!matches.isEmpty()) {
        currentMatchIndex = (currentMatchIndex - 1 + matches.size()) % matches.size();
        MatchResult match = matches.get(currentMatchIndex);
        textArea.selectRange(match.start(), match.end());
        textArea.requestFocus();
        textArea.scrollTo(textArea.getParagraphIndex(match.start()));
    }
}

额外说明(永久高亮需求)

如果需要对匹配文本设置永久样式,原生TextArea无法实现,需使用第三方富文本组件(如RichTextFX)。示例替换逻辑如下:

// 假设使用RichTextArea代替TextArea
import org.fxmisc.richtext.RichTextArea;

private RichTextArea richTextArea;

// 在handleNextClick中替换为:
richTextArea.setStyle(match.start(), match.end(), "-fx-fill: red; -fx-font-weight: bold;");

内容的提问来源于stack exchange,提问作者user16612111

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 14:00:49