JTree nodes with HTML and Images

Hi,

I'm displaying html-documents as nodes. But images included in the html are not shown in every situation.

Thanks.

Full, compilable source, save as one file:

import java.awt.Component;

import java.awt.Dimension;

import java.awt.event.WindowAdapter;

import java.awt.event.WindowEvent;

import java.io.IOException;

import javax.swing.JEditorPane;

import javax.swing.JFrame;

import javax.swing.JScrollPane;

import javax.swing.JTree;

import javax.swing.text.BadLocationException;

import javax.swing.text.html.HTML;

import javax.swing.text.html.HTMLDocument;

import javax.swing.text.html.HTMLEditorKit;

import javax.swing.tree.DefaultMutableTreeNode;

import javax.swing.tree.DefaultTreeCellRenderer;

import javax.swing.tree.DefaultTreeModel;

publicclass Main

{

publicstaticvoid main(String[] args)

{

JFrame jframe =new JFrame();

jframe.addWindowListener(

new WindowAdapter(){

publicvoid windowClosing(WindowEvent e)

{System.exit(0);}

}

);

DefaultMutableTreeNode root =new DefaultMutableTreeNode("<HTML>Text and the google logo <img src=http://www.google.com/images/logo_sm.gif></HTML>" );

MyTree mytree =new MyTree( root );

jframe.add(mytree);

jframe.setBounds( 50, 50, 600, 300 );

jframe.setVisible(true );

}

}

class MyTreeextends JTree

{

HTMLEditorKit htmlKit =new HTMLEditorKit();

public MyTree( DefaultMutableTreeNode root )

{

super( root );

setCellRenderer(new HTMLTreeCellRenderer() );

DefaultTreeModel model = (DefaultTreeModel)getModel();

String html ="<HTML><PRE>some text and the google logo? <img src=http://www.google.com/images/logo_sm.gif><PRE></HTML>";

HTMLDocument htmlSampleDoc = (HTMLDocument)(htmlKit.createDefaultDocument());

NodeObject nodeObj =null;

try

{

htmlKit.insertHTML( htmlSampleDoc, 0, html, 0, 0, HTML.Tag.HTML );

nodeObj =new NodeObject( htmlSampleDoc );

}

catch (BadLocationException e)

{

e.printStackTrace();

}

catch (IOException e)

{

e.printStackTrace();

}

DefaultMutableTreeNode newNode =new DefaultMutableTreeNode();

newNode.setUserObject( nodeObj );

model.insertNodeInto( newNode, root, 0 );

expandPath(new javax.swing.tree.TreePath(root) );

}

}

//==========================================================

class NodeObject

{

public HTMLDocument doc;

public NodeObject( HTMLDocument doc )

{

this.doc = doc;

}

}

//==========================================================

class HTMLTreeCellRendererextends DefaultTreeCellRenderer

{

public HTMLTreeCellRenderer()

{

super();

}

public Component getTreeCellRendererComponent(JTree jtree, Object value,

boolean sel,boolean expanded,

boolean leaf,int row,boolean hasFocus)

{

super.getTreeCellRendererComponent(jtree, value, sel, expanded, leaf, row, hasFocus);

DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;

Object userObject = node.getUserObject();

if( userObject ==null )

returnthis;

String className = userObject.getClass().getName();

if( className.endsWith("String") )

{

returnthis;

}

elseif( className.endsWith("NodeObject") )

{

NodeObject nodeObject = (NodeObject)node.getUserObject();

JScrollPane jsp =new JScrollPane();

HTMLDocument doc = nodeObject.doc;

JEditorPane jedpane =new JEditorPane();

jedpane.setContentType("text/html");

jedpane.setDocument( doc );

jsp =new JScrollPane(jedpane);

jsp.setPreferredSize(new Dimension( 400, 150) );

return jsp;

}

returnthis;

}

}

//end of file

[7419 byte] By [MarcMoosera] at [2007-11-26 17:31:02]
# 1

I tried your code out and I took out the tree. The scroll pane and the html document work fine. So it has something to do with the putting it in the tree? Hope that helps.

(Unrelated)

Also, I noticed this code:

