在 Java 中创建高级标签式记事本





5.00/5 (7投票s)
在本文中,我们将用 Java 创建一个记事本,其中包含多选项卡文档、文档选择器、打开/保存/关闭多个文档、运行外部程序、不同的外观和感觉、在浏览器中查看文件等功能。
引言
众所周知,Java 是世界上流行且强大的编程语言,它具有平台独立性。我们使用了很多用 Java 开发的编辑器,如 Eclipse、NetBeans、Intelli-J、JEdit 等。这些编辑器与记事本相同,但增加了开发的功能。
在本文中,我们将只介绍主要功能,如打开、保存、关闭、运行等,但不会介绍如何在 Java 中创建语法高亮。
好了,用 StyledDocument
在 Java 中创建语法高亮很容易。你可以在互联网上轻松找到在 Java 中创建语法高亮的源代码。我们还将像 Notepad++ 一样,在文档文本更改或保存时更改选项卡图标。首先,设计你的记事本,包括菜单、菜单项、工具栏等。
下载源代码以查看高级选项卡式记事本的完整代码以及创建的 jar 应用程序。我使用的是 JTextPane
对象而不是 JTextArea
。这使得执行新建和打开操作很容易。但是,要执行保存或编辑操作,在执行新建或打开操作后,你需要先从 JTabbedPane
中获取当前的 textpane
控件以供使用/执行其他操作。
开始吧
- 创建
JList
、JTabbedPane
、JSplit
对象。 - 将
JList
对象和JTabbedPane
对象添加到JSplit
中。 - 将
JSplit
对象添加到Frame
的Container
中。
Using the Code
从 JTabbedPane 中的当前选项卡(选定选项卡)获取 JTextPane 控件
- 获取添加到
JTabbedPane
中的选定索引。 - 将
JTabbedPane
的选定索引的组件强制转换为JScrollPane
(如果添加了JScrollPane
)。 - 从强制转换的
JScrollPane
中获取Viewport
。 - 最后,将
index 0
的组件强制转换为JTextPane
。
这是获取添加到当前选项卡中的 JTextPane
组件的代码。
int sel = _tabbedPane.getSelectedIndex();
JScrollPane jscroll=(JScrollPane)_tabbedPane.getComponentAt(sel);
JViewport jview=jscroll.getViewport();
JTextPane textpane=(JTextPane)jview.getComponent(0);
这是四行代码。但你也可以像这样在一两行内完成
int sel = _tabbedPane.getSelectedIndex();
JTextPane textPane =
(JTextPane)(((JScrollPane)_tabbedPane.getComponentAt(sel)).getViewport()).getComponent(0);
现在对 textPane
对象执行所有操作。
好的,现在让我们看看重要的函数。根据你想要的操作,向该菜单项添加 ActionListener
。
1) 新建
这个功能很容易实现。只需将新的选项卡和 textPane
添加到 JTabbedPane
。在这里,我还要检查是否启用了深色主题,如果启用了,则将背景颜色设置为组件。这是代码。
public void File_New_Action()
{
//crerate textpane object
JTextPane _textPane=new JTextPane();
_textPane.setFont(new Font("Calibri",Font.PLAIN,14));
if(isDarkTheme){
_textPane.setBackground(new Color(10, 10, 20));
_textPane.setForeground(new Color(250, 250, 250));
}
JScrollPane jsp=new JScrollPane(_textPane);
// add key listener & Undoable edit listener to text pane
_textPane.addKeyListener(new KeyTypedAction());
_textPane.getDocument().addUndoableEditListener(_undoManager);
//add tab to _tabbedPane with control textpane
_tabbedPane.addTab("Document "+count+" ",jsp);
//add caret listener & mouse listener to text pane
_textPane.addCaretListener(new CaretAction());
_textPane.addMouseListener(new TextPane_MouseAction());
int index=_tabbedPane.getTabCount()-1;
_tabbedPane.setSelectedIndex(index);
// set save icon to added tab
_tabbedPane.setIconAt(index, new ImageIcon(this.getClass().getResource("resources/save.png")));
listModel.addElement("Document "+count+" ");
_list.setSelectedIndex(index);
//change the title
setTitle("Tabbed Notepad in Java - [ Document "+count+" ]");
filenameLabel.setText("Document "+count);
count++;
}
2) 打开
此功能与新建功能相同,只是需要读取文件。在这里,我使用 FileDialog
并将 setMultipleMode
设置为 true
以获取多个文件。将选项卡添加到 tabbedpane 作为选定的文件名,更改框架的标题等。创建 textPane
对象后,将其添加到 KeyListener
和 UndoableListener
。这是打开功能的代码。
public void File_Open_Action()
{
FileDialog fd = new FileDialog(new JFrame(), "Select File",FileDialog.LOAD);
fd.setMultipleMode(true);
fd.show();
if (fd.getFiles()!=null)
{
File[] files=fd.getFiles();
for(File item : files)
{
String filename = item.toString();
String file=filename;
if(filename.contains("\\")){
file = filename.substring(filename.lastIndexOf("\\") + 1);
}
else if(filename.contains("/")){
file = filename.substring(filename.lastIndexOf("/") + 1);
}
int count=_tabbedPane.getTabCount();
JTextPane _textPane=new JTextPane();
_textPane.setFont(new Font("Calibri",Font.PLAIN,14));
if (isDarkTheme) {
_textPane.setBackground(new Color(10, 10, 20));
_textPane.setForeground(new Color(250, 250, 250));
}
JScrollPane jsp=new JScrollPane(_textPane);
_textPane.addKeyListener(new KeyTypedAction());
_textPane.getDocument().addUndoableEditListener(_undoManager);
_textPane.addCaretListener(new CaretAction());
_textPane.addMouseListener(new TextPane_MouseAction());
_tabbedPane.addTab(file,jsp);
_tabbedPane.setSelectedIndex(count);
_tabbedPane.setIconAt(count, new ImageIcon(this.getClass().getResource("resources/save.png")));
listModel.addElement(file);
_list.setSelectedIndex(count);
setTitle("Tabbed Notepad in Java - [ "+file+" ]");
filenameLabel.setText(filename);
filesHoldListModel.addElement(filename);
BufferedReader d;
StringBuffer sb = new StringBuffer();
try
{
d = new BufferedReader(new FileReader(filename));
String line;
while((line=d.readLine())!=null)
sb.append(line + "\n");
_textPane.setText(sb.toString());
d.close();
}
catch(FileNotFoundException fe)
{
System.out.println("File not Found");
}
catch(IOException ioe){}
_textPane.requestFocus();
}
}
}
3) 全部保存
好了,实现另存为和保存功能很容易。在这里,我们将看到保存全部功能。这个功能很容易实现。使用 for
循环从 0
到 tabbedpane 的最大索引,然后将其设置为 tabbedpane 的选定索引。从添加的 filenameLabel
获取文件名,并通过获取当前选项卡中的当前 textpane 来保存该文件。
为了识别完整的文件名路径,我创建了 filesHoldList
列表,并将完整的文件名路径设置为添加到 statusStrip
以保存文档的 filenameLabel
。这是代码。
public void File_SaveAll_Action()
{
if (_tabbedPane.getTabCount() > 0)
{
int maxindex = _tabbedPane.getTabCount() - 1;
for (int i = 0; i <= maxindex; i++)
{
_tabbedPane.setSelectedIndex(i);
String filename = filenameLabel.getText();
int sel = _tabbedPane.getSelectedIndex();
JTextPane textPane = (JTextPane) (((JScrollPane) _tabbedPane.getComponentAt(sel)).getViewport()).getComponent(0);
if (filename.contains("\\")||filename.contains("/"))
{
File f = new File(filename);
if (f.exists())
{
try
{
DataOutputStream d = new DataOutputStream(new FileOutputStream(filename));
String line = textPane.getText();
d.writeBytes(line);
d.close();
String tabtext = _tabbedPane.getTitleAt(sel);
if (tabtext.contains("*")) {
tabtext = tabtext.replace("*", "");
_tabbedPane.setTitleAt(sel, tabtext);
setTitle("Tabbed Notepad in Java - [ " + tabtext + " ]");
_tabbedPane.setIconAt(sel, new ImageIcon(this.getClass().getResource("resources/save.png")));
}
}
catch (Exception ex)
{
System.out.println("File not found");
}
textPane.requestFocus();
}
}
}
}
}
4) 关闭
这个功能就是从 tabbedpane 中删除选定的选项卡。但在此之前,你必须显示一个对话框以保存修改后的更改。如果没有任何文档被修改,则删除该选项卡,否则显示保存对话框。下载源代码以查看 CloseAll
功能代码。这是代码。
public void File_Close_Action()
{
if (_tabbedPane.getTabCount() > 0)
{
int sel = _tabbedPane.getSelectedIndex();
String tabtext = _tabbedPane.getTitleAt(sel);
if (tabtext.contains("*"))
{
int n = JOptionPane.showConfirmDialog(null, "Do you want to save " + tabtext + " before close ?",
"Save or Not", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
tabtext.replace("*", "");
if (n == 0)
{
String filename = filenameLabel.getText();
JTextPane textPane = (JTextPane) (((JScrollPane) _tabbedPane.getComponentAt(sel)).getViewport()).getComponent(0);
if (filename.contains("\\")||filename.contains("/"))
{
File_Save_Action();
_tabbedPane.remove(sel);
listModel.removeAllElements();
//adding all elements to list after removing the tab
for (int i = 0; i < _tabbedPane.getTabCount(); i++)
{
String item = _tabbedPane.getTitleAt(i);
if (item.contains("*"))
{
item = item.replace("*", "").trim();
}
listModel.addElement(item);
}
_list.setSelectedIndex(_tabbedPane.getTabCount()-1);
rowLabel.setText("Row :");
colLabel.setText("Col :");
if(_tabbedPane.getTabCount()==0)
{
setTitle("Tabbed Notepad in Java");
filenameLabel.setText("");
rowLabel.setText("Row :");
colLabel.setText("Col :");
}
}
else if (filename.contains("Document "))
{
File_SaveAs_Action();
_tabbedPane.remove(sel);
listModel.removeAllElements();
//adding all elements to list after removing the tab
for (int i = 0; i < _tabbedPane.getTabCount(); i++)
{
String item = _tabbedPane.getTitleAt(i);
if (item.contains("*"))
{
item = item.replace("*", "").trim();
}
listModel.addElement(item);
}
_list.setSelectedIndex(_tabbedPane.getTabCount() - 1);
rowLabel.setText("Row :");
colLabel.setText("Col :");
if (_tabbedPane.getTabCount() == 0)
{
setTitle("Tabbed Notepad in Java");
filenameLabel.setText("");
rowLabel.setText("Row :");
colLabel.setText("Col :");
}
}
}
if (n == 1)
{
_tabbedPane.remove(sel);
listModel.removeAllElements();
//adding all elements to list after removing the tab
for (int i = 0; i < _tabbedPane.getTabCount(); i++)
{
String item = _tabbedPane.getTitleAt(i);
if (item.contains("*"))
{
item = item.replace("*", "").trim();
}
listModel.addElement(item);
}
_list.setSelectedIndex(_tabbedPane.getTabCount() - 1);
rowLabel.setText("Row :");
colLabel.setText("Col :");
if (_tabbedPane.getTabCount() == 0)
{
setTitle("Tabbed Notepad in Java");
filenameLabel.setText("");
rowLabel.setText("Row :");
colLabel.setText("Col :");
}
}
}
else
{
_tabbedPane.remove(sel);
listModel.removeAllElements();
//adding all elements to list after removing the tab
for (int i = 0; i < _tabbedPane.getTabCount(); i++)
{
String item = _tabbedPane.getTitleAt(i);
if (item.contains("*"))
{
item = item.replace("*", "").trim();
}
listModel.addElement(item);
}
_list.setSelectedIndex(_tabbedPane.getTabCount() - 1);
rowLabel.setText("Row :");
colLabel.setText("Col :");
if (_tabbedPane.getTabCount() == 0)
{
setTitle("Tabbed Notepad in Java");
filenameLabel.setText("");
rowLabel.setText("Row :");
colLabel.setText("Col :");
}
}
}
else
{
setTitle("Tabbed Notepad in Java");
filenameLabel.setText("");
rowLabel.setText("Row :");
colLabel.setText("Col :");
}
}
在 Java 中实现剪切、复制、粘贴、撤销、重做等编辑操作很容易。
Java 不提供预先创建的字体对话框。你必须创建自己的字体对话框。这是我创建的字体对话框代码,它将字体设置为 tabbedpane 中当前选定选项卡中的当前 textpane。
这是代码
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import javax.swing.*;
import javax.swing.event.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public final class FontAction extends JDialog implements ListSelectionListener,ActionListener
{
String[] fontNames=GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
String[] fontStyles={
" Plain ",
" Bold ",
" Italic ",
" Plain+Bold ",
" Plain+Italic ",
" Bold+Italic ",
" Plain+Bold+Italic "
};
List lst=new List();
JList fontsList;
JList fontStyleList;
JList fontSizeList;
JPanel jp1,jp2;
DefaultListModel model;
JLabel displayLabel;
JButton ok,cancel;
JTextPane textPane;
static boolean isDarkTheme = false;
public FontAction(JTextPane tx)
{
textPane=tx;
Container cp=getContentPane();
isDarkTheme = getNodeTextContent("lookAndFeel").equals("GlobalDark");
fontsList=new JList(fontNames);
fontStyleList=new JList(fontStyles);
fontsList.setFont(new Font("Calibri",Font.PLAIN,14));
fontStyleList.setFont(new Font("Calibri",Font.PLAIN,14));
model=new DefaultListModel();
fontSizeList = new JList(model);
fontSizeList.setFont(new Font("Calibri", Font.PLAIN, 14));
for(int i=1;i<=160;i++)
{
model.addElement(" "+i+" ");
}
fontsList.setSelectedIndex(8);
fontStyleList.setSelectedIndex(0);
fontSizeList.setSelectedIndex(21);
fontsList.addListSelectionListener(this);
fontStyleList.addListSelectionListener(this);
fontSizeList.addListSelectionListener(this);
jp1=new JPanel();
jp2=new JPanel();
JPanel jp3=new JPanel();
jp3.add(new JScrollPane(fontsList));
JPanel jp4=new JPanel();
jp4.setLayout(new GridLayout(0,2));
jp4.add(new JScrollPane(fontStyleList));
jp4.add(new JScrollPane(fontSizeList));
jp1.add(jp3,BorderLayout.WEST);
jp1.add(jp4,BorderLayout.EAST);
displayLabel=new JLabel("Java Programming",JLabel.CENTER);
displayLabel.setFont(new Font("Arial",Font.PLAIN,21));
jp1.add(displayLabel);
ok=new JButton(" OK ");
cancel=new JButton(" Cancel ");
if (isDarkTheme) {
fontsList.setBackground(new Color(40, 40, 40));
fontStyleList.setBackground(new Color(40, 40, 40));
fontSizeList.setBackground(new Color(40, 40, 40));
displayLabel.setForeground(new Color(240, 240, 240));
}
ok.addActionListener(this);
cancel.addActionListener(this);
jp2.add(ok);
jp2.add(cancel);
cp.add(jp1,BorderLayout.CENTER);
cp.add(jp2,BorderLayout.SOUTH);
}
@Override
public void valueChanged(ListSelectionEvent evt)
{
String fontname=fontsList.getSelectedValue().toString();
String fontstyle=fontStyleList.getSelectedValue().toString().trim();
int fontsize=Integer.parseInt(fontSizeList.getSelectedValue().toString().trim());
switch(fontstyle)
{
case "Plain":
displayLabel.setFont(new Font(fontname, Font.PLAIN, fontsize));
break;
case "Bold":
displayLabel.setFont(new Font(fontname, Font.BOLD, fontsize));
break;
case "Italic":
displayLabel.setFont(new Font(fontname, Font.ITALIC, fontsize));
break;
case "Plain+Bold":
displayLabel.setFont(new Font(fontname, Font.PLAIN + Font.BOLD, fontsize));
break;
case "Plain+Italic":
displayLabel.setFont(new Font(fontname, Font.PLAIN + Font.ITALIC, fontsize));
break;
case "Bold+Italic":
displayLabel.setFont(new Font(fontname, Font.BOLD + Font.ITALIC, fontsize));
break;
case "Plain+Bold+Italic":
displayLabel.setFont(new Font(fontname, Font.PLAIN + Font.BOLD + Font.ITALIC, fontsize));
break;
}
}
@Override
public void actionPerformed(ActionEvent evt)
{
Object source=evt.getSource();
if(source==ok)
{
String fontname = fontsList.getSelectedValue().toString();
String fontstyle = fontStyleList.getSelectedValue().toString().trim();
int fontsize = Integer.parseInt(fontSizeList.getSelectedValue().toString().trim());
switch (fontstyle)
{
case "Plain":
textPane.setFont(new Font(fontname, Font.PLAIN, fontsize));
break;
case "Bold":
textPane.setFont(new Font(fontname, Font.BOLD, fontsize));
break;
case "Italic":
textPane.setFont(new Font(fontname, Font.ITALIC, fontsize));
break;
case "Plain+Bold":
textPane.setFont(new Font(fontname, Font.PLAIN + Font.BOLD, fontsize));
break;
case "Plain+Italic":
textPane.setFont(new Font(fontname, Font.PLAIN + Font.ITALIC, fontsize));
break;
case "Bold+Italic":
textPane.setFont(new Font(fontname, Font.BOLD + Font.ITALIC, fontsize));
break;
case "Plain+Bold+Italic":
textPane.setFont(new Font(fontname, Font.PLAIN + Font.BOLD + Font.ITALIC, fontsize));
break;
}
this.dispose();
}
else if(source==cancel)
{
this.dispose();
}
}
//**************************************************
// returns content of node
//**************************************************
public String getNodeTextContent(String nodetag) {
String content = "";
try {
File fXmlFile = new File("files/viewsfile.xml");
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(fXmlFile);
doc.getDocumentElement().normalize();
NodeList nList = doc.getElementsByTagName("views");
for (int temp = 0; temp < nList.getLength(); temp++) {
Node nNode = nList.item(temp);
Element eElement = (Element) nNode;
content = eElement.getElementsByTagName(nodetag).item(0).getTextContent();
}
} catch (ParserConfigurationException | SAXException | IOException | DOMException e) {}
return content;
}
}
通过单击文档选择器选择选项卡
正如你所知,每个高级编辑器都有这个功能,你可以单击文档名称,然后该文档选项卡的名称将被激活,就像 Visual Studio 中的 Solution Explorer 一样。这个功能很容易实现,只需创建一个实现 ListSelectionListener
接口的类,并定义 valueChanged()
函数。然后将 addListSelectionListener()
添加到文档选择器列表,并使用已实现的 ListSelectionListener
类对象。从 tabbedpane 获取每个选项卡的标题,将该值与列表中选定的项目进行比较,然后将该选项卡设置为选定。这是代码
class SelectTabFromListItem implements ListSelectionListener
{
public void valueChanged(ListSelectionEvent evt)
{
if(_list.getSelectedValue()!=null)
{
String list_item=_list.getSelectedValue().toString().trim();
if(_tabbedPane.getTabCount() >0)
{
int tabcount=_tabbedPane.getTabCount();
for(int i=0;i<tabcount;i++)
{
String title=_tabbedPane.getTitleAt(i).trim();
if (title.contains("*"))
{
title = title.replace("*", "").trim();
}
if(title.equals(list_item))
{
_tabbedPane.setSelectedIndex(i);
setTitle("Tabbed Notepad in Java -
[ "+_tabbedPane.getTitleAt(_tabbedPane.getSelectedIndex())+" ]");
}
}
}
}
}
}
更改框架标题、选项卡更改时的文件名标签文本
这个功能很容易实现,只需创建一个实现 ChangeListener
接口的类,并定义 stateChanged()
函数。然后将 addChangeListener()
添加到 tabbedpane
,并使用已实现的 ChangeListener
类对象。这是代码
class TabChanged implements ChangeListener
{
public void stateChanged(ChangeEvent evt)
{
if(_tabbedPane.getTabCount()>0)
{
Object[] files=filesHoldListModel.toArray();
String tabtext=_tabbedPane.getTitleAt(_tabbedPane.getSelectedIndex()).trim();
if(tabtext.contains("*"))
{
tabtext=tabtext.replace("*", "");
}
for(Object filename : files)
{
String file=filename.toString().substring
(filename.toString().lastIndexOf("\\")+1);
if(file.equals(tabtext))
{
filenameLabel.setText(filename.toString());
setTitle("Tabbed Notepad in Java -
[ "+_tabbedPane.getTitleAt(_tabbedPane.getSelectedIndex())+" ]");
}
}
if(tabtext.contains("Document "))
{
filenameLabel.setText(tabtext);
setTitle("Tabbed Notepad in Java -
[ "+_tabbedPane.getTitleAt(_tabbedPane.getSelectedIndex())+" ]");
}
}
}
}
要设置深色主题,我们将 setDefaultLookAndFeelDecorated()
设置为 true
,并将调用方法 MetalLookAndFeel.setCurrentTheme()
。我们将使用 javax.swing.plaf.ColorUIResource
类。有关 ColorUIResource
的更多信息,请参阅以下链接。
http://docs.oracle.com/javase/7/docs/api/javax/swing/plaf/class-use/ColorUIResource.html
我创建了一个名为 JavaGlobalDarkTheme
的类,在其中定义了所有这些方法,并在 setGlobalDarkLookAndFeel()
方法中调用它们。
public static void setGlobalDarkLookAndFeel()
{
MetalLookAndFeel.setCurrentTheme(new JavaGlobalDarkTheme().darkTheme);
try {
UIManager.setLookAndFeel(new MetalLookAndFeel());
} catch (Exception ev) {
}
JFrame.setDefaultLookAndFeelDecorated(true);
TabbedNotepad tb = new TabbedNotepad();
tb.setExtendedState(JFrame.MAXIMIZED_BOTH);
BufferedImage image = null;
try {
image = ImageIO.read(tb.getClass().getResource("resources/myicon.png"));
} catch (IOException e) {
}
tb.setIconImage(image);
tb.setSize(800, 600);
tb.setLocation(100, 50);
tb.setVisible(true);
}
}