javaSE综合实例之记事本详细解析--长篇(初学者)

第一次发布:2020年12月26日

如有更新,将在更新笔记专栏记录,欢迎学习交流

文章目录:

       1.记事本主界面

       2.记事本主界面各版块设计

       3.文件菜单各项功能实现

       4.编辑菜单各项功能实现

       5.格式菜单各项功能实现

       6.查看菜单各项功能实现

       7.帮助菜单各项功能实现

       8.核心源码

       9.完整源码链接

项目结构图:

                   

目前实现功能演示视频:

java记事本完整版演示视频

尚未解决的问题:

(1)生成exe程序后,该如何读取使用该程序打开的文件

(2)如何解决系统默认的剪切、复制、粘贴、删除等与程序内部设计的命令产生冲突(比如在程序里粘贴快捷键也是设置:Ctrl +V,当使用时,会粘贴两遍。解决思路,估计是有两个剪贴板)

(3)通用组件是否可以合并使用,类似前端页面的共同部分,尚未优化

(4)打印、页面设置的实际功能尚未实现,如何调用系统本身的程序尚未掌握

1.创建一个Note.java的主程序:通过下列代码就实现了窗口的创建,接下来就是各种菜单及其功能的添加。

package com.study.main;

import javax.swing.*;

/**
 * 第1步:创建主程序,继承JFrame
 * JFrame是实现窗口需要
 */
public class Note extends JFrame{

    /**
     * main方法程序开始运行的地方
     * @param args
     */
    public static void main(String[] args) {
        new Note();//无参构造方法创建匿名对象
    }

    /**
     * 无参构造方法
     */
    public Note(){
        this.initJFrame();
    }

    private void initJFrame() {
        this.setTitle("无标题--记事本");
        this.setSize(1200,1000);
        this.setLocation(200,200);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

}

2.整体布局的设想

1.整个布局是采用BorderLayout布局,分为北、中、南三部分

    (1)北边是工具菜单面板

    (2)中间是文本域部分

    (3)南边是状态栏显示部分

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

2.因此:步骤1中的代码需要做以下修改

    (1)在initJFrame()方法下的第一行添加容器的布局
         this.setLayout(new BorderLayout());//设置容器布局

    (2)在src目录下创建包:com.study.myPanel。包下创建三个类
           1)工具栏面板类:ToolBarPanel,继承JPanel
           2)状态栏面板类:StatementPanel,继承JPanel
           3)文本域类:MyTextArea,继承textArea

==================================================================================================================================================================================

    (3)在Note.java类下声明(2)中三个类以及StatePanel、JScrollPanel、JTextArea类,同时创建初始化面板的方法
           1)Note.java类下的声明:
                    private ToolBarPanel toolBarPanel;//工具条面板

                    private JScrollPane jScrollPane;//文本域面板
                    public MyTextArea textArea;//文本域

                    private JPanel Statement;//状态栏面板
                    private StatementPanel statementPanel;//状态栏具体内容面板

           2)创建的方法
                        /**
                         * 添加菜单项到容器中
                         */
                        public void initMenu(){
                            //文件、编辑、格式、查看、帮助菜单的具体内容
                        }

                        /**
                         * 初始化工具条菜单面板
                         */
                        private void initToolBarPanel(){
                            toolBarPanel=new ToolBarPanel();
                        }

                        /**
                         * 初始化具有滚动条的面板:用于文本域
                         */
                        private void initJScrollPane(){
                            textArea=new MyTextArea();
                            jScrollPane=new JScrollPane(textArea);
                        }

                        /**
                         * 初始化状态栏面板
                         */
                        private void initStatePanel(){
                            statePanel=new JPanel();//这个面板是为了装状态栏面板
                            statePanel.setLayout(new FlowLayout(FlowLayout.RIGHT));//使用靠右对齐的流式布局
                            statePanel.setSize(this.getWidth(),35);
        
                            JLabel jbl=new JLabel();
                            jbl.setSize(this.getWidth(),35);//创建这个标签是为当隐藏状态栏具体内容时状态栏面板的高度不至于发生太大的变化
                            //状态栏的具体内容
                            statementPanel=new StatementPanel();//包含具体内容的面板
        
                            //把相关组件添加到状态栏面板中
                            statePanel.add(jbl);
                            statePanel.add(statementPanel);
                        }

==================================================================================================================================================================================            
            
    (4)在initJFrame()方法下的第二行开始依次添加面板到容器指定位置
             this.initMenu();
             this.initToolBarPanel();
             this.initJScrollPane();
             this.initStatePanel();
             this.add(toolBarPanel,BorderLayout.NORTH);
             this.add(jScrollPane,BorderLayout.CENTER);
             this.add(statePanel,BorderLayout.SOUTH);

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

3.将上述的初始化类以及相关方法添加完成

(1)initMenu()方法,使用到的组件如下,依次完成对应创建添加到容器(this)即可
 
     //菜单面板:添加到容器的方法是:this.setJMenuBar(menuBar);
    JMenuBar menuBar;

    JMenu file;//文件菜单
    //依次是:新建、新窗口、打开、保存、另存为、页面设置、打印、退出
    JMenuItem newFile, newWindow, openFile, saveFile, anotherSaveFile, settingPage, printFile, exit;

    JMenu edit;//编辑菜单
    //依次是:撤销、剪切、复制、粘贴、删除、使用Bing查找、查找、查找下一个、查找上一个、替换、转到、全选、时间/日期    
    JMenuItem revoke, cut, copy, paste, del, useBing, search, searchNext, searchPrevious, replace, turn, selectAll, datetime;

    JMenu format;//格式菜单
    JCheckBoxMenuItem turnLine;//自动换行
    JMenuItem fonts;//字体

    //查看菜单
    JMenu view, zoom;//查看、缩放
    JCheckBoxMenuItem state;//状态栏
    JMenuItem enlarge, narrow, restore;//加大、缩小、恢复

    JMenu help;//帮助菜单
    JMenuItem helps, sendMs, aboutNote;//查看帮助、发送反馈、关于记事本

==================================================================================================================================================================================

(2)initToolBarPanel()方法,完成该方法,完成ToolBarPanel类创建即可,ToolBarPanel类简单如下:
/**
 * 工具条面板的具体内容
 */
public class ToolBarPanel extends JPanel {

    //工具条管理菜单
    JToolBar toolBar;

    //菜单条管理工具,依次是:新建、打开、保存、剪切、复制、粘贴
    JButton bt_newFile, bt_openFile, bt_saveFile, bt_cut, bt_copy, bt_paste;
    /**
     * 创建无参构造方法
     */
    public ToolBarPanel(){
        this.setLayout(new FlowLayout(FlowLayout.LEFT));//设置布局为靠左对齐的流式布局
        this.initComponent();
    }
    
    /**
     * 实现各种组件的创建添加工作
     */
    private void initComponent() {
        toolBar=new JToolBar();
        //依次创建添加即可,示例一个如下
        bt_newFile = new JButton(new ImageIcon("image/xj.jpg"));//创建具有图像填充的单击按钮
        bt_newFile.setToolTipText("新建");//提示文字,当鼠标放上去时显示的文字
        toolBar.add(bt_newFile);//添加到面板中

        //其它组件。。。。。。

        //最后添加到面板中
        this.add(toolBar);
    }

}

==================================================================================================================================================================================

(3)initJScrollPane()方法,还剩MyTextArea类未完成

public class MyTextArea extends JTextArea {
    
    public MyTextArea(){
        //其它设置
    }
}


==================================================================================================================================================================================

(4)initStatePanel()方法,还剩StatementPanel类未完成

public class StatementPanel extends JPanel {

    //状态栏标签
    private JLabel rowAndColumn, characterState, windows, encoding;

    public StatementPanel(){
        this.setLayout(new FlowLayout(FlowLayout.RIGHT));//面板布局设置为靠右对齐的流式布局
        this.initComponent();
    }

    private void initComponent() {
        //组件的创建略,新建添加即可,注意设置一定的空格
    }
}


==================================================================================================================================================================================

4.至此,已经完成了所有基本组件的创建工作,效果如下图

3.实现文件菜单的添加及其功能实现

AA.功能菜单的实现,需要使用到监听事件,因此首先需要给Note实现监听事件的相关接口,这里首先使用到的接口是:ActionListener及其方法。之后就是添加监听了
    1.实现接口及其方法
       public class Note extends JFrame implements ActionListener {
            ......
            @Override
            public void actionPerformed(ActionEvent e) {
                //实现监听方法
            }
        }

    2.添加监听及其实现监听方法
        (1)Note.java下创建方法
             public void initAddActionListener(){
                //示例一个添加监听
                newFile.addActionListener(this);//实现新建文件的监听
                //其它类似,不再重复
             }

        (2)在initJFrame()方法中,在创建完成组件之后,调用上述方法
                this.initAddActionListener();

        (3)在actionPerformed(ActionEvent e)()方法中,实现具体的监听事件的调用
                @Override
                public void actionPerformed(ActionEvent e) {
                    //实现监听方法,其它监听类似
                    if(e.getSource==newFile)
                        //实现具体方法的调用,通常我们是建一个方法或一个类,然后实现具体的方法   
                }

==================================================================================================================================================================================

BB.以在同一个类中创建实现方法或创建实现类的方法为例,实现文件菜单的各个选项功能

1.新建:尝试由易到难,逐步加深
     1.1.新建文件要求:
        (1)文本域没有内容:直接新建,同时创建新窗口,关闭旧窗口
        (2)文本域有内容:先区分此时编辑的文件是已经存在的文件还是新创建的文件
                已经存在的文件:内容是否有变化
                    1)有变化: 弹出选择提示信息:是否保存,还是取消操作  
                          根据选择消息执行:
                               保存:直接执行保存操作;
                               不保存:执行(1)步骤
                               取消:没有操作
                    2)没有变化: 直接执行(1)步骤

                新文件:弹出选择提示信息:是否保存,还是取消操作
                    1)保存的操作:
                         <1>弹出保存对话框,实现保存,然后执行(1)步骤                                            
                    2)不保存的操作:直接执行(1)步骤
                    3)取消的操作:没有操作
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
     1.2.逐步实现
        (1)文本域没有内容:直接新建,同时创建新窗口,关闭旧窗口
             思路:01.新建则创建一个新的窗口对象,关闭则关闭一个就窗口对象。如何识别新旧?
             思路:02.新旧识别可以通过其方法 isActive() 判断,为true表示我们正在操作的窗口,反之则不是.当单个对象我们能很快知道当前窗口是哪个?但是如果我们打开多个窗口呢?
             思路:03.解决打开多个窗口时,找到我们活动的那个窗口问题,可以使用一个list集合解决,如创建集合:List<Note> list=new ArrayList<>();使用了集合,又需要考虑一个问题:当关闭了对象,
                     我们需要移除这个对象,同时在它的位置添加新的对象,这样集合中间就不会存在空值。那么程序执行到哪里实现哪个对象的删除与添加呢,以确保添加与删除的操作是几乎同时进行的?
             思路:04.根据程序执行过程可以找出,当新的窗口即将显示可见之前进行删除与添加的操作最合适。至于是哪个对象,遍历集合,执行isActive()方法,谁为true就是谁。
             思路:05.那么如何把对象在集合的位置传回去呢?通过构造一个有参构造方法即可,同时设置其为全局变量,这样不用在多个方法中传参

        (2)具体代码实现,我们需要把this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE):
                1)我们需要把initJFrame()方法中
                        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);修改为:
                        this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

                2)创建实现方法actionCreateNewFile(),方法actionCreateNewFile()的具体内容:

                    <1>实现思路1如下:
                            this.dispose();//关闭窗口
                            new Note();//新建窗口

                    <2>实现思路2如下:
                            if(this.isActive()==true){//窗口当前活动状态为真
                                 this.dispose();
                                 new Note();
                            }

                    <3>实现思路3如下:
                            aa.在Note.java下创建一个静态的Note类集合:static List<Note> list=new ArrayList<>();
                            bb.在无参构造方法的第二行添加:list.add(this);表示通过无参构造创建对象,直接添加到集合末尾
                            cc.方法的具体内容
                            if(list !=null){//确保不为空的情况下执行
                                for(int i=0;i<list.size();i++){//遍历对象集合
                                    Note node=list.get(i);
                                    if(node.isActive==true){//找到活动的窗口,即我们操作的窗口
                                        node.dispose();
                                        list.set(i,new Note());//指定位置添加对象
                                     }
                                }
                            }

                    <4>实现思路4和5如下:
                            aa.把<3>中的方法做简单修改:
                                把:node.dispose();和list.set(i,new Note());修改为
                                new Note(i);//调用有参构造方法创建新对象
                                break;//退出循环
                            bb.在Note中创建有参构造方法,同时创建一个全局变量:int remark=-5;
                                public Note(int remark){
                                    this.initJFrame();
                                }
                            aa.在initJFrame()方法的标题设置之前添加如下代码:
                                if(remark>=0){//用于表示是新建文件时创建对象执行的代码
                                    if(list !=null){//空值判断
                                        for (int i = 0; i < list.size(); i++) {//找到匹配值
                                            if(i==remark){//实现方法
                                                list.get(remark).dispose();//关闭指定窗口
                                                list.set(remark,this);//添加新对象到集合指定位置                                           remark=-5;//还原默认值,不影响无参构造方法的使用
                                                 break;//退出循环
                                            }
                                        }
                                    }
                                 } 
    

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
        (3)文本域有内容的实现步骤:
                1)把(2)中做好的actionCreateNewFile()方法内容抽取出来称为独立的方法
                        public void createNewFile();
                2)在actionCreateNewFile()方法中编写方法体:
            	      //1.判断文本域是否有内容
                      if(textArea.getText() ==null || textArea.getText().equals("")){
                            this.createNewFile();//没有内容直接新建
                       }else{
                            //2.有内容弹首先判断是新文件还是旧文件
                            if(pathFile ==null){//全局变量文件是空,是新文件
                                /*
                                Object[] options = {"保存", "不保存"};// 选择消息
                                int row = JOptionPane.showOptionDialog(null, "你是否要保存文件?", "记事本", JOptionPane.DEFAULT_OPTION,JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
                                if(row==0){//保存
                                    this.saveFileDialog();//打开保存文件对话框
                                    this.createNewFile();//执行创建方法
                                }else if(row==1){//不保存
                                    this.createNewFile();
                                }
                                */
                                this.doActionAccordingToOptionMessage("new");
                             }else{//已经存在文件
                                //判断内容是否发生改变
                                if(fileContent.equals(textArea.getText())){//没有发生改变
                                    this.saveFiles(pathFile.getAbsolutePath());//执行直接保存文件的操作
                                }else{//弹出选择对话框
                                /*
                                    Object[] options = {"保存", "不保存"};// 选择消息
                                    int row = JOptionPane.showOptionDialog(null, "你是否要保存文件?", "记事本", JOptionPane.DEFAULT_OPTION,JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
                                    if(row==0){//保存
                                        this.saveFiles(pathFile.getAbsolutePath());//执行直接保存文件的操作
                                        this.createNewFile();//执行创建方法
                                    }else if(row==1){//不保存
                                        this.createNewFile();//执行创建方法
                                    }
                                  */
                                   this.doActionAccordingToOptionMessage("old");
                                  }
                                }
                              }

                3)在Note类下创建两个全局变量:
                        String fileContent="";//用于记录打开或已经保存了的正在编辑的文件内容
                        File pathFile=null;//用于记录打开或已经保存了的正在编辑的文件
                4)创建saveFileDialog()方法:
                    //保存文件操作(参数是指明是第一次保存,还是另存)
                    public void saveFileDialog(String title) {
                        JFileChooser chooser = new JFileChooser("C:\\Users\\shinelon\\Desktop");//默认打开对话框显示的位置
                        chooser.setDialogType(JFileChooser.SAVE_DIALOG);//对话框类型
                        chooser.setDialogTitle(title);//设置显示的对话框标题
                        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);//显示文件和目录的指令。
                        chooser.setSelectedFile(new File("新建文本文档.txt"));//默认保存的文件名
                        chooser.showSaveDialog(null);//打开对话框
                        chooser.setVisible(true);//显示其可见

                         //获取保存文件的绝对路径和名称
                         File selectedFile = chooser.getSelectedFile();//获取保存的文件
                         if(!title.equals("新建保存...")) {
                            this.updateParameters(File selectedFile);
                         }
                         this.saveFiles(savePath);//执行保存文件
                      }
               
                5)创建saveFiles()方法:
                        //读取文件到指定位置
                        public void saveFiles(File selectedFile){
                            String savePath = selectedFile.getAbsolutePath();//获取保存文件的
                            try {
                                PrintStream pl = new PrintStream(savePath);//打印流保存的文件位置
                                System.setOut(pl);//正确输出打印流
                                System.out.println(textArea.getText());//输入内容
                                pl.close();
                             } catch (IOException aa) {aa.printStackTrace();}
                        }

                6)创建updateParameters(File selectedFile)方法:
                    updatTiltle="*"+selectedFile.getName();//更新未保存标题
                    initTitle=selectedFile.getName();//更新已保存标题
                    this.setTitle(initTitle);//更新标题
                    pathFile=selectedFile;//更新打开的文件
                    fileContent=textArea.getText();//更新打开文件的内容
                        
                7)发现 2)中消息选择部分的方法相似,我们可以拆分为两个小方法,方便其它方法调用:
                      方法1:public int showOptionMessageByJOptionPane();
                                 Object[] options = {"保存", "不保存"};// 选择消息
                                 return JOptionPane.showOptionDialog(null, "你是否要保存文件?", "记事本", JOptionPane.DEFAULT_OPTION,JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
                      方法2:public void doActionAccordingToOptionMessage(String fileStyle);
                                 int row=this.showOptionMessageByJOptionPane();
                                 if(row==0){//保存
                                    if(fileStyle.equals("new"))
                                        this.saveFileDialog("保存...");//打开保存文件对话框
                                    else
                                        this.saveFiles(pathFile.getAbsolutePath());//执行直接保存文件的操作
                                    this.createNewFile();//执行创建方法
                                  }else if(row==1){//不保存
                                    this.createNewFile();
                                  }
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
执行测试发现新建文件存在问题:
     (1)当文档有内容时,新建选择不保存并没有新建窗口,也没有关闭原来的窗口
            解决方法:在Note类下创建一个全局的静态变量整数activeFrame,用于记录当前活动窗口在集合中的位置,默认值为0,只有一个对象的情况下。
            1)Note类下创建静态成员变量:static int activeFrame=0;
            2)修改createNewFile()方法:
                if(list !=null){//确保不为空的情况下执行
                     for(int i=0;i<list.size();i++){//遍历对象集合
                         if(i==activeFrame){//找到活动的窗口,即我们操作的窗口
                             new Note(i);
                             break;
                         } 
                      }
                 }
            3)修改initJFrame()方法中, if(remark>=0){},在break;退出循环前加上:activeFrame=i;
     (2)新建的窗口的位置总是在固定的地方,大小也是固定的。
            想要改变位置和大小,可以将initJFrame()方法中,this的设置部分做如下修改:
                int width=1200,height=1000;//窗体尺寸大小
                int x=200,y=200;//屏幕上显示位置的初始值
                if(remark >=0){
                    if(list !=null){
                        for (int i = 0; i < list.size(); i++) {
                            if(i==remark){
                                width=list.get(i).getWidth();//即将关闭活动窗体的宽
                                height=list.get(i).getHeight();//即将关闭活动窗体的高
                                x=list.get(i).getX();//即将关闭活动窗体与屏幕左边的距离
                                y=list.get(i).getY();//即将关闭活动窗体与屏幕顶部的距离
                                list.get(remark).dispose();//关闭活动窗体
                                list.set(remark,this);//创建新窗体并添加到指定位置
                                activeFrame=i;//记录当前活动窗体的下标
                                remark=-5;//还原初始值
                                break;
                            }
                        }
                    }
                }
                this.setTitle("无标题--记事本");
                this.setSize(width,height);
                this.setLocation(x,y);
                this.setVisible(true);
