使用EditPlus
、Eclipse
等工具时会发现,当在这些工具中输入代码时,如果输入的单词是程序关键字、类名等,则这些关键字将会自动变色。使用JTextPane组件,就可以开发出这种带有语法高亮的编辑器 JTextPane
使用StyledDocument
作为它的model
对象,而StyleDocument允许对文档的不同段落分别设置不同的颜色、字体属性 。
Document
使用Element
来表示文档中的组成部分,Element
可以表示章(chapter
)、段落(paragraph
)等. 在普通文档中,Element
也可以表示一行。
AttributeSet接口 为了设置StyledDocument
中文字的字体、颜色,Swing
提供了AttributeSet
接口来表示文档字体、颜色等属性 。
Swing
为StyledDocument
提供了DefaultstyledDocument
实现类,该实现类就是JTextPane
的model
实现类;为AttributeSet
接口提供了MutableAttributeSet
子接口,并为该接口提供了SimpleAttributeSet实现类 ,程序通过这些接口和实现类就可以很好地控制JTextPane
中文字的字体和颜色。
StyledDocument StyledDocument
提供了如下一个方法来设置文档中局部文字的字体、颜色
方法
描述
void setParagraphAttributes(int offset, int length, AttributeSet s, boolean replace)
设置文档中从offset
开始,长度为length
处的文字使用s属性(控制字体、颜色等),最后一个参数控制新属性是替换原有属性,还是将新属性累加到原有属性上。
StyleConstants工具类 AttributeSet
的常用实现类是MutableAttributeSet
,为了给MutableAttributeSet
对象设置字体、颜色等属性,Swing
提供了StyleConstants
工具类,该工具类里大致包含了如下常用的静态方法来设置MutableAttributeSet
里的字体、颜色等。
方法
描述
static void setAlignment(MutableAttributeSet a, int align)
设置文本对齐方式。
static void setBackground(MutableAttributeSet a, Color fg)
设置背景色
static void setBold(MutableAttributeSet a, boolean b)
设置是否使用粗体字
static void setFirstLineIndent(MutableAttributeSet a, float i)
设置首行缩进的大小。
static void setFontFamily(MutableAttributeSet a, String fam)
设置字体。
static void setFontSize(MutableAttributeSet a, int s)
设置字体大小。
static void setForeground(MutableAttributeSet a, Color fg)
设置字体前景色。
static void setItalic(MutableAttributeSet a, boolean b)
设置是否采用斜体字。
static void setLeftIndent(MutableAttributeSet a, float i)
设置左边缩进大小。
static void setLineSpacing(MutableAttributeSet a, float i)
设置行间距。
static void setRightIndent(MutableAttributeSet a, float i)
设置右边缩进大小。
static void setStrikeThrough(MutableAttributeSet a, boolean b)
设置是否为文字添加删除线。
static void setSubscript(MutableAttributeSet a, boolean b)
设置将指定文字设置成下标。
static void setSuperscript(MutableAttributeSet a, boolean b)
设置将指定文字设置成上标。
static void setUnderline(MutableAttributeSet a, boolean b)
设置是否为文字添加下画线。
上面这些方法用于控制文档中文字的外观样式 ,如果读者对这些外观样式不是太熟悉,则可以参考Word
里设置“字体”属性的设置效果。 图12.60显示了Document
及其相关实现类,以及相关辅助类的类关系图。
程序 文本编辑器设置 字体 颜色 下面程序简单地定义了三个SimpleAttributeSet
对象,并为这三个对象设置了对应的文字、颜色、字体等属性,并使用三个SimpleAttributeSet
对象设置文档中三段文字的外观。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import java.awt.*;import javax.swing.*;import javax.swing.text.*;public class JTextPaneTest { JFrame mainWin = new JFrame ("测试JTextPane" ); JTextPane txt = new JTextPane (); StyledDocument doc = txt.getStyledDocument(); SimpleAttributeSet android = new SimpleAttributeSet (); SimpleAttributeSet java = new SimpleAttributeSet (); SimpleAttributeSet javaee = new SimpleAttributeSet (); public void init () { StyleConstants.setForeground(android, Color.RED); StyleConstants.setFontSize(android, 24 ); StyleConstants.setFontFamily(android, "Dialog" ); StyleConstants.setUnderline(android, true ); StyleConstants.setForeground(java, Color.BLUE); StyleConstants.setFontSize(java, 30 ); StyleConstants.setFontFamily(java, "Arial Black" ); StyleConstants.setBold(java, true ); StyleConstants.setForeground(javaee, Color.GREEN); StyleConstants.setFontSize(javaee, 32 ); StyleConstants.setItalic(javaee, true ); txt.setEditable(false ); txt.setText("疯狂Android讲义\n" + "疯狂Java讲义\n" + "轻量级Java EE企业应用实战\n" ); doc.setCharacterAttributes(0 , 12 , android, true ); doc.setCharacterAttributes(12 , 12 , java, true ); doc.setCharacterAttributes(24 , 30 , javaee, true ); mainWin.add(new JScrollPane (txt), BorderLayout.CENTER); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); int inset = 100 ; mainWin.setBounds(inset, inset, screenSize.width - inset * 2 , screenSize.height - inset * 2 ); mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWin.setVisible(true ); } public static void main (String[] args) { new JTextPaneTest ().init(); } }
上面程序其实很简单,程序中的第一段粗体字代码为三个SimpleAttributeSet
对象设置了字体、字体大小、颜色等外观样式,第二段粗体字代码使用前面的三个SimpleAttributeSet
对象来控制文档中三段文字的外观样式。运行上面程序,将看到如图12.61所示的界面。
从图12.61中可以看出,窗口中文字具有丰富的外观,而且还可以选中这些文字,表明它们依然是文字,而不是直接绘制上去的图形。 如果希望开发出类似于Editplus
、Eclipse
等的代码编辑窗口,程序可以扩展JTextPane
的子类,为该对象添加按键监听器和文档监听器。当文档内容被修改时,或者用户在该文档内进行击键动作时,程序负责分析该文档的内容,对特殊关键字设置字体颜色。
为了保证具有较好的性能,程序并不总是分析文档中的所有内容,而是只分析文档中被改变的部分,这个要求看似简单,只为文档添加文档监听器即可:当文档内容改变时分析被改变部分,并设置其中关键字的颜色。但问题是:Documentlistener
监听器里的三个方法不能改变文档本身,所以程序还是必须通过监听按键事件来启动语法分析,DocumentListener
监听器中仅仅记录文档改变部分的位置和长度。
除此之外,程序还提供了一个SyntaxFormatter
类根据语法文件来设置文档中的文字颜色。
程序 带语法高亮的文本编辑器 单词着色 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 import java.util.*;import java.io.*;import java.awt.*;import java.awt.event.*;import javax.swing.*;import javax.swing.event.*;import javax.swing.text.*;public class MyTextPane extends JTextPane { private static final long serialVersionUID = 7177281290506716762L ; protected StyledDocument doc; protected SyntaxFormatter formatter = new SyntaxFormatter ("my.stx" ); private SimpleAttributeSet normalAttr = formatter.getNormalAttributeSet(); private SimpleAttributeSet quotAttr = new SimpleAttributeSet (); private int docChangeStart = 0 ; private int docChangeLength = 0 ; public MyTextPane () { StyleConstants.setForeground(quotAttr, new Color (255 , 0 , 255 )); StyleConstants.setFontSize(quotAttr, 16 ); this .doc = super .getStyledDocument(); this .setMargin(new Insets (3 , 40 , 0 , 0 )); this .addKeyListener(new KeyAdapter () { public void keyReleased (KeyEvent ke) { syntaxParse(); } }); doc.addDocumentListener(new DocumentListener () { public void changedUpdate (DocumentEvent e) { } public void insertUpdate (DocumentEvent e) { docChangeStart = e.getOffset(); docChangeLength = e.getLength(); } public void removeUpdate (DocumentEvent e) { } }); } public void syntaxParse () { try { Element root = doc.getDefaultRootElement(); int cursorPos = this .getCaretPosition(); int line = root.getElementIndex(cursorPos); Element para = root.getElement(line); int start = para.getStartOffset(); start = start > docChangeStart ? docChangeStart : start; int length = para.getEndOffset() - start; length = length < docChangeLength ? docChangeLength + 1 : length; String s = doc.getText(start, length); String[] tokens = s.split("\\s+|\\.|\\(|\\)|\\{|\\}|\\[|\\]" ); int curStart = 0 ; boolean isQuot = false ; for (String token : tokens) { int tokenPos = s.indexOf(token, curStart); if (isQuot && (token.endsWith("\"" ) || token.endsWith("\'" ))) { doc.setCharacterAttributes(start + tokenPos, token.length(), quotAttr, false ); isQuot = false ; } else if (isQuot && !(token.endsWith("\"" ) || token.endsWith("\'" ))) { doc.setCharacterAttributes(start + tokenPos, token.length(), quotAttr, false ); } else if ((token.startsWith("\"" ) || token.startsWith("\'" )) && (token.endsWith("\"" ) || token.endsWith("\'" ))) { doc.setCharacterAttributes(start + tokenPos, token.length(), quotAttr, false ); } else if ((token.startsWith("\"" ) || token.startsWith("\'" )) && !(token.endsWith("\"" ) || token.endsWith("\'" ))) { doc.setCharacterAttributes(start + tokenPos, token.length(), quotAttr, false ); isQuot = true ; } else { formatter.setHighLight(doc, token, start + tokenPos, token.length()); } curStart = tokenPos + token.length(); } } catch (Exception ex) { ex.printStackTrace(); } } public void paint (Graphics g) { super .paint(g); Element root = doc.getDefaultRootElement(); int line = root.getElementIndex(doc.getLength()); g.setColor(new Color (230 , 230 , 230 )); g.fillRect(0 , 0 , this .getMargin().left - 10 , getSize().height); g.setColor(new Color (40 , 40 , 40 )); for (int count = 0 , j = 1 ; count <= line; count++, j++) { g.drawString(String.valueOf(j), 3 , (int ) ((count + 1 ) * 1.535 * StyleConstants.getFontSize(normalAttr))); } } public static void main (String[] args) { JFrame frame = new JFrame ("文本编辑器" ); frame.getContentPane().add(new JScrollPane (new MyTextPane ())); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final int inset = 50 ; Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); frame.setBounds(inset, inset, screenSize.width - inset * 2 , screenSize.height - inset * 2 ); frame.setVisible(true ); } } class SyntaxFormatter { private Map<SimpleAttributeSet, ArrayList<String>> attMap = new HashMap <>(); SimpleAttributeSet normalAttr = new SimpleAttributeSet (); public SyntaxFormatter (String syntaxFile) { StyleConstants.setForeground(normalAttr, Color.BLACK); StyleConstants.setFontSize(normalAttr, 16 ); Scanner scaner = null ; try { scaner = new Scanner (new File (syntaxFile)); } catch (FileNotFoundException e) { throw new RuntimeException ("丢失语法文件:" + e.getMessage()); } int color = -1 ; ArrayList<String> keywords = new ArrayList <>(); while (scaner.hasNextLine()) { String line = scaner.nextLine(); if (line.startsWith("#" )) { if (keywords.size() > 0 && color > -1 ) { SimpleAttributeSet att = new SimpleAttributeSet (); StyleConstants.setForeground(att, new Color (color)); StyleConstants.setFontSize(att, 16 ); attMap.put(att, keywords); } keywords = new ArrayList <>(); color = Integer.parseInt(line.substring(1 ), 16 ); } else { if (line.trim().length() > 0 ) { keywords.add(line.trim()); } } } if (keywords.size() > 0 && color > -1 ) { SimpleAttributeSet att = new SimpleAttributeSet (); StyleConstants.setForeground(att, new Color (color)); StyleConstants.setFontSize(att, 16 ); attMap.put(att, keywords); } } public SimpleAttributeSet getNormalAttributeSet () { return normalAttr; } public void setHighLight (StyledDocument doc, String token, int start, int length) { SimpleAttributeSet currentAttributeSet = null ; outer: for (SimpleAttributeSet att : attMap.keySet()) { ArrayList<String> keywords = attMap.get(att); for (String keyword : keywords) { if (keyword.equals(token)) { currentAttributeSet = att; break outer; } } } if (currentAttributeSet != null ) { doc.setCharacterAttributes(start, length, currentAttributeSet, false ); } else { doc.setCharacterAttributes(start, length, normalAttr, false ); } } }
配置文件 展开/折叠
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #FF0000 System Class String Integer Object #0000FF abstract assert boolean break byte case catch char class const continue default do double else enum extends false final finally float for goto if import implements int interface instanceof long native new null package private protected public return short static strictfp super switch synchronized this throw throws transient try true void volatile while
上面程序中的粗体字代码负责分析当前单词与哪种颜色关键字匹配,并为这段文字设置字体颜色。其实这段程序为文档中的单词设置颜色并不难,难点在于找出每个单词与哪种关键字匹配,并要标识出该单词在文档中的位置,然后才可以为该单词设置颜色。 运行上面程序,会看到如图12.62所示的带语法高亮的文本编辑器
上面程序已经完成了对不同类型的单词进行着色,所以会看到如图12.62示的运行界面。如果进行改进,则可以为上面的编辑器增加括号配对、代码折叠等功能,这些都可以通过JTextPane
组件来完成对于此文本编辑器,只要传入不同的语法文件,程序就可以为不同的源代码显示语法高亮