最近在一个Swing项目中,需要用到超级链接(文字或图片超链),直接使用JLabel中HTML显示功能无法实现,因为JLabel可以显示超级链接,但是无法相应超级链接上的鼠标动作。以前用过JEditorPane和JTextPane,十分庞大,在这个项目中不适合。在网上google了一下,找到一个大牛的文章:http://blog.palantirtech.com/2007/07/13/stupid-fast-hyperlinks-for-swing/ 。很不错,实现了在JLabel上显示超级链接,不过,非常简单,只能显示一个超级链接,而且不能显示图片链接。同时,他的实现使得原来JLabel可以通过html中的<br>标签来实现文字换行的功能也随之失效(已经完全不支持html文本显示了)。因此我重新写了一个HyperLinkLabel。也不是很完善,但是已经够用了,不但JLabel 之setIcon()正常使用,而且,可以不用HTML来实现文本换行显示,可以显示任意文字、文字链接、图片链接、图片、文字字体等;以及所有这些元素的任意混合、可以超级链接点击响应等
一共写了四个类,一个是Jabel的继承;一个是BasicLabelUI的继承(关键类);还有两个是用来存储传给label对象的文字解析出来的各种元素,如链接、图片等。
- HyperlinkLabel.java
- package com.palantir.ui;
- import java.awt.BorderLayout;
- import java.awt.Color;
- import java.awt.Cursor;
- import java.awt.Image;
- import java.awt.Rectangle;
- import java.awt.Toolkit;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.awt.event.MouseEvent;
- import java.awt.event.MouseListener;
- import java.awt.event.MouseMotionListener;
- import java.util.ArrayList;
- import javax.swing.ImageIcon;
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.JPanel;
- import javax.swing.SwingConstants;
- import javax.swing.Timer;
- public class HyperlinkLabel extends JLabel implements MouseListener, MouseMotionListener{
- public static final Color DEFAULT_LINK_COLOR = Color.BLUE;
- public static final String LINK_CLICKED = "HyperlinkLabel.LinkClicked";
- private String actionCommand = LINK_CLICKED;
- private Color linkColor=DEFAULT_LINK_COLOR;
- private boolean isPaintingLink = false;
- public String label=null;
- private ArrayList<Paragraph> paragraphs=null;
- private HyperLink selectedlink=null;
- public HyperlinkLabel(){
- addMouseListener(this);
- addMouseMotionListener(this);
- setUI(HyperlinkLabelUI.instance);
- }
- private void init(){
- isPaintingLink = false;
- label=null;
- if(paragraphs!=null)
- paragraphs.clear();
- paragraphs=null;
- selectedlink=null;
- }
- public void setLinkColor(final Color linkColor) {
- final Color plinkColor = this.linkColor;
- this.linkColor = linkColor;
- firePropertyChange( "link-color", plinkColor, linkColor );
- repaint();
- }
- public Color getLinkColor() {
- return linkColor;
- }
- @Override
- public void setText(String label){
- init();
- this.label=label;
- }
- @Override
- public String getText(){
- return this.label;
- }
- public ArrayList<Paragraph> getParagraph(){
- return this.paragraphs;
- }
- public void setParagraph(ArrayList<Paragraph> paragraphs){
- this.paragraphs=paragraphs;
- }
- public HyperLink getSelectedLink(){
- return selectedlink;
- }
- public void rePaintLink(){
- if(selectedlink==null)
- return;
- }
- /**
- * Let HyperlinkLabelUI assign the text position so mouse over can be
- * computed properly.
- * @param x
- * @param y
- * @param width ignored (set to 0) as it isn't referenced in isOnLink
- * @param height
- */
- private HyperLink isOnLink(MouseEvent mev){
- if(paragraphs==null)
- return null;
- for(int i=0;i<paragraphs.size();i++){
- if(paragraphs.get(i).hyperlinks!=null){
- for(int j=0;j<paragraphs.get(i).hyperlinks.size();j++){
- HyperLink link=paragraphs.get(i).hyperlinks.get(j);
- if(link.linkRect.contains(mev.getPoint()))
- return link;
- }
- }
- }
- return null;
- }
- //TODO: override paintComponent to change link color on mouseover?
- public void mousePressed(MouseEvent mev) { }
- public void mouseReleased(MouseEvent mev) { }
- public void mouseClicked(MouseEvent mev) {
- if( isEnabled() && (selectedlink=isOnLink(mev))!=null && mev.getButton() == MouseEvent.BUTTON1) {
- fireActionPerformed(new ActionEvent(this, 0, actionCommand));
- }
- }
- public void mouseEntered(MouseEvent mev) {
- resetCursor(mev);
- }
- public void mouseExited(MouseEvent mev) {
- setCursor(Cursor.getDefaultCursor());
- }
- public void mouseMoved(MouseEvent mev) {
- resetCursor(mev);
- }
- public void mouseDragged(MouseEvent mev) {
- resetCursor(mev);
- }
- private void resetCursor(MouseEvent mev) {
- boolean onLink = (selectedlink=isOnLink(mev))!=null;
- boolean isDefaultCursor = getCursor().equals(Cursor.getDefaultCursor());
- // not sure if checking whether it's currently the default cursor is cheaper
- // than setting it each time, but I know setCursor doesn't short-circuit if unchanged.
- if( isEnabled() && onLink && isDefaultCursor) {
- setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
- }
- else if(!onLink && !isDefaultCursor) {
- setCursor(Cursor.getDefaultCursor());
- }
- }
- public void setActionCommand(String cmd) {
- actionCommand = cmd;
- }
- public void addActionListener(ActionListener al) {
- listenerList.add(ActionListener.class, al);
- }
- public void removeActionListener(ActionListener al) {
- listenerList.remove(ActionListener.class, al);
- }
- public void fireActionPerformed(ActionEvent aev){
- for(Object al : listenerList.getListeners(ActionListener.class)) {
- ((ActionListener) al).actionPerformed(aev);
- }
- }
- /**
- * Lie about foreground color so BasicLabelUI.paintEnabledText paints
- * the link text in the link color
- */
- @Override
- public Color getForeground() {
- if(isPaintingLink())
- return linkColor;
- else
- return super.getForeground();
- }
- public boolean isPaintingLink() {
- return isPaintingLink;
- }
- public void setPaintingLink(boolean isPaintingLink) {
- this.isPaintingLink = isPaintingLink;
- }
- public static void main(String[] args) {
- JFrame f = new JFrame("HyperlinkLabel Demo");
- f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
- f.setSize(500,333);
- f.setLocation(200,200);
- final JPanel contentPanel = new JPanel(new BorderLayout());
- final HyperlinkLabel lbl = new HyperlinkLabel();
- lbl.setIconTextGap(5);
- lbl.setVerticalTextPosition(SwingConstants.TOP);
- lbl.setBounds(new Rectangle(lbl.getWidth(),lbl.getHeight()));
- lbl.setText("游戏名称:xxxxxxxxxxx/n<font size=12 color=red style=bold>游戏类型:</font><a src=1>yyyyyyyy</a><img src=/icon.png /><img src=/icon.png width=12 height=12/><img src=/game1313getor/icon.png width=12 height=12/>/n发布时间:xxxxx/n试玩次数:<a src=2><img src=/game1313getor/icon.png/ width=20 height=20/></a><a src=2><img src=/icon.png/ width=20 height=20/></a><a src=2><img src=/game1313getor/icon.png/ width=20 height=20/></a>/n下载次数:0");
- lbl.setBorder(javax.swing.BorderFactory.createEtchedBorder() );
- Image logoImag = Toolkit.getDefaultToolkit().getImage(lbl.getClass().getResource("/demo1.gif"));
- ImageIcon logo=new ImageIcon(logoImag.getScaledInstance(128,128,Image.SCALE_DEFAULT));
- logoImag=null;
- lbl.setIcon(logo);
- JLabel logoLabel = new JLabel(logo);
- logoLabel.setBackground(Color.WHITE);
- logoLabel.setOpaque(true);
- contentPanel.add(logoLabel, BorderLayout.NORTH);
- final JLabel picLbl = new JLabel(logo);
- final Timer swapTimer = new Timer(2000, new ActionListener() {
- public void actionPerformed(ActionEvent aev) {
- contentPanel.remove(picLbl);
- contentPanel.add(lbl, BorderLayout.CENTER);
- contentPanel.validate();
- contentPanel.repaint();
- ((Timer) aev.getSource()).stop();
- }
- });
- lbl.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent aev) {
- HyperLink link=lbl.getSelectedLink();
- int linkID=0;
- if(link.targetUrl!=null)
- linkID=Integer.parseInt(link.targetUrl);
- if(linkID==2){
- contentPanel.remove(lbl);
- contentPanel.add(picLbl, BorderLayout.CENTER);
- contentPanel.validate();
- contentPanel.repaint();
- swapTimer.start();
- }
- }
- });
- contentPanel.add(lbl, BorderLayout.CENTER);
- f.setContentPane(contentPanel);
- f.setVisible(true);
- }
- }
- HyperlinkLabelUI.java
- package com.palantir.ui;
- import java.awt.Color;
- import java.awt.Font;
- import java.awt.FontMetrics;
- import java.awt.Graphics;
- import java.awt.Graphics2D;
- import java.awt.Image;
- import java.awt.Point;
- import java.awt.Rectangle;
- import java.awt.font.LineMetrics;
- import java.util.ArrayList;
- import java.util.StringTokenizer;
- import javax.swing.ImageIcon;
- import javax.swing.JLabel;
- import javax.swing.plaf.basic.BasicLabelUI;
- public class HyperlinkLabelUI extends BasicLabelUI {
- public static HyperlinkLabelUI instance = new HyperlinkLabelUI();
- @Override
- protected void paintEnabledText(JLabel l, Graphics g, String s, int textX, int textY){
- if(textX<126)
- textX=126;
- if(textY>18)
- textY=18;
- if(l instanceof HyperlinkLabel) {
- int accChar = l.getDisplayedMnemonic();
- drawString((HyperlinkLabel)l,g, ((HyperlinkLabel)l).getText(), accChar, textX, textY);
- }else {
- super.paintEnabledText(l, g, s, textX, textY);
- return;
- }
- }
- protected void paintUnderline(LineMetrics lm, Graphics g, Color c, int linkWidth, int textX, int textY){
- Graphics2D g2d = (Graphics2D) g;
- double baseline = textY + Math.ceil(lm.getUnderlineOffset());
- if(linkWidth > 0) {
- g.setColor(c);
- g2d.drawLine(textX, (int) baseline, textX + linkWidth, (int) baseline);
- }
- }
- protected void drawString(HyperlinkLabel l, Graphics g, String s, int accChar, int textX, int textY ){
- if(l.getParagraph()==null){
- //-----------------------get lines-------------------
- ArrayList<Paragraph> paragraphs=new ArrayList<Paragraph>(0);
- //System.out.println(l.getText());
- StringTokenizer tokenizer=new StringTokenizer(l.getText(),"/n");
- int count=0;
- int height = g.getFontMetrics().getHeight()+2;
- FontMetrics fm = l.getFontMetrics(l.getFont());
- while(tokenizer.hasMoreTokens()){
- Paragraph paragraph=new Paragraph();
- String token=tokenizer.nextToken();
- int lastElementWidth=0;
- int yPos=textY + (height*count);
- //------------------get all elements in a line--------------------
- while(token!=null&&token.length()>0){
- System.out.println("lastElementWidth="+lastElementWidth);
- int firstTextIndex=token.indexOf("<");
- if(firstTextIndex>0){//
- String txt=token.substring(0,firstTextIndex);
- token=token.substring(firstTextIndex);
- TextElement text=new TextElement(new Point(textX+lastElementWidth, yPos ),txt);
- paragraph.addText(text);
- lastElementWidth+=fm.stringWidth(txt);
- //continue;
- }else if(token.indexOf("<img")==0){//imgage element
- int imgEndIndex=token.indexOf("/>");
- imgEndIndex+="/>".length();
- String imgText=token.substring(0,imgEndIndex);
- token=token.substring(imgEndIndex);
- Image img=HyperLink.parseImage(imgText);
- ImageElement imgElement=new ImageElement(new Point(textX+lastElementWidth, yPos ),new ImageIcon(img));
- lastElementWidth=lastElementWidth+imgElement.img.getIconWidth();
- paragraph.addImage(imgElement);
- }else if(token.indexOf("<a")==0){//link element
- int linkEndIndex=token.indexOf("</a>");
- linkEndIndex+="</a>".length();
- String linkText=token.substring(0,linkEndIndex);
- token=token.substring(linkEndIndex);
- HyperLink link=HyperLink.parseLink(linkText, new Point(textX+lastElementWidth,yPos),g);
- if(link.linkText!=null)
- lastElementWidth=lastElementWidth+fm.stringWidth(link.linkText);
- else
- lastElementWidth=lastElementWidth+link.image.getIconWidth();
- paragraph.addHyperLink(link);
- //-------------------set the rect for every link-----------------------
- if(link.linkText!=null){
- int linkWidth = fm.stringWidth(link.linkText);
- LineMetrics lm=fm.getLineMetrics(link.linkText, g);
- Rectangle rec=new Rectangle();
- rec.x=link.startPoint.x;
- rec.y=link.startPoint.y- Math.round(lm.getHeight())+1;
- linkWidth = fm.stringWidth(link.linkText);
- rec.width=linkWidth;
- rec.height=Math.round(lm.getHeight())+2;
- link.setRectangle(rec);
- }else{
- LineMetrics lm = null;
- lm=fm.getLineMetrics("", g);
- Rectangle rec=new Rectangle();
- rec.x=link.startPoint.x;
- rec.y=link.startPoint.y- Math.round(lm.getHeight())+1;
- rec.width=link.image.getIconWidth();
- rec.height=Math.round(lm.getHeight())+2;
- link.setRectangle(rec);
- }
- }else if(token.indexOf("<font")==0){//font element
- Font labelFont=l.getFont();
- fm = l.getFontMetrics(labelFont);
- int index=token.indexOf(">");
- int fontEndIndex=token.indexOf("</font>");
- fontEndIndex+="</font>".length();
- String fontText=token.substring(0,fontEndIndex);
- token=token.substring(fontEndIndex);
- String txt=fontText.substring(fontText.indexOf(">")+1,fontText.indexOf("</font>"));
- String tagContent=fontText.substring("<font".length(),index);
- StringTokenizer fontTokenizer=new StringTokenizer(tagContent);
- String family=labelFont.getFamily();
- int size=0;
- int style=Font.PLAIN;
- Color color=g.getColor();
- while(fontTokenizer.hasMoreTokens()){
- String key=fontTokenizer.nextToken();
- if(key!=null){
- key=key.trim();
- String value=null;
- if(key.indexOf("=")>0){
- value=key.substring(key.indexOf("=")+1);
- value=value.trim();
- if(key.startsWith("color")){
- if(value.equals("red")){
- color=Color.RED;
- }
- }else if(key.startsWith("size")){
- size=Integer.parseInt(value);
- }else if(key.startsWith("style")){
- if(value.equals("bold")){
- style=Font.BOLD;
- }
- }else if(key.startsWith("family")){
- family=value;
- }
- }
- }
- }
- Font littleFont = new Font(family, style, size);
- FontMetrics fmm=l.getFontMetrics(littleFont);
- yPos=textY + (fm.getHeight()*(count-1));
- yPos+=fmm.getHeight();
- TextElement text=new TextElement(new Point(textX+lastElementWidth, yPos),txt);
- text.font=littleFont;
- text.color=color;
- paragraph.addText(text);
- lastElementWidth+=fmm.stringWidth(txt);
- //fm = l.getFontMetrics(labelFont);
- }else{//not any tag in the whole line
- TextElement text=new TextElement(new Point(textX+lastElementWidth, yPos),token);
- paragraph.addText(text);
- lastElementWidth+=fm.stringWidth(token);
- break;
- }
- }
- paragraphs.add(paragraph);
- count++;
- }
- l.setParagraph(paragraphs);
- }
- //draw the lines
- ArrayList<Paragraph> paragraphs=l.getParagraph();
- FontMetrics fm = l.getFontMetrics(l.getFont());
- Color linkColor = l.getLinkColor();
- Color clr=g.getColor();
- for(int i=0;i<paragraphs.size();i++){
- Paragraph p=paragraphs.get(i);
- if(p.hyperlinks!=null)
- for(int j=0;j<p.hyperlinks.size();j++){
- HyperLink link=p.hyperlinks.get(j);
- if(link!=null){
- if(link.linkText!=null){
- g.drawString( link.linkText, link.startPoint.x, link.startPoint.y );
- l.setPaintingLink(true);
- super.paintEnabledText(l, g, link.linkText,link.startPoint.x, link.startPoint.y);
- l.setPaintingLink(false);
- LineMetrics lm = fm.getLineMetrics(link.linkText, g);
- int linkWidth = fm.stringWidth(link.linkText);
- paintUnderline(lm, g, linkColor, linkWidth,link.startPoint.x, link.startPoint.y);
- g.setColor(clr);
- }else{
- LineMetrics lm = null;
- lm=fm.getLineMetrics("", g);
- g.drawImage( link.image.getImage(), link.startPoint.x, link.startPoint.y- Math.round(lm.getHeight())+1,null,null );
- l.setPaintingLink(true);
- super.paintEnabledText(l, g, link.linkText,link.startPoint.x, link.startPoint.y);
- l.setPaintingLink(false);
- g.setColor(clr);
- }
- }
- }
- if(p.imgs!=null){
- LineMetrics lm = null;
- lm=fm.getLineMetrics("", g);
- for(int j=0;j<p.imgs.size();j++){
- ImageElement img=p.imgs.get(j);
- g.drawImage( img.img.getImage(), img.startPoint.x, img.startPoint.y- Math.round(lm.getHeight())+1,null,null );
- }
- }
- if(p.texts!=null){
- for(int j=0;j<p.texts.size();j++){
- TextElement text=p.texts.get(j);
- if(text.font!=null){
- Font labelFont=l.getFont();
- g.setFont(text.font);
- if(text.color!=null){
- g.setColor(text.color);
- }
- g.drawString( p.texts.get(j).text, p.texts.get(j).startPoint.x, p.texts.get(j).startPoint.y );
- g.setColor(clr);
- g.setFont(labelFont);
- }else{
- g.drawString( p.texts.get(j).text, p.texts.get(j).startPoint.x, p.texts.get(j).startPoint.y );
- }
- }
- }
- }
- }
- }
- HyperLink.java
- /*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
- package com.palantir.ui;
- import java.awt.Graphics;
- import java.awt.Image;
- import java.awt.Point;
- import java.awt.Rectangle;
- import java.awt.Toolkit;
- import java.util.HashMap;
- import java.util.StringTokenizer;
- import javax.swing.ImageIcon;
- /**
- * @author Sainese
- */
- //<a>text</a>
- //<a><img src=xxx width=aa height=bb/></a>
- //No regrex so , so no spaces in tags.
- public class HyperLink {
- public Point startPoint;
- public Rectangle linkRect;
- public String linkText;
- public ImageIcon image;
- public String targetUrl;
- private static HashMap map=new HashMap();
- public HyperLink(Point startPoint,String linkText,String targetUrl){
- this.startPoint=startPoint;
- this.linkText=linkText;
- this.targetUrl=targetUrl;
- }
- public HyperLink(Point startPoint,Image image,String targetUrl){
- this.startPoint=startPoint;
- this.image=new ImageIcon(image);
- this.targetUrl=targetUrl;
- }
- public void setRectangle(Rectangle rec){
- this.linkRect=rec;
- }
- public static HyperLink parseLink(String originString,Point startPoint,Graphics g){
- HyperLink link=null;
- String targetUrl=null;
- if(originString.startsWith("<a")&&originString.endsWith("</a>")){
- int EmbedObjectStartIndex=originString.indexOf(">");
- int srcStartIndex=originString.indexOf("src=");
- if(srcStartIndex>0){
- srcStartIndex+=4;
- targetUrl=originString.substring(srcStartIndex,EmbedObjectStartIndex);
- }
- int endIndex=originString.indexOf("</a>");
- EmbedObjectStartIndex+=1;
- String embeddedString=originString.substring(EmbedObjectStartIndex,endIndex);
- //System.out.println("embeddedString="+embeddedString);
- if(embeddedString.startsWith("<img")&&embeddedString.endsWith("/>")){
- link=new HyperLink(startPoint,parseImage(embeddedString),targetUrl);
- }else{
- link=new HyperLink(startPoint,embeddedString,targetUrl);
- }
- }
- return link;
- }
- public static Image parseImage(String originString){
- String src=null;
- int width=0;
- int height=0;
- int srcStartIndex="<img".length();
- originString=originString.substring(srcStartIndex,originString.indexOf("/>"));
- StringTokenizer tokenizer=new StringTokenizer(originString," ");
- while(tokenizer.hasMoreTokens()){
- String token=tokenizer.nextToken();
- if(token!=null){
- token=token.trim();
- if(token.startsWith("src")){
- src=token.substring(token.indexOf("=")+1);
- }else if(token.startsWith("width")){
- width=Integer.parseInt(token.substring(token.indexOf("=")+1));
- }else if(token.startsWith("height")){
- height=Integer.parseInt(token.substring(token.indexOf("=")+1));
- }
- }
- }
- src=src.trim();
- Object obj=map.get(src);
- Image img =null;
- if(obj!=null){
- img=(Image)obj;
- //System.out.println("image has been created before");
- }else{
- img = Toolkit.getDefaultToolkit().getImage(HyperLink.class.getResource(src));
- if(width==0){
- width=img.getWidth(null);
- height=img.getHeight(null);
- }
- img=img.getScaledInstance(width,height,Image.SCALE_DEFAULT);
- map.put(src, img);
- //System.out.println("create a brand new image");
- }
- return img;
- }
- }
- Paragraph.java
- /*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
- package com.palantir.ui;
- import java.util.ArrayList;
- /**
- *
- * @author Sainese
- */
- public class Paragraph{
- /*public Point prefixTextPos;
- public Point suffixTextPos;
- public String prefixText;
- public String suffixText;
- public HyperLink hyperlink;*/
- public ArrayList<TextElement> texts;
- public ArrayList<ImageElement> imgs;
- public ArrayList<HyperLink> hyperlinks;
- public void destroy(){
- if(texts!=null)
- texts.clear();
- if(imgs!=null)
- imgs.clear();
- if(hyperlinks!=null)
- hyperlinks.clear();
- texts=null;
- imgs=null;
- hyperlinks=null;
- }
- public void addText(TextElement txt){
- if(texts==null)
- texts=new ArrayList<TextElement>(1);
- texts.add(txt);
- }
- public void addImage(ImageElement img){
- if(imgs==null)
- imgs=new ArrayList<ImageElement>(1);
- imgs.add(img);
- }
- public void addHyperLink(HyperLink link){
- if(hyperlinks==null)
- hyperlinks=new ArrayList<HyperLink>(1);
- hyperlinks.add(link);
- }
- }
- ImageElement.java
- /*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
- package com.palantir.ui;
- import java.awt.Point;
- import javax.swing.ImageIcon;
- /**
- *
- * @author Sainese
- */
- public class ImageElement {
- public Point startPoint;
- public ImageIcon img;
- public ImageElement(Point startPoint,ImageIcon img){
- this.startPoint=startPoint;
- this.img=img;
- }
- }
- TextElement.java
- /*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
- */
- package com.palantir.ui;
- import java.awt.Color;
- import java.awt.Font;
- import java.awt.Point;
- /**
- *
- * @author Sainese
- */
- public class TextElement {
- public Point startPoint;
- public String text;
- public Font font;
- public Color color;
- public TextElement(Point startPoint,String text){
- this.startPoint=startPoint;
- this.text=text;
- }
- public void setColor(Font f){
- font=f;
- }
- public void setColor(Color clr){
- color=clr;
- }
- }
代码已经在工程中使用,可以直接Copy使用。最初作者的名字和包名应该我都还留着。其实已经没有几行代码是他的了。这个HyperLinkLabel显示的是/n和一些html tag的混合体字符串。当时因为敢工期,所以,很多tag都随手写下,有些错误如<a src=xxx>等tag不符合html规范的,不过没关系,达到我要的目的就可以了。