思考01:为了不用到处使用activeFrame进行更新标记,使用线程来监控活动窗体
     1)Note类实现Runnable接口,并且实现重写其方法
     2)代码编写如下:
            @Override
            public void run() {
                while (true){
                    try {
                        if(list !=null){
                            for (int i = 0; i < list.size(); i++) {
                                if(list.get(i).isActive()==true){
                                    activeFrame=i;
                                 }
                             }
                         }
                         Thread.sleep(10);
                       } catch (InterruptedException e) {
                            e.printStackTrace();
                       }
                   }
                }
     3)在initJFrame()方法最后启用线程即可:new Thread(this).start();
     4)删除其它方法给activeFrame进行更新

思考02:保存文件过程中,如果选择的保存文件名在保存目录下已经存在,应该给予提示信息,是否替换原来的文件
解决方法:遍历该文件夹下的所有同类格式的文件,然后遍历查找是否有完全一样的文件,存在弹出询问是否替换操作,不存在则直接执行下一步操作。
问题关键:如何在
具体代码:
    (1)创建一个方法:public boolean existSameFile(File selectFile)
            String name = selectFile.getName();
            File parentFile = selectFile.getParentFile();
            File[] files = parentFile.listFiles();
            if (files != null) {
                for (File file1 : files) {
                    if(name.equals(file1.getName()))
                        return true;
                }

            }
            return false;
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------    
    (2)创建如下方法以及实现,判断选择的文件是否已经存在:
          private boolean replaceExistSameFile(File selectFile){
            Object[] options = {"是", "否"};// 选择消息
            int row=JOptionPane.showOptionDialog(null, selectFile.getName()+" 已存在。\n要替换它吗?", "确认另存为", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[1]);
            return row ==0 ? true : false;
          }
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
    (3)修改saveFileDialog(String title)方法
         //1.打开保存对话框
        JFileChooser chooser = new JFileChooser("C:\\Users\\shinelon\\Desktop");//默认打开对话框显示的位置
        chooser.setDialogType(JFileChooser.SAVE_DIALOG);//对话框类型
        chooser.setDialogTitle(title);//设置显示的对话框标题
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);//显示文件和目录的指令。
        chooser.setSelectedFile(new File("新建文本文档.txt"));//默认保存的文件名
        chooser.showSaveDialog(null);//打开对话框
        chooser.setVisible(true);//显示其可见
        System.out.println(JFileChooser.APPROVE_OPTION);

        //2.获取保存文件进行重名判断
        File selectedFile = chooser.getSelectedFile();//获取保存的文件
        boolean exist=this.existSameFile(selectedFile);//判断选择的文件是否已经存在
            while(exist){
                if(this.replaceExistSameFile(selectedFile)){//询问是否替代?true为替换,false不替换
                    break;
                }else{
                    chooser = new JFileChooser("C:\\Users\\shinelon\\Desktop");//默认打开对话框显示的位置
                    chooser.setDialogType(JFileChooser.SAVE_DIALOG);//对话框类型
                    chooser.setDialogTitle(title);//设置显示的对话框标题
                    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);//显示文件和目录的指令。
                    chooser.setSelectedFile(new File("新建文本文档.txt"));//默认保存的文件名
                    chooser.showSaveDialog(null);//打开对话框
                    chooser.setVisible(true);//显示其可见
                    selectedFile = chooser.getSelectedFile();//获取保存的文件
                    exist=this.existSameFile(selectedFile);
                }
            }

        //3.执行保存文件
        this.saveFiles(selectedFile);//执行保存文件

        //4.更新相关参数(只有在保存和另存为时更新)
        if (!title.equals("新建保存...")) {
            this.updateParameters(selectedFile);
        }
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 




新建文件的基本工作已经完成。当然还有需要思考的地方,如:当生存.exe程序之后,如果已经建好的文件用它打开,
这时它该如何读取打开文件的相关信息,这个待学习了解,将单独写在其它文章中。届时更新再添加链接。
==================================================================================================================================================================================
注:从这个功能开始,如果有重复调用方法,将直接给出方法名
2.新窗口:直接调用actionOpenNewWindows()方法,新建窗口调用无参构造即可,同时添加到list集合中,方法的具体内容如下:
     list.add(new Note());
实验发现,没有起作用,尽管创建了新对象,但是会覆盖之前的窗口,但是根据上面1中的问题解决方法,可以得到想要的结果。修改如下:
     list.add(new Note(-1));
     //activeFrame=list.size()-1;//该行使用线程之后就可省略了
问题:窗口显示位置和大小一直是初始值
解决:只需修改一下上面1中问题2即可解决
                int width=1200,height=1000;//窗体尺寸大小
                int x=200,y=200;//屏幕上显示位置的初始值
                    if(list !=null){
                        if(remark>=0){//创建新建文件执行
                            for (int i = 0; i < list.size(); i++) {
                                if(i==remark){
                                    width=list.get(i).getWidth();//即将关闭活动窗体的宽
                                    height=list.get(i).getHeight();//即将关闭活动窗体的高
                                    x=list.get(i).getX();//即将关闭活动窗体与屏幕左边的距离
                                    y=list.get(i).getY();//即将关闭活动窗体与屏幕顶部的距离
                                    list.get(remark).dispose();//关闭活动窗体
                                    list.set(remark,this);//创建新窗体并添加到指定位置
                                    remark=-5;//记录当前活动窗体的下标
                                    break;
                                }
                            }
                        }else if(remark==-1){//创建新窗口执行
                            width = list.get(activeFrame).getWidth();//即将关闭活动窗体的宽
                            height = list.get(activeFrame).getHeight();//即将关闭活动窗体的高
                            x = list.get(activeFrame).getX();//即将关闭活动窗体与屏幕左边的距离
                            y = list.get(activeFrame).getY();//即将关闭活动窗体与屏幕顶部的距离
                        }
                    }
                this.setTitle("无标题--记事本");
                this.setSize(width,height);
                this.setLocation(x,y);
                this.setVisible(true);
                new Thread(this).start();

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
思考:既然使用了线程进行活动窗口的监听,而且创建新窗口可以直接用线程监听的变量解决找到位置的问题,那么上面的代码还可以进行优化:
(1)createNewFile()方法只需要一句话:new Note(activeFrame);可以用这句话替代所有调用该方法的地方。然后去除该方法。
(2)上述initJFrame()获取窗口的位置信息代码可以优化如下,这样简略了许多:
           if (list != null&&remark !=-5) {//表示不是第一次启动程序
                //除第一次外,创建新文件和新窗口都获取活动窗口的位置信息
                width = list.get(activeFrame).getWidth();//即将关闭活动窗体的宽
                height = list.get(activeFrame).getHeight();//即将关闭活动窗体的高
                x = list.get(activeFrame).getX();//即将关闭活动窗体与屏幕左边的距离
                y = list.get(activeFrame).getY();//即将关闭活动窗体与屏幕顶部的距离
                if (remark >= 0) {//创建新文件打开的窗口,需要移除关闭旧窗口和添加新窗口
                        list.get(activeFrame).dispose();//关闭活动窗体
                        list.set(activeFrame, this);//创建新窗体并添加到指定位置
                        remark = -5;//记录当前活动窗体的下标
                }
            }
==================================================================================================================================================================================

