你以为用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-

相关推荐
超浪的晨3 分钟前
Maven 与单元测试:JavaWeb 项目质量保障的基石
java·开发语言·学习·单元测试·maven·个人开发
不会理财的程序员不是好老板6 分钟前
Java Spring Boot项目中集成Swagger完整步骤
java·开发语言·spring boot
Kookoos15 分钟前
ABP VNext + GraphQL Federation:跨微服务联合 Schema 分层
后端·微服务·.net·graphql·abp vnext·schema 分层
不太厉害的程序员16 分钟前
eclipse更改jdk环境和生成webservice客户端代码
java·ide·后端·eclipse·webservice
悟能不能悟18 分钟前
ode with me是idea中用来干嘛的插件
java·ide·intellij-idea
Murphy_lx18 分钟前
C++多态的原理
java·开发语言·c++
神仙别闹20 分钟前
基于JSP+MySQL 实现(Web)毕业设计题目收集系统
java·前端·mysql
小韩学长yyds36 分钟前
大疆无人机开发:MQTT 赋能机场系统集成的Java实战之旅
java·无人机
前端伪大叔37 分钟前
第 6 篇:《量化使用看图说话!plot-dataframe 图表可视化教程》
后端·github
考虑考虑39 分钟前
JDK21中的虚拟线程
java·后端·java ee