你以为用Java做个记事本很简单?我...

🏆本文收录于「滚雪球学SpringBoot」(全网一个名)专栏,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

前言

  有时候,灵感就像一阵风,突然间吹过,你无法预料它的到来,只能尽快抓住。这次,我突然想做一个类似 TXT 记事本的小工具。你没听错,就是那个每天记录生活琐事、随便写写的记事本。听起来简单对吧?"只要放个文本框,写个菜单,怎么都能完成。"但要我说,这个简单的"记事本"背后却有着不为人知的思考与细节。每一行代码,都是我多年来在开发过程中忽视的Java基础,甚至每一个微小的优化,都在体现我对该门开发语言背后的一次重新认识。

  做一个小工具,看似不起眼,但在开发过程中,我却时常被这些"细节"所困扰。每次碰到挑战,哪怕是一个看似简单的需求,都让我忍不住嘴角微翘:这不仅是撸代码的挑战,更是对自己技术基础的一次重新审视。而这个简易的记事本,居然成了我对"编码"这门艺术的一次深刻打磨(还好底子够厚)。

技术点概述

  1. Java Swing:一开始我并没有对 Swing 产生浓厚兴趣,甚至认为它是过时的技术。直到有一天,我意识到,Swing 其实是我快速构建桌面应用最便捷的工具。每次我需要快速搭建一个应用,Swing 就像一把利剑,助我轻松应对所有界面设计上的挑战。从最初的迷茫,到现在的得心应手,Swing 成了我构建应用时的"万能钥匙"。
  2. Java IO:文件操作,对我而言,既是最简单的任务,也是最具挑战的部分。初次接触文件读写时,我常常为路径、编码和格式问题而抓狂。而随着经验的积累,我渐渐学会了如何优雅地通过 Java IO 处理这些文件。保存、打开、读取,每个环节都变得得心应手。可以说,文件处理不再是难题,反而成了我最常用的工具之一。

  在这个小项目中,我结合了这两项技术,用它们来构建这个简易的记事本应用。表面看,它只是一个简单的记事本,但对我来说,它代表了我多年来对编程技术的理解与提升。

1. 项目需求分析

  很多人看见记事本时,可能觉得"这不就是个简单的文本框吗?"的确,它是个文本框,但背后涉及到的细节可不止这些。我的目标是做一个 简洁、高效、易用 的记事本工具,它不只让你"写"字,还让你能愉快地操作、管理文件。为此,我设计了以下几个功能:

  1. 编辑文本:无论是随手记下的想法,还是临时需要记录的内容,都可以在这个文本框中自由输入。编辑功能流畅、简洁,用户可以随时进行修改,省去了繁琐的操作。
  2. 保存文件:保存功能是最基础也是最重要的。每次记录的内容,都能通过保存功能妥善保存,避免数据丢失。
  3. 打开文件:不止是写,还能随时回看。用户可以通过打开文件的功能,重新加载之前保存的内容,进行编辑或查看。
  4. 字体样式和字号选择:为了让用户能更舒适地编辑文本,我加入了字体和字号选择的功能,用户可以自由调整文本的显示效果,给每一段文字加点"个人风格"。
  5. 文件另存为:有时候,我们需要保存不同版本的文件,或者将文件保存为新的文件格式。这个功能的存在,可以避免原文件被误修改或覆盖。
  6. 弹窗提示保存与取消操作:当你打开一个未保存的文件时,程序会在退出前弹出提示,提醒你是否保存修改过的内容。这是避免误操作的小细节,让你不会丢失任何数据。

  这些看似简单的需求,其实背后涉及到很多细节和优化。比如,字体选择功能,我并没有仅仅实现一个下拉框,而是让用户能轻松预览字体效果;又比如在保存文件时,我对文件路径做了自动判断,确保用户不会保存到错误的位置。每个细节都是我对用户体验的精雕细琢。

2. Java Swing 简介

  在我多年的开发经历中,Java Swing 见证了我从初学者到有一定开发经验的过程。最开始,我对 Swing 充满了恐惧。各种布局、组件、事件监听等概念让我头晕目眩,特别是在布局管理器的使用上,我从未想过有那么多种不同的布局方式,每种方式的使用场景也让我感到困惑。然而,随着时间的推移,我逐渐理解了它们的设计思路,掌握了如何利用这些组件和布局来实现我想要的界面效果。

  最初,我花了很多时间调试界面组件,尤其是 JTextArea 和 JScrollPane 的配合。每次调整窗口大小时,文本框内容的显示问题就会让我抓狂。于是我开始慢慢理解:布局并不是单纯地"摆放控件",而是要让每一个控件都能在不同尺寸的窗口中自如适应,确保它们之间的互动更自然。

  经过无数次的尝试与调整,我终于学会了用 Swing 设计简洁、流畅的桌面应用。每次看到界面稳定运行时,那种满足感让我觉得:这份辛苦是值得的。