jframe.addWindowListener(

new WindowAdapter() {

public void windowClosing(WindowEvent e)

{System.exit(0);}

}

);

you could replace it with:

jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

zadoka at 2007-7-8 23:59:00 > top of Java-index,Desktop,Core GUI APIs...
# 2

Well, here's a theory: HTMLEditorKit uses a ViewFactory that loads images aynchronously. You're using a DefaultTreeCellRenderer which is used to paint the node. When it paints, if the image has not been fully loaded, you're not going to see anything. The handling of HTML within components, e.g. JLabel, overrides the ViewFactory to load the images synchronously.

In order to override this behavior, you need to:

1) subclass ViewFactory and special case images, like so:

public View create(Element elem) {

View view = super.create(elem);

if (view instanceof ImageView) {

((ImageView)view).setLoadsSynchronously(true);

}

return view;

}

2) subclass HTMLEditorKit and return an instance of your custom ViewFactory, like so:

private MyViewFactory vf;

public ViewFactory getViewFactory() {

if (vf == null) vf = new MyViewFactory();

return vf;

}

Jasprea at 2007-7-8 23:59:00 > top of Java-index,Desktop,Core GUI APIs...
# 3

> Well, here's a theory: HTMLEditorKit uses a

> ViewFactory that loads images aynchronously. You're

> using a DefaultTreeCellRenderer which is used to

> paint the node. When it paints, if the image

> has not been fully loaded, you're not going to see

> anything. The handling of HTML within components,

> e.g. JLabel, overrides the ViewFactory to load the

> images synchronously.

>

> In order to override this behavior, you need to:

> 1) subclass ViewFactory and special case images, like

> so:

> > public View create(Element elem) {

>View view = super.create(elem);

>if (view instanceof ImageView) {

>((ImageView)view).setLoadsSynchronously(true);

>}

>return view;

>}

> code]

>

> 2) subclass HTMLEditorKit and return an instance of

> your custom ViewFactory, like so:

> [code]

> private MyViewFactory vf;

> public ViewFactory getViewFactory() {

>if (vf == null) vf = new MyViewFactory();

> return vf;

> }

>

Can you create a Short, Self Contained, Compilable and Executable, Example Program?

jalzabrewera at 2007-7-8 23:59:00 > top of Java-index,Desktop,Core GUI APIs...
# 4

> Can you create a Short, Self Contained, Compilable

> and Executable, Example Program?

There's no reason for me to post the boilerplate code to extend the specified classes. If you need help doing that, then you need to go here: [url http://forum.java.sun.com/forum.jspa?forumID=54]New To Java[/url]

Jasprea at 2007-7-8 23:59:00 > top of Java-index,Desktop,Core GUI APIs...
# 5
I was hoping you could provide more then "theory: HTMLEditorKit uses a ViewFactory that loads images aynchronously" with in a context that other users could better understand with the example code that you provided.
jalzabrewera at 2007-7-8 23:59:00 > top of Java-index,Desktop,Core GUI APIs...
# 6

> I was hoping you could provide more then "theory:

> HTMLEditorKit uses a ViewFactory that loads images

> aynchronously" with in a context that other users

> could better understand with the example code that

> you provided.

Well, that part is not theory. That is really how it's implemented if you look at the Java source code. The theory was that the asynchronous loading causes him problems because a CellRenderer paints its current state whenever the UI asks it to paint and it does not update until another repaint.

Whatever, here's some code. It's just the OP's code with my proposed solution incorporated

import java.awt.*;

import java.io.IOException;

import javax.swing.*;

import javax.swing.text.*;

import javax.swing.text.html.*;

import javax.swing.tree.*;

public class HTMLTree

{

public static void main(String[] args)

{

JFrame jframe = new JFrame();

jframe.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

DefaultMutableTreeNode root = new DefaultMutableTreeNode( "<HTML>Text and the google logo <img src=http://www.google.com/images/logo_sm.gif></HTML>" );

MyTree mytree = new MyTree( root );

jframe.add(mytree);

jframe.setBounds( 50, 50, 600, 300 );

jframe.setVisible( true );

}

}

class MyTree extends JTree

