Mixing Swing Components with JavaFX Graphics

By Josh Marinacci, October 15, 2008

With JavaFX you can seamlessly mix existing Swing components, even custom components, with graphics and effects. This example combines the Swing JXMapViewer component from SwingLabs.org with a custom overlay and text in JavaFX graphics. Notice that you can still pan and zoom the map around with the mouse even though it is inside of a JavaFX scene.

Map data provided by OpenStreetMap.org

Understanding the Code

The JXMapViewer class is a complex Swing component from the SwingLabs.org web site. It renders images from tile-based mapping servers like OpenStreetMap.org in Swing applications, complete with pan and zoom behavior. Because this class already exists, it would be great to use it in JavaFX programs rather than having to completely rewrite it in JavaFX.

Most of the standard Swing components like JButton and JSlider have JavaFX wrappers in the javafx.ext.swing package. For those components you can use the JavaFX wrapper class. You might occasionally have an existing custom Swing component that you would like to use in a JavaFX program. For these components you can use the SwingComponent.wrap() utility function to automatically create a Swing wrapper.

This example has a static method on the SetupMap Java class which creates the mapping component by using normal Java code. This is the same code that you would write in a plain Java desktop application, as shown in Figure 1.

Source Code
public class SetupMap {
    
    public static JComponent create() {
        JXMapViewer map = new JXMapViewer();
        final int max = 17;
        TileFactoryInfo info = new TileFactoryInfo(1,max-2,max,
                256, true, true, // tile size is 256 and x/y orientation is normal
                "http://tile.openstreetmap.org",
                "x","y","z") {
            public String getTileUrl(int x, int y, int zoom) {
                zoom = max-zoom;
                String url = this.baseURL +"/"+zoom+"/"+x+"/"+y+".png";
                return url;
            }

        };
        TileFactory tf = new DefaultTileFactory(info);
        map.setTileFactory(tf);
        map.setZoom(11);
        map.setAddressLocation(new GeoPosition(51.5,0));
        
        return map;
    }
}

Figure 1: Creating the JXMapViewer Component

To use the JXMapViewer component in a JavaFX graphic scene create an instance of SwingComponent from the original component by using the SwingComponent.wrap() function. Then you must give the component a size manually (400x200 in this case) as shown in Figure 2.

Source Code
var map = SetupMap.create();
map.setPreferredSize(new java.awt.Dimension(400,300));
var mapComp = SwingComponent.wrap(map);

Figure 2: Wrapping the JXMapViewer in a SwingComponent Class

Because a SwingComponent is a subclass of javafx.scene.Node you can do the cool things that you could do on other nodes, such as animation, transforms, and special effects. In this example I put a rounded rectangle clip over the component to give it round corners, then I added a border on top of the component. See Figure 3.

Source Code
mapComp.clip = Rectangle {
    width: 400 height: 300
    arcHeight: 30 arcWidth: 30
    };

var mapGroup = Group {
    opacity: 0.01
    translateX: 300-400/2
    translateY: 200-300/2
    scaleX: bind scale
    scaleY: bind scale
    content: [
        mapComp,
        Rectangle { width: 400 height: 300 
            arcHeight: 30 arcWidth: 30
            stroke: Color.WHITE 
            strokeWidth: 3
            fill: null
        }
    ]
};

Figure 3: Adding Round Corners and a Border

Finally I used a scale effect to make the map zoom in and put animated text on top of the entire scene, as Figure 4 shows. During all of these transitions the Swing component is still "live", meaning it can receive mouse events and repaint itself the way any normal Swing component would do. The text above the map has its blocksMouse variable set to true so that any clicks on the text won't penetrate to the map. However, if you start dragging on the map and move over the text the map will still receive the drag events correctly. The JavaFX runtime handles all of the conversion necessary to make Swing interact seamlessly with JavaFX graphics.

Source Code
var bigText:Text = Text {
    content: "Swing + JavaFX"
    font: Font.font("Verdana",FontWeight.BOLD,50)
    fill: Color.BLACK
    stroke: Color.ORANGE strokeWidth: 2
    y: 130 x: -600
    blocksMouse: true
    onMouseEntered:function(e:MouseEvent) {
        bigText.fill = Color.WHITE
    }
    onMouseExited:function(e:MouseEvent) {
        bigText.fill = Color.BLACK
    }
};

var scale = 0.3;
var zoomIn = Timeline {
    keyFrames: [
        at(0s) { scale=>0.3 tween Interpolator.LINEAR},
        at(0s) { mapGroup.opacity=>0.0 tween Interpolator.LINEAR},
        at(1s) { scale=>1.0 tween Interpolator.LINEAR},
        at(1s) { mapGroup.opacity=>1.0 tween Interpolator.LINEAR},
        at(0.5s) { bigText.x => -300 tween Interpolator.LINEAR},
        at(2s) { bigText.x => 85 tween Interpolator.LINEAR},
    ]
};


Stage {
    width: 600
    height: 400
    scene: Scene {
        fill: Color.BLACK
        content: [
            Rectangle { x: 35 width: 40 height: 20 fill: Color.web("#444444") stroke: Color.web("#888888")
                onMousePressed: function(e:MouseEvent) {
                    zoomIn.playFromStart();
                }
            },
            Text { content: "Show" x: 40 y: 15 fill: Color.BLACK},
            mapGroup,
            bigText,
        ]
    }
}

Figure 4: Putting Text Above the Map