常用组件介绍:

  • JTextArea:多行文本输入区域,用于编辑文本内容。
  • JMenuBar 和 JMenu:菜单栏和菜单,用于显示应用中的功能选项。
  • JFileChooser:文件选择器,用于打开和保存文件。

3. 项目实现

  一开始,我的目标是简单地搭建一个基本的界面,能让用户输入文本、保存文件、设置字体等。但随着需求的不断丰富,我发现每一步都需要不断优化代码,解决隐藏的问题。例如,在文件保存时,我不得不考虑编码问题、路径问题、扩展名问题等,甚至在用户关闭应用时,我都要细心处理文件未保存的情况。

3.1 代码演示

  首先,我们创建一个基本的窗口框架,其中包含一个文本编辑区域和菜单栏。然后,我们会逐步实现文件操作功能。具体代码如下演示,仅供参考:

java 复制代码
/**
 * @Author bug菌
 * @Source 公众号:猿圈奇妙屋
 * @date: 2025-07-31 15:56
 */
public class NotepadApp {
    private JFrame frame;
    private JTextArea textArea;
    private JFileChooser fileChooser;
    private String currentFilePath = null; // 当前文件路径
    private JLabel statusLabel;           // 状态栏标签
    private UndoManager undoManager;      // 撤销/重做管理器

    public NotepadApp() {
        frame = new JFrame("简易记事本");
        textArea = new JTextArea();
        fileChooser = new JFileChooser();
        undoManager = new UndoManager();

        // 设置基本布局
        frame.setLayout(new BorderLayout());
        frame.add(new JScrollPane(textArea), BorderLayout.CENTER);

        // 设置文本区
        textArea.setFont(new Font("Serif", Font.PLAIN, 14)); // 默认字体
        textArea.setLineWrap(true); // 自动换行
        textArea.setWrapStyleWord(true); // 按字换行
        textArea.getDocument().addUndoableEditListener(e -> undoManager.addEdit(e.getEdit())); // 撤销/重做监听

        // 状态栏
        statusLabel = new JLabel("  未保存  ");
        frame.add(statusLabel, BorderLayout.SOUTH); // 状态栏显示

        // 创建菜单栏
        JMenuBar menuBar = new JMenuBar();
        frame.setJMenuBar(menuBar);
        createMenu(menuBar);

        // 设置窗口
        frame.setSize(600, 400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null); // 居中显示

        // 设置图标
        ImageIcon icon = new ImageIcon("notepad_icon.png"); // 添加图标
        frame.setIconImage(icon.getImage());
    }

    // 创建菜单
    private void createMenu(JMenuBar menuBar) {
        JMenu fileMenu = new JMenu("文件");
        JMenu editMenu = new JMenu("编辑");

        // 文件菜单项
        JMenuItem openItem = new JMenuItem("打开");
        openItem.addActionListener(e -> openFile());
        fileMenu.add(openItem);

        JMenuItem saveItem = new JMenuItem("保存");
        saveItem.addActionListener(e -> saveFile());
        fileMenu.add(saveItem);

        JMenuItem saveAsItem = new JMenuItem("另存为");
        saveAsItem.addActionListener(e -> saveFileAs());
        fileMenu.add(saveAsItem);

        JMenuItem exitItem = new JMenuItem("退出");
        exitItem.addActionListener(e -> exitApplication());
        fileMenu.add(exitItem);

        // 编辑菜单项
        JMenuItem undoItem = new JMenuItem("撤销");
        undoItem.addActionListener(e -> undoAction());
        editMenu.add(undoItem);

        JMenuItem redoItem = new JMenuItem("重做");
        redoItem.addActionListener(e -> redoAction());
        editMenu.add(redoItem);

        JMenuItem fontItem = new JMenuItem("字体样式");
        fontItem.addActionListener(e -> changeFont());
        editMenu.add(fontItem);

        // 将菜单添加到菜单栏
        menuBar.add(fileMenu);
        menuBar.add(editMenu);
    }