3.打开(注:这里是选择单文件打开)
    3.1.思路分析:
        (1)打开文件是在程序已经启动的情况下进行的,打开文件首先是弹出选择文件的对话框
        (2)获取有效的打开文件,进行文件的读取,把文件内容读取到记事本的文本域中
        (3)最后需要对相关变量进行更新:如文件、标题、保存内容等
    3.2.代码实现,actionOpenFile()方法:
        (1)弹出选择文件的对话框
             //1.打开对话框
             JFileChooser chooser = new JFileChooser("C:\\Users\\shinelon\\Desktop");//默认打开显示的位置
             chooser.setDialogTitle("选择打开文件...");
             chooser.setDialogType(JFileChooser.OPEN_DIALOG);//对话框类型
             chooser.setSelectedFile(new File(".txt"));//只显示后缀为“.txt”的文件
             chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);//显示文件和目录的指令。
             chooser.showOpenDialog(this);//打开对话框
             chooser.setVisible(true);

        //2.获取选择的文件
        File selectedFile = chooser.getSelectedFile();
        if(selectedFile !=null){//执行(2)(3)}//需要判断是否已经选择了文件

        (2)把文本信息读取到文本域中
            //3.读取文件到文本域
            this.readFile(selectedFile);

        (3)最后需要对相关变量进行更新:如文件、标题、保存内容等
            //4.更新相关参数
            this.updateParameters(selectedFile);


==================================================================================================================================================================================

4.保存
    4.1.思路分析:
        (1)保存文件分新文件和已经创建了打开的文件
        (2)新文件:执行弹出保存对话框,执行保存文件的操作,同时更新相关变量
        (3)已有的文件:则直接执行保存文件的操作,同时更新相关变量

    4.2.代码实现,方法actionSaveFile():
                //1.执行判断
                if(pathFile !=null){//旧文件
                    if(!fileContent.equals(textArea.getText())){//只在内容发生变化时执行
                        this.saveFiles(pathFile);//直接执行保存文件的方法
                        this.updateParameters(pathFile);//直接执行保存文件的方法
                    }
                }else{//新文件
                    this.saveFileDialog("保存...");//执行保存文件对话框
                }
                //2.更新相关信息
                注:在this.saveFileDialog("保存...");中已经实现

==================================================================================================================================================================================

5.另存为
    5.1.思路分析:
        (1)该操作执行和保存新文件一样,仅是显示标题和更新参数不一样

    5.2.代码实现,创建方法actionSaveFileNewPosition():
        this.saveFileDialog("另存为...");

补充说明:实现标题的更换:
   在线程方法的执行中,在Thread.sleep(10);的上一行加上以下代码即可实现
                if(fileContent.equals(textArea.getText()))
                    this.setTitle(initTitle);
                else
                    this.setTitle(updatTiltle);
   

==================================================================================================================================================================================

6.页面设置(功能未实现,只做了一个界面,及界面里面的操作)
    记录几个最原始制作的思路:
       (1)分层,主要是一层套着一层,使用了JTextFile来解决,包括哪些分层的标题
       (2)横向与纵向的变换,通过创建了水平和垂直的相关组件解决
       (3)左右上下距离的变化,显示内容里面是一个文本域,通过改变文本域的位置和大小解决
       (4)变换数值,设置了最小和最大值,以及不合要求时显示的位置和大小,使用线程监听变化

==================================================================================================================================================================================

7.打印设置(功能未实现,只做了一个界面)
    基本是只做了基本的界面显示,尚未完成的功能待学习了解之后更新。

==================================================================================================================================================================================

8.退出程序
    思路: (1)退出时判断文本域是否有内容
          (2)有内容,判断是新建文件还是已经存在的文件
                1)新建的文件:pathFile==null;询问是否保存
                    01)保存则执行打开保存对话框,然后退出
                    02)不保存直接退出
                    03)取消没有任何操作
                2)旧文件:pathFile !=null;判断内容是否有变化
                    01)有变化询问是否保存
                        001)保存则执行保存文件,然后退出
                        002)不保存直接退出
                        003)取消没有任何操作
                    02)没变化直接退出
          (3)没有内容直接退出
    代码实现:
       (1)private void actionExit()方法
        if(textArea.getText() !=null && !textArea.getText().equals("")){//1.判断是否有内容
            //2.判断新旧文件
            if(pathFile !=null){//旧文件
                //3.旧文件判断内容是否改变
                if (!fileContent.equals(textArea)) {//改变
                    //保存选择消息
                    if(this.readExitSystem("old"))
                        return;//结束方法的执行
                }
            }else{//新文件
               if(this.readExitSystem("new"))
                   return;//结束方法的执行
            }
        }
        System.exit(0);

      (2)readExitSystem(String exitStyle)方法     
        int i = this.showOptionMessageByJOptionPane();
        if(i==0)//保存
            if(exitStyle.equals("new"))
               this.saveFileDialog("保存为...");
            else
                this.saveFiles(pathFile);
        else if(i==2)//取消
            return true;
        return false;
==================================================================================================================================================================================
文件菜单暂时做到如此,有待更新再从这里开始补充(以上2020-12-23,周三)

效果演示视频:

记事本文件菜单演示

4.编辑菜单的详细过程

一、准备工作:
    1.观察编辑菜单的各项可操作前提
        (1)文本域从未发生过任何变化时,撤销命令不可执行
        (2)文本域没有内容时:剪切、复制、删除、查找、查找上一个、查找下一个不可以操作
        (3)文本域有内容时,如果没有选择内容,剪切、复制、删除不可以操作
        (4)转到命令在自动换行被选中时,不可以操作
    2.给文本域添加单击监听、鼠标监听、键盘监听;即让Note实现上述接口,然后可以添加监听
    3.文本域一开始有显示垂直方向的滚动条
==================================================================================================================================================================================

二、准备工作的实现代码:
    1.解决第1个问题的(2)(3)项(最后一项功能实现解析)通过使用线程时刻对文本域内容的变化即可实现
        代码实现:在run()方法中的循环里边的Thread.sleep(10);的上面添加如下代码:
                //查找操作只在有内容的情况下可以使用
                if (textArea.getText() == null || textArea.getText().equals("")) {
                    search.setEnabled(false);
                    searchNext.setEnabled(false);
                    searchPrevious.setEnabled(false);
                } else {
                    search.setEnabled(true);
                    searchNext.setEnabled(true);
                    searchPrevious.setEnabled(true);
                }
                //剪切、复制、删除只有在选中的情况可以操作
                if (textArea.getSelectedText() == null || textArea.getSelectedText().equals("")) {
                    cut.setEnabled(false);
                    copy.setEnabled(false);
                    del.setEnabled(false);
                } else {
                    cut.setEnabled(true);
                    copy.setEnabled(true);
                    del.setEnabled(true);
                }

    2.给文本域添加鼠标监听、键盘监听;即让Note实现上述接口,然后可以添加监听
        在addListener()方法中添加文本域的上述监听:
            textArea.addMouseListener(this);
            textArea.addKeyListener(this);

    3.文本域一开始有显示垂直方向的滚动条,在initJScrollPane()方法中滚动面板对象创建之后添加代码:     jScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
==================================================================================================================================================================================

三、编辑菜单功能分析与代码:
    1.撤销
        1.1.之前不可操作功能未实现,它的实现应该是在initMenu()方法中创建之后设置为:
                revoke.setEnable(false);
        1.2.解决它何时可用,解决方法如下:
               思路:只要文本域内容一有变化,该功能就可以永远实现,直至程序退出。使用线程监听
                     当if!textArea.getText().equals("")&&textArea.getText() !=null时设置其可用:revoke.setEnabled(true);
                     通过使用集合revokeList来创建初始的5个元素,使用构造代码块初始化
                        {
                            revokeList.add("");//存储原内容,点击撤销第一次的内容
                            revokeList.add("");//存储变化了的内容,点击撤销第二次回见
                            revokeList.add("00");//存储执行撤销的命令
                            revokeList.add("00");//存储是否更新第二个元素的命令
                            revokeList.add("00");//存储是否更新第一个元素的命令
                        }
               具体实现:
                   (1)在Note类型创建全局变量:List<String> revokeList=new ArrayList<>();
                   (2)撤销内容的变化与剪切、粘贴、删除、输入内容有关。其中
                            剪切和删除视为同一种操作,都是使内容变少,用“11”表示
                            粘贴视为一种操作,输入内容也视为单独一种操作,分别用“22”和“33”表示
                            输入内容记录输入前和停止输入时的变化。
                   (3)创建方法void revokeContentChange();str是执行操作标志
                            revokeList.set(0,textArea.getText());
                            revokeList.set(2,"88");//表示奇数次点击撤销命令执行的标识符

                   (4)创建执行撤销命令方法void actionRevoke();
                            //剪切、删除、粘贴之后进入撤销操作执行的命令
                            if(!revokeList.get(3).equals("88")&&(revokeList.get(4).equals("11")||revokeList.get(4).equals("22")||revokeList.get(4).equals("66"))) {
                                    revokeList.set(1,textArea.getText());
                                }
                                if(revokeList.get(2).equals("55")){//反复执行显示撤销之前的内容
                                    textArea.setText(revokeList.get(1));
                                    revokeList.set(2,"88");
                                    //显示返回时撤销回来部分的颜色
                                    new ActionSearch(textArea).showSelectionTextColor(textArea.getCaretPosition()-revokeList.get(1).length()+revokeList.get(0).length(),textArea.getCaretPosition());
                                }else{//反复执行显示撤销之后的内容
                                    textArea.setText(revokeList.get(0));
                                    revokeList.set(2,"55");
                                }
                                revokeList.set(3,"88");


==================================================================================================================================================================================

    2.剪切

        2.1.使用工具系统的剪贴板,由于复制和粘贴均需使用到,所以就作为全局变量,在Note类下获取:
                // 获取系统剪贴板对象
                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();

        2.2.创建actionCut()方法,思路是把文本域选中的内容放到剪贴中,同时选中内容替换为"":
                //0.撤销内容更新判断
                if(!revokeList.get(4).equals("00")&&!revokeList.get(4).equals("11"))
                    this.revokeContentChange();
                revokeList.set(3,"11");
                revokeList.set(4,"11");

                //1.获取文本域选中的剪切内容
                String selectedText = textArea.getSelectedText();
                //2.将剪切内容放入转换对象中
                Transferable selection = new StringSelection(selectedText);
                //3.文本域中将剪切部分用空字符替换
                textArea.replaceRange("", textArea.getSelectionStart(),         textArea.getSelectionEnd());
                //4.将剪切内容转换后的文本放置到剪贴板中
                clipboard.setContents(selection, null);



==================================================================================================================================================================================

    3.复制

        3.1.与剪贴相似,唯一区别就是不用把文本域内容替换 void actionCopy();
                //1.获取文本域选中的剪切内容
                String selectedText = textArea.getSelectedText();
                //2.将剪切内容放入转换对象中
                Transferable selection = new StringSelection(selectedText);
                //3.将剪切内容转换后的文本放置到剪贴板中
                clipboard.setContents(selection, null);


==================================================================================================================================================================================

    4.粘贴

        4.1.执行方法void actionPaste();
                //0.撤销内容更新判断
                if(!revokeList.get(4).equals("00")&&!revokeList.get(4).equals("22"))
                    this.revokeContentChange();
                revokeList.set(3,"22");
                revokeList.set(4,"22");

            //1.判断剪贴板中是否含有字符串内容
            if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
                try {
                    //2.获取剪贴板的文本内容
                    String data = (String) clipboard.getData(DataFlavor.stringFlavor);
                    //3.将文本选中内容替换成剪贴板获取的内容
                    textArea.replaceRange(data, textArea.getSelectionStart(), textArea.getSelectionEnd());

                } catch (UnsupportedFlavorException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
             }
            

==================================================================================================================================================================================

    5.删除

        5.1.执行void actionDelete();把选中内容替换为""即可
                //0.撤销内容更新判断
                if(!revokeList.get(4).equals("00")&&!revokeList.get(4).equals("11"))
                    this.revokeContentChange();
                revokeList.set(3,"11");
                revokeList.set(4,"11");

                //1.文本域中将删除部分用空字符替换
                textArea.replaceRange("", textArea.getSelectionStart(),         textArea.getSelectionEnd());




