记事本按整体被分为三个部分(阶段),一个是图形的构建,一个是在图形界面上添加的功能,最后一个部分是实现这些功能的逻辑。
主要用的是swing,然后注册event等各种监听事件,最后是在监听到用户操作时执行相应的业务逻辑。
以下主要是创建记事本界面用到的类,包括窗口、菜单条、编辑区、最下面的状态栏、滚动条、右键菜单等等,重点是swing里是用名称表达的,比如菜单类Jmenu,文本区JtextArea,顾名思义即可:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
感觉最简单就是界面这一块,只要创建好小的容器,然后一级级的添加进大的容器里就可以,比如Jmenu.add(JmenuItem),Jmenubar.add(Jmenu)、JFrame.add(Jmenubar)、JFrame.add(JTextArea).
整个界面完成之后,给每个菜单项以及右键菜单的每个项添加ActionListener监听,这里的主类已经实现了监听接口,所以在main和构造方法里创建好界面之后,直接在下面重写接口的所有方法,这里就一个:
public interface ActionListener extends EventListener {
/**
* Invoked when an action occurs.
*/
public void actionPerformed(ActionEvent e);
}
当用户操作时, actionPerformed(ActionEvent e)会被执行并且获得事件,然后用e.getSource()获得事件源,判断事件源是哪个对象,比如如果是if(e.getSource() == exitButton),那么就开始执行{是否有文件未保存?是否要退出?}。
由于事件源太多,在actionPerformed里实现所有逻辑代码过于臃长,所以
先把方法抽调出来,然后再在各个方法体里实现具体功能:
public void actionPerformed(ActionEvent e) {
if (e.getSource() == popupMenu_Cut || e.getSource() == menuEdit_cut) {//
copy();
editArea.replaceSelection("");
} else if (e.getSource() == popupMenu_Copy || e.getSource() == menuEdit_copy) {//
copy();
} else if (e.getSource() == popupMenu_Paste || e.getSource() == menuEdit_paste) {//
} else if (e.getSource() == popupMenu_Delete || e.getSource() == menuEdit_delete) {//
editArea.replaceSelection("");
} else if (e.getSource() == popupMenu_SelectAll || e.getSource() == menuEdit_selectAll) {//
editArea.selectAll();
} else if (e.getSource() == menuFile_new) {//
newFile();
} else if (e.getSource() == menuFile_open) {//
open();
} else if (e.getSource() == menuFile_save) {//
if (isChanged)
save();
} else if (e.getSource() == menuFile_saveas) {//
saveAs();
} else if (e.getSource() == menuFile_exit) {//
doSomethingOnWindowClosing();
} else if (e.getSource() == menuEdit_find) {
find();
} else .....
比如另存为的方法长这样:
private void saveAs() {
JFileChooser fileChooser = new JFileChooser("C:\\");
fileChooser.setDialogTitle("另存为");
// fileChooser.setApproveButtonText("确定");
if (absoluteFile != null) {
fileChooser.setSelectedFile(absoluteFile);
}
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);// 有文件名才能点确定
int result = fileChooser.showSaveDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {// 选择文件名后确定
File filename = fileChooser.getSelectedFile();
if (!filename.getPath().endsWith(".txt")) {
String path = filename.getAbsolutePath() + ".txt";
filename = new File(path);
}
if (filename.exists() && JFileChooser.APPROVE_OPTION != JOptionPane.showOptionDialog(this,
filename.getName() + " 已存在,是否要替换?", "确认另存为", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
null, null, null)) {
} else {
new FileWriteThread(editArea, filename).start(); // 使用多线程另存文件
this.isChanged = false;
absoluteFile = filename;
this.setTitle(absoluteFile.getName());
}
} else {
// 取消、关闭提示框,返回
}
}
由于要监听的按钮实在太多,强烈建议使用内部类,像这样直接又方便:
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
replaceDialog.dispose();
}
});
对我来说最拖时间的是布局管理器和性能提升这两个方面。
比如这两个界面神烦:
窗口里头的组件各种错位,最后逼不得已用绝对定位setBounds(x,y,width,height)处理了一个,然后用gridbaglayout(格子、袋布局,很强大,每个格子大小自定,位置自定,组件大小、内外边距、拉伸比例或者不能拉伸自定等),另外还有水平排列、垂直排列,行和列排列等一些布局管理器,很整齐所以也没有那么灵活。据说Frame容器默认的是border管理器,也就是东南西北中五个位置,而Jpanel是Flowlayout,也就是流动的一行放不下流动到下一行。
做完界面、按钮等之后就到实现打开、保存、剪切、查找、替换、字体等功能了。
大概上是这样的,打开文件,先判断是否有正在编辑的内容未被保存,如有提示是否保存,然后再跳出一个JFileChooser(看名字就知道干嘛的了),用户选择完文件后判断Unicode、asni、UTF-8或者默认然后使用输入流读入文本到textArea。保存呢当确定保存后如果是新建文件同样需要选择保存位置和文件名,否则直接写入原有的文件。剪切复制粘贴这些用的是java一些api组合而成。查找先获取光标位置,按用户是否区分大小写和方向截断一个String,用lastindexof或者indexof,不过我是按512K字符分块查找的。全部替换一开始用的是递归,从前面找到先压入栈,找完后在从文本后替换回来(如果前面先替换后面位置就乱了),最后打开一个10m的文件一替换递归方法被调用3700次就stackoverflow了,所以只能分块替换或者全部提取到一个String里头查找替换平行进行。后来用Windows的notepad.exe测一下,写一个有20M个你字的txt文件让它替换,竟然过一会就替换完了怎么做到的!!我自己的测下卡死了。这里我主要考虑的是减少内存的消耗和时间的消耗,提升流畅性,看看以后有啥好的算法。
最后想着弄个exe文件的不过精简jre这块没做好太大没有什么打包的欲望,不过还是做了个exe,在main函数里加上:
if (args.length > 0 && args[0].contains(":")) {
File file = new File(args[0]);
notepad.openFile(file);
}
这样直接在拖文件到exe上就可以打开了。
源码1500行就不贴了,把源码和文件附上,供学习交流,如果发现大bug和有好的修改建议,直接评论区。
附件地址:上传了没看见在哪。。。