    // 打开文件
    private void openFile() {
        int returnValue = fileChooser.showOpenDialog(frame);
        if (returnValue == JFileChooser.APPROVE_OPTION) {
            File selectedFile = fileChooser.getSelectedFile();
            try {
                currentFilePath = selectedFile.getAbsolutePath();
                String content = new String(Files.readAllBytes(selectedFile.toPath()), StandardCharsets.UTF_8); // UTF-8 编码读取
                textArea.setText(content);
                statusLabel.setText("  已打开: " + currentFilePath);
            } catch (IOException e) {
                showError("无法打开文件");
            }
        }
    }

    // 保存文件
    private void saveFile() {
        if (currentFilePath == null) {
            saveFileAs();
        } else {
            try {
                Files.write(Paths.get(currentFilePath), textArea.getText().getBytes(StandardCharsets.UTF_8)); // UTF-8 编码保存
                statusLabel.setText("  已保存: " + currentFilePath);
            } catch (IOException e) {
                showError("保存文件失败");
            }
        }
    }

    // 另存为
    private void saveFileAs() {
        int returnValue = fileChooser.showSaveDialog(frame);
        if (returnValue == JFileChooser.APPROVE_OPTION) {
            File selectedFile = fileChooser.getSelectedFile();
            currentFilePath = selectedFile.getAbsolutePath();
            if (!currentFilePath.endsWith(".txt")) {
                currentFilePath += ".txt"; // 自动加上 .txt 扩展名
            }
            saveFile();
        }
    }

    // 改变字体
    private void changeFont() {
        String[] fonts = {"Serif", "Arial", "Verdana"};
        String selectedFont = (String) JOptionPane.showInputDialog(frame,
                "选择字体",
                "字体样式",
                JOptionPane.PLAIN_MESSAGE,
                null,
                fonts,
                fonts[0]);
        if (selectedFont != null) {
            String[] sizes = {"12", "14", "16", "18", "20"};
            String selectedSize = (String) JOptionPane.showInputDialog(frame,
                    "选择字号",
                    "字号",
                    JOptionPane.PLAIN_MESSAGE,
                    null,
                    sizes,
                    sizes[1]);
            if (selectedSize != null) {
                textArea.setFont(new Font(selectedFont, Font.PLAIN, Integer.parseInt(selectedSize)));
            }
        }
    }

    // 撤销
    private void undoAction() {
        if (undoManager.canUndo()) {
            undoManager.undo();
        }
    }

    // 重做
    private void redoAction() {
        if (undoManager.canRedo()) {
            undoManager.redo();
        }
    }

    // 退出应用程序
    private void exitApplication() {
        if (confirmExit()) {
            System.exit(0);
        }
    }

    // 弹窗提示是否保存文件
    private boolean confirmExit() {
        int response = JOptionPane.showConfirmDialog(frame,
                "是否保存文件?",
                "退出",
                JOptionPane.YES_NO_CANCEL_OPTION,
                JOptionPane.QUESTION_MESSAGE);
        if (response == JOptionPane.YES_OPTION) {
            saveFile();
            return true;
        } else if (response == JOptionPane.NO_OPTION) {
            return true;
        }
        return false;
    }

    // 显示错误信息
    private void showError(String message) {
        JOptionPane.showMessageDialog(frame, message, "错误", JOptionPane.ERROR_MESSAGE);
    }

    // 启动应用程序
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            NotepadApp app = new NotepadApp();
            app.frame.setVisible(true);
        });
    }
}

3.2 代码解析

  代码的核心部分主要包括界面设计、文件操作、字体选择和撤销重做等功能的实现。每个功能我都逐步进行了实现,并在每个环节中进行细致优化。例如,在 文件操作 部分,我采用了 Files.readAllBytes() 和 Files.write() 来保证文件操作的高效与安全;在 字体选择 部分,我通过弹窗让用户可以自由选择字体和字号,并实时预览效果。

  这个小小的记事本,虽然看起来简单,但每一行代码都承载着我对编程的热爱与执着。从每个细节的实现中,我不断打磨自己的技术,精益求精。

1. 类成员变量

  • frame : 这是主窗口 (JFrame) 的对象,所有界面的展示都会依赖于这个窗口对象。
  • textArea : JTextArea 对象,用户用来输入和编辑文本。它会包含实际的文本内容,并在窗口的中央显示。
  • fileChooser : JFileChooser 是一个文件选择对话框,用来打开或保存文件。
  • currentFilePath: 存储当前打开或保存的文件路径,用来标识文件。
  • statusLabel : JLabel 用来在窗口的底部显示当前的状态信息,如文件是否已保存。
  • undoManager : UndoManager 用来管理撤销和重做操作,保证文本编辑操作可以回退或恢复。

