<JAVA - 大作业(1)文本编辑器 >
<JAVA - 大作业(1)文本编辑器 >
背景
-
JAVA上机大作业:qq / 代码评价系统
-
第一次上机主题是练习JAVA自带的GUI图形化编程
-
目的:实现一个跟window10记事本界面相同,功能相近的文本编辑器,时间有剩余的话再添加类似notepad++上的一些额外功能。
-
记事本结构
-
标题栏
显示标题title -
菜单栏
文件、编辑、格式、查看、帮助 - 菜单项-
<文件>下拉栏有:新建、新窗口、打开、保存、退出
-
<编辑>下拉栏有:撤销、恢复、剪切、复制、粘贴、删除
-
<格式>下拉栏有:字体
-
<帮助>下拉栏有:关于
-
-
文本区域
- 底侧滚动条
-
设计思路
-
菜单栏:
- 主体就是一个JFrame,设置一些属性,然后设置菜单栏JMenuBar,add菜单项JMenu,菜单项的下拉栏add菜单子项JMenuItem
-
文本区域:
- 完成菜单栏之后就是一个占屏幕率超大的JTextArea,注意该文本区域需要将他设置为静态,并且写一个静态get函数得到它,因为有跨类的需要改该文本属性。由于有滚动条,后续还要加上显示行号的功能,所以用一个JScrollPane装载它。
-
文件菜单子栏:
-
新建:就新建一个窗口new TextEdition(),并且把当前窗口退出dispose(),退出前调用一次保存函数savefile()
-
新窗口:
- 新建一个窗口,并且当前窗口不退出,两窗口相互不影响,即默认退出操作使用DISPOSE_ON_CLOSE(当然可以自己写,退出时加上是否保存询问)
-
打开:
- 使用JFileChooser,界面为打开界面,打开跳出一个Dialog对话框,选择文件进行打开,InputStreamReader+BufferedReader
-
保存:
- 使用JFileChooser,界面为保存界面,跟打开操作一样,OutputStreamWriter+BufferedWriter
-
退出:
- 调用保存函数savefile(),并且dispose()当前窗口
-
-
编辑菜单子栏:
-
撤销:
- 撤销与恢复都需要用到撤销管理类um = new UndoManager(),在监听器初始化时注册撤销可编辑监听器 - 文本区域getDocument().addUndoableEditListener,撤销操作就是 um.undo()
-
恢复:
- 恢复操作与上一致,监听器设置好以后,调用um.redo()即是恢复
-
剪切:
- 剪切与复制、粘贴是一起完成,需要用到系统剪切板clipboard = this.getToolkit().getSystemClipboard(),剪切就是复制+删除操作
-
复制:
- 先将文本区域的文本放到string里面,创建能传输指定 String 的 StringSelection,把系统剪切板的内容设置成StringSelection:
clipboard.setContents(editText,null);
- 先将文本区域的文本放到string里面,创建能传输指定 String 的 StringSelection,把系统剪切板的内容设置成StringSelection:
-
粘贴:
- 先将系统剪切板的内容放在一个Transferable里面,之后再将Transferable转成string,文本区域append即可
-
删除
- getSelectionStart,getSelectionEnd得到头尾,再replaceRange替换成空文本即可
-
-
格式菜单子栏:
- 字体
- 新建一个窗体设置字体,用JComboBox实现下拉列表,一个JTextField放演示文本,再是确认取消按钮,三个JPanel分别装这三部分整理布局排版,确认按钮点击后就调用静态get函数得到父窗体的文本区域设置其属性。
- 字体
-
帮助菜单子栏:
- 关于
- 弹出一个新窗体,里面用label写文本显示
- 关于
TextEdition - 主窗体类实现
主窗体属性
ublic class TextEditor extends JFrame implements ActionListener{
// 其实我们这里也可以用ItemListener用于捕获带有item的组件产生的事件,
// 而ActionListener是所有监听器的父类,可以监听到所有的事件,由于担心还会有其他的事件需要监听,
// 所以就直接用 ActionListener 了
//菜单栏
private JMenuBar menuBar;
//菜单项
private JMenu menu_File,menu_Edit,menu_Help,menu_Format;
//file菜单项内容
private JMenuItem item_new,item_newwindow,item_open,item_save,item_exit;
//edit菜单项内容
private JMenuItem item_undo,item_redo,item_cut,item_copy,item_stick,item_delete;
//help菜单项内容
private JMenuItem item_about;
//format菜单项内容
private JMenuItem item_word_format;
//文本区域
private static JTextArea edit_text_area; //static因为要在其他窗口对其他字体格式进行改动
//文本滚动条
private JScrollPane scroll_bar;
//撤销管理
private UndoManager um;
//系统剪切板
private Clipboard clipboard;
//文件选择器
private JFileChooser fileChooser;
主窗体构造器
public TextEditor()
{
initMenuBar();
initEditText();
initListener();
this.setJMenuBar(menuBar);
this.setSize(800,600);
this.add(scroll_bar);
this.setTitle("黄龙士文本编辑器");
this.setVisible(true);
this.setLocationRelativeTo(null); //窗体位于正中间位置
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // EXIT_ON_CLOSE https://www.cnblogs.com/lihaiming93/p/4752422.html 后续还要改,加上退出询问
}
initMenuBar() - 初始化菜单栏
private void initMenuBar() {
//初始化菜单栏
menuBar = new JMenuBar();
//file菜单项
menu_File = new JMenu("文件(F)");
menu_File.setMnemonic('f'); // 设置快捷键-alt+f:打开 - 也可以传入KeyEvent.VK_F
item_new = new JMenuItem("新建");
item_newwindow = new JMenuItem("新窗口");
item_open = new JMenuItem("打开");
item_save = new JMenuItem("保存");
item_save.setAccelerator(KeyStroke.getKeyStroke('S',java.awt.Event.CTRL_MASK,false));//给item添加快捷键 CTRL_MASK = ctrl键
item_exit = new JMenuItem("退出");
menu_File.add(item_new);
menu_File.add(item_newwindow);
menu_File.add(item_open);
menu_File.add(item_save);
menu_File.add(item_exit);
//edit菜单项
menu_Edit = new JMenu("编辑(E)");
menu_Edit.setMnemonic('e');
item_undo = new JMenuItem("撤销");
item_redo = new JMenuItem("恢复");
item_cut = new JMenuItem("剪切");
item_copy = new JMenuItem("复制");
item_stick = new JMenuItem("粘贴");
item_delete = new JMenuItem("删除");
menu_Edit.add(item_undo);
menu_Edit.add(item_redo);
menu_Edit.add(item_cut);
menu_Edit.add(item_copy);
menu_Edit.add(item_stick);
menu_Edit.add(item_delete);
//help菜单项
menu_Help = new JMenu("帮助(H)");
menu_Help.setMnemonic('h');
item_about = new JMenuItem("关于");
menu_Help.add(item_about);
//format菜单项
menu_Format = new JMenu("格式(O)");
menu_Format.setMnemonic('o');
item_word_format = new JMenuItem("字体(F)"); //CTRL_MASK = ctrl键
item_word_format.setAccelerator(KeyStroke.getKeyStroke('F',java.awt.Event.CTRL_MASK,false));//给item添加快捷键
menu_Format.add(item_word_format);
//加入菜单栏
menuBar.add(menu_File);
menuBar.add(menu_Edit);
menuBar.add(menu_Format);
menuBar.add(menu_Help);
}
initEditText() - 初始化文本区域
private void initEditText() {
edit_text_area = new JTextArea();
scroll_bar = new JScrollPane(edit_text_area);
//垂直滚动条总是出现 - 自动出现:JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED||VERTICAL_SCROLLBAR_ALWAYS
scroll_bar.setRowHeaderView(new LineNumberHeaderView()); //显示行号
// 具体显示行号类:https://blog.csdn.net/wangjianyu0115/article/details/50780269
scroll_bar.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scroll_bar.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
um = new UndoManager();
clipboard = this.getToolkit().getSystemClipboard();
}
saveFile - 保存文件
private void saveFile() {
// TODO Auto-generated method stub
File file =null;
int result; //int型值,showSaveDialog返回值用来判断用户是否选择了文件或路径。
fileChooser = new JFileChooser("C:\\Users\\62473\\Desktop\\"); //打开用户默认目录
result = fileChooser.showSaveDialog(rootPane); // 设置Dialog的根View根布局 显示对话框
if(result == JFileChooser.APPROVE_OPTION) {
file = fileChooser.getSelectedFile(); // 若点击了确定按钮,给file填文件路径
}
else return; //按取消键后不会运行下面代码
try{
OutputStreamWriter write = new OutputStreamWriter(new FileOutputStream(file),"UTF-8"); // 对字符进行编码转换 输出流
BufferedWriter writer = new BufferedWriter(write);
for (String value : edit_text_area.getText().split("\n")) { // 实现在JTextArea多行如何转换到实际文本多行
writer.write(value);
writer.newLine();//换行
}
writer.close();
}
catch(IOException e) {
e.printStackTrace();
}
}
openfile() - 打开文件
private void openFile() {
File file =null;
int result;
fileChooser =new JFileChooser("C:\\Users\\62473\\Desktop\\");
result = fileChooser.showOpenDialog(rootPane);
if(result == JFileChooser.APPROVE_OPTION) {
file = fileChooser.getSelectedFile(); // 若点击了确定按钮,给file填文件路径
}
else return; //按取消键后不会运行下面代码
if(file.isFile() && file.exists()) {
try {
edit_text_area.setText(""); //清空目前文本内容
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(file),"UTF-8");
BufferedReader reader = new BufferedReader(inputStreamReader);
String readLine;
while ((readLine = reader.readLine()) != null) {
edit_text_area.append(readLine+'\n');
}
reader.close();
}
catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
actionPerformed(ActionEvent e) - 监听事件响应
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
//https://zhidao.baidu.com/question/2204274734088183468.html
if(e.getSource()==item_about)
{
new helpWindow();
}
else if (e.getSource()==item_word_format) {
new aboutFormat();
}
else if (e.getSource()==item_new) {
this.saveFile(); //新建文件前保存原文件
new TextEditor();
this.dispose();
}
else if (e.getSource()==item_newwindow) {
new TextEditor(); //添加新窗口,父窗口不会退出
}
else if (e.getSource()==item_exit){
this.saveFile();
this.dispose(); //退出询问(目前有无更改都会弹出保存窗口)
}
else if (e.getSource()==item_save) {
this.saveFile();
}
else if (e.getSource()==item_open) {
this.openFile();
}
else if (e.getSource()==item_undo&&um.canUndo()) { //撤销可以撤销是撤销上一步文本操作
um.undo();
}
else if (e.getSource()==item_redo&&um.canRedo()) { //恢复就是恢复上一步文本操作(需要被撤销)
um.redo();
}
else if (e.getSource()==item_copy) {
String temptext = edit_text_area.getSelectedText(); //得到选取文本
StringSelection editText = new StringSelection(temptext); //创建能传输指定 String 的 Transferable
clipboard.setContents(editText,null); // 将剪贴板的当前内容设置到指定的 transferable 对象,
// 并将指定的剪贴板所有者作为新内容的所有者注册。
}
else if (e.getSource()==item_cut) {
String temptext = edit_text_area.getSelectedText();
StringSelection editText = new StringSelection(temptext);
clipboard.setContents(editText,null);
int start= edit_text_area.getSelectionStart(); //复制+删除
int end = edit_text_area.getSelectionEnd();
edit_text_area.replaceRange("",start,end);
}
else if (e.getSource()==item_stick) {
Transferable contents = clipboard.getContents(this);
DataFlavor flavor= DataFlavor.stringFlavor;
if( contents.isDataFlavorSupported(flavor))
{
try
{
String str;
str = (String)contents.getTransferData(flavor);
edit_text_area.append(str);
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
}
else if (e.getSource()==item_delete) {
int start= edit_text_area.getSelectionStart(); //删除
int end = edit_text_area.getSelectionEnd();
edit_text_area.replaceRange("",start,end);
}
}
initListener() - 初始化监听器
public void initListener()
{
// 菜单iter监听
item_new.addActionListener(this);
item_newwindow.addActionListener(this);
item_open.addActionListener(this);
item_save.addActionListener(this);
item_exit.addActionListener(this);
item_undo.addActionListener(this);
item_redo.addActionListener(this);
item_cut.addActionListener(this);
item_copy.addActionListener(this);
item_stick.addActionListener(this);
item_delete.addActionListener(this);
item_word_format.addActionListener(this);
item_about.addActionListener(this);
//注册撤销/恢复可编辑监听器
edit_text_area.getDocument().addUndoableEditListener(new UndoableEditListener(){
public void undoableEditHappened(UndoableEditEvent e) {
um.addEdit(e.getEdit());
}
});
}
getEdit_text_area() - 静态get函数得到文本区域
public static JTextArea getEdit_text_area() {
//返回文本编辑区给其他窗口
return edit_text_area;
}
main() - main函数
public static void main(String[] args) {
TextEditor test = new TextEditor();
}
}
aboutFormat - 字体窗口实现
字体窗口属性
public class aboutFormat extends JFrame implements ItemListener,ActionListener {
private JComboBox choose_word_style; //下拉列表
private JComboBox choose_word_big;
private JComboBox choose_word_pattern;
private JComboBox choose_word_color;
private String[] styles = {"宋体","黑体","楷体","微软雅黑","隶书"};
private String[] colors = {"黑色","蓝色","绿色","红色","白色","黄色"};
private String[] word_big = {"2","4","8","16","24","32","64","72"};
private String[] pattern = {"常规","倾斜","粗体"};
private JPanel paneNorth;//用于装四个ComboBox
private JPanel paneCenter;//用来装演示区
private JPanel paneSouth;//用来装按钮
private JTextField showText;//演示文本
private JButton btn_ok;
private JButton btn_cancel;
private Font selectedFont = TextEditor.getEdit_text_area().getFont(); //用来封装改变的属性
private String selectedStyle = "宋体"; //默认字体属性
private int selectedBig = 32;
private int selectedPattern = Font.PLAIN;
private Color selectedColor = TextEditor.getEdit_text_area().getForeground(); //颜色要单独加上改
字体窗口构造器
public aboutFormat() {
initBox();
initText();
initButton();
initLocation();
initListener();
addBtnListener();
this.setSize(550,240);
this.setTitle("文字格式");
this.setVisible(true);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
addBtnListener() - 注册监听
private void addBtnListener() {
// TODO Auto-generated method stub //注册按钮监听
btn_cancel.addActionListener(this);
btn_ok.addActionListener(this);
}
initListener() - 初始化监听器
private void initListener() { //注册下拉框监听
// TODO Auto-generated method stub
choose_word_style.addItemListener(this);
choose_word_big.addItemListener(this);
choose_word_pattern.addItemListener(this);
choose_word_color.addItemListener(this);
}
initButton() - 初始化按钮
private void initButton() {
// TODO Auto-generated method stub
btn_ok = new JButton("OK");
btn_cancel = new JButton("CANCEL");
}
initText() - 初始化演示文本
private void initText() {
// TODO Auto-generated method stub
showText = new JTextField("字体展示");
showText.setHorizontalAlignment(JTextField.CENTER); //文本居中
showText.setFont(selectedFont);
showText.setEditable(false); //不可编辑
showText.setSize(200,200);
// showText.setForeground(Color.green);//字体颜色
}
initLocation() - 初始化布局
/**
* 初始化布局
* 将每个控件按照一定得布局排在this窗口中
*/
public void initLocation() {
paneNorth = new JPanel();
paneNorth.add(new JLabel("字体:")); //添加下拉区
paneNorth.add(choose_word_style);
paneNorth.add(new JLabel("字号:"));
paneNorth.add(choose_word_big);
paneNorth.add(new JLabel("字形:"));
paneNorth.add(choose_word_pattern);
paneNorth.add(new JLabel("颜色:"));
paneNorth.add(choose_word_color);
paneNorth.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); //让add的组件不置顶
this.add(paneNorth,BorderLayout.NORTH);
paneCenter = new JPanel(); //添加展示区
paneCenter.add(showText);
this.add(paneCenter, BorderLayout.CENTER); //添加按钮去
paneSouth = new JPanel();
paneSouth.add(btn_ok);
paneSouth.add(btn_cancel);
this.add(paneSouth, BorderLayout.SOUTH);
}
initBox() - 初始化下拉栏
/**
* 初始化几个comboBox
* 把相应的选项加入
*/
public void initBox() {
choose_word_style = new JComboBox(styles); //数组传入就不用addItem
choose_word_big = new JComboBox(word_big);
choose_word_pattern = new JComboBox(pattern);
choose_word_color = new JComboBox(colors);
}
actionPerformed(ActionEvent e) - 监听事件响应
@Override
public void actionPerformed(ActionEvent e) { //对按钮的监听
// TODO Auto-generated method stub
if (e.getSource() == btn_cancel) {
this.dispose();//销毁当前窗口
}else if (e.getSource() == btn_ok) { // 调用父窗体的实例,拿到text_area并对其setFont
TextEditor.getEdit_text_area().setFont(selectedFont); //改字体
TextEditor.getEdit_text_area().setForeground(selectedColor); //改字体颜色
this.dispose();
}
}
itemStateChanged(ItemEvent e) - 下拉栏的监听
@Override
public void itemStateChanged(ItemEvent e) { //对下拉框的监听
// TODO Auto-generated method stub
if (e.getItem() == "宋体") {
selectedStyle = "宋体";
renewFont();
}else if (e.getItem() == "黑体") {
selectedStyle = "黑体";
renewFont();
}else if (e.getItem() == "楷体") {
selectedStyle = "楷体";
renewFont();
}else if (e.getItem() == "微软雅黑") {
selectedStyle = "微软雅黑";
renewFont();
}else if (e.getItem() == "隶书") {
selectedStyle = "隶书";
renewFont();
}else if (e.getItem() == "常规") {
selectedPattern = Font.PLAIN;
renewFont();
}else if (e.getItem() == "倾斜") {
selectedPattern = Font.ITALIC;
renewFont();
}else if (e.getItem() == "粗体") {
selectedPattern = Font.BOLD;
renewFont();
}else if (e.getItem() == "2") {
selectedBig = 2;
renewFont();
}else if (e.getItem() == "4") {
selectedBig = 4;
renewFont();
}else if (e.getItem() == "8") {
selectedBig = 8;
renewFont();
}else if (e.getItem() == "16") {
selectedBig = 16;
renewFont();
}else if (e.getItem() == "24") {
selectedBig = 24;
renewFont();
}else if (e.getItem() == "32") {
selectedBig = 32;
renewFont();
}else if (e.getItem() == "64") {
selectedBig = 64;
renewFont();
}else if (e.getItem() == "72") {
selectedBig = 72;
renewFont();
}else if (e.getItem() == "红色") {
selectedColor = Color.red;
renewFont();
}else if (e.getItem() == "黑色") {
selectedColor = Color.black;
renewFont();
}else if (e.getItem() == "蓝色") {
selectedColor = Color.blue;
renewFont();
}else if (e.getItem() == "黄色") {
selectedColor = Color.yellow;
renewFont();
}else if (e.getItem() == "绿色") {
selectedColor = Color.green;
renewFont();
}else if (e.getItem() == "白色") {
selectedColor = Color.WHITE;
renewFont();
}
}
renewFont() - 更改默认字体
private void renewFont() {
// TODO Auto-generated method stub
selectedFont = new Font(selectedStyle,selectedPattern,selectedBig);
showText.setFont(selectedFont);
showText.setForeground(selectedColor);
}
}
helpWindow - 帮助窗口实现
帮助窗口属性
public class helpWindow extends JFrame {
private JButton btn_ok;
private JLabel about_label;
private JPanel panel ;
private BoxLayout boxlayout; //指定在容器中是否对控件进行水平或者垂直放置
帮助窗口构造器
public helpWindow() {
panel = new JPanel(); //轻量级面板容器
boxlayout = new BoxLayout(panel,BoxLayout.Y_AXIS); //Y_AXIS - 控件垂直放置
panel.setLayout(boxlayout);
btn_ok = new JButton("OK");
btn_ok.setAlignmentX(CENTER_ALIGNMENT); //alignment对齐
// about_label = new JLabel("https://www.cnblogs.com/ymjun/");
about_label = new JLabel("<html><br /><h3 style='text-align:center;color:brown;'>黄龙士文本编辑器</h3>" //JLabel里面可以嵌入html
+ "blog:https://www.cnblogs.com/ymjun/<br />"
+ "github:https://github.com/iym070010<br />"
+ "email:iym070010@163.com<br />"
+ "使用方式跟.txt文本编辑器一致<br />"
+ "<br /><br />"
+ "</html>",JLabel.CENTER);
about_label.setAlignmentX(CENTER_ALIGNMENT);
panel.add(about_label);
panel.add(btn_ok);
this.add(panel);
this.setSize(400,300);
this.setTitle("关于");
this.setVisible(true);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
btn_ok.addActionListener(e->{ //窗口关闭
this.dispose();
});
}
}
LineNumberHeaderView - TextArea行数显示插件
-
直接从网上拿的,网址:JTextArea行号显示工具类
-
貌似就是Notepad++的行号显示实现类,效果几乎一模一样
//package com.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
//TEXTAREA 行号显示插件
public class LineNumberHeaderView extends javax.swing.JComponent { //貌似就是Notepad++行号显示的实现类
/**
* JAVA TextArea行数显示插件
*/
private static final long serialVersionUID = 1L;
private final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, 11);
public final Color DEFAULT_BACKGROUD = new Color(228, 228, 228);
public final Color DEFAULT_FOREGROUD = Color.BLACK;
public final int nHEIGHT = Integer.MAX_VALUE - 1000000;
public final int MARGIN = 5;
private int lineHeight;
private int fontLineHeight;
private int currentRowWidth;
private FontMetrics fontMetrics;
public LineNumberHeaderView() {
setFont(DEFAULT_FONT);
setForeground(DEFAULT_FOREGROUD);
setBackground(DEFAULT_BACKGROUD);
setPreferredSize(9999);
}
public void setPreferredSize(int row) {
int width = fontMetrics.stringWidth(String.valueOf(row));
if (currentRowWidth < width) {
currentRowWidth = width;
setPreferredSize(new Dimension(2 * MARGIN + width + 1, nHEIGHT));
}
}
@Override
public void setFont(Font font) {
super.setFont(font);
fontMetrics = getFontMetrics(getFont());
fontLineHeight = fontMetrics.getHeight();
}
public int getLineHeight() {
if (lineHeight == 0) {
return fontLineHeight;
}
return lineHeight;
}
public void setLineHeight(int lineHeight) {
if (lineHeight > 0) {
this.lineHeight = lineHeight;
}
}
public int getStartOffset() {
return 4;
}
@Override
protected void paintComponent(Graphics g) {
int nlineHeight = getLineHeight();
int startOffset = getStartOffset();
Rectangle drawHere = g.getClipBounds();
g.setColor(getBackground());
g.fillRect(drawHere.x, drawHere.y, drawHere.width, drawHere.height);
g.setColor(getForeground());
int startLineNum = (drawHere.y / nlineHeight) + 1;
int endLineNum = startLineNum + (drawHere.height / nlineHeight);
int start = (drawHere.y / nlineHeight) * nlineHeight + nlineHeight - startOffset;
for (int i = startLineNum; i <= endLineNum; ++i) {
String lineNum = String.valueOf(i);
int width = fontMetrics.stringWidth(lineNum);
g.drawString(lineNum + " ", MARGIN + currentRowWidth - width - 1, start);
start += nlineHeight;
}
setPreferredSize(endLineNum);
}
}