==================================================================================================================================================================================

    6.使用Bing搜索,这个参考其它资料

        6.1.执行方法void actionSearchByBing();
                String url = "https://cn.bing.com/search";//搜索的网址
                this.openBrowser(url);//调用执行的方法
        6.2.参考方法如下void openBrowser(String url):

        try {
            // 获取操作系统的名字
            String osName = System.getProperty("os.name", "");
            if (osName.startsWith("Mac OS")) {
                // 苹果的打开方式
                Class<?> fileMgr = Class.forName("com.apple.eio.FileManager");
                Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[]{String.class});
                openURL.invoke(null, new Object[]{url});
            } else if (osName.startsWith("Windows")) {
                // windows的打开方式。
                Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
            } else {
                // Unix or Linux的打开方式
                String[] browsers = {"firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape"};
                String browser = null;
                for (int count = 0; count < browsers.length && browser == null; count++)
                    // 执行代码,在brower有值后跳出,
                    // 这里是如果进程创建成功了,==0是表示正常结束。
                    if (Runtime.getRuntime().exec(new String[]{"which", browsers[count]}).waitFor() == 0) {
                        browser = browsers[count];
                    }
                if (browser == null) {
                    throw new Exception("Could not find web browser");
                } else {
                    // 这个值在上面已经成功的得到了一个进程。
                    Runtime.getRuntime().exec(new String[]{browser, url});
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


==================================================================================================================================================================================

    7.查找(单独创建一个类,继承JDialog,实现ActionListener接口)
        查找类的基本结构Search.java:
public class Search extends JDialog implements ActionListener{

    //查找内容标签与文本域
    JLabel bq_content;
    JTextField txf1;

    //方向框设置
    JLabel bq_up, bq_down;
    JTextField txf2;
    //分别表示查找方向:向上和向下
    JRadioButton rbtUp, rbtDown;
    ButtonGroup rbg;

    //查找下一个和取消按钮
    JButton bt_searchNext, bt_Cancle;

    //复选框,分别表示:区分大小写、循环
    JCheckBox jckCase, jckRecycle;

    String searchText = "";//查找内容
    String replaceText = "";//替换内容

    private DealTextArea dealTextArea;//含向上或向下查找方法类
    private JTextArea textArea;//文本域

     //初始化查找内容
    {
        String[] strings = ReadData.readDatas();//读取初始查找数据
        searchText = strings[0];
        replaceText = strings[1];

    }


    public Search(JTextArea textArea) {
        //省略
    }

    public void initialComponent() {
        //省略
    }

    public void addActionListener() {
        //省略
    }

    public void initialDialog() {
        //省略
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == bt_searchNext) {
           //省略

        } else if (e.getSource() == bt_Cancle) {
            //省略
        }
    }

    //text是查找的内容,position是光标所在位置
    public int doSearching(String text, int position) {
       //省略
    }

}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

        7.1.要求:
            (1)区分向上和向下查找
            (2)进入查找时,查找内容自动填充上次查找的文本内容
            (3)区分大小写查找,默认不区分大小写
            (4)可以循环查找

        7.2.解决由简入繁:
            (1)区分向上和向下查找
                    思路01:向上查找,以光标所在位置开始,向前选择查找文本的长度,所得对比对象
                           如果和查找内容一致,将其用蓝色背景显示。当光标所在的位置大于等于查找文本时,进行查找,否则查找结束。
                    思路02:向下查找,以光标所在位置开始,向后选择查找文本的长度,所得对比对象
                           如果和查找内容一致,将其用蓝色背景显示。当光标所在的位置小于等于文本域长度-查找文本时,进行查找,否则查找结束。

                    01.public void lookSameTextToUp();非循环向上查找代码:
                        //1.获取查找内容及其长度
                        String searchText=txf1.getText();
                        int searchLen=searchText.length();

                        //2.获取光标位置
                        int position=textArea.getCaretPosition();

                        //3.开始查找
                        for(int i=position;i>=position-searchLen;i--){
                            //4.获取查找内容
                            textArea.setSelectionStart(i - searchLen);
                            textArea.setSelectionEnd(i);
                            String selectedText=textArea.getSelectedText();
                            //5.进行比较
                            if(searchText.equals(selectedText)){
                                textArea.setSelectionColor(Color.blue);//给找到的内容用蓝色显示出来
                                textArea.setCaretPosition(i - searchLen);//光标往前设置
                            }
                        }

                      小结:以上执行并不能得到想要的结果:每点击一次,往前找,如果有相同的,内容背景显示蓝色,点击下一次,再查找下一个。如果查找完了,给出提示:“已经到尽头了,没有查找的内容了”
                      解决:
                           (1)循环的执行应该只是网上查找,找到即结束返回查找到的位置,在调用方法处设置其找到的内容背景颜色,如果在循环显示,方法一结束,则无法再显示了。
                           (2)光标位置、内容获取,均可通过调用方法时再获取

                      综上,上面的方法应该进行改造
                           001.方法void lookSameTextToUp()改为:
                                   int lookSameTextToUp(String searchText,int position);
                           内容如下:
                                //1.光标移位
                                position--;
                                //说明:比如单行:“得到得”。光标放在最后,位置是4,向上找到第一个,选中部分为最后一个“得”,此时光标位置还是4,
                                //如果不向前一位,那么永远只找到最后一个就结束。所以需减1
                                //2.获取查找内容及其长度
                                int searchLen=searchText.length();

                                //3.开始查找
                                for(int i=position;i>=searchLen;i--){
                                    //4.获取查找内容
                                    //textArea.setSelectionStart(i - searchLen);
                                    //textArea.setSelectionEnd(i);
                                    //String selectedText=textArea.getSelectedText();
                                    String selectedText=this.getSelectedText(i-searchLen,i);
                                    //5.进行比较
                                    if(searchText.equals(selectedText)){
                                        return i-searchLen;//找到返回的选中内容开始部分
                                    }
                                 }
                                 return -1; //没找到返回-1
 
                           002.监听事件查找下一个执行如下内容:
                                 //1.获取查找内容
                                String searchText = txf1.getText();
                                //2.获取光标位置
                                int position = textArea.getCaretPosition();
                                //3.执行查找
                                this.doSearching(searchText,position);
                              
                           003.方法void doSearching(String searchText,int position):进行查询方向的确定
                                int row=-1;
                                //3.查找方向
                                if(rbtUp.isSelected()==true)//向上查找
                                    row=this.lookSameTextToUp(searchText,position);
                                else//向下查找
                                    row=this.lookSameTextToDown(searchText,position);
                                //4.执行查找结果
                                this.showSelectionTextColor(row,row+searchText.length());
     
                           004.方法String getSelectedText(int start,int end)对001优化:该方法获取选中内容
                                textArea.setSelectionStart(start);
                                textArea.setSelectionEnd(end);
                                return textArea.getSelectedText();

                           005.方法void showSelectionTextColor(int start,int end)优化:该方法显示查询结果
                               if(start !=-1){//找到
                                    //设置选中内容及其背景色
                                    textArea.setSelectionStart(start);
                                    textArea.setSelectionEnd(end);
                                    textArea.setSelectionColor(Color.blue);
                                }else{//没找到
                                    //设置文本域选中部分为文本域自身背景色,以及提示消息
                                    textArea.setSelectionColor(textArea.getBackground());
                                    JOptionPane.showMessageDialog(null,"已经到尽头了,没有查找的内容了");
                                }

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

                    02.public void lookSameTextToDown();非循环向下查找代码,这里和向上类似,直接列出优化的代码:
                        001.int lookSameTextToDown(String searchText,int position)方法向下查找

                                //1.获取查找内容及其长度
                                int searchLen=searchText.length();

                                //2.开始查找
                                for(int i=position;i<=textArea.getText().length()-searchLen;i++){
                                    //3.获取查找内容
                                    String selectedText=this.getSelectedText(i-searchLen,i);
                                    //4.进行比较
                                    if(searchText.equals(selectedText)){
                                        return i-searchLen;//找到返回的选中内容开始部分
                                    }
                                 }
                                 return -1; //没找到返回-1
                        002.在001中调用的方法在01中均已实现


----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

            (2)进入查找时,查找内容自动填充上次查找的文本内容
                   解决这个问题的思路就是:利用构造代码块初始化查找的内容
                   001.先在项目下创建一个文本文件夹files,文件夹下创建一个data.txt文件
                   002.创建一个读取和保存文件的类,里面只有两个静态方法,一个读取,一个保存
                   003.类名:DataUtils.java
                   004.读取文件的方法:public static String[] readData();使用try()with{}结构,该结构可自动关闭流文件
                     String[] str=new String[2];//数组存储查找和替换内容
                     try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File("files/data.txt")), "UTF-8"))) {
                        str[0] = br.readLine();//论行读取
                        str[1] = br.readLine();

                     } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                     } catch (FileNotFoundException e) {
                        e.printStackTrace();
                     } catch (IOException e) {
                        e.printStackTrace();
                     }
                     return str;

                    005.保存文件的方法:public static void saveData(String searchText,String replaceText);使用try()with{}结构,该结构可自动关闭流文件 
                      
                     try (BufferedWriter br = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File("files/data.properties")), "UTF-8"))) {
                        br.write(searchText+"\n");
                        br.write(replaceText);
                     } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                     } catch (FileNotFoundException e) {
                        e.printStackTrace();
                     } catch (IOException e) {
                        e.printStackTrace();
                     }
                    006.在Search类下创建两个全局变量String searchText="",replaceText="";
                    007.在Search类下创建构造代码块
                        {
                            String[] datas=DataUtils.readData();
                            searchText=datas[0];
                            replaceText=datas[1];
                        } 
                    008.在Search类下执行组件创建时,把查找内容文本域的内容设置为searchText即可
              
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

            (3)区分大小写查找,默认不区分大小写
                  思考:区分大小写应该在哪里进行判断比较好呢?需不需要单独写成一个方法?
                  001.在(1)中方法void doSearching(String searchText,int position)解决大小写判断最好
                  002.由于不少地方需要使用到,有必要单独写成一个方法: boolean isLowerCase();
                       if(jckCase.isSelected()==false){//不区分大小写处理
                           return true;
                        return false;
                  003.在(1)中方法void doSearching(String searchText,int position)首先实现
                       if(this.isLowerCase())//不区分大小写处理
                            searchText=searchText.toLowerCase();//统一小写,也可以统一大写)
                  004.在(1)中方法String getSelectedText(int start,int end)中实现文本域的统一小写处理
                       if(this.isLowerCase())
                            return textArea.getSelectedText().toLowerCase();
                       return textArea.getSelectedText();

                  功能完成!
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

            (4)可以循环查找
                 分析:前面已经实现了按方向查找,但是每个方向都有循环与非循环之分。
                     向上循环,每次找到最前面时,光标就设置到最后,从尾开始即可
                     向下循环,每次找到最后面时,光标就设置到最前,从头开始即可
                 问题:循环查找如果没有怎么办?第几遍判断比较合适?
                 分析:至少全部过一遍才能确定,也就是至少从头或从尾开始一轮才能决定
                     这样,需要一个变量计算循环执行的次数,还要一个变量表示其是否找到过
                 实现:
                    001.创建一个方法获取是否循环的布尔值:boolean isReturnSearch();
                        if(jckRecycle.isSelected()==true)
                            return true;//循环
                        return false;//不循环
                    002.修改方法:void doSearching(String searchText,int position);步骤3
                        //3.查找方向
                        if(rbtUp.isSelected()==true)//向上查找
                            if(this.isReturnSearch())//循环查找                            
                                row=this.lookSameTextToUpRecycle(searchText,position);
                            else
                                row=this.lookSameTextToUp(searchText,position);
                        else//向下查找
                            if(this.isReturnSearch())//循环查找
                                row=this.lookSameTextToDownRecycle(searchText,position);
                            else
                                row=this.lookSameTextToDown(searchText,position);

                    003.创建方法lookSameTextToUpRecycle(String searchText,int position);

                             if(position<=0)
                                position=textArea.getText().length();

                              int recycleTimes=0;//统计完整循环次数
                              boolean existSame=false;//假设没找到
                              while(true){
                            
                                for(int i=position;i>=searchLen;i--){
                                    //4.获取查找内容
                                    String selectedText=this.getSelectedText(i-searchLen,i);
                                    //5.进行比较
                                    if(searchText.equals(selectedText)){
                                        return i-searchLen;//找到返回的选中内容开始部分
                                    }
                                 }

                                 position=textArea.getText().length();
                                 recycleTimes++;
                                 if(recycleTimes>1){//第二遍开始进行是否存在相同内容判断
                                    if(existSame==false)//第二遍还没找到,说明没有,给出提示
                                        return -1; //没找到返回-1
                                 }
                                 
                               }

                     004.创建方法lookSameTextToDownRecycle(String searchText,int position);

                             if(position>=textArea.getText().length())
                                position=0;

                              int recycleTimes=0;//统计完整循环次数
                              boolean existSame=false;//假设没找到
                              while(true){
                            
                                for(int i=position;i<=position-searchLen;i++){
                                    //4.获取查找内容
                                    String selectedText=this.getSelectedText(i,i+searchLen);
                                    //5.进行比较
                                    if(searchText.equals(selectedText)){
                                        return i;//找到返回的选中内容开始部分
                                    }
                                 }

                                 position=textArea.getText().length();
                                 recycleTimes++;
                                 if(recycleTimes>1){//第二遍开始进行是否存在相同内容判断
                                    if(existSame==false)//第二遍还没找到,说明没有,给出提示
                                        return -1; //没找到返回-1
                                 }
                                 
                               }

观察整理优化代码:for循环部分,还有while循环部分,很多相似,是否可以进行优化呢?优化整理如下:
(0)设置一个成员变量:int upTimes=0;用于检测非循环向上查找

(1)监听事件调用方法
     //1.获取查找内容
     String searchText = txf1.getText();
     //2.获取光标位置
     int position = textArea.getCaretPosition();
     //3.执行查找
     this.doSearching(searchText,position);

(2)doSearching(String searchText,int position)方法执行:
        //1.判断是否区分大小写
        if(this.isLowerCase())//不区分大小写
            searchText=searchText.toLowerCase();
        int row=-1;
        //2.查找方向与查找方式
        if(rbtUp.isSelected()==true)//向上查找
            if(this.isReturnSearch())//循环查找
                row=this.lookSameTextByRecycle("up",searchText,position);
            else//非循环查找
                row=this.lookSameTextToUp("normal",searchText,position);
        else//向下查找
            if(this.isReturnSearch())//循环查找
                row=this.lookSameTextByRecycle("down",searchText,position);
            else//非循环查找
                row=this.lookSameTextToDown(searchText,position);
        //3.执行查找结果
        this.showSelectionTextColor(row,row+searchText.length());

(3)showSelectionTextColor(int start,int end)方法,显示结果
        if(start !=-1){
            textArea.setSelectionStart(start);
            textArea.setSelectionEnd(end);
            textArea.setSelectionColor(Color.blue);
        }else{
            textArea.setSelectionStart(textArea.getCaretPosition());
            textArea.setSelectionEnd(textArea.getCaretPosition());
            JOptionPane.showMessageDialog(null,"已经到尽头了,没有查找的内容了");
        }

(4)boolean isLowerCase()方法,判断是否区分大小写,区分返回false,否则返回true:
        if(jckCase.isSelected()==false)
            return true;//不区分大小写处理
        return false;

(5)boolean isReturnSearch()方法,判断是否循环查找,循环返回true,否则返回false:
        if(jckRecycle.isSelected()==true)
            return true;//循环
        return false;//不循环

(6)int lookSameTextByRecycle(String direction,String searchText,int position)方法,返回查找结果
        upTimes=0;//恢复初始值
        if(direction.equals("up")){//向上查找
            position--;
            if(position<=0)
                position=textArea.getText().length();//如果光标在最前面,则设置到最后面
        }else{//向下查找
            if(position>=textArea.getText().length())
                position=0;//如果光标在最后面,则设置到最前面
        }

        int row=-1;
        int recycleTimes=0;//统计完整循环次数
        boolean existSame=false;//假设没找到
        while(true){//循环查找
            if(direction.equals("up")){
                row= this.lookSameTextToUp("recycle",searchText,position);
            }else{
                row=this.lookSameTextToDown(searchText,position);
            }
            if(row !=-1)
                return row;
            if(direction.equals("up")){
                position=textArea.getText().length();
            }else{
                position=0;
            }
            recycleTimes++;
            if(recycleTimes>1){//第二遍开始进行是否存在相同内容判断
                if(existSame==false)//第二遍还没找到,说明没有,给出提示
                    return -1; //没找到返回-1
            }

        }  

(7)int lookSameTextToUp(String style,String searchText,int position)方法,向上查找
        if(style.equals("normal")&&position<=textArea.getText().length()&&upTimes !=0)
            position--;
        int row=-1;
        //1.获取查找内容及其长度
        int searchLen=searchText.length();
        //2.开始查找
        for(int i=position;i>=searchLen;i--){
            //3.获取查找内容
            String selectedText=this.getSelectedText(i-searchLen,i);
            //4.进行比较
            if(searchText.equals(selectedText)){
                row= i-searchLen;
                break;
            }
        }
        upTimes++;
        return row;

(8)String getSelectedText(int start,int end)方法,获取文本域选择内容
        textArea.setSelectionStart(start);
        textArea.setSelectionEnd(end);
        if(this.isLowerCase())//不区分大小写
            return textArea.getSelectedText().toLowerCase();
        return textArea.getSelectedText();


(9)int lookSameTextToDown(String searchText,int position)方法,向下查找
        upTimes=0;//恢复初始值
        int row=-1;
        //1.获取查找内容及其长度
        int searchLen=searchText.length();
        //2.开始查找
        for(int i=position;i<=textArea.getText().length()-searchLen;i++){
            //3.获取查找内容
            String selectedText=this.getSelectedText(i,i+searchLen);
            //4.进行比较
            if(searchText.equals(selectedText)){
                row= i;
                break;
            }
        }
        return row;

==================================================================================================================================================================================

    8.查找下一个

        8.1.分析:查找下一个是向下非循环查找,查找的内容是历史查找内容保存记录(即最近一次查找的内容)。因此,查找内容的获取就通过读取保存记录即可。其它步骤均与查找中相似
                revoke.setEnable(false);
        8.2.实现:
            (1)执行方法:void actionSearchNext();
            (2)具体实现:
                 //1.读取保存的记录
                 String[] datas=DataUtils.readData();//第一个元素保存的是查找内容,第二个元素保存的是替换内容。
                 //2.获取查找内容
                 String searchText=datas[0];
                 //3.获取光标位置
                 int position=textArea.getCaretPosition();
                 //4.执行查找操作(这个方法中有一个成员变量upTimes,解决方式在本类下定义一个一样的变量即可)
                 int row=new Search(textArea).lookSameTextToDown(searchText,position);
                 //5.显示查找结果
                new Search(textArea).showSelectionTextColor(row,row+searchText.length());


==================================================================================================================================================================================

    9.查找上一个

        9.1.查找下一个与查找上一个思路一致。但是之前在查找类中定义了一个全局变量在方法中,如何解决呢?方式1就是在Note类下也定义一个和它一样的全局变量即可。
        9.2.实现:
            (1)Note类下定义成员变量:int upTiems=0;执行方法:void actionSearchPrevious();
            (2)具体实现:
             //1.读取保存的记录
             String[] datas=DataUtils.readData();//第一个元素保存的是查找内容,第二个元素保存的是替换内容。
             //2.获取查找内容
             String searchText=datas[0];
             //3.获取光标位置
             int position=textArea.getCaretPosition();
             //4.执行查找操作
             int row=new Search(textArea).lookSameTextToUp("normal",searchText,position);
             //5.显示查找结果
             new Search(textArea).showSelectionTextColor(row,row+searchText.length());

观察发现查找上一个和查找下一个的执行步骤几乎相同,只是在第4步执行的查找方向不一样,因此可以优化:
(1)void actionSearchNext()方法:
     this.actionSearchNextOrPrevious("next");

(2)void actionSearchPrevious()方法:
     this.actionSearchNextOrPrevious("previous");

(3)void actionSearchNextOrPrevious(String direction)方法:
      //1.读取保存的记录
      String[] datas=DataUtils.readData();//第一个元素是查找内容,第二个元素是替换内容。
      //2.获取查找内容
      String searchText=datas[0];
      //3.获取光标位置
      int position=textArea.getCaretPosition();

      //4.执行查找操作
      int row=-1;
      if(direction.equals("next"))
          row=new Search(textArea).lookSameTextToDown(searchText,position);
      else
          row=new Search(textArea).lookSameTextToUp("normal",searchText,position);

      //5.显示查找结果
      new Search(textArea).showSelectionTextColor(row,row+searchText.length());
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

问题:这样调用方法也会把查找的对话框弹出来,这不是想要的结果,如何解决呢?
解决:创建一个处理类,没有界面,专门处理这些查找,方便后面替换操作时调用

步骤01.创建处理类:ActionSearch.java
       该类有一个私有属性:private MyTextArea textArea;
       一个无参构造,一个有参有参构造,有参构造如下:
       public ActionSearch(MyTextArea textArea){this.textArea=textArea;}

步骤02.Search类方法的提取与修改以及ActionSearch类的设计

步骤03.逐个方法提取,原则:不受本类的过度约束

提取方法列举(由简单到复杂):
    方法1:显示查找结果的方法可以提取设计为:
           public void showSelectionTextColor(int start,int end);
           内容如下:
           if(start !=-1){
                textArea.setSelectionStart(start);
                textArea.setSelectionEnd(end);
                textArea.setSelectionColor(Color.blue);
            }else{
                textArea.setSelectionStart(textArea.getCaretPosition());
                textArea.setSelectionEnd(textArea.getCaretPosition());
                JOptionPane.showMessageDialog(null,"已经到尽头了,没有查找的内容了");
            }

    方法2:获取文本域选择内容的方法可以提取修改如下:
           //参数isCase是表示是否区分大小写,true是不区分,false区分
           public String getSelectedText(boolean isCase,int start,int end);  
           内容如下:       
           textArea.setSelectionStart(start);
           textArea.setSelectionEnd(end);
           if(isCase)//true表示不区分大小写,统一做小写处理
               return textArea.getSelectedText().toLowerCase();
           return textArea.getSelectedText();

    方法3:循环查找可以提取:
           //参数isCase是表示是否区分大小写,true是不区分,false区分
           public int lookSameTextByRecycle(String direction,String searchText,int position);  
           内容如下(之前的update参数可以在调用时处理):
            if(direction.equals("up")){//向上查找
               position--;
               if(position<=0)
                  position=textArea.getText().length();//如果光标在最前面,则设置到最后面
            }else{//向下查找
               if(position>=textArea.getText().length())
                  position=0;//如果光标在最后面,则设置到最前面
            }

            int row=-1;
            int recycleTimes=0;//统计完整循环次数
            boolean existSame=false;//假设没找到
            while(true){//循环查找
                if(direction.equals("up")){
                    row= this.lookSameTextToUp("recycle",searchText,position);
                }else{
                    row=this.lookSameTextToDown(searchText,position);
                }
                if(row !=-1)
                    return row;
                if(direction.equals("up")){
                    position=textArea.getText().length();
                }else{
                    position=0;
                }
                recycleTimes++;
                if(recycleTimes>1){//第二遍开始进行是否存在相同内容判断
                    if(existSame==false)//第二遍还没找到,说明没有,给出提示
                        return -1; //没找到返回-1
                }

            }  

    方法4:向上查找方法可以提取:
           //参数isCase是表示是否区分大小写,true是不区分,false区分
           public int lookSameTextToUp(String style,int upTimes,String searchText,int position);
           内容如下:
            if(style.equals("normal")&&position<=textArea.getText().length()&&upTimes !=0)
            position--;
        int row=-1;
        //1.获取查找内容及其长度
        int searchLen=searchText.length();
        //2.开始查找
        for(int i=position;i>=searchLen;i--){
            //3.获取查找内容
            String selectedText=this.getSelectedText(i-searchLen,i);
            //4.进行比较
            if(searchText.equals(selectedText)){
                row= i-searchLen;
                break;
            }
        }
        upTimes++;
        return row;


    方法5:向下查找方法可以提取:
           //参数isCase是表示是否区分大小写,true是不区分,false区分
           public int lookSameTextToDown(String searchText,int position);
           内容如下(之前的update参数可以在调用时处理):
            int row=-1;
            //1.获取查找内容及其长度
            int searchLen=searchText.length();
            //2.开始查找
            for(int i=position;i<=textArea.getText().length()-searchLen;i++){
                //3.获取查找内容
                String selectedText=this.getSelectedText(i,i+searchLen);
                //4.进行比较
                if(searchText.equals(selectedText)){
                    row= i;
                    break;
                }
            }
            return row;
然后对应修改Search类和查找上一个和查找下一个的方法调用即可。
技巧:如果不是多次调用,就用匿名对象调用方法,如果多次使用,可以创建一个私有的成员对象

==================================================================================================================================================================================

    10.替换(单独创建一个类,继承JDialog,实现ActionListener接口)
类设计如下:

//替换对话界面
public class Replace extends JDialog implements ActionListener{
    //查找与替换的标签、输入框
    JLabel bq_sear, bq_rep;
    JTextField txf_searContent, txf_repContent;

    //查找下一个、替换、全部替换、取消按钮
    JButton bt_searNext, bt_RepOne, bt_RepAll, bt_Cancle;

    //复选框,分别表示:区分大小写、循环
    JCheckBox jckCase, jckRecycle;

    //声明并初始化查找和替换的值
    String searchText = "";
    String replaceText = "";

    private MyTextArea textArea;
    private ActionSearch actionSearch;
    
    //构造代码块执行读取保存查找替换内容的文件
    {
        String[] strings = ReadData.readDatas();
        searchText = strings[0];
        replaceText = strings[1];

    }

    public Replace(MyTextArea textArea) {
        this.textArea=textArea;
        actionSearch=new ActionSearch(textArea);
        //有参构造方法
    }

    public void initialComponent() {
        //组件创建

    }

    public void addActionListener() {
       //添加监听事件
    }

    public void initialDialog() {
        //对话框设置
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == bt_searNext) {
           //单个向下查找
           
        } else if (e.getSource() == bt_RepOne) {
            //逐个替换

        } else if (e.getSource() == bt_RepAll) {//全部替换
           //全部替换
        } else if (e.getSource() == bt_Cancle) {
            //退出前保存查找替换文本框的内容
            SaveData.saveDatas(txf_searContent.getText(), txf_repContent.getText());
            this.dispose();
        }

    }


}

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

        10.1.分析:
                (1)替换是可以实现向下查找,也可以实现向下的循环查找
                (2)具有区分大小写选择
                (3)替换按钮是点击一次,替换一次查找到的内容,同时替换的内容背景色是蓝色,替换结束给出提示
                (4)全部替换是一次性执行全部替换,并且给出提示
        10.2.替换仅是多了一项把查找到的内容替换成指定内容,其处理方式方法并没有太多的区别。
        10.3.详细解析过程:
            (0)创建方法:boolean isLowerCase();
                 if(jckCase.isSelected()==false)//不区分大小写
                     return true;
                  return false;
            (1)查找下一个,执行过程和查找类中是一样的
                //1.获取查找内容
                String searchText = txf_searContent.getText();
                //2.判断是否区分大小写
                boolean isCase=this.isLowerCase();
                if(isCase)
                   searchText=searchText.toLowerCase();
                //3.获取光标位置
                int position=textArea.getCaretPosition();
         
                //4.执行查找方法
                int row=-1;
                if(jckRecycle.isSelected()==true)//循环查找
                    row=actionSearch.lookSameTextByRecycle
                                            ("down",0,isCase,searchText,position);
                else//非循环查找
                    row=actionSearch.lookSameTextToDown(isCase,searchText,position);

                //5.显示查找结果
                actionSearch.showSelectionTextColor(row,row+searchText.length());
                 
            (2)替换功能:单个替换与全部替换进行了优化。单个功能是找到并替换,同时显示背景色为蓝色,当是最后一个结束时,不再显示背景色,同时给出提示;而全部替换始终没有背景色,结束直接给提示信息。
                 注意:逐个替换的开始位置以光标所在位置开始往后替换,不会从头开始!

              001.在监听事件下:逐个替换和全部替换调用的都是同一个方法,只是参数值不一样。
                    this.actionReplaceText("notRecycle");//逐个替换
                    this.actionReplaceText("recycle");//全部替换
              002.方法void actionReplaceText(String actionStyle);
                    //1.获取查找内容与替换内容
                    searchText = txf_searContent.getText();
                    replaceText = txf_repContent.getText();

                    //2.判断当前是否有选中的内容
                    int row = -1;
                    String selectedText = textArea.getSelectedText();
               if (selectedText != null && selectedText.equals(searchText)) {//有,先进行替换,然后显示替换文本背景色为蓝色
                    int selectionStart = textArea.getSelectionStart();
                    int selectionEnd = textArea.getSelectionEnd();
                    textArea.replaceRange(replaceText,selectionStart,selectionEnd);
                    actionSearch.showSelectionTextColor(selectionStart, selectionEnd);
                } else {//没有则先查找再替换
                    //3.判断是否区分大小写
                    boolean isCase=this.isLowerCase();
                    if(isCase)
                        searchText=searchText.toLowerCase();
                    if(actionStyle.equals("recycle")){//全部替换
                        while (true){
                            row=this.showReplaceResult("recycle",isCase);
                            if(row ==-1)//替换结束,退出循环
                                break;
                        }
                    }else{//逐个替换
                        this.showReplaceResult("notRecycle",isCase);
                    }
                }

              003.方法int showReplaceResult(String actionStyle,boolean isCase);
                    //1.获取光标位置
                    int position=textArea.getCaretPosition();
                    //2.执行方法
                    int row=actionSearch.lookSameTextByRecycle
                                    ("down",0,isCase,searchText,position);
                    if(row !=-1) {
                        //3.显示查找结果
                        textArea.replaceRange(replaceText, row, row+searchText.length());
                        if(!actionStyle.equals("recycle"))
                        actionSearch.showSelectionTextColor
                                        (row, row+searchText.length());
                    }else{
                        textArea.setSelectionStart(textArea.getCaretPosition());
                        textArea.setSelectionEnd(textArea.getCaretPosition());
                        if(!actionStyle.equals("recycle"))
                            JOptionPane.showMessageDialog(null,"替换结束,已到文末!");
                        else
                            JOptionPane.showMessageDialog(null,"替换结束,已全部替换!");
                    }
                    return row;

