Christopher has posted 9 posts at DZone. You can read more from them at their website. View Full User Profile

DJ Native Swing 0.9.6: Integrate Native Components from Other Libraries

08.27.2008
| 11353 views |
  • submit to reddit

DJ Native Swing is a great way to get a web browser, Flash player, multimedia player or an HTML editor in a Swing application. But until recently, it was not possible to apply its advanced integration logic to foreign native components. This has changed with the latest release.

In release 0.9.6, the library was split in two: the framework library with all the integration logic, and its SWT-based implementation which offers the rich component suite. Let us have a look at how we can make use of the framework part.

For the sake of the example, let us consider a Canvas subclass, which is the standard component used to peer native components. Here is the code of this subclass:

private static class CCanvas extends Canvas {
public void paint(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g.drawString("Some native component", 20, 80);
}
}

If we add this canvas class to a JDesktopPane in some internal frames, here is the default behavior:

AWT Component without Native Swing

As we can see, Z-ordering is completely messed up.

Now, let us use the Native Swing framework to see the difference. First, we need to add the DJNativeSwing.jar to the classpath. Then we need to initialize the framework:

public static void main(String[] args) {
// Not necessary, this is according to taste.
UIUtils.setPreferredLookAndFeel();
// This is the actual Native Swing initialization
NativeSwing.initialize();
// Rest of the main method...
}

Finally, we create a Swing-like component that will contain our native component but that would be taken care by Native Swing:

  private static class NSPanel extends JPanel {
private CCanvas nativeComponent;
public NSPanel(NSOption... options) {
super(new BorderLayout(0, 0));
nativeComponent = new CCanvas();
add(new NativeComponentWrapper(nativeComponent).createEmbeddableComponent(options), BorderLayout.CENTER);
}
// Add methods to act on the native component.
}

The options that are passed in the constructor allow to define different behaviors. In the case of a JInternalFrame, it is desirable at least to call the constructor with the following option (and another one is required if iconification is desired):

new NSPanel(NSComponentOptions.proxyComponentHierarchy())

And the result when we re-run our application is the following:

 AWT component with Native Swing

This decoupling from the SWT-based implementation (in other words, the isolation of the framework) has certain interesting effects: one can now start to think of all his favorite libraries (JDIC browser?, Some Native media player that has Java bindings?) in contexts where certain integration issues were show stoppers.

I hope these features will help improving the state of Java on the desktop, and I am waiting forward to hearing from your experiments with foreign libraries!


 

References
Published at DZone with permission of its author, Christopher Deckers. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Carl Antaki replied on Thu, 2008/08/28 - 6:53am

Christopher how hard do you think it would take to launch the native SWT filedialog?

Christopher Deckers replied on Thu, 2008/08/28 - 6:59am in response to: Carl Antaki

Hi Carl,

Launching the SWT file dialog is a piece of cake. The problem is handling its modality because it completely escapes the AWT view of modality.

Native Swing SWT-based implementation keeps track of AWT modality to apply it to SWT components but not the other way round. It may be possible to implement this but it is not so easy.

Cheers,
-Christopher

Carl Antaki replied on Thu, 2008/08/28 - 9:13am

Thank you Christopher.

Great job for your library.

Regarding the modality do you mean that the dialog will appear behind the Swing JFrameor is the hard part making sure the Dialog is always modal?

I would love to see File Dialog support in DJ Native Swing, many people including msyelf are sick with the default JFileChooser which isn't on par with the native File Dialog. I've asked this many times in the Java.net forums but I didn't get any real answers; it was supposed to be fixed for Java 7 but I'm not even sure it's still on the list.

Thanks,

Carl

Christopher Deckers replied on Thu, 2008/08/28 - 9:21am in response to: Carl Antaki

Hi Carl,

I'll add that item to my TODO lis. I don't promise anything though ;)

Cheers,
-Christopher

Carl Antaki replied on Thu, 2008/08/28 - 9:32am

Thanks! I will try to have a look at the source code and see if I can come up with something :)

Christopher Deckers replied on Thu, 2008/08/28 - 4:13pm in response to: Carl Antaki

Oups, I noticed I forgot to answer one of your questions.

> Regarding the modality do you mean that the dialog will appear behind the Swing JFrameor is the hard part making sure the Dialog is always modal?