{

HTMLEditorKit htmlKit = new HTMLEditorKit();

public MyTree( DefaultMutableTreeNode root )

{

super( root );

setCellRenderer( new HTMLTreeCellRenderer() );

DefaultTreeModel model = (DefaultTreeModel)getModel();

String html = "<HTML><PRE>some text and the google logo? <img src=http://www.google.com/images/logo_sm.gif><PRE></HTML>";

HTMLDocument htmlSampleDoc = (HTMLDocument)(htmlKit.createDefaultDocument());

NodeObject nodeObj = null;

try

{

htmlKit.insertHTML( htmlSampleDoc, 0, html, 0, 0, HTML.Tag.HTML );

nodeObj = new NodeObject( htmlSampleDoc );

}

catch (BadLocationException e)

{

e.printStackTrace();

}

catch (IOException e)

{

e.printStackTrace();

}

DefaultMutableTreeNode newNode = new DefaultMutableTreeNode();

newNode.setUserObject( nodeObj );

model.insertNodeInto( newNode, root, 0 );

expandPath( new javax.swing.tree.TreePath(root) );

}

}

//==========================================================

class NodeObject

{

public HTMLDocument doc;

public NodeObject( HTMLDocument doc )

{

this.doc = doc;

}

}

//==========================================================

class HTMLTreeCellRenderer extends DefaultTreeCellRenderer

{

JEditorPane jedpane = new JEditorPane();

public HTMLTreeCellRenderer()

{

super();

jedpane.setEditorKit(new MyHTMLEditorKit());

}

public Component getTreeCellRendererComponent(JTree jtree, Object value,

boolean sel, boolean expanded,

boolean leaf, int row, boolean hasFocus)

{

super.getTreeCellRendererComponent(jtree, value, sel, expanded, leaf, row, hasFocus);

DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;

Object userObject = node.getUserObject();

if( userObject == null )

return this;

String className = userObject.getClass().getName();

if( className.endsWith("String") )

{

return this;

}

else if( className.endsWith("NodeObject") )

{

NodeObject nodeObject = (NodeObject)node.getUserObject();

JScrollPane jsp = new JScrollPane();

HTMLDocument doc = nodeObject.doc;

jedpane.setDocument( doc );

jsp = new JScrollPane(jedpane);

jsp.setPreferredSize( new Dimension( 400, 150) );

return jsp;

}

return this;

}

}

class MyViewFactory extends HTMLEditorKit.HTMLFactory {

public View create(Element elem) {

View view = super.create(elem);

if (view instanceof ImageView) {

((ImageView)view).setLoadsSynchronously(true);

}

return view;

}

}

class MyHTMLEditorKit extends HTMLEditorKit {

private MyViewFactory vf;

public ViewFactory getViewFactory() {

if (vf == null) vf = new MyViewFactory();

return vf;

}

}

Jasprea at 2007-7-8 23:59:00 > top of Java-index,Desktop,Core GUI APIs...
# 7

Thank you for your answer. I think it's quite near to a solution. I have added this to my code, but it somehow still doesn't work.

(By the way -- do you think even the scrollbars within the nodes could work?..)

