Răsfoiți Sursa

feat:完成模拟键盘输入的程序,提供UI界面操作,使用JavaFX中的robot实现模拟键盘的输入,自定义Java的事件机制

yang yi 3 săptămâni în urmă
comite
5ba04415c9

+ 56 - 0
pom.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>space.anyi</groupId>
+    <artifactId>AutoInputRobot</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <description>模拟键盘输入的程序</description>
+    <packaging>jar</packaging>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <javafx.version>21.0.1</javafx.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <complier.encoding>UTF-8</complier.encoding>
+    </properties>
+<dependencies>
+    <dependency>
+        <groupId>org.openjfx</groupId>
+        <artifactId>javafx-controls</artifactId>
+        <version>${javafx.version}</version>
+    </dependency>
+</dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>3.6.0</version>
+                <configuration>
+                    <createDependencyReducedPom>true</createDependencyReducedPom>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <transformers>
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                                    <mainClass>space.anyi.autoInputRobot.Application</mainClass>
+                                </transformer>
+                            </transformers>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 5 - 0
src/main/java/module-info1.java1

@@ -0,0 +1,5 @@
+//声明一个模块
+module space.anyi.autoInputRobot {
+    requires javafx.controls;
+    exports space.anyi.autoInputRobot.ui to  javafx.graphics;
+}

+ 16 - 0
src/main/java/space/anyi/autoInputRobot/Application.java

@@ -0,0 +1,16 @@
+package space.anyi.autoInputRobot;
+
+import space.anyi.autoInputRobot.ui.View;
+
+/**
+ * @ProjectName: AutoInputRobot
+ * @FileName: Application
+ * @Author: 杨逸
+ * @Data:2025/11/12 18:01
+ * @Description:
+ */
+public class Application {
+    public static void main(String[] args) {
+        javafx.application.Application.launch(View.class,args);
+    }
+}

+ 336 - 0
src/main/java/space/anyi/autoInputRobot/core/AutoInputRobot.java

