Skip to main content

Embedding Swing components in a JEditorPane

Posted by aim on July 19, 2007 at 7:15 AM PDT

In the early days of Swing we spent many lunches arguing over the best way to do GUI layout. Tim Prinzing, the original architect of Swing's text package, believed that one sensical approach would be to leverage the power and popularity of HTML for GUI screen layout, and so from the very beginning he made sure it was possible to use the tag to embed GUI components within HTML inside a JEditorPane. The page below embeds a JButton in the center of an HTML table:
<br /> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><br /> <html><br /> <body></p> <table width="90%" height="90%" align="center"> <tr align="center"> <td align="center"> <object classid="javax.swing.JButton" label="just do it"> </object></td> </tr> </table> <p> </body><br /> </html><br />

I've used a JButton here for simplicity, but this would also work with any java.awt.Component subclass.
And here is the Swing code that loads page into a JEditorPane:

    JEditorPane htmlPane = new JEditorPane();
    htmlPane.setContentType("text/html");
    htmlPane.setEditable(false); // very important, as the default is true (sorry about that!)
    try {
          htmlPane.setPage(Demo.class.getResource("resources/jbutton.html"));
    } catch (Exception e) {
          // handle load failure
    }

Now I'm not suggesting that we should abandon the use of tools and good layout managers in favor of HTML, but there may be an occasion where you'd find some utility in being able to embed components within HTML. In my case this is in writing some new demos for SwingSet3; I want to provide an HTML description (containing links, etc) of the demo and rather than placing that html adjacent to the running demo, it's a little more fun to just embed the demo within the descriptive html.

Displaying the components in the page is the easy part. The tricky bit is in how to access the handle to the component instance in order to make the GUI do something useful in your application. You'd think you could simply dig around in the
editorpane's view hierarchy until you find a component of the proper class, however setPage() is asynchronous and it turns out it's hard to know precisely when you can count on finding the instantiated component of interest.

What you really want to do, if you're brave enough to tease open javax.swing.text, is to take control of the view factory to insert your own code at precisely the point when your component is instantiated. And just to rile up all those who think inheritence is the root of all evil, I'm going to neatly pack up all the code to do this in one simple JEditorPane subclass:

public class HTMLPanel extends JEditorPane {
    public HTMLPanel() {      
        setContentType("text/html");
        setEditorKit(new CompEditorKit()); // install our hook
        setEditable(false); // VERY IMPORTANT!
    }  
    protected class CompEditorKit extends HTMLEditorKit {
        @Override
        public ViewFactory getViewFactory() {
            return new CompFactory();
        }      
    }  
    protected class CompFactory extends HTMLEditorKit.HTMLFactory {
        public CompFactory() {
            super();
        }
        @Override
        public View create(Element element) {
            AttributeSet attrs = element.getAttributes();
    Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute);
    Object o = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute);
    if (o instanceof HTML.Tag) {      
                if ((HTML.Tag) o == HTML.Tag.OBJECT) {
                    return new CompView(element);
                }
            }
            return super.create(element);
        }
    }  
    protected class CompView extends ObjectView {
        public CompView(Element element) {
            super(element);
        }
        @Override
        protected Component createComponent() {
            Component component = super.createComponent();  // COMPONENT IS CREATED HERE
                                
            // DO SOMETHING USEFUL WITH COMPONENT INSTANCE HERE
    return component;
        }
    }
}

Note that we're using the "label" attribute in the tag to set the label property on the button. By default, the text package's ObjectView factory (that we extended above) will
use the JavaBean introspector to match up any html tag attributes with writable bean properties of type String. However,
if a property name happens to also be defined as an attribute type in javax.swing.text.html.HTML.Attribute, then
this mechanism will fail due to a bug in ObjectView. Unfortunately this happens to be the case for both the "text" and "name" properties (two I'd like to set for a JButton, drats).

So if you are putting more than one component in the page and want to use an attribute value to identify them, you cannot rely on "name" being set for you. You'll have to specifically check for some other non-conflicting attribute.

Now for the caveats. We've been appropriately skewered by developers for our less than stellar HTML support. We are steadily improving it, however JEditorPane may struggle with more complex layouts. Also, I don't even gloss over how CSS might play in all this, however if you can imagine skinning Swing components with CSS, I recommend checking out Josh Marinacci's 2003 article on Swing and CSS.

I do think Tim's idea of leveraging HTML for GUI layout still has merit, however the irony is that using HTML doesn't really make it any easier to get good ones. I've spent untold hours struggling in Dreamweaver to get my nested tables behaving just right; frankly I'd rather use GridBag and a good beer. But arguing over the holy grail keeps us all engaged.

Related Topics >>