The hard part is in making sure the dialog is always modal. If I remember correctly, it is always on top but not modal as AWT has its own modality handling. And this is for Windows, it could be different on other systems...

Cheers,
-Christopher

 

Christopher Deckers replied on Thu, 2008/08/28 - 4:17pm in response to: Carl Antaki

Don't hesitate to contact me by e-mail directly if you have any questions.

Cheers,
-Christopher

igor vus replied on Fri, 2009/02/06 - 5:13am

Hello Christopher
I use DJ Native Swing 0.9.6 (JWebBrowser) to embed some web page to my app.
I add JWebBrowser in the following way
add(new NativeComponentWrapper(webBrowser).createEmbeddableComponent(NSComponentOptions.proxyComponentHierarchy()), BorderLayout.CENTER);
It is work when I run app as desktop app. But I need use this is applet and it isn’t work.
If just add JWebBrowser as
add(webBrowser);
It is works, but I have problem with Z-ordering. How can I use JWebBrowser in applet app?
Thanks, Ihor

Christopher Deckers replied on Fri, 2009/03/27 - 1:14pm in response to: igor vus

Hi,

I am not sure what your problem is, but maybe using a JApplet instead of Applet would solve the issue.

-Christopher

igor vus replied on Wed, 2009/06/10 - 6:53am in response to: Christopher Deckers

I used JApplet. Issue disappear when I update DJ Native Swing to 0.9.7 version

igor vus replied on Wed, 2009/06/10 - 7:29am

Thank you Christopher.

Great job for your library.

But I have following issue with your library
 I have panel which contain jwebbrowser. Then I put transparent background rectangle under this panel (in upper layer).  And under this rectangle I’ve added JInternalFrame .

 

All components are visible under this rectangle but jwebbrowser is not. I’ve try all NSComponentOptions but this don’t help.

 

I’ve added source of sample example,  if you will have time please look to it. It will be very helpful for me

import java.awt.Dimension;
import java.beans.PropertyVetoException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;

import chrriis.dj.nativeswing.NSComponentOptions;
import chrriis.dj.nativeswing.NativeComponentWrapper;
import chrriis.dj.nativeswing.NativeSwing;
import chrriis.dj.nativeswing.swtimpl.NativeInterface;
import chrriis.dj.nativeswing.swtimpl.components.JWebBrowser;

public class TestL
{
public static void main(String[] args) throws PropertyVetoException
{
NativeInterface.open();
NativeSwing.initialize();
JPanel panel1 = new JPanel();
panel1.setPreferredSize(new Dimension(300, 300));
JButton button = new JButton("test");
JWebBrowser browser = new JWebBrowser();
browser.setPreferredSize(new Dimension(200, 200));
panel1.add(button);
panel1.add(new NativeComponentWrapper(browser).createEmbeddableComponent(NSComponentOptions
.proxyComponentHierarchy()));
JPanel panel3 = new JPanel();
panel3.setPreferredSize(new Dimension(100, 100));

final TransparentBackgroundPainter wi = new TransparentBackgroundPainter(panel1);

JInternalFrame iframe = new JInternalFrame();
iframe.setClosable(true);
iframe.setResizable(true);
iframe.addInternalFrameListener(new InternalFrameAdapter()
{
@Override
public void internalFrameClosed(InternalFrameEvent e)
{
wi.dispose();
}
});

iframe.add(panel3);
iframe.pack();
iframe.show();
wi.getPainter().add(iframe);

JFrame frame = new JFrame("Test");
frame.getContentPane().add(panel1);
frame.pack();
frame.show();
}

}


 ------------------------------------------------------------------------------------------

import java.awt.Color;
import java.awt.Graphics;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseMotionAdapter;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

/**
* Prevents mouse and key input to a {@link JComponent} or {@link JFrame}, while dimming the component
*/
public class TransparentBackgroundPainter extends AbstractComponentDecorator implements KeyEventDispatcher
{

/**
* Place the transparent background over the entire frame.
*
* @param frame {@code JFrame}
*/
public TransparentBackgroundPainter(JFrame frame)
{
this(frame.getLayeredPane());
}

/**
* Place the transparent background over the given component.
*
* @param target {@code JComponent}
*/
public TransparentBackgroundPainter(JComponent target)
{
super(target);
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this);
getPainter().addMouseListener(new MouseAdapter()
{
});
getPainter().addMouseMotionListener(new MouseMotionAdapter()
{
});
}