@@ -0,0 +1,336 @@
+package space.anyi.autoInputRobot.core;
+
+import javafx.application.Platform;
+import javafx.scene.input.KeyCode;
+import javafx.scene.robot.Robot;
+import space.anyi.autoInputRobot.exception.ContentEmptyException;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @ProjectName: AutoInputRobot
+ * @FileName: AutoInputRobot
+ * @Author: 杨逸
+ * @Data:2025/11/12 18:02
+ * @Description:
+ */
+public class AutoInputRobot {
+    private Robot robot;
+
+    // 需要Shift键的特殊字符集合
+    private static final Set<Character> SHIFT_CHARS = new HashSet<>();
+
+    static {
+        // 初始化需要Shift键的字符
+        SHIFT_CHARS.add('~');
+        SHIFT_CHARS.add('!');
+        SHIFT_CHARS.add('@');
+        SHIFT_CHARS.add('#');
+        SHIFT_CHARS.add('$');
+        SHIFT_CHARS.add('%');
+        SHIFT_CHARS.add('^');
+        SHIFT_CHARS.add('&');
+        SHIFT_CHARS.add('*');
+        SHIFT_CHARS.add('(');
+        SHIFT_CHARS.add(')');
+        SHIFT_CHARS.add('_');
+        SHIFT_CHARS.add('+');
+        SHIFT_CHARS.add('{');
+        SHIFT_CHARS.add('}');
+        SHIFT_CHARS.add('|');
+        SHIFT_CHARS.add(':');
+        SHIFT_CHARS.add('"');
+        SHIFT_CHARS.add('<');
+        SHIFT_CHARS.add('>');
+        SHIFT_CHARS.add('?');
+        SHIFT_CHARS.add('A');SHIFT_CHARS.add('B');SHIFT_CHARS.add('C');SHIFT_CHARS.add('D');
+        SHIFT_CHARS.add('E');SHIFT_CHARS.add('F');SHIFT_CHARS.add('G');SHIFT_CHARS.add('H');
+        SHIFT_CHARS.add('I');SHIFT_CHARS.add('J');SHIFT_CHARS.add('K');SHIFT_CHARS.add('L');
+        SHIFT_CHARS.add('M');SHIFT_CHARS.add('N');SHIFT_CHARS.add('O');SHIFT_CHARS.add('P');
+        SHIFT_CHARS.add('Q');SHIFT_CHARS.add('R');SHIFT_CHARS.add('S');SHIFT_CHARS.add('T');
+        SHIFT_CHARS.add('U');SHIFT_CHARS.add('V');SHIFT_CHARS.add('W');SHIFT_CHARS.add('X');
+        SHIFT_CHARS.add('Y');SHIFT_CHARS.add('Z');
+    }
+
+    /**
+     *延迟开始模拟输入的毫秒
+     */
+    private int delay = 5000;
+    private String contentFilePath;
+    private HashSet<FinishEventListener> listenerSet = new HashSet<>();
+
+    public AutoInputRobot(Robot robot, int delay, String contentFilePath) {
+        this.robot = robot;
+        if (delay > this.delay) this.delay = delay;
+        this.contentFilePath = contentFilePath;
+    }
+
+    public void startAutoInput() throws ContentEmptyException {
+        // 等待指定延迟时间
+        try {
+            Thread.sleep(delay);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("输入延迟被中断", e);
+        }
+
+        // 读取文件内容
+        String content = readFile(contentFilePath);
+        if (content == null || content.isEmpty()) {
+            throw new ContentEmptyException();
+        }
+
+        // 在JavaFX应用线程中执行自动输入
+        Platform.runLater(() -> {
+            try {
+                typeString(content);
+                finishNotify(new FinishEvent(this));
+            } catch (Exception e) {
+                System.err.println("自动输入过程中发生错误: " + e.getMessage());
+                e.printStackTrace();
+            }
+        });
+    }
+
+    /**
+     * @param finishEvent 要通知的事件
+     * @description:
+     * @author: 杨逸
+     * @data:2025/11/12 20:11:41
+     * @since 1.0.0
+     */
+    private void finishNotify(FinishEvent finishEvent) {
+        for (FinishEventListener finishEventListener : listenerSet) {
+            finishEventListener.handler(finishEvent);
+        }
+    }
+
+    /**
+     * @param listener 要添加的监听器
+     * @description:
+     * @author: 杨逸
+     * @data:2025/11/12 20:11:33
+     * @since 1.0.0
+     */
+    public void addFinishListener(FinishEventListener listener) {
+        if (!listenerSet.contains(listener)) {
+            listenerSet.add(listener);
+        }
+    }
+
+    /**
+     * @param filename 要读取的文件名
+     * @return {@code String }
+     * @description:
+     * @author: 杨逸
+     * @data:2025/11/12 18:09:18
+     * @since 1.0.0
+     */
+    private String readFile(String filename) {
+        StringBuilder content = new StringBuilder();
+        try (BufferedReader reader = new BufferedReader(
+                new InputStreamReader(new FileInputStream(filename), StandardCharsets.UTF_8))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                content.append(line).append("\n");
+            }
+        } catch (IOException e) {
+            System.err.println("读取文件失败: " + e.getMessage());
+            return null;
+        }
+        return content.toString();
+    }
+
+    private void typeString(String text) {
+        for (char c : text.toCharArray()) {
+            // 只处理ASCII字符
+            if (isAscii(c)) {
+                typeChar(c);
+                // 添加适当的延迟,使输入更加自然
+                //try {
+                //    Thread.sleep(30);
+                //} catch (InterruptedException e) {
+                //    Thread.currentThread().interrupt();
+                //    break;
+                //}
+            }
+        }
+    }
+
+    /**
+     * 检查字符是否为ASCII字符
+     * @param c 要检查的字符
+     * @return 如果是ASCII字符返回true,否则返回false
+     */
+    private boolean isAscii(char c) {
+        return c <= 127;
+    }
+
+    /**
+     * @param c 要输入的字符
+     * @description:
+     * @author: 杨逸
+     * @data:2025/11/12 18:08:32
+     * @since 1.0.0
+     */
+    private void typeChar(char c) {
+        // 只处理ASCII字符
+        if (!isAscii(c)) {
+            return;
+        }
+
+        boolean needShift = SHIFT_CHARS.contains(c) || Character.isUpperCase(c);
+
+        if (needShift) {
+            robot.keyPress(KeyCode.SHIFT);
+        }
+
+        try {
+            KeyCode keyCode = charToKeyCode(c);
+            if (keyCode != null) {
+                robot.keyPress(keyCode);
+                robot.keyRelease(keyCode);
+            }
+        } finally {
+            // 确保释放Shift键
+            if (needShift) {
+                robot.keyRelease(KeyCode.SHIFT);
+            }
+        }
+    }
+
+    /**
+     * 将字符转换为对应的KeyCode
+     * @param c 要转换的字符
+     * @return 对应的KeyCode,如果不支持则返回null
+     */
+    private KeyCode charToKeyCode(char c) {
+        // 处理特殊控制字符
+        switch (c) {
+            case '\n':
+                return KeyCode.ENTER;
+            case '\t':
+                return KeyCode.TAB;
+            case ' ':
+                return KeyCode.SPACE;
+            case '\b':
+                return KeyCode.BACK_SPACE;
+            case '\r':
+                return KeyCode.ENTER;
+            case '\u001b': // ESC
+                return KeyCode.ESCAPE;
+        }
+
+        // 处理可打印字符
+        if (c >= '!' && c <= '~') { // ASCII可打印字符范围
+            // 字母
+            if (c >= 'A' && c <= 'Z') {
+                return KeyCode.valueOf(String.valueOf(c));
+            }
+            if (c >= 'a' && c <= 'z') {
+                return KeyCode.valueOf(String.valueOf(Character.toUpperCase(c)));
+            }
+
+            // 数字
+            if (c >= '0' && c <= '9') {
+                return KeyCode.valueOf("DIGIT" + (c - '0'));
+            }
+
+            // 特殊字符 - 映射到基础键位,Shift键由typeChar方法处理
+            switch (c) {
+                // 需要Shift键的字符映射到基础键位
+                case '!': return KeyCode.DIGIT1;
+                case '"': return KeyCode.QUOTE;
+                case '#': return KeyCode.DIGIT3;
+                case '$': return KeyCode.DIGIT4;
+                case '%': return KeyCode.DIGIT5;
+                case '&': return KeyCode.DIGIT7;
+                case '(': return KeyCode.DIGIT9;
+                case ')': return KeyCode.DIGIT0;
+                case '*': return KeyCode.DIGIT8;
+                case '+': return KeyCode.EQUALS;
+                case ':': return KeyCode.SEMICOLON;
+                case '<': return KeyCode.COMMA;
+                case '>': return KeyCode.PERIOD;
+                case '?': return KeyCode.SLASH;
+                case '@': return KeyCode.DIGIT2;
+                case '^': return KeyCode.DIGIT6;
+                case '_': return KeyCode.MINUS;
+                case '{': return KeyCode.OPEN_BRACKET;
+                case '|': return KeyCode.BACK_SLASH;
+                case '}': return KeyCode.CLOSE_BRACKET;
+                case '~': return KeyCode.BACK_QUOTE;
+
+                // 不需要Shift键的字符
+                case '\'': return KeyCode.QUOTE;
+                case ',': return KeyCode.COMMA;
+                case '-': return KeyCode.MINUS;
+                case '.': return KeyCode.PERIOD;
+                case '/': return KeyCode.SLASH;
+                case ';': return KeyCode.SEMICOLON;
+                case '=': return KeyCode.EQUALS;
+                case '[': return KeyCode.OPEN_BRACKET;
+                case '\\': return KeyCode.BACK_SLASH;
+                case ']': return KeyCode.CLOSE_BRACKET;
+                case '`': return KeyCode.BACK_QUOTE;
+            }
+        }
+
+        return null; // 不支持的字符
+    }
+
+    /**
+     * @param c 字符
+     * @return KeyCode 返回字符对应的键码
+     * @description:
+     * @author: 杨逸
+     * @data:2025/11/12 18:08:14
+     * @since 1.0.0
+     */
+    private KeyCode getKeyCode(char c) {
+        // 字母
+        if (c >= 'A' && c <= 'Z') {
+            return KeyCode.valueOf(String.valueOf(c));
+        }
+
+        // 数字
+        if (c >= '0' && c <= '9') {
+            return KeyCode.valueOf("DIGIT" + (c - '0'));
+        }
+
+        // 不需要Shift的特殊字符
+        switch (c) {
+            case '[':
+                return KeyCode.OPEN_BRACKET;
+            case ']':
+                return KeyCode.CLOSE_BRACKET;
+            case ';':
+                return KeyCode.SEMICOLON;
+            case '\'':
+                return KeyCode.QUOTE;
+            case ',':
+                return KeyCode.COMMA;
+            case '.':
+                return KeyCode.PERIOD;
+            case '/':
+                return KeyCode.SLASH;
+            case '\\':
+                return KeyCode.BACK_SLASH;
+            case '`':
+                return KeyCode.BACK_QUOTE;
+            case '-':
+                return KeyCode.MINUS;
+            case '=':
+                return KeyCode.EQUALS;
+            default:
+                return null; // 未知字符
+        }
+    }
+
+}