[edit: I found that the code above actually works, I didn't see it when I first sended this post. Thank you very much]

Full Code:

import java.awt.Component;

import java.io.IOException;

import javax.swing.*;

import javax.swing.text.*;

import javax.swing.text.html.*;

import javax.swing.tree.DefaultMutableTreeNode;

import javax.swing.tree.DefaultTreeCellRenderer;

import javax.swing.tree.DefaultTreeModel;

public class Main

{

public static void main(String[] args)

{

JFrame jframe = new JFrame();

jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

DefaultMutableTreeNode root = new DefaultMutableTreeNode(

"<HTML>Text and the google logo " +

"<img src=http://www.google.com/images/logo_sm.gif></HTML>" );

MyTree mytree = new MyTree( root );

jframe.add(mytree);

jframe.setBounds( 50, 50, 600, 300 );

jframe.setVisible( true );

}

}

//==========================================================

class NodeObject

{

public HTMLDocument doc;

public NodeObject( HTMLDocument doc )

{

this.doc = doc;

}

}

//==========================================================

class MyTree extends JTree

{

MyHTMLEditorKit htmlKit = new MyHTMLEditorKit();

public MyTree( DefaultMutableTreeNode root )

{

super( root );

//System.err.println( htmlKit.getViewFactory().getClass().getName() );

setCellRenderer( new HTMLTreeCellRenderer() );

DefaultTreeModel model = (DefaultTreeModel)getModel();

String html = "<HTML><PRE>some text and the google logo? " +

"<img src=http://www.google.com/images/logo_sm.gif><PRE></HTML>";

HTMLDocument htmlSampleDoc = (HTMLDocument)(htmlKit.createDefaultDocument());

//MyHTMLDocument htmlSampleDoc = new MyHTMLDocument();

NodeObject nodeObj = null;

try

{

htmlKit.insertHTML( htmlSampleDoc, 0, html, 0, 0, HTML.Tag.HTML );

nodeObj = new NodeObject( htmlSampleDoc );

}

catch (BadLocationException e)

{

e.printStackTrace();

}

catch (IOException e)

{

e.printStackTrace();

}

DefaultMutableTreeNode newNode = new DefaultMutableTreeNode();

newNode.setUserObject( nodeObj );

model.insertNodeInto( newNode, root, 0 );

expandPath( new javax.swing.tree.TreePath(root) );

}

}

//==========================================================

class HTMLTreeCellRenderer extends DefaultTreeCellRenderer

{

public HTMLTreeCellRenderer()

{

super();

}

public Component getTreeCellRendererComponent(JTree jtree, Object value,

boolean sel, boolean expanded,

boolean leaf, int row, boolean hasFocus)

{

super.getTreeCellRendererComponent(jtree, value, sel, expanded, leaf, row, hasFocus);

DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;

Object userObject = node.getUserObject();

if( userObject == null )

return this;

String className = userObject.getClass().getName();

if( className.endsWith("String") )

{

return this;

}

else if( className.endsWith("NodeObject") )

{

NodeObject nodeObject = (NodeObject)node.getUserObject();

//JScrollPane jsp = new JScrollPane();

HTMLDocument doc = nodeObject.doc;

JEditorPane jedpane = new JEditorPane();

jedpane.setContentType("text/html");

jedpane.setDocument( doc );

//jsp = new JScrollPane(jedpane);

//jsp.setPreferredSize( new Dimension( 340, 140) );

return jedpane;

}

return this;

}

}

//=====================================================

class MyHTMLEditorKit extends HTMLEditorKit

{

private MyViewFactory vf;

public MyHTMLEditorKit()

{

super();

}

public ViewFactory getViewFactory()

{

if (vf == null) vf = new MyViewFactory();

return vf;

}

}

//=========================================

class MyViewFactory extends HTMLEditorKit.HTMLFactory

{

public MyViewFactory()

{

super();

}

public View create(Element elem)

{

View view = super.create(elem);

if (view instanceof ImageView)

{

((ImageView)view).setLoadsSynchronously(true);

}

return view;

}

}

Message was edited by:

MarcMooser

MarcMoosera at 2007-7-8 23:59:00 > top of Java-index,Desktop,Core GUI APIs...
# 8
Ohh, your code seems to work! Again thank you very much!
MarcMoosera at 2007-7-8 23:59:00 > top of Java-index,Desktop,Core GUI APIs...
# 9

the reason why your code does not work and mine does is because of the HTMLTreeCellRenderer. I did a couple things:

1) Use a member JEditorPane since you don't gain anything by creating new ones every time.

2) Set the EditorKit on the JEditorPane to use MyHTMLEditorKit

Your JScrollPanes are not going to work. Why? Because, just like your original problem, the cell renderer is simply a painting mechanism. In other words, the HTMLTreeCellRenderer just paints the image of scrollpane and its contents to the screen, it doesn't actually handle events and such. If you need it to be functional, you need to make it a TreeCellEditor as well as a TreeCellRenderer.

Jasprea at 2007-7-8 23:59:00 > top of Java-index,Desktop,Core GUI APIs...
# 10
Well, I'm now already able to use the scrollbars in the editor-modus. But the renderer-modus seems to be more difficult.Message was edited by: MarcMooser
MarcMoosera at 2007-7-8 23:59:00 > top of Java-index,Desktop,Core GUI APIs...