/** Remove the transparent background */
public void dispose()
{
super.dispose();
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this);
}

/**
* Consume events targeted at our target component. Return true to consume the event.
*
* @param e the event which should be consumed
*/
public boolean dispatchKeyEvent(KeyEvent e)
{
return SwingUtilities.isDescendingFrom(e.getComponent(), getComponent());
}

/**
* The default dims the blocked component.
*
* @param g {@code Graphics}
*/
public void paint(Graphics g)
{
Color bg = getComponent().getBackground();
Color c = new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 128);
Rectangle r = getDecorationBounds();
g = g.create();
g.setColor(c);
g.fillRect(r.x, r.y, r.width, r.height);
g.dispose();
}
}

---------------------------------------------------------------------------------------------------------------------

import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;

/**
* Provide a method for consistently augmenting the appearance of a given component by painting something on it <i>after</i>
* the component itself gets painted. If not explicitly removed via {@link #dispose}, an instance of this object will
* live as long as its target component.
* <p>
* By default, the decorator matches the location and size of the decorated component, but the bounds can be adjusted by
* overriding {@link #getDecorationBounds()}. The {@link #synch()} method should be called whenever the bounds returned
* by {@link #getDecorationBounds()} would change.
*/
public abstract class AbstractComponentDecorator
{
public static final Rectangle DEFAULT_BOUNDS = null;
public static final int TOP = 0;

private Painter painter;
private JComponent component;
private Container parent;
private Component layerRoot;
private Listener listener;
private int position;
private Rectangle bounds;

/** Create a decorator for the given component.
*
* @param target {@code JComponent}
*/
public AbstractComponentDecorator(JComponent c)
{
this(c, TOP);
}

/**
* Create a decorator with the given position within its layer. Use {@link #TOP} to cover other decorations
*
* @see JLayeredPane
* @param target {@code JComponent}
* @param position position within its layer
*/
public AbstractComponentDecorator(JComponent c, int position)
{
component = c;
this.position = position;
this.bounds = DEFAULT_BOUNDS;
parent = c.getParent();
painter = new Painter();
listener = new Listener();
component.addHierarchyListener(listener);
component.addComponentListener(listener);
attach();
}

/**
* Indicate whether any of the decoration is visible.
*
* @return true if decoration is visible
*/
public boolean isVisible()
{
return painter.isVisible();
}

/** Use this to change the visibility of the decoration.
*
*@param visible parameter for to change the visibility of the decoration
*/
public void setVisible(boolean visible)
{
painter.setVisible(visible);
}

protected void attach()
{
if (layerRoot != null)
{
layerRoot.removePropertyChangeListener(listener);
layerRoot = null;
}
RootPaneContainer rpc = (RootPaneContainer) SwingUtilities.getAncestorOfClass(RootPaneContainer.class,
component);
if (rpc != null && SwingUtilities.isDescendingFrom(component, rpc.getLayeredPane()))
{
JLayeredPane lp = rpc.getLayeredPane();
int layer = JLayeredPane.MODAL_LAYER.intValue();
lp.add(painter, new Integer(layer), position);
}
else
{
// Always detach when the target component's window is null
// or is not a suitable container,
// otherwise we might prevent GC of the component
Container parent = painter.getParent();
if (parent != null)
{
parent.remove(painter);
}
}
// Track size changes in the decorated component's parent
if (parent != null)
{
parent.removeComponentListener(listener);
}
parent = component.getParent();
if (parent != null)
{
parent.addComponentListener(listener);
}
synch();
}

/**
* Ensure the size of the decorator matches the current decoration bounds
*/
protected void synch()
{
Container painterParent = painter.getParent();
if (painterParent != null)
{
Rectangle decorated = getDecorationBounds();
Rectangle clipRect = decorated;

Point pt = SwingUtilities.convertPoint(component, clipRect.x, clipRect.y, painterParent);
if (clipRect.width <= 0 || clipRect.height <= 0)
{
setPainterBounds(-1, -1, 0, 0);
setVisible(false);
}
else
{
setPainterBounds(pt.x, pt.y, clipRect.width, clipRect.height);
setVisible(true);
}
painterParent.repaint();
}
}

/**
* Return the bounds, relative to the decorated component, of the decoration. The default covers the entire
* component. Note that this method will be called from the constructor, so be careful when overriding and
* referencing derived class state.
*
* @return the bounds, relative to the decorated component
*/
protected Rectangle getDecorationBounds()
{
return bounds != DEFAULT_BOUNDS ? bounds : new Rectangle(0, 0, component.getWidth(), component.getHeight());
}

protected void setPainterBounds(int x, int y, int w, int h)
{
painter.setLocation(x, y);
painter.setSize(w, h);
repaint();
}

protected JComponent getComponent()
{
return component;
}

/**
*Return the painter
*
* @return the painter
*/
public JComponent getPainter()
{
return painter;
}

/**
* Force a refresh of the underlying component and its decoration.
*/
public void repaint()
{
JLayeredPane p = (JLayeredPane) painter.getParent();
if (p != null)
{
p.repaint(painter.getBounds());
}
}

/**
* Stop decorating.
*/
public void dispose()
{
component.removeHierarchyListener(listener);
component.removeComponentListener(listener);
if (parent != null)
{
parent.removeComponentListener(listener);
parent = null;
}
if (layerRoot != null)
{
layerRoot.removePropertyChangeListener(listener);
layerRoot = null;
}
Container painterParent = painter.getParent();
if (painterParent != null)
{
Rectangle bounds = painter.getBounds();
painterParent.remove(painter);

painterParent.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
}
component.repaint();
}

/**
* Define the decoration's appearance. The point (0,0) represents the upper left corner of the decorated component.
* The default clip mask will be the extents of the decoration bounds, as indicated by
* {@link #getDecorationBounds()}, which defaults to the decorated component bounds.
*/
public abstract void paint(Graphics g);

/** Used to hook into the Swing painting architecture. */
private class Painter extends JComponent
{
{
setFocusable(false);
}

public JComponent getComponent()
{
return AbstractComponentDecorator.this.getComponent();
}

/**
* Delegate to the containing decorator to perform the paint.
*/
public void paintComponent(Graphics g)
{
if (!component.isShowing())
return;
Graphics g2 = g.create();
AbstractComponentDecorator.this.paint(g2);
g2.dispose();
}
}

/** Tracks changes to component configuration. */
private final class Listener extends ComponentAdapter implements HierarchyListener, PropertyChangeListener
{
public void hierarchyChanged(HierarchyEvent e)
{
if ((e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) != 0)
{
attach();
}
}

public void propertyChange(PropertyChangeEvent e)
{
if (JLayeredPane.LAYER_PROPERTY.equals(e.getPropertyName()))
{
attach();
}
}

public void componentMoved(ComponentEvent e)
{
attach();
}

public void componentResized(ComponentEvent e)
{
attach();
}

public void componentHidden(ComponentEvent e)
{
setVisible(false);
}

public void componentShown(ComponentEvent e)
{
setVisible(true);
}
}
}


 