+ 16 - 0
src/main/java/space/anyi/autoInputRobot/core/FinishEvent.java

@@ -0,0 +1,16 @@
+package space.anyi.autoInputRobot.core;
+
+import java.util.EventObject;
+
+/**
+ * @ProjectName: AutoInputRobot
+ * @FileName: FinishEvent
+ * @Author: 杨逸
+ * @Data:2025/11/12 19:58
+ * @Description: 自动输入事件完成
+ */
+public class FinishEvent extends EventObject {
+    public FinishEvent(Object source) {
+        super(source);
+    }
+}

+ 14 - 0
src/main/java/space/anyi/autoInputRobot/core/FinishEventListener.java

@@ -0,0 +1,14 @@
+package space.anyi.autoInputRobot.core;
+
+import java.util.EventListener;
+
+/**
+ * @ProjectName: AutoInputRobot
+ * @FileName: FinishEventListener
+ * @Author: 杨逸
+ * @Data:2025/11/12 20:04
+ * @Description: 完成自动输入事件监听器
+ */
+public interface FinishEventListener extends EventListener {
+    void handler(FinishEvent finishEvent);
+}

+ 18 - 0
src/main/java/space/anyi/autoInputRobot/exception/ContentEmptyException.java

@@ -0,0 +1,18 @@
+package space.anyi.autoInputRobot.exception;
+
+/**
+ * @ProjectName: AutoInputRobot
+ * @FileName: ContetnEmptyException
+ * @Author: 杨逸
+ * @Data:2025/11/12 18:13
+ * @Description: 文件内容为空的异常
+ */
+public class ContentEmptyException extends RuntimeException {
+    public ContentEmptyException() {
+        super();
+    }
+
+    public ContentEmptyException(String message) {
+        super(message);
+    }
+}