是否还可以再优化一些,需要再琢磨了,比如循环、大小写判断是否可以放到actionSearch类中。
思考:是否可以使用String的各种方法来实现查找替换呢?是可以的。待日后更新贴上!

==================================================================================================================================================================================

    11.转到

        11.1.当点击时,弹出对话框,输入跳转到的行号(记事本是从0开始的,为了习惯,一般0行显示1)
            创建这么一个类:TurningLine继承自JDialog;代码很少,直接贴上实现的方法:
//转到指定行
public class TurningPage extends JDialog implements ActionListener {
    JLabel bq1;
    JTextField txf1;
    JButton bt1, bt2;
    private JTextArea textArea;

    //文本域对象
    public TurningPage(JTextArea textArea) {//省略}

    public void initialComponent() {
       //省略
    }

    public void initialDialog() {
        //省略
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == bt1) {//确定
            //获取文本域总行数
            int lineNum = textArea.getLineCount();
            //获取输入的跳转行数
            int turnNum = Integer.parseInt(txf1.getText());
            if (turnNum > lineNum&&turnNum <=0) {
                JOptionPane.showMessageDialog(this, "行数超过了总行数", "记事本 - 跳转",
                        JOptionPane.PLAIN_MESSAGE);
                return;//结束方法的执行,不关闭对话窗口
            } else {
                try {
                    //跳转到指定行:行数从0开始计,所以要减一。这里的意思是从光标所在行偏移至指定行。
                    textArea.setCaretPosition(textArea.getLineStartOffset(turnNum - 1));
                } catch (Exception ee) {
                }
                this.dispose();//关闭对话窗口
            }

        } else if (e.getSource() == bt2) {
            this.dispose();//取消,退出对话框
        }

    }

}



==================================================================================================================================================================================

    12.全选

        12.1.设置文本选中区域,即设置开始和结束地方即可
            方法:void actionSelectAll();
                textArea.setSelectionStart(0);//开始位置       
                textArea.setSelectionEnd(textArea.getText().length());//结束位置
                textArea.setSelectionColor(Color.blue);//选中区域颜色

==================================================================================================================================================================================

    13.时间/日期:方法 void actionCurrentTimeAndDate();

        13.1.方法1:添加在文末:
            textArea.append("\t\t" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        13.2.方法2:添加在选中位置:
            textArea.replaceRange(""+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), textArea.getSelectionStart(),         textArea.getSelectionEnd());

==================================================================================================================================================================================
补充知识:以上已经实现了编辑文件菜单的所有功能,但是还有键盘输入事件未监听和实现,只需实现监听,调用对应方法即可。
补充问题:
    001.当关闭窗口右上角退出按钮时,也需要做到对文本域内容的监听。如果有内容执行判断是否是新旧文件,是否需要保存,同时窗口对象是从集合中移除,如果是最后一个对象,退出程序。
    002.默认情况下,可以使用键盘操作,但是应该实现键盘的监听操作,例如输入字符,删除字符等,以实现撤销命令的预期结果。
    003.在查找和替换对话中,当关闭或退出对话框时,应该将查找文本和替换文本保存到文件中。
        调用DataUtils.saveData(searchText,replaceText);//这两变量是全局变量