2. 构造函数:初始化界面和控件

主要步骤:

  • 创建了主窗口 frame,并设置标题为"简易记事本"。
  • 初始化 textAreafileChooser
  • 通过 frame.setLayout(new BorderLayout()) 使用了 BorderLayout 布局管理器,将文本区域 (JTextArea) 放置在窗口的中央,并用 JScrollPane 包裹使其支持滚动。
  • textArea 设置字体和自动换行功能,确保文本区在超出边界时自动换行。
  • 添加了撤销和重做功能,通过监听 textArea 的文档编辑事件 (UndoableEditListener),每次文本编辑都会通过 undoManager.addEdit() 方法记录。

状态栏:

  • 使用 JLabel 显示文件的保存状态,初始化为"未保存"。

菜单栏:

  • 创建了文件菜单("打开","保存","另存为","退出")和编辑菜单("撤销","重做","字体样式")。
  • 每个菜单项都有一个事件监听器,监听用户操作并触发对应的功能。

3. 文件操作功能

打开文件 (openFile()):
  • 弹出文件选择对话框 (fileChooser.showOpenDialog()) 让用户选择一个文件。
  • 读取选中文件的内容并显示在 textArea 中。使用 Files.readAllBytes() 读取文件,并将其转换为 UTF-8 编码的字符串,显示到文本区域。
  • 更新状态栏显示当前文件路径。
保存文件 (saveFile()):
  • 判断 currentFilePath 是否为 null,如果是,则调用"另存为"功能。
  • 使用 Files.write()textArea 中的文本保存到指定路径的文件,并以 UTF-8 编码保存。
  • 更新状态栏为已保存的文件路径。
另存为 (saveFileAs()):
  • 弹出保存对话框让用户选择保存的文件位置和文件名。
  • 如果文件名没有扩展名 .txt,自动附加该扩展名后保存。

4. 文本编辑功能

改变字体 (changeFont()):
  • 弹出两个对话框:一个让用户选择字体("Serif","Arial","Verdana"),另一个让用户选择字号(12,14,16,18,20)。
  • 用户选择后,通过 textArea.setFont() 改变文本区域的字体和字号。
撤销和重做 (undoAction() 和 redoAction()):
  • undoManager 是一个用于管理撤销和重做操作的类。undoAction() 方法调用 undoManager.undo() 执行撤销操作,redoAction() 方法调用 undoManager.redo() 执行重做操作。
  • 如果没有可撤销或可重做的操作,UndoManager 会进行检查并且不会执行。

5. 退出应用程序与保存提示

退出应用程序 (exitApplication()):
  • 在用户尝试退出时,弹出确认框询问是否保存文件。
  • 如果用户选择保存,则执行保存操作后退出;如果用户选择不保存,则直接退出;如果用户选择取消,则不会退出。
确认保存 (confirmExit()):
  • 弹出 JOptionPane.showConfirmDialog() 提示用户是否保存当前文件。
  • 根据用户选择执行相应的操作:保存文件、退出或者取消退出。

6. 错误提示

显示错误信息 (showError()):
  • 使用 JOptionPane.showMessageDialog() 弹出一个错误提示框,显示错误信息(如文件无法打开或保存失败)。

7. 启动应用程序

启动应用程序 (main()):
  • SwingUtilities.invokeLater() 用来在事件调度线程中启动应用程序,确保 GUI 组件的线程安全。
  • 创建 NotepadApp 实例并使其界面可见。