+ 252 - 0
src/main/java/space/anyi/autoInputRobot/ui/View.java

@@ -0,0 +1,252 @@
+package space.anyi.autoInputRobot.ui;
+
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Scene;
+import javafx.scene.control.*;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.DialogPane;
+import javafx.scene.image.Image;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.robot.Robot;
+import javafx.stage.FileChooser;
+import javafx.stage.Stage;
+import space.anyi.autoInputRobot.core.AutoInputRobot;
+import space.anyi.autoInputRobot.exception.ContentEmptyException;
+
+import java.io.File;
+
+/**
+ * @ProjectName: AutoInputRobot
+ * @FileName: View
+ * @Author: 杨逸
+ * @Data:2025/11/12 19:01
+ * @Description: 自动输入窗口
+ */
+public class View extends Application {
+    private final Robot robot = new Robot();
+    private TextField filePathField;
+    private TextField delayField;
+    private Button selectFileButton;
+    private Button startButton;
+    private Label statusLabel;
+    private File selectedFile;
+    private boolean isInputting = false; // 标记是否正在输入
+    private CheckBox alwaysOnTopCheckBox; // 窗口置顶复选框
+
+    @Override
+    public void start(Stage primaryStage) {
+        primaryStage.setTitle("自动输入机器人");
+        primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/icon.jpg")));
+        // 创建UI组件
+        filePathField = new TextField();
+        filePathField.setPromptText("请选择要输入的文件");
+        filePathField.setEditable(false);
+        filePathField.setPrefWidth(300);
+
+        delayField = new TextField("5000");
+        delayField.setPromptText("延迟时间(毫秒)");
+        delayField.setPrefWidth(100);
+
+        selectFileButton = new Button("选择文件");
+        startButton = new Button("开始输入");
+        statusLabel = new Label("请选择文件并设置延迟时间");
+        alwaysOnTopCheckBox = new CheckBox("窗口置顶");
+
+        // 创建布局
+        HBox fileSelectionBox = new HBox(10);
+        fileSelectionBox.getChildren().addAll(filePathField, selectFileButton);
+        fileSelectionBox.setAlignment(Pos.CENTER_LEFT);
+
+        HBox delayBox = new HBox(10);
+        delayBox.getChildren().addAll(new Label("延迟时间(毫秒):"), delayField);
+        delayBox.setAlignment(Pos.CENTER_LEFT);
+
+        HBox buttonBox = new HBox(10);
+        buttonBox.getChildren().addAll(startButton);
+        buttonBox.setAlignment(Pos.CENTER);
+
+        HBox optionsBox = new HBox(10);
+        optionsBox.getChildren().addAll(alwaysOnTopCheckBox);
+        optionsBox.setAlignment(Pos.CENTER_LEFT);
+
+        VBox root = new VBox(15);
+        root.setPadding(new Insets(20));
+        root.getChildren().addAll(
+                new Label("自动输入机器人"),
+                fileSelectionBox,
+                delayBox,
+                optionsBox,
+                buttonBox,
+                statusLabel
+        );
+        root.setAlignment(Pos.CENTER);
+
+        // 设置按钮事件
+        selectFileButton.setOnAction(e -> selectFile(primaryStage));
+        startButton.setOnAction(e -> startAutoInput());
+
+        // 设置复选框事件
+        alwaysOnTopCheckBox.setOnAction(e -> primaryStage.setAlwaysOnTop(alwaysOnTopCheckBox.isSelected()));
+
+        // 初始状态设置
+        startButton.setDisable(true);
+
+        // 创建场景并显示
+        Scene scene = new Scene(root, 400, 250);
+        primaryStage.setScene(scene);
+        primaryStage.setResizable(false);
+
+        // 设置窗口关闭事件处理器
+        primaryStage.setOnCloseRequest(event -> {
+            if (isInputting) {
+                Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
+                alert.setTitle("确认关闭");
+                alert.setHeaderText("自动输入正在进行中");
+                alert.setContentText("关闭窗口将中断自动输入过程,确定要关闭吗?");
+
+                ButtonType okButton = new ButtonType("确定关闭", ButtonBar.ButtonData.OK_DONE);
+                ButtonType cancelButton = new ButtonType("取消", ButtonBar.ButtonData.CANCEL_CLOSE);
+                alert.getButtonTypes().setAll(okButton, cancelButton);
+
+                // 显示对话框并等待用户响应
+                alert.showAndWait().ifPresent(response -> {
+                    if (response == cancelButton) {
+                        event.consume(); // 取消关闭事件
+                    }
+                });
+            }
+        });
+
+        primaryStage.show();
+    }
+
+    /**
+     * 选择文件
+     */
+    private void selectFile(Stage primaryStage) {
+        FileChooser fileChooser = new FileChooser();
+        fileChooser.setTitle("选择要输入的文件");
+        fileChooser.getExtensionFilters().addAll(
+                new FileChooser.ExtensionFilter("文本文件", "*.txt"),
+                new FileChooser.ExtensionFilter("所有文件", "*.*")
+        );
+
+        selectedFile = fileChooser.showOpenDialog(primaryStage);
+        if (selectedFile != null) {
+            // 确保选择的是文件而不是文件夹
+            if (selectedFile.isFile()) {
+                filePathField.setText(selectedFile.getAbsolutePath());
+                startButton.setDisable(false);
+                statusLabel.setText("文件已选择: " + selectedFile.getName());
+            } else {
+                statusLabel.setText("请选择文件,而不是文件夹");
+                selectedFile = null;
+            }
+        }
+    }
+
+    /**
+     * 开始自动输入
+     */
+    private void startAutoInput() {
+        if (selectedFile == null) {
+            statusLabel.setText("请先选择文件");
+            return;
+        }
+
+        try {
+            int delay = Integer.parseInt(delayField.getText());
+            if (delay < 0) {
+                statusLabel.setText("延迟时间必须为正数");
+                return;
+            }
+
+            statusLabel.setText("准备开始自动输入...");
+            startButton.setDisable(true);
+            selectFileButton.setDisable(true);
+            isInputting = true; // 标记开始输入
+
+            // 在新线程中执行自动输入,避免阻塞UI
+            final String absolutePath = selectedFile.getAbsolutePath();
+            new Thread(() -> {
+                try {
+                    AutoInputRobot autoInputRobot = new AutoInputRobot(robot,delay, absolutePath);
+                    //输入完成的事件监听器
+                    autoInputRobot.addFinishListener(finishEvent -> {
+                        System.out.println("触发"+System.currentTimeMillis());
+                        Platform.runLater(()->{
+                            statusLabel.setText("自动输入完成!");
+                            startButton.setDisable(false);
+                            selectFileButton.setDisable(false);
+                            isInputting = false; // 标记输入结束
+                        });
+                    });
+                    System.out.println("开始"+System.currentTimeMillis());
+                    autoInputRobot.startAutoInput();
+
+                } catch (ContentEmptyException e) {
+                    Platform.runLater(() -> {
+                        showExceptionDialog("文件内容为空", e);
+                        statusLabel.setText("错误: 文件内容为空");
+                        startButton.setDisable(false);
+                        selectFileButton.setDisable(false);
+                        isInputting = false; // 标记输入结束
+                    });
+                } catch (Exception e) {
+                    Platform.runLater(() -> {
+                        showExceptionDialog("发生异常", e);
+                        statusLabel.setText("错误: " + e.getMessage());
+                        startButton.setDisable(false);
+                        selectFileButton.setDisable(false);
+                        isInputting = false; // 标记输入结束
+                    });
+                }
+            }).start();
+
+        } catch (NumberFormatException e) {
+            statusLabel.setText("请输入有效的延迟时间");
+        }
+    }
+
+    /**
+     * 显示异常信息的弹窗
+     */
+    private void showExceptionDialog(String title, Exception e) {
+        // 创建自定义对话框
+        Dialog<Void> dialog = new Dialog<>();
+        dialog.setTitle(title);
+        dialog.setHeaderText(null);
+
+        // 创建文本域显示完整异常信息
+        TextArea textArea = new TextArea();
+        textArea.setEditable(false);
+        textArea.setWrapText(true);
+
+        // 构建异常信息字符串
+        StringBuilder sb = new StringBuilder();
+        sb.append("异常类型: ").append(e.getClass().getSimpleName()).append("\n");
+        sb.append("异常信息: ").append(e.getMessage()).append("\n\n");
+
+        // 添加堆栈跟踪
+        sb.append("堆栈跟踪:\n");
+        for (StackTraceElement element : e.getStackTrace()) {
+            sb.append("  at ").append(element.toString()).append("\n");
+        }
+
+        textArea.setText(sb.toString());
+        textArea.setPrefSize(600, 400);
+
+        // 设置对话框内容
+        DialogPane dialogPane = dialog.getDialogPane();
+        dialogPane.setContent(textArea);
+        dialogPane.getButtonTypes().add(ButtonType.OK);
+
+        // 显示对话框
+        dialog.showAndWait();
+    }
+
+}

BIN
src/main/resources/icon.jpg