Christopher Deckers replied on Wed, 2009/06/10 - 1:21pm in response to: igor vus

Hi,

Change the content of the main method from the TestL class with code like this:

NativeInterface.open();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JPanel panel1 = new JPanel();
panel1.setPreferredSize(new Dimension(300, 300));
JButton button = new JButton("test");
JWebBrowser browser = new JWebBrowser(JWebBrowser.proxyComponentHierarchy());
browser.navigate("http://www.google.com");
browser.setPreferredSize(new Dimension(200, 200));
panel1.add(button);
panel1.add(browser);
// Rest of your code here
}
});

This code constructs the component correctly: UI is initialized in the UI thread, the native component is properly added to its hierarchy, so at least something shows up. BUT, the atual content is not visible and unfortunately this is norma. Native components do not behave like any normal Swing component: they do not support alpha blending for example.

Nevertheless, there is something you can do: when you show the layer, you can force the native component to create a background buffer every second or so and stop this thread when you hide the layer. The code for the background buffer effect is shown in the demo under the "Additional Features" and is called "Pseudo Transparency".

Hope this helps!
-Christopher

 

igor vus replied on Fri, 2009/06/19 - 6:02am in response to: Christopher Deckers

 Christopher,
Thank you very much. This was very helpful for me.

Adil Badshah replied on Thu, 2009/11/26 - 7:09am