实现001.Note类实现WindowListener接口及其方法,其中稍微修改一下第三节中退出命令的操作方法即可:
    (1)需要在initJFrame()方法最后添加对象的窗口监听:this.addWindowListener(this);
        @Override
        public void windowClosing(WindowEvent e) {//据窗口右上角按钮关闭
            this.closeOrExitWindows();//调用执行方法
        }

     (2)修改退出方法:
                  private void actionExit(){
                    this.closeOrExitWindows();
                  }

     (3)创建方法:void closeOrExitWindows()
            if(list !=null){//需要在集合不为空的情况下执行
                if(list.size()<=1){
                    if(this.exitCurrentWindows()==false)//保存或不保存命令执行结束返回值
                        System.exit(-1);//退出程序
                    else//取消命令,正常显示窗口
                        this.setDefaultCloseOperation(JFrame.NORMAL);
                }else{//多个对象时,只关闭当前使用对象
                    if(this.exitCurrentWindows()==false) {
                        list.get(activeFrame).dispose();//关闭该窗口
                        list.remove(activeFrame);//移除该对象
                    }
                }
            }

     (4)修改方法:boolean exitCurrentWindows()
            //1.判断是否有内容
            if (textArea.getText() != null && !textArea.getText().equals("")) {
                //2.判断新旧文件
                if (pathFile != null) {//旧文件
                    //3.旧文件判断内容是否改变
                    if (!fileContent.equals(textArea)) {//改变
                        //保存选择消息
                        if (this.readExitSystem("old"))
                            return true;
                    }
                 } else {//新文件
                    if (this.readExitSystem("new"))
                    return true;
                 }
              }
              return false;
     (5)剩下的调用方法在第3节文件菜单。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
实现002.对窗口的命令只分析键盘输入与删除:
        (1)实现键盘输入时撤销内容变化的监听:
            @Override
            public void keyReleased(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_SPACE) {//按下空格键
                    if (!revokeList.get(4).equals("66")&&!revokeList.get(4).equals("00")) 
                        this.revokeContentChange();
                    revokeList.set(3,"66");
                    revokeList.set(4,"66");
                }
            }

        (2)实现键盘删除时撤销内容变化的监听:
             if(e.getKeyCode()==KeyEvent.VK_BACK_SPACE) {
                if(textArea.getCaretPosition()>0)
                    textArea.replaceRange("", textArea.getCaretPosition(), textArea.getCaretPosition());//退格键删除
             }else if(e.getKeyCode()==KeyEvent.VK_DELETE) {
                this.actionDelete();//delete键删除
             }

==================================================================================================================================================================================
关于查找和替换,以及查找上一个和查找下一个,它们是相互关联的。
(1)如果查找中选择了循环查找、区分大小写,那么替换中也是对应选择了的
(2)查找上一个或下一个是否要循环查找、是否区分大小写,完全是由查找或替换退出时的状态决定的。
(3)所以需要对查找和替换部分进行优化处理,为了不过多改动,应该是使用保存文件的方法进行处理
解决方案:在退出时保存是否区分大小写和是否循环状态进行存储,这样在初始化时就可以直接使用了

001.修改保存和读取文件的方法(都是静态方法)
    保存方法:void saveDatas(String searchText,String replaceText,String cases,String re);
             //cases是表示是否区分大小写,re表示是否循环
              br.write(searchText+","+replaceText+","+cases+","+re);

    读取方法:String[] readDatas();
              String str=br.readLine();
              return str.split(",");//表示以“,”为标志分隔成一个字符串数组

余下具体实现参见文末项目完整代码整理

==================================================================================================================================================================================

5.格式菜单功能实现与代码示例

格式菜单功能项分析:
    1.自动换行:
        (1)选中时水平滚动条消失,可以实现自动换行。同时转到功能不可操作(转到是编辑菜单项)
        (2)不选中时,水平滚动条存在,不会自动换行,需要按Enter键,转到某行功能可以操作
        (3)初始状态,不选中,具有水平滚动条,不会自动换行
    2.功能实现:
        (1)初始状态具有水平滚动条,需要在初始化滚动面板时设置显示水平滚动条,即在方法
             initJScrollPane()中设置:
             textArea.setLineWrap(false);//设置文本域自动换行功能为false
jScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);即可。
        (2)添加监听,实现方法:void actionAutoLineWrap();
             if(turnLine.isSelected()==true) {//自动换行被选中执行
                turn.setEnabled(false);//编辑菜单“转到”不可操作
                textArea.setLineWrap(true);//设置文本域自动换行功能为真
                jScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);//水平滚动条消失
             }else {//未被选中
                turn.setEnabled(true);//编辑菜单“转到”可操作
                textArea.setLineWrap(false);//设置文本域自动换行功能为false
                jScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);//水平滚动条显示
             }
==================================================================================================================================================================================

    3.字体(单独创建一个对话框类):
        (1)字体、字形、字号是通过列表显示
        (2)点击确认时,应用到文本域中

    4.功能实现:
        (1)界面实现,就是简单的swing组件应用,省略。
        (2)功能的实现也是简单。
        (3)列举自认为比较需要加强学习的知识点:
             001.获取系统字体库的名称
                 //获取系统字体库
                 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
                 //获取所有系统字体库里字体的名称
                 String[] lt1Str = ge.getAvailableFontFamilyNames();
             002.给文本域设置字体
                 //获取选择的字体名称、样式、大小
                 String fontName = txf_fontName.getText();
                 String fontStyle = txf_fontStyle.getText();
                 int fontSize = this.setSize(txf_fontSize.getText());
                 //设置具体字体
                 textArea.setFont(new Font(fontName, 5,fontSize));

6.查看菜单功能实现与代码示例

功能1:状态栏显示与隐藏
      实现分析:设计的时候状态栏是有两个面板,只要把包含具体内容的那个面板实现隐藏与显示即可。
                 奇数次单击该功能时,隐藏;偶数次单击该功能时,显示。这是一个复选框,使用其方法isSelected()即可实现:
                 if(state.isSelected()==true)//选中
                    statementPanel.setVisible(false);//不可见
                 else//未选中
                    statementPanel.setVisible(true);//可见

==================================================================================================================================================================================

功能2:状态栏的行号和列号是根据光标的位置一直在变化中,字号的百分比也随字号增减而改变
       (1)获取行号和列号的时刻准确数据,首先需要MyTextArea类实现接口CaretListener的方法
       (2)在MyTextArea类添加一个有参构造方法和一个私有属性:
                private StatementPanel statementPanel;

                public MyTextArea(StatementPanel statementPanel){
                    this.statementPanel=statementPanel;//通过有参构造确保在同一个窗口中操作的是同一个对象
                    this.addCaretListener(this);//给对象添加监听
                }
       (3)在MyTextArea类实现监听方法:    
            @Override
            public void caretUpdate(CaretEvent e) {
                if (e.getSource() == this) {
                    try {
                        int offset = e.getDot(); // 获取插入符号的位置,即光标位置
                        int rows = this.getLineOfOffset(offset);// 偏移的行
                        int columns = e.getDot() - this.getLineStartOffset(rows) + 1;// 偏移的列=光标位置-偏移行的长度
                        statementPanel.changeRowAndColumnText("  第" + (rows + 1) + "行,第" + columns + "列     ");
                    } catch (Exception ee) {
                        ee.printStackTrace();
                    }
                }
            }  
      
       (4)在StatementPanel类添加一个改变属性内容的方法void changeRowAndColumnText(String str): 
            rowAndColumn.setText(str);
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

       (5)百分比变化与字号的增大与缩小有关,具体实现在功能3,可先实现如下方法(6)

       (6)在StatementPanel类添加一个改变百分比的方法void changeCharacterSize(String str):
            characterSize.setText(str);

 
==================================================================================================================================================================================

功能3:实现字体的增大、减小、恢复
       要求:单击增大或减小时,以10%的递增或递减,范围[10%,500%]。点击恢复时,显示改变之前的正常字号大小
       实现分析:
        (1)获取正常字号的值:应该是在递增或递减或恢复时第一次执行前获取是符合要求的。不能一开始创建对象就获取,因为中途可能会有改变。
        (2)如何知道是第一次执行,使用全局变量记录:int fontChangeTimes=0;
        (3)如何保存初始字号,使用全局变量记录:Font fontChange=null;
        (4)更新文本域字号变化的方法:void textAreaFontSizeChange(String action);
             if(fontChangeTimes==0) //判断是否是最初状态,最初状态记录初始font值
                fontChange= textArea.getFont();

             if(action.equals("enlarge") && fontChangeTimes<40)//次数小于40均可增大
                fontChangeTimes++;
             else if(action.equals("narrow") && fontChangeTimes>10)//次数大于10均可减小
                fontChangeTimes--;

             int size=(int)(fontChange.getSize()*(1+0.1*fontChangeTimes));//获取字号值
             if(action.equals("restore"))//直接设置初始值
                textArea.setFont(fontChange);
             else
                textArea.setFont(new Font(fontChange.getName(),fontChange.getStyle(),size));//更新文本域字号
             statementPanel.changeCharacterSize(size+"%");//更新状态栏的百分比
               
        (5)增大、缩小、恢复调用方法省略。

7.查看功能如果是链接到指定网址的话,在编辑菜单中已经实现,只需把地址传入调用方法即可。

8.从头梳理,以及主要核心代码

第一部分:Note.java程序入口在该类
package com.study.main;

......

/**
 * 第1步:创建主程序,继承JFrame
 * JFrame是实现窗口需要
 */
public class Note extends JFrame implements ActionListener, MouseListener, KeyListener, WindowListener, Runnable {
   //菜单面板
    private JMenuBar menuBar;

    //文件菜单
    private JMenu file;
    private JMenuItem newFile, newWindow, openFile, saveFile, anotherSaveFile, settingPage, printFile, exit;

    //编辑菜单
    private JMenu edit;
    private JMenuItem revoke, cut, copy, paste, del, useBing, search, searchNext, searchPrevious, replace, selectAll, datetime;
    public JMenuItem turn;
    //格式菜单
    private JMenu format;
    private JCheckBoxMenuItem turnLine;
    private JMenuItem fonts;

    //查看菜单
    private JMenu view, zoom;
    private JCheckBoxMenuItem state;
    private JMenuItem enlarge, narrow, restore;

    //帮助菜单
    private JMenu help;
    private JMenuItem helps, sendMs, aboutNote;

    //工具条面板
    private ToolBarPanel toolBarPanel;

    //文本域面板
    public JScrollPane jScrollPane;
    //文本域
    public MyTextArea textArea;

    //状态栏面板
    private JPanel statePanel;
    //状态栏具体内容面板
    private StatementPanel statementPanel;


    //记录打开窗口的集合
    public static List<Note> list = new ArrayList<>();
    //记录活动的窗口
    public static int activeFrame = 0;
    //标记创建窗口的方式:-1是代表打开新窗口,整数表示新建文件窗口
    int remark = -5;

    //未保存文件的标题
    public String updateTiltle = "*无标题--记事本";
    //保存了文件的标题
    public String initTitle = "无标题--记事本";

    //执行具体方法类
    private ActionDeals actionDeals;

    //获取输入内容
    private String inputContent="";

    //获取光标点击时的位置
    private int startPosition=0;


    /**
     * main方法程序开始运行的地方
     *
     * @param args
     */
    public static void main(String[] args) {
        list.add(new Note());//无参构造方法创建匿名对象
    }

    /**
     * 无参构造方法
     */
    public Note() {
        this.initJFrame();
    }

    /**
     * 有参构造方法
     */
    public Note(int remark) {
        this.remark = remark;
        this.initJFrame();
    }

    /**
     * 添加菜单项到容器中
     */
    public void initMenu() {
        //文件、编辑、格式、查看、帮助菜单的具体内容
        //具体内容略(都是一些创建对象)
        this.setJMenuBar(menuBar);
    }

    /**
     * 初始化工具条菜单面板
     */
    private void initToolBarPanel() {
        toolBarPanel = new ToolBarPanel();
    }

    /**
     * 初始化具有滚动条的面板:用于文本域
     */
    private void initJScrollPane() {
        textArea = new MyTextArea(statementPanel);
        jScrollPane = new JScrollPane(textArea);
        jScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        jScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
    }

    /**
     * 初始化状态栏面板
     */
    private void initStatePanel() {
        statePanel = new JPanel();//这个面板是为了装状态栏面板
        statePanel.setLayout(new FlowLayout(FlowLayout.RIGHT));//使用流式布局管理器

        //具体内容略(都是一些创建对象)

        //把相关组件添加到状态栏面板中
        statePanel.add(statementPanel);
        statePanel.add(jbl);
    }

    /**
     * 添加组件的监听事件
     */
    private void addListener() {
       //具体内容略(都是一些添加监听操作)

    }

    /**
     * 窗口的整体设计
     */
    private void initJFrame() {
        //具体内容略(在之前的菜单文件中已基本分析完)
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        //具体内容略(都是一些对应监听对象的执行方法调用)
    }

    @Override
    public void keyTyped(KeyEvent e) {
        
    }

    @Override
    public void keyPressed(KeyEvent e) {
        //具体内容略(都是一些对应监听对象的执行方法调用)
    }

    @Override
    public void keyReleased(KeyEvent e) {
         if (e.getKeyCode() == KeyEvent.VK_SPACE) {//松开空格键
            //0.获取变化的内容
            inputContent=textArea.getText().substring(startPosition,textArea.getCaretPosition());

            //1.设置撤消提示为:true,表示没有执行撤消操作
            actionDeals.revoking=true;

            //2.判断是否为第一次操作
            actionDeals.carryContentChange("22");

            //3.执行撤销内容的更新
            actionDeals.updateRevokeText("22",inputContent);

            //4.更新起点
            startPosition=textArea.getCaretPosition();

            //5.更新inputContent
            inputContent="";

        }
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON3) {//单击右键
            //右键弹出的列表选项菜单
            MyPopupMenu myPopupMenu = new MyPopupMenu(textArea, actionDeals);
            //表示在使用容器显示,显示在右边
            myPopupMenu.show(e.getComponent(), e.getX(), e.getY());

        }else if(e.getSource()==textArea){
            startPosition=textArea.getCaretPosition();
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
    }

