市面上有很多文本编辑器。有些在终端中运行,有些在 GUI 中运行,有些在浏览器中运行,还有些在浏览器引擎中运行。许多都非常好用,有些甚至非常出色。但有时候,对于任何问题,最令人满意的答案就是你自己构建的答案。
不要搞错:构建一个真正优秀的文本编辑器比看起来要难得多。但话说回来,构建一个基本的文本编辑器也没有你想象的那么难。事实上,大多数编程工具包已经为你准备好了大部分文本编辑器的组件供你使用。文本编辑周围的组件,例如菜单栏、文件选择器对话框等等,都很容易放置到位。因此,构建一个基本的文本编辑器是一项令人惊喜的有趣且具有启发性的编程课程,尽管它是中级的。你可能会发现自己渴望使用自己构建的工具,而且你使用得越多,就越有可能受到启发去添加更多功能,从而学习更多关于你正在使用的编程语言的知识。
为了使这个练习更具现实意义,最好选择一种具有良好 GUI 工具包的语言。有很多选择,包括 Qt、FLTK 或 GTK,但请务必先查看文档,以确保它具有你期望的功能。在本文中,我使用 Java 及其内置的 Swing 组件集。如果你想使用不同的语言或不同的工具集,本文仍然可以为你提供如何解决问题的一些思路。
无论你选择哪种工具包,用任何主要的工具包编写文本编辑器都出奇地相似。如果你是 Java 新手,需要更多关于入门的信息,请先阅读我的猜数字游戏文章。
项目设置
通常,我使用并推荐像 Netbeans 或 Eclipse 这样的 IDE,但我发现,在练习一门新语言时,做一些体力劳动可能会有所帮助,这样你就能更好地理解在使用 IDE 时被隐藏起来的东西。在本文中,我假设你正在使用文本编辑器和终端进行编程。
在开始之前,为自己创建一个项目目录。在项目文件夹中,创建一个名为 src
的目录来存放你的源文件。
$ mkdir -p myTextEditor/src
$ cd myTextEditor
在你的 src
目录中创建一个名为 TextEdit.java
的空文件
$ touch src/TextEditor.java
在你最喜欢的文本编辑器(我是指你没有编写的那个最喜欢的编辑器)中打开该文件,准备开始编码!
包和导入
为了确保你的 Java 应用程序具有唯一的标识符,你必须声明一个 package 名称。典型的格式是使用反向域名,如果你实际上拥有域名,这将特别容易。如果你没有,你可以使用 local
作为顶级域名。与 Java 和许多语言一样,该行以分号结尾。
在命名你的 Java 包之后,你必须告诉 Java 编译器 (javac
) 在构建代码时要使用哪些库。在实践中,这通常是你随着编码的进行而添加的东西,因为你很少自己知道你需要哪些库。但是,有些库是事先显而易见的。例如,你知道这个文本编辑器是基于 Swing GUI 工具包的,所以导入 javax.swing.JFrame
和 javax.swing.UIManager
以及其他相关库是理所当然的。
package com.example.textedit;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.filechooser.FileSystemView;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
为了本次练习的目的,你预先获得了所有需要的库的先见之明。在现实生活中,无论你喜欢哪种语言,你都会在研究如何解决任何给定问题时发现库,然后你会将其导入到你的代码中使用。而且不用担心——如果你忘记包含库,你的编译器或解释器会警告你!
主窗口
这是一个单窗口应用程序,因此此应用程序的主类是一个 JFrame,它附加了一个 ActionListener
来捕获菜单事件。在 Java 中,当你使用现有的 widget 元素时,你会用你的代码“扩展”它。这个主窗口需要三个字段:窗口本身(JFrame 的一个实例)、文件选择器的返回值指示器以及文本编辑器本身 (JTextArea)。
public final class TextEdit extends JFrame implements ActionListener {
private static JTextArea area;
private static JFrame frame;
private static int returnValue = 0;
令人惊讶的是,这几行代码完成了实现基本文本编辑器大约 80% 的工作,因为 JTextArea 是 Java 的文本输入字段。剩下的 80 行代码大部分用于处理辅助功能,例如保存和打开文件。
构建菜单
JMenuBar
widget 旨在位于 JFrame 的顶部,提供你想要的尽可能多的条目。但是,Java 不是一种拖放式编程语言,因此对于你添加的每个菜单,你还必须编写一个函数。为了使这个项目易于管理,我提供了四个功能:创建新文件、打开现有文件、将文本保存到文件以及关闭应用程序。
在大多数流行的工具包中,创建菜单的过程基本相同。首先,你创建菜单栏本身,然后你创建一个顶级菜单(例如“文件”),然后你创建子菜单项(例如“新建”、“保存”等等)。
public TextEdit() { run(); }
public void run() {
frame = new JFrame("Text Edit");
// Set the look-and-feel (LNF) of the application
// Try to default to whatever the host system prefers
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
Logger.getLogger(TextEdit.class.getName()).log(Level.SEVERE, null, ex);
}
// Set attributes of the app window
area = new JTextArea();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(area);
frame.setSize(640, 480);
frame.setVisible(true);
// Build the menu
JMenuBar menu_main = new JMenuBar();
JMenu menu_file = new JMenu("File");
JMenuItem menuitem_new = new JMenuItem("New");
JMenuItem menuitem_open = new JMenuItem("Open");
JMenuItem menuitem_save = new JMenuItem("Save");
JMenuItem menuitem_quit = new JMenuItem("Quit");
menuitem_new.addActionListener(this);
menuitem_open.addActionListener(this);
menuitem_save.addActionListener(this);
menuitem_quit.addActionListener(this);
menu_main.add(menu_file);
menu_file.add(menuitem_new);
menu_file.add(menuitem_open);
menu_file.add(menuitem_save);
menu_file.add(menuitem_quit);
frame.setJMenuBar(menu_main);
}
现在剩下要做的就是实现菜单项描述的功能。
编程菜单操作
你的应用程序响应菜单选择,因为你的 JFrame 附加了一个 ActionListener
。当你在 Java 中实现事件处理程序时,你必须“覆盖”其内置函数。这听起来比实际情况更严重。你不是在重写 Java;你只是在实现事件处理程序已定义但未实现的函数。
在这种情况下,你必须覆盖 actionPerformed
方法。因为 File 菜单中的几乎所有条目都与文件有关,所以我的代码提前定义了一个 JFileChooser。其余代码被分成 if
语句的子句,该语句查看收到了什么事件并据此采取行动。每个子句都与其他子句截然不同,因为每个项目都暗示着完全独特的东西。最相似的是 Open 和 Save,因为它们都使用 JFileChooser 来选择文件系统中的一个点,以获取或放置数据。
“New”选项清除 JTextArea 而不发出警告,而 Quit 关闭应用程序而不发出警告。这两个“功能”都很危险,所以如果你想对这段代码进行一些小的改进,那是个不错的起点。友好的警告内容尚未保存是任何优秀文本编辑器的重要功能,但为了简单起见,这是一个未来的功能。
@Override
public void actionPerformed(ActionEvent e) {
String ingest = null;
JFileChooser jfc = new JFileChooser(FileSystemView.getFileSystemView().getHomeDirectory());
jfc.setDialogTitle("Choose destination.");
jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
String ae = e.getActionCommand();
if (ae.equals("Open")) {
returnValue = jfc.showOpenDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File f = new File(jfc.getSelectedFile().getAbsolutePath());
try{
FileReader read = new FileReader(f);
Scanner scan = new Scanner(read);
while(scan.hasNextLine()){
String line = scan.nextLine() + "\n";
ingest = ingest + line;
}
area.setText(ingest);
}
catch ( FileNotFoundException ex) { ex.printStackTrace(); }
}
// SAVE
} else if (ae.equals("Save")) {
returnValue = jfc.showSaveDialog(null);
try {
File f = new File(jfc.getSelectedFile().getAbsolutePath());
FileWriter out = new FileWriter(f);
out.write(area.getText());
out.close();
} catch (FileNotFoundException ex) {
Component f = null;
JOptionPane.showMessageDialog(f,"File not found.");
} catch (IOException ex) {
Component f = null;
JOptionPane.showMessageDialog(f,"Error.");
}
} else if (ae.equals("New")) {
area.setText("");
} else if (ae.equals("Quit")) { System.exit(0); }
}
}
从技术上讲,这就是这个文本编辑器的全部内容。当然,没有什么事情是真正完成的,而且,除了测试和打包步骤之外,还有足够的时间来发现遗漏的必需品。如果你没有注意到暗示:这段代码中肯定缺少一些东西。你知道是什么了吗?(这主要在猜数字游戏文章中提到。)
测试
你现在可以测试你的应用程序了。从终端启动你的文本编辑器
$ java ./src/TextEdit.java
error: can’t find main(String[]) method in class: com.example.textedit.TextEdit
似乎代码没有 main 方法。有几种方法可以解决这个问题:你可以在 TextEdit.java
中创建一个 main 方法,并让它运行 TextEdit
类的实例,或者你可以创建一个单独的文件来包含 main 方法。两者效果一样好,但后者在大型项目中更符合实际预期,因此值得习惯于处理协同工作的单独文件以构成完整的应用程序。
在 src
中创建一个 Main.java
文件并在你最喜欢的编辑器中打开
package com.example.textedit;
public class Main {
public static void main(String[] args) {
TextEdit runner = new TextEdit();
}
}
你可以再次尝试,但现在有两个文件相互依赖才能运行,因此你必须编译代码。Java 使用 javac
编译器,你可以使用 -d
选项设置目标目录
$ javac src/*java -d .
这将创建一个新的目录结构,该结构完全按照你的包名称建模:com/example/textedit
。这个新的类路径包含文件 Main.class
和 TextEdit.class
,它们是构成你的应用程序的两个文件。你可以使用 java
通过引用 Main 类的位置和名称(而不是文件名)来运行它们
$ java info/slackermedia/textedit/Main
你的文本编辑器打开了,你可以输入内容,打开文件,甚至保存你的工作。

以 Java 包的形式分享你的工作
虽然对于某些程序员来说,以源文件和热情鼓励学习如何运行它们的方式交付应用程序似乎是可以接受的,但 Java 使打包你的应用程序以便其他人可以运行它变得非常容易。你已经拥有了大部分所需的结构,但你确实需要向 Manifest.txt
文件添加一些元数据
$ echo "Manifest-Version: 1.0" > Manifest.txt
用于打包的 jar
命令很像 tar 命令,因此许多选项对你来说可能看起来很熟悉。要创建 JAR 文件
$ jar cvfme TextEdit.jar
Manifest.txt
com.example.textedit.Main
com/example/textedit/*.class
从命令的语法中,你可能会猜测它会创建一个名为 TextEdit.jar
的新 JAR 文件,其必需的清单数据位于 Manifest.txt
中。它的主类被定义为包名称的扩展,并且类本身是 com/example/textedit/Main.class
。
你可以查看 JAR 文件的内容
$ jar tvf TextEdit.jar
0 Wed Nov 25 META-INF/
105 Wed Nov 25 META-INF/MANIFEST.MF
338 Wed Nov 25 com/example/textedit/textedit/Main.class
4373 Wed Nov 25 com/example/textedit/textedit/TextEdit.class
如果你想查看你的元数据是如何集成到 MANIFEST.MF
文件中的,你甚至可以使用 xvf
选项提取它。
使用 java
命令运行你的 JAR 文件
$ java -jar TextEdit.jar
你甚至可以创建一个桌面文件,以便你的应用程序可以通过单击应用程序菜单中的图标来启动。
改进它
就目前的状态而言,这是一个非常基本的文本编辑器,最适合用于快速笔记或简短的 README 文档。一些改进(例如添加垂直滚动条)通过少量研究就可以快速轻松地完成,而另一些改进(例如实现广泛的首选项系统)则需要真正的工作。
但如果你一直想学习一门新语言,这可能是一个完美的自学实践项目。正如你所看到的,创建一个文本编辑器在代码方面并不繁琐,而且范围可控。如果你经常使用文本编辑器,那么编写自己的文本编辑器会令人满意且有趣。所以打开你最喜欢的文本编辑器(你编写的那个),开始添加功能吧!
1 条评论