3.3 主要功能

  1. 编辑文本

    • 用户可以在 JTextArea 中输入和编辑文本内容。
    • JTextArea 是 Swing 提供的一个用于显示和编辑多行文本的组件,已在代码中定义,并通过 setLineWrap(true)setWrapStyleWord(true) 使文本能够自动换行并按单词边界换行。
  2. 打开文件

    • 用户可以通过菜单中的 "打开" 项,选择本地文件并加载文件内容。
    • 通过 JFileChooser 实现文件选择对话框,用户选择的文件会被读取并显示到 JTextArea 中,文件的路径保存在 currentFilePath 中,便于后续的保存操作。
  3. 保存文件

    • 用户可以保存文本内容到本地文件,保存时默认使用 UTF-8 编码,避免乱码。
    • 若文件路径为空,调用 "另存为" 功能,要求用户选择文件路径并保存文件。保存时,系统会根据 currentFilePath 判断文件是否已有路径。
    • 文件保存的同时,状态栏会更新显示文件的保存状态与路径。
  4. 字体样式和字号选择

    • 用户可以通过菜单中的 "字体样式" 项,选择所需的字体(如 Serif, Arial, Verdana)以及字号(如 12、14、16 等),并在 JTextArea 中实时改变显示的字体效果。
    • 使用 JOptionPane 弹出对话框来让用户选择字体和字号,选择后通过 textArea.setFont(...) 方法更新文本样式。
  5. 文件另存为

    • 用户可以选择 "另存为" 功能,将当前文本内容保存到新文件中。
    • 在 "另存为" 的对话框中,系统自动判断文件扩展名是否为 .txt,若用户未指定扩展名,程序会自动添加 .txt 作为文件扩展名。
  6. 保存提示

    • 在尝试退出程序时,若当前文本尚未保存,系统会弹出提示,询问用户是否保存文件。
    • JOptionPane.showConfirmDialog() 用于弹出确认对话框,选择 "是" 后保存文件,选择 "否" 后直接退出,选择 "取消" 后不退出。
  7. 编辑文本

    • 用户可以在主界面的文本区域 (JTextArea) 中直接输入、修改和删除文本内容,支持基本的文本操作和自动换行,适合快速记录笔记或编写文档。
  8. 撤销 / 重做: 支持对文本操作进行撤销和重做。

    • 撤销:用户误操作后可以通过 "编辑" 菜单中的"撤销"来回退到上一步状态。
    • 重做:如果撤销后发现不需要回退,可以使用"重做"恢复操作。
    • 实现基于 UndoManager 类,自动监听文本更改。
  9. 字体样式设置

    • 用户可通过菜单选择字体名称(如 Serif、Arial、Verdana)和字号(如 12、14、16、18、20),即时应用于文本内容,改善阅读或编辑体验。
  10. 状态栏提示

    • 窗口底部的状态栏动态显示当前文件的保存状态和路径,便于用户确认文件是否保存。

3.4 流程图

  以下是记事本程序的基本流程图,为大家展示了用户如何通过菜单操作实现文件的打开、保存、字体选择等功能:

3.5 本地运行演示

  当我看到记事本应用流畅运行时,内心的成就感是无可比拟的。每一项功能的实现,都让我回想起过去在编程中的学习和成长。虽然这只是一个简单的工具,但它象征着我多年来对技术的理解和沉淀。每一次的进步,都是我不断挑战自我的结果。

完整界面

如下是程序运行成功会自动弹出的一个界面,演示如下:

首先,点击【文件】可支持以下功能:打开、保存、另存为、退出。

【点击】编辑可支持的功能有:撤销、重做、字体样式。

打开文件

接着,我们来演示下打开文件:

接着我们任意打开一个文件,可对比我们本地的TXT记事本。

虽然功能上没有txt记事本丰富,但是,纯手搓的,是不是非常可以了!只需要把这些其他的功能逐步完善,那堪比记事本,甚至可谈超越。

保存文件

我们直接通过打开的文本进行新增内容,效果如下:

另存为

我们同样也可以将文件另存为,功能上与文件另存为毫无区别。

  剩下的其他功能及未实现的功能点,这就留给大家去测试及完善啦,基础的模型已经替大家搭建好了。

4. 总结

  通过本项目的实现,我们成功构建了一个简易的记事本应用,支持 文本编辑、文件打开、保存、另存为 ,并且实现了 字体选择、字号设置、保存提示 等功能。通过使用 Java Swing 和 Java IO,我们实现了图形界面的搭建和文件的读取写入。

  通过这个记事本项目,大家不仅可以学会如何使用 Swing 开发桌面应用,还能深入了解了文件操作的基本方式。同时希望,大家可以在此基础上继续扩展功能,考虑加入 文本搜索、撤销重做文件加密保护 等功能,进一步提升应用的实用性和安全性。

  我相信,编程不止是一个解决问题的工具,它更是一种创造和表达的方式。每一次的编程经历,都是对自己的一次磨砺,让我不断突破,不断成长。

📣 关于我

我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主&最具价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-

相关推荐
曹牧11 分钟前
文档格式:OFD
java
豆角焖肉36 分钟前
Maven进阶与搭建私服
java·maven
大不点wow1 小时前
Java序列化与反序列化:让对象走出JVM
java·开发语言·jvm
止语Lab1 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活1 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
噢,我明白了1 小时前
Java中日期和字符串的处理
java·开发语言·日期
程序员天天困1 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn1 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
dkbnull1 小时前
Spring Boot请求处理组件对比详解
java·spring boot
jun_bai1 小时前
Orthanc服务器使用java上传dicom影像文件
java·运维·服务器