    @Override
    public void mouseReleased(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    @Override
    public void windowOpened(WindowEvent e) {
    }

    //右上角关闭执行的方法
    @Override
    public void windowClosing(WindowEvent e) {
        actionDeals.actionCloseOrExitWindows();
    }

    @Override
    public void windowClosed(WindowEvent e) {
    }

    @Override
    public void windowIconified(WindowEvent e) {
    }

    @Override
    public void windowDeiconified(WindowEvent e) {
    }

    @Override
    public void windowActivated(WindowEvent e) {
    }

    @Override
    public void windowDeactivated(WindowEvent e) {
    }

 @Override
    public void run() {
        while (true) {
            try {
                //查找操作只在有内容的情况下可以使用
                if (textArea.getText() == null || textArea.getText().equals("")) {
                    search.setEnabled(false);
                    searchNext.setEnabled(false);
                    searchPrevious.setEnabled(false);
                } else {
                    search.setEnabled(true);
                    searchNext.setEnabled(true);
                    searchPrevious.setEnabled(true);
                }
                //剪切、复制、删除只有在选中的情况可以操作
                if (textArea.getSelectedText() == null || textArea.getSelectedText().equals("")) {
                    cut.setEnabled(false);
                    copy.setEnabled(false);
                    del.setEnabled(false);
                    toolBarPanel.bt_copy.setEnabled(false);
                    toolBarPanel.bt_cut.setEnabled(false);

                } else {
                    cut.setEnabled(true);
                    copy.setEnabled(true);
                    del.setEnabled(true);
                    toolBarPanel.bt_copy.setEnabled(true);
                    toolBarPanel.bt_cut.setEnabled(true);
                }
                if (list != null) {
                    for (int i = 0; i < list.size(); i++) {
                        if (list.get(i) !=null&&list.get(i).isActive() == true) {
                            activeFrame = i;
                        }
                    }
                }
                if (!textArea.getText().equals("") && textArea.getText() != null) {
                    revoke.setEnabled(true);
                }
                //时刻显示选中内容的颜色
                textArea.setSelectionColor(Color.BLUE);
                if (actionDeals.fileContent.equals(textArea.getText()))
                    this.setTitle(initTitle);
                else
                    this.setTitle(updateTiltle);
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

==================================================================================================================================================================================

第二部分:具体功能实现类

1.ActionDeals.java类,包含处理监听事件调用的各种具体方法

package com.study.service;

.......

public class ActionDeals {

    private MyTextArea textArea;

    private ActionSearch actionSearch;

    private StatementPanel statementPanel;

    //记录打开或者已经保存了的新建文件
    private File pathFile = null;
    //记录打开或者已经保存了的新建文件内容
    public String fileContent = "";

    //记录字号变化次数,用于改变字号的命令
    private int fontChangeTimes = 0;
    //记录字体初始值,用于改变字号的命令
    private Font fontChange = null;

    // 获取系统剪贴板对象
    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();

    //记录文本域的集合,用于撤销命令的操作
    public List<String> revokeList = new ArrayList<>();

    //声明当前窗口对象集合
    private Note note;

    //记录是否连续剪切或删除同一处前的内容
    public static List<Integer> countRevoke=new ArrayList<>();


    //初始化revokeList集合
    {
        revokeList.add("");
        revokeList.add("");
        revokeList.add("00");
        revokeList.add("00");
        revokeList.add("00");
        countRevoke.add(0);
        countRevoke.add(0);
    }

    public ActionDeals() {
    }

    public ActionDeals(MyTextArea textArea, StatementPanel statementPanel, Note note) {
        this.textArea = textArea;
        this.statementPanel = statementPanel;
        this.note=note;
        actionSearch = new ActionSearch(textArea);
    }

    //文件菜单部分的功能实现

    /**
     * 文件菜单之新建步骤01:
     * <p>
     * 创建新文件的执行方法
     */
    public void actionCreateNewFile() {
        //1.判断文本域是否有内容
        if (textArea.getText() == null || textArea.getText().equals("")) {
            new Note(note.activeFrame);//没有内容直接新建
        } else {
            //2.有内容弹首先判断是新文件还是旧文件
            if (pathFile == null) {//全局变量文件是空,是新文件
                this.doActionAccordingToOptionMessage("new");
            } else {//旧文件
                //3.判断内容是否发生改变
                if (fileContent.equals(textArea.getText())) {//没有发生改变
                    DataUtils.saveFileData(textArea.getText(), pathFile.getAbsolutePath());//直接执行保存文件的操作
                } else {//发生改变
                    this.doActionAccordingToOptionMessage("old");//有变化执行保存文件操作
                }
            }
        }
    }


    /**
     * 文件菜单之新建步骤02:
     * <p>
     * 执行新旧文件保存操作
     *
     * @param fileStyle 新文件("new")或旧文件("old")
     */
    public void doActionAccordingToOptionMessage(String fileStyle) {
        //1.获取询问对话框结果值:0是保存,1是不保存,2是取消
        int row = this.showOptionMessageByJOptionPane();
        //2.根据返回结果执行对应命令
        if (row == 0) {//保存
            if (fileStyle.equals("new"))
                this.saveFileDialog("新建保存...");//打开保存文件对话框
            else
                DataUtils.saveFileData(textArea.getText(), pathFile.getAbsolutePath());//执行直接保存文件的操作
            new Note(note.activeFrame);//执行创建新窗口,同时关闭旧窗口的方法
        } else if (row == 1) {//不保存
            new Note(note.activeFrame);
        }
    }

    /**
     * 文件菜单之新建步骤03:
     * <p>
     * 是否保存消息询问
     *
     * @return 返回选择结果int类型
     */
    public int showOptionMessageByJOptionPane() {
        Object[] options = {"保存", "不保存", "取消"};// 选择消息
        return JOptionPane.showOptionDialog(null, "你是否要保存文件?", "记事本", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
    }

    /**
     * 文件菜单之新建步骤04:
     * 文件菜单之保存文件步骤02:
     * 文件菜单之文件另存为步骤02:
     * <p>
     * 打开保存文件对话框
     *
     * @param title 对话框标题
     */
    public void saveFileDialog(String title) {
        //1.打开保存对话框
        JFileChooser chooser = new JFileChooser("C:\\Users\\shinelon\\Desktop");//默认打开对话框显示的位置
        chooser.setDialogType(JFileChooser.SAVE_DIALOG);//对话框类型
        chooser.setDialogTitle(title);//设置显示的对话框标题
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);//显示文件和目录的指令。
        chooser.setSelectedFile(new File("新建文本文档.txt"));//默认保存的文件名
        chooser.showSaveDialog(null);//打开对话框
        chooser.setVisible(true);//显示其可见
        System.out.println(JFileChooser.APPROVE_OPTION);

        //2.获取保存文件进行重名判断
        File selectedFile = chooser.getSelectedFile();//获取保存的文件

        //文件不为空,表示选择有文件,否则不执行操作
        if (selectedFile != null) {
            boolean exist = this.existSameFile(selectedFile);//判断选择的文件是否已经存在
            while (exist) {
                if (this.replaceExistSameFile(selectedFile)) {//替换同名文件
                    break;
                } else {//不替换
                    chooser = new JFileChooser("C:\\Users\\shinelon\\Desktop");//默认打开对话框显示的位置
                    chooser.setDialogType(JFileChooser.SAVE_DIALOG);//对话框类型
                    chooser.setDialogTitle(title);//设置显示的对话框标题
                    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);//显示文件和目录的指令。
                    chooser.setSelectedFile(new File("新建文本文档.txt"));//默认保存的文件名
                    chooser.showSaveDialog(null);//打开对话框
                    chooser.setVisible(true);//显示其可见
                    selectedFile = chooser.getSelectedFile();//获取保存的文件
                    exist = this.existSameFile(selectedFile);
                }
            }

            //3.执行保存文件
            DataUtils.saveFileData(textArea.getText(), selectedFile.getAbsolutePath());

            //4.更新相关参数(只有在保存单击事件和另存为单击事件下更新)
            if (!title.equals("新建保存...")) {
                this.updateParameters(selectedFile);
            }
        }


    }

    /**
     * 文件菜单之新建步骤05:
     * <p>
     * 判断保存选择的文件是否重名
     *
     * @param selectFile 判断文件
     * @return 结果返回布尔值,true表示重名,false表示不重名
     */
    public boolean existSameFile(File selectFile) {
        //1.获取文件名
        String name = selectFile.getName();
        //2.获取文件所在当前最近一级文件夹下的所有文件
        File[] files = selectFile.getParentFile().listFiles();
        //3.遍历对比
        if (files != null) {
            for (File file1 : files) {
                if (name.equals(file1.getName()))//文件名相同
                    return true;
            }
        }
        return false;
    }

    /**
     * 文件菜单之新建步骤06:
     * <p>
     * 判断是否替换重名文件
     *
     * @param selectFile 重名文件
     * @return 返回结果布尔值,true表示替换,否表示不替换
     */
    public boolean replaceExistSameFile(File selectFile) {
        Object[] options = {"是", "否"};// 选择消息
        int row = JOptionPane.showOptionDialog(null, selectFile.getName() + " 已存在。\n要替换它吗?", "确认另存为", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[1]);
        return row == 0 ? true : false;
    }

    /**
     * 文件菜单之新建步骤08:
     * 文件菜单之打开文件步骤02:
     * 文件菜单之保存文件步骤03:
     * <p>
     * 更新显示标题、文件信息
     *
     * @param selectedFile 更新的文件
     */
    public void updateParameters(File selectedFile) {
        note.updateTiltle = "*" + selectedFile.getName();
        note.initTitle = selectedFile.getName();
        note.setTitle(note.initTitle);
        pathFile = selectedFile;
        fileContent = textArea.getText();
    }

    /**
     * 文件菜单之打开窗口:
     * <p>
     * 打开新窗口
     */
    public void actionOpenNewWindows() {
        note.list.add(new Note(-1));
    }

    /**
     * 文件菜单之打开文件步骤01:
     * <p>
     * 打开文件
     */
    public void actionOpenFile() {
        //1.打开对话框
        JFileChooser chooser = new JFileChooser("C:\\Users\\shinelon\\Desktop");//默认打开显示的位置
        chooser.setDialogTitle("选择打开文件...");
        chooser.setDialogType(JFileChooser.OPEN_DIALOG);//对话框类型
        chooser.setSelectedFile(new File(".txt"));//只显示后缀为“.txt”的文件
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);//显示文件和目录的指令。
        chooser.showOpenDialog(null);//打开对话框
        chooser.setVisible(true);

        //2.获取选择的文件
        File selectedFile = chooser.getSelectedFile();
        if (selectedFile != null) {
            //3.读取文件到文本域
            textArea.setText(DataUtils.readFileData(selectedFile.getAbsolutePath()));
            //4.更新相关参数
            this.updateParameters(selectedFile);

            //5.更新撤销相关内容
            this.carryContentChange("22");
            this.updateRevokeText("22","");
        }
    }

    /**
     * 文件菜单之保存文件步骤01:
     * <p>
     * 保存文件
     */
    public void actionSaveFile() {
        //1.判断将要保存文件是否是新文件
        if (pathFile != null) {//旧文件
            //2.直接执行保存文件的方法
            DataUtils.saveFileData(textArea.getText(), pathFile.getAbsolutePath());
            //3.直接执行保存文件的方法
            this.updateParameters(pathFile);

        } else {//新文件
            //2.调用保存对话框保存新文件
            this.saveFileDialog("保存...");
        }
    }

    /**
     * 文件菜单之文件另存为步骤01:
     * <p>
     * 文件另存为
     */
    public void actionSaveFileNewPosition() {
        //直接调用保存对话框
        this.saveFileDialog("另存为...");
    }

    /**
     * 文件菜单之页面设置
     */
    public void actionSettingPage() {
        new SettingPage();
    }

    /**
     * 文件菜单之打印
     */
    public void actionPrintSetting() {
        new PrintSetting();
    }

    /**
     * 文件菜单之退出当前窗口步骤01:
     * <p>
     * 退出当前窗口
     */
    public void actionCloseOrExitWindows() {
        //1.判断当前集合是否存在对象
        if (note.list != null) {
            //2.集合对象的个数
            if (note.list.size() <= 1) {//单个对象
                //3.文本域是否有内容判断处理,保存与不保存均退出程序,否则正常显示
                if (this.exitCurrentWindows())//执行保存或不保存命令或没有内容
                    System.exit(0);//退出程序
                else
                    note.setDefaultCloseOperation(JFrame.NORMAL);
            } else {
                //3.文本域是否有内容判断处理,保存与不保存均关闭当前窗口,同时移除出集合,否则不执行操作
                if (this.exitCurrentWindows()) {//执行保存或不保存命令或没有内容
                    note.dispose();//关闭该窗口
                    note.list.remove(note.activeFrame);//移除该对象
                }else{
                    note.setDefaultCloseOperation(JFrame.NORMAL);
                }
            }
        }
    }

    /**
     * 文件菜单之退出当前窗口步骤02:
     *
     * @return 返回布尔值结果,保存和不保存或没有内容返回true,取消返回false
     */
    public boolean exitCurrentWindows() {
        //1.判断是否有内容
        if (textArea.getText() != null && !textArea.getText().equals("")) {//有内容
            //2.判断新旧文件
            if (pathFile != null) {//旧文件
                //3.旧文件判断内容是否改变
                if (fileContent.equals(textArea)==true) {//内容改变
                    //保存选择消息
                    if (this.readExitSystem("old") == false)//没有保存,选择了取消
                        return false;
                }
            } else {//新文件
                if (this.readExitSystem("new")==false)//没有保存,选择了取消
                    return false;
            }
        }
        return true;
    }

    /**
     * 文件菜单之退出当前窗口步骤02:
     * <p>
     * 保存询问判断
     *
     * @param exitStyle 文件类型:新文件或者旧文件
     * @return 返回布尔值结果,保存和不保存返回true,取消返回false
     */
    public boolean readExitSystem(String exitStyle) {
        //1.获取选择结果,0保存,1不保存,2取消
        int i = this.showOptionMessageByJOptionPane();
        //2.执行选择结果
        if (i == 0) {//保存
            if (exitStyle.equals("new"))
                this.saveFileDialog("保存为...");
            else
                DataUtils.saveFileData(textArea.getText(), pathFile.getAbsolutePath());
        }else if (i == 2) {//取消
            return false;
        }
        return true;
    }


    //编辑菜单部分的功能实现

    /**
     * 编辑菜单之撤消实现步骤01:
     * <p>
     * 撤消功能
     */
    public void actionRevoke() {
        //1.执行撤消命令
        if (revokeList.get(2).equals("55")==true) {//偶次数执行撤消命令
            //2.内容显示
            textArea.setText(revokeList.get(1));
            revokeList.set(2, "88");

        } else {//奇次数执行撤销命令(从点击撤消命令开始到点击或执行其它操作为止,再次点击则重新计数)
            //2.内容显示
            textArea.setText(revokeList.get(0));
            revokeList.set(2, "55");
        }

    }

    /**
     * 编辑菜单之剪切或复制的功能实现
     *
     * @param action 执行的操作:剪切("cut")或复制("copy")
     */
    public void actionCutOrCopy(String action) {
        //1.获取文本域选中的剪切内容
        String selectedText = textArea.getSelectedText();

        //2.将剪切内容放入转换对象中
        Transferable selection = new StringSelection(selectedText);

        if (action.equals("cut")) {
            //3.撤销内容更新判断
            this.carryContentChange("11");

            //4.文本域中将剪切部分用空字符替换
            textArea.replaceRange("", textArea.getSelectionStart(), textArea.getSelectionEnd());

            //5.执行撤销内容的更新
            this.updateRevokeText("11",selectedText);

        }

        //4.将剪切内容转换后的文本放置到剪贴板中
        clipboard.setContents(selection, null);

    }

    /**
     * 编辑菜单之粘贴功能实现
     */
    public void actionPaste() {

        //1.判断剪贴板中是否含有字符串内容
        if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
            try {
                //2.获取剪贴板的文本内容
                String data = (String) clipboard.getData(DataFlavor.stringFlavor);

                //3.撤销内容更新判断
                this.carryContentChange("22");

                //4.将文本选中内容替换成剪贴板获取的内容
                textArea.replaceRange(data, textArea.getSelectionStart(), textArea.getSelectionEnd());

                //5.执行撤销内容的更新
                this.updateRevokeText("22",data);

            } catch (UnsupportedFlavorException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

    /**
     * 编辑菜单之删除功能实现
     */
    public void actionDel() {
        //1.获取将要删除的内容
        String deleteContent=textArea.getSelectedText();

        //2.撤销内容更新判断
        this.carryContentChange("11");

        //3.文本域中将删除部分用空字符替换
        textArea.replaceRange("", textArea.getSelectionStart(), textArea.getSelectionEnd());

        //4.执行撤销内容的更新
        this.updateRevokeText("11",deleteContent);

    }

    /**
     * 编辑菜单之撤消实现步骤02:
     * <p>
     * 判断是否执行更新撤消内容
     */
    public void carryContentChange(String str) {

        if (revokeList.get(4).equals(str)==false){
            revokeList.set(0, revokeList.get(1));
            revokeList.set(2, "88");
            revokeList.set(3,str);
        }
    }

    /**
     * 执行撤销内容的更新
     * @param str 执行什么操作:剪切、粘贴、删除、输入
     * @param actionContent 改变的内容部分
     */
    public void updateRevokeText(String str,String actionContent){
        //1.是否重复执行相同操作
        if(revokeList.get(3).equals(str)==true&&revokeList.get(4).equals(str)==true){//是
            //2.当前光标的位置
            countRevoke.set(1,textArea.getCaretPosition());
            //3.分操作判断
            if(str.equals("11")){//剪切和删除
                //4.不是连续同一个地方的操作
                if(countRevoke.get(0) != countRevoke.get(1)&&countRevoke.get(0)!=countRevoke.get(1)+actionContent.length())//非连续操作
                    revokeList.set(0,revokeList.get(1));
                //5.更新恢复内容
                revokeList.set(1,textArea.getText());
            }else if(str.equals("22")){//输入和粘贴
                //4.不是连续同一个地方的操作
                if(countRevoke.get(1) != (countRevoke.get(0)+actionContent.length()))//连续在后面粘贴
                    revokeList.set(0,revokeList.get(1));
                //5.更新恢复内容
                revokeList.set(1,textArea.getText());
            }
            //6.更新最初光标位置(操作前)
            countRevoke.set(0,countRevoke.get(1));
        }else{
            revokeList.set(1,textArea.getText());
            //6.更新最初光标位置(操作前)
            countRevoke.set(0,textArea.getCaretPosition());
            System.out.println("初始值:"+countRevoke.get(0));
        }
        //7.更新标记字符串
        revokeList.set(3,str);
        revokeList.set(4,str);
    }

    /**
     * 编辑菜单之使用Bing搜索功能实现
     */
    public void actionUseBing() {
        String url = "https://cn.bing.com/search";//搜索的网址
        actionSearch.openBrowser(url);//调用执行的方法
    }

    /**
     * 编辑菜单之查找替换相关功能实现
     *
     * @param DO_ACTION 操作命令参数
     *                  DO_ACTION = 0 表示执行查找功能
     *                  DO_ACTION = 1 表示执行查找下一个功能
     *                  DO_ACTION = 2 表示执行查找上一个功能
     *                  DO_ACTION = 3 表示执行替换功能
     */
    public void actionAboutSearchOrReplace(Integer DO_ACTION) {
        if (DO_ACTION == 0)
            new Search(textArea);
        else if (DO_ACTION == 1)
            actionSearch.actionSearchNextOrPrevious("down");
        else if (DO_ACTION == 2)
            actionSearch.actionSearchNextOrPrevious("up");
        else if (DO_ACTION == 3)
            new Replace(textArea);
    }

    /**
     * 编辑菜单之转到指定行功能实现
     */
    public void actionTurn() {
        new TurningLine(textArea);
    }

    /**
     * 编辑菜单之全选功能实现
     */
    public void actionSelectAll() {
        actionSearch.showSelectionTextColor("", 0, textArea.getText().length(), "");
    }

    /**
     * 编辑菜单之时间/日期功能实现
     */
    public void actionDatetime() {
        textArea.replaceRange("" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), textArea.getSelectionStart(), textArea.getSelectionEnd());
    }


    //格式菜单功能实现

    /**
     * 格式菜单之自动换行功能实现
     */
    public void actionTurnLine(boolean turnLineIsSelected) {
        if (turnLineIsSelected) {//选中复选框,自动换行
            note.turn.setEnabled(false);//转到指定行不可操作
            textArea.setLineWrap(true);//文本域可以自动换行
            note.jScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);//不显示水平滚动条
        } else {
            note.turn.setEnabled(true);
            textArea.setLineWrap(false);
            note.jScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);//显示水平滚动条
        }
    }

    /**
     * 格式菜单之字体设置功能实现
     */
    public void actionFonts() {
        new MyFont(textArea);
    }


    //查看菜单功能实现


    /**
     * @param DO_ACTION       DO_ACTION = 10 表示恢复功能
     *                        DO_ACTION = 12 表示缩放功能
     *                        DO_ACTION = 21 表示增大功能
     */
    public void actionTextAreaFontSizeChange(Integer DO_ACTION) {
        System.out.println("times =" + fontChangeTimes);
        if (fontChangeTimes == 0) //判断是否是最初状态,最初状态记录初始font值
            fontChange = textArea.getFont();

        if (DO_ACTION == 21 && fontChangeTimes < 40)//次数小于40均可增大
            fontChangeTimes++;
        else if (DO_ACTION == 12 && fontChangeTimes > -9)//次数大于10均可减小
            fontChangeTimes--;

        int size = (int) (fontChange.getSize() * (1 + 0.1 * fontChangeTimes));//获取字号值
        if (DO_ACTION == 10) {//直接设置初始值
            fontChangeTimes = 0;
            textArea.setFont(fontChange);
        } else {
            textArea.setFont(new Font(fontChange.getName(), fontChange.getStyle(), size));//更新文本域字号
        }
        statementPanel.changeCharacterSize((100 + fontChangeTimes * 10) + "%");//更新状态栏的百分比

    }

    /**
     * 查看功能之状态栏功能实现
     */
    public void actionState(boolean stateIsSelected) {
        if (stateIsSelected)
            statementPanel.setVisible(false);
        else
            statementPanel.setVisible(true);
    }


    //帮助菜单功能实现

    /**
     * 帮助菜单之查看帮助功能实现
     */
    public void actionHelps() {
        String url="https://cn.bing.com/search?q=%E8%8E%B7%E5%8F%96%E6%9C%89%E5%85%B3+windows+10+%E4%B8%AD%E7%9A%84%E8%AE%B0%E4%BA%8B%E6%9C%AC%E7%9A%84%E5%B8%AE%E5%8A%A9&filters=guid:%224466414-zh-hans-dia%22%20lang:%22zh-hans%22&form=T00032&ocid=HelpPane-BingIA&rdr=1&rdrig=D377191E0D1B4E5FA77F134253B637C1\n";
        actionSearch.openBrowser(url);
    }

    //剩余两个帮助菜单功能未实现

}

==================================================================================================================================================================================

2.ActionSearch.java实现查找替换的具体功能

package com.study.service;

......

public class ActionSearch implements MouseListener {
    private MyTextArea textArea;
    int row = -1;
    public ActionSearch(){}
    public ActionSearch(MyTextArea textArea){
        this.textArea=textArea;
        textArea.addMouseListener(this);
    }

    //查找上一个和查找下一个的执行方法
    public void actionSearchNextOrPrevious(String direction){
        //1.读取保存的记录
        String[] datas=DataUtils.readDatas();//第一个元素是查找内容,第二个元素是替换内容。
        //2.获取查找内容、是否区分大小写,是否循环
        String searchText=datas[0];
        String cases=datas[2];
        String re=datas[3];
        boolean isCase=false;
        if(cases.equals("false")){
            searchText=searchText.toLowerCase();
            isCase=true;
        }

        //3.获取光标位置
        int position=textArea.getCaretPosition();

            //4.执行查找操作
            if(direction.equals("down")){
                if(re.equals("true")) //循环查找
                    row=this.lookSameTextByRecycle("down",isCase,searchText,position);
                else
                    row=this.lookSameTextToDown(isCase,searchText,position);
            }else {
                System.out.println("row ="+row);
                if(row !=-1)
                    if(row <searchText.length())
                        position=0;
                    else
                        position=row;
                if(re.equals("true")) //循环查找
                    row=this.lookSameTextByRecycle("up",isCase,searchText,position);
                else
                    row=this.lookSameTextToUp(isCase,searchText,position);
            }

        //5.显示查找结果
        this.showSelectionTextColor(direction,row,row+searchText.length(),searchText);
    }

    public void showSelectionTextColor(String direction,int start,int end,String searchText){
        if(start !=-1){
            textArea.setSelectionStart(start);
            textArea.setSelectionEnd(end);
        }else{
            if(direction.equals("up"))
                textArea.setCaretPosition(0);
            else
                textArea.setCaretPosition(textArea.getText().length());
            JOptionPane.showMessageDialog(null,"找不到“"+searchText+"”");
        }
    }

    //参数isCase是表示是否区分大小写,true是不区分,false区分
    public String getSelectedText(boolean isCase,int start,int end){
        textArea.setSelectionStart(start);
        textArea.setSelectionEnd(end);
        if(isCase)//true表示不区分大小写,统一做小写处理
            return textArea.getSelectedText().toLowerCase();
        return textArea.getSelectedText();
    }


    //查找对话框执行方法
    public void doSearching(boolean upOrDown,boolean isRecycle,boolean isCase,String searchText){
        int position=textArea.getCaretPosition();


        //3.查找方向
        if (upOrDown == true) {//向上查找
            System.out.println("row ="+row);
            if(row !=-1){
                if(row <searchText.length())
                    position=0;
                else
                    position=row;
            }

            if (isRecycle) { //循环查找
                row = this.lookSameTextByRecycle("up",isCase, searchText, position);
            } else {
                row = this.lookSameTextToUp(isCase, searchText, position);
            }
            //upTimes++;
            //4.执行查找结果
            this.showSelectionTextColor("up",row,row+searchText.length(),searchText);
        }else {//向下查找
            //upTimes = 0;
            if (isRecycle) //循环查找
                row = this.lookSameTextByRecycle("down", isCase, searchText, position);
            else
                row = this.lookSameTextToDown(isCase, searchText, position);
            //4.执行查找结果
            this.showSelectionTextColor("down",row,row+searchText.length(),searchText);
        }
    }

    public int lookSameTextByRecycle(String direction,boolean isCase,String searchText,int position){
        int recycleTimes=0;//统计完整循环次数
        boolean existSame=false;//假设没找到
        while(true){//循环查找
            if(direction.equals("up")){
                row= this.lookSameTextToUp(isCase,searchText,position);
            }else{
                row=this.lookSameTextToDown(isCase,searchText,position);
            }
            if(row !=-1)
                return row;
            if(direction.equals("up")){
                position=textArea.getText().length();
            }else{
                position=0;
            }
            recycleTimes++;
            if(recycleTimes>1){//第二遍开始进行是否存在相同内容判断
                if(existSame==false)//第二遍还没找到,说明没有,给出提示
                    return -1; //没找到返回-1
            }

        }
    }


    public int lookSameTextToUp(boolean isCase,String searchText,int position){
        int num=-1;
        //1.获取查找内容及其长度
        int searchLen=searchText.length();
        //2.正常开始查找
        for(int i=position;i>=searchLen;i--){
            //3.获取查找内容
            String selectedText=this.getSelectedText(isCase,i-searchLen,i);
            //4.进行比较
            if(searchText.equals(selectedText)){
                num= i-searchLen;
                break;
            }
        }
        return num;
    }


    public int lookSameTextToDown(boolean isCase,String searchText,int position){

        int num=-1;
        //1.获取查找内容及其长度
        int searchLen=searchText.length();
        //2.开始查找
        for(int i=position;i<=textArea.getText().length()-searchLen;i++){
            //3.获取查找内容
            String selectedText=this.getSelectedText(isCase,i,i+searchLen);
            //4.进行比较
            if(searchText.equals(selectedText)){
                num= i;
                break;
            }
        }
        return num;
    }

    public void openBrowser(String url){
        try {
            // 获取操作系统的名字
            String osName = System.getProperty("os.name", "");
            if (osName.startsWith("Mac OS")) {
                // 苹果的打开方式
                Class<?> fileMgr = Class.forName("com.apple.eio.FileManager");
                Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[]{String.class});
                openURL.invoke(null, new Object[]{url});
            } else if (osName.startsWith("Windows")) {
                // windows的打开方式。
                Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
            } else {
                // Unix or Linux的打开方式
                String[] browsers = {"firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape"};
                String browser = null;
                for (int count = 0; count < browsers.length && browser == null; count++)
                    // 执行代码,在brower有值后跳出,
                    // 这里是如果进程创建成功了,==0是表示正常结束。
                    if (Runtime.getRuntime().exec(new String[]{"which", browsers[count]}).waitFor() == 0) {
                        browser = browsers[count];
                    }
                if (browser == null) {
                    throw new Exception("Could not find web browser");
                } else {
                    // 这个值在上面已经成功的得到了一个进程。
                    Runtime.getRuntime().exec(new String[]{browser, url});
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        if(e.getSource()==textArea)
            if(textArea.getCaretPosition()==textArea.getText().length()||textArea.getCaretPosition()==0)
                row=-1;

    }

    @Override
    public void mousePressed(MouseEvent e) {

    }

    @Override
    public void mouseReleased(MouseEvent e) {

    }

    @Override
    public void mouseEntered(MouseEvent e) {

    }

    @Override
    public void mouseExited(MouseEvent e) {

    }
}


==================================================================================================================================================================================

3.其它类不再列举,基本都是一些组件的创建,没什么需要特别分析和理解了。

9.第一版记事本完整代码链接:https://download.csdn.net/download/preston555/13784172

猜你喜欢

转载自blog.csdn.net/preston555/article/details/111476602
今日推荐