Hello cristopher can you kindly guide me how can i use D J Native Swing for Webpage snapshot

Thnx in Advance

Adil Badshah replied on Thu, 2009/11/26 - 7:59am

with some Code Exmple??

Adil Badshah replied on Thu, 2009/11/26 - 8:48am

In this code its generating the image but without css applied in it, so after browsing capturing the image would be css applied, kindly help me out

File outputFile =  makeRandomFileName(getOuptutTypeExtension());
outputFile.deleteOnExit();

JEditorPane pane = new JEditorPane();
pane.setContentType("text/html");
pane.setPage("file:/C:/HelloWorld.html");
final JFrame frame = new JFrame();
frame.pack();

// Time Delay for the correct loading of the file.
try {
Thread.sleep(5000);
} catch(NumberFormatException nfe) {
}

frame.add(pane);
frame.pack();

Dimension prefSize = pane.getPreferredSize();
pane.setSize(prefSize);

BufferedImage img = new BufferedImage( prefSize.width, prefSize.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) img.getGraphics();

SwingUtilities.paintComponent(g, pane, frame,
0, 0, prefSize.width, prefSize.height);
 
ImageIO.write(img, "png", outputFile);

 

Christopher Deckers replied on Thu, 2009/11/26 - 8:55am in response to: Adil Badshah

Hi,

There is an example that creates thumbnails in the demo application.

Please, next time use the Help forum.

-Christopher

Christopher Deckers replied on Thu, 2009/11/26 - 2:53pm in response to: Adil Badshah

The DJ Native Swing demo shows the code of all the examples.

 

So:

- download DJ Native Swing

- launch the demo application

- Find the one with thumbnails (under "advanced features")

- Adapt the code to suit your needs.

 

And please you the help form I mentioned in my previous post.

Cheers,

-Christopher

Adil Badshah replied on Tue, 2009/12/01 - 3:27am

Hello Christopher Thanks Very much for replying, I have downloaded SWT Demo application but when i am running its giving an error "Unable to lanch the apllication" Can you kindly guide me or if there is any other source for the code of Thumnail like you said kindly guide me..

Thanks

Christopher Deckers replied on Tue, 2009/12/01 - 7:51am in response to: Adil Badshah

Hi,

What OS are you? If not on Windows, you need to download the right version of SWT as indicated in the readme file.

Please use the Help forum next time: https://sourceforge.net/forum/forum.php?forum_id=671154

-Christopher

Adil Badshah replied on Thu, 2009/12/03 - 6:28am

Hi

 I am using Windows and Thanks very much christopher i have downloaded the source and like you said about Thumnail under Advanced feature i am working on that Its creating Webpage Snapshot , and sorry for replying here next time i will definately use forum.. Thanks again

Adil

tuan tran replied on Fri, 2010/01/08 - 10:56pm

Dear all !

 

I 'd like to embed MS Word into my app using

DJ Native Swing

 Is it posible ?

If posible, how can i do it ?

 

Please give me the sample code ?

 

Thanks !

 

tuan tran replied on Fri, 2010/01/08 - 11:32pm

Dear all ! I found the solution how to embed word to my app that just use JWebBrowser and change the URL =filepath but , menu of Word does not appear( File, Edit, View, Insert,...).

 

How can i do to make these menu display when word was embed into my app.

 

 

Thanks very much !

Umme Habiba replied on Mon, 2011/12/05 - 5:17am

Hi Chris,

 

I want to add MouseListener with JFlashPlayer and JVLCPlayer.

I tried this as follows:

 nativeComponent = flashPlayer.getNativeComponent();

        nativeComponent.addMouseListener(new MouseAdapter() {
      @Override
      public void mousePressed(MouseEvent e) {
          // do something...
      }
      @Override
      public void mouseReleased(MouseEvent e) {
         // do something...
      }

    });

 

But, this is not working. Can you please help me, how could I make this work?

 

thanks

Christopher Deckers replied on Tue, 2011/12/06 - 4:19pm in response to: Umme Habiba

Hi Umme,

First, please use the SourceForge forum when you have a question.

About your problem, this is a limitation of web browser plugins. Some may block events, some may let them go through, and some may have different behaviors depending on the operating system.

Hope this helps,
-Christopher

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.