Use JavaFX to Add Google Maps to your NetBeans RCP Application

By utilizing the GMapsFX library which provides a JavaFX API for Google Maps, it is relatively straightforward to add a map component to a desktop application built on the NetBeans Rich Client Platform (RCP).

Assume we would like to add a map to a new TopComponent in the Editor mode in the frame, or for those who are not as familiar with NetBeans jargon, we would like to add a Map to a new tab within the main portion of the application frame.

First create a new NetBeans Module project which will utilize the GMapsFX component.

enter image description here

Enter project details
enter image description here

Use RELEASE80 of the NetBeans Platform. Please also note that the application will require Java 8 in order to run.
enter image description here

Once the project has been created, add the GMapsFX library as a dependency to the project.
The binaries are available on Maven Central and the source is available at GitHub: https://github.com/rterp/GMapsFX
enter image description here

Once the dependency has been added, right click on the project in NetBeans and select “New” -> “Other”. Select the “Module Development” category and then select “Window” file type. This will create a new TopComponent class which will be used to host the map component.

 

enter image description here

Enter a prefix for the new TopComponent class.
enter image description here

Once this is finished a new GMapTopCompoent class will be created. The GoogleMapView component can then be added to this class in order to display the map.

Below is the code for the entire TopCompoment class including code in which I added the map component as well as a couple of map markers and an info window, all without having to interact with the underlying Google Maps JavaScript API.

package com.lynden.gmapsfx.module;

import com.lynden.gmapsfx.GoogleMapView;
import com.lynden.gmapsfx.MapComponentInitializedListener;
import com.lynden.gmapsfx.javascript.object.Animation;
import com.lynden.gmapsfx.javascript.object.GoogleMap;
import com.lynden.gmapsfx.javascript.object.InfoWindow;
import com.lynden.gmapsfx.javascript.object.InfoWindowOptions;
import com.lynden.gmapsfx.javascript.object.LatLong;
import com.lynden.gmapsfx.javascript.object.MapOptions;
import com.lynden.gmapsfx.javascript.object.MapTypeIdEnum;
import com.lynden.gmapsfx.javascript.object.Marker;
import com.lynden.gmapsfx.javascript.object.MarkerOptions;
import java.awt.BorderLayout;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.windows.TopComponent;
import org.openide.util.NbBundle.Messages;

/**
 * Top component which displays something.
 */
@ConvertAsProperties(
        dtd = "-//com.lynden.gmapsfx.module//GMap//EN",
        autostore = false
)
@TopComponent.Description(
        preferredID = "GMapTopComponent",
        //iconBase="SET/PATH/TO/ICON/HERE", 
        persistenceType = TopComponent.PERSISTENCE_ALWAYS
)
@TopComponent.Registration(mode = "editor", openAtStartup = true)
@ActionID(category = "Window", id = "com.lynden.gmapsfx.module.GMapTopComponent")
@ActionReference(path = "Menu/Window" /*, position = 333 */)
@TopComponent.OpenActionRegistration(
        displayName = "#CTL_GMapAction",
        preferredID = "GMapTopComponent"
)
@Messages({
    "CTL_GMapAction=GMap",
    "CTL_GMapTopComponent=GMap Window",
    "HINT_GMapTopComponent=This is a GMap window"
})


public final class GMapTopComponent extends TopComponent implements MapComponentInitializedListener {

    protected GoogleMapView mapComponent;
protected GoogleMap map;

public GMapTopComponent() {
    initComponents();
    setName(Bundle.CTL_GMapTopComponent());
    setToolTipText(Bundle.HINT_GMapTopComponent());
    setLayout(new BorderLayout());
    JFXPanel panel = new JFXPanel();
    Platform.setImplicitExit(false);

    Platform.runLater(() -> {
        mapComponent = new GoogleMapView();
        mapComponent.addMapInializedListener(this);
        BorderPane root = new BorderPane(mapComponent);
        Scene scene = new Scene(root);
        panel.setScene(scene);
    });

        add(panel, BorderLayout.CENTER);        

    }


  @Override
    public void mapInitialized() {
        //Once the map has been loaded by the Webview, initialize the map details.
        LatLong center = new LatLong(47.606189, -122.335842);



MapOptions options = new MapOptions();
        options.center(center)
                .mapMarker(true)
                .zoom(9)
                .overviewMapControl(false)
                .panControl(false)
                .rotateControl(false)
                .scaleControl(false)
                .streetViewControl(false)
                .zoomControl(false)
                .mapType(MapTypeIdEnum.ROADMAP);

        map = mapComponent.createMap(options);

        //Add a couple of markers to the map.
        MarkerOptions markerOptions = new MarkerOptions();
        LatLong markerLatLong = new LatLong(47.606189, -122.335842);
        markerOptions.position(markerLatLong)
                .title("My new Marker")
                .animation(Animation.DROP)
                .visible(true);

        Marker myMarker = new Marker(markerOptions);

        MarkerOptions markerOptions2 = new MarkerOptions();
        LatLong markerLatLong2 = new LatLong(47.906189, -122.335842);
        markerOptions2.position(markerLatLong2)
                .title("My new Marker")
                .visible(true);

        Marker myMarker2 = new Marker(markerOptions2);

        map.addMarker(myMarker);
        map.addMarker(myMarker2);

        //Add an info window to the Map.
        InfoWindowOptions infoOptions = new InfoWindowOptions();
        infoOptions.content("<h2>Center of the Universe</h2>")
                .position(center);

        InfoWindow window = new InfoWindow(infoOptions);
        window.open(map, myMarker);

    }    

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 300, Short.MAX_VALUE)
        );
    }// </editor-fold>                        

    // Variables declaration - do not modify                     
    // End of variables declaration                   
    @Override
    public void componentOpened() {
        // TODO add custom code on component opening
    }

    @Override
    public void componentClosed() {
        // TODO add custom code on component closing
    }

    void writeProperties(java.util.Properties p) {
        // better to version settings since initial version as advocated at
        // http://wiki.apidesign.org/wiki/PropertyFiles
        p.setProperty("version", "1.0");
        // TODO store your settings
    }

    void readProperties(java.util.Properties p) {
        String version = p.getProperty("version");
        // TODO read your settings according to their version


       }
    }

What is important to note is that no map related classes can be instantiated until the GoogleMapView has been initialized. This is because the underlying JavaScript peer objects can’t be created until the JavaScript runtime has been initialized. The TopComponent is added as a MapComponentInitializedListener so that it can determine when it is safe to begin manipulating the map and its associated objects.

This module is now ready to be included as a dependency in a NetBeans application in development, or be added as a plug-in to an existing Netbeans RCP application at runtime, or even added to the IDE itself.

One word of caution: The underlying JavaFX WebView and Javascript runtime which the GoogleMapView component is making use of to render the map appears to be a memory hog. I have had to play with memory settings in order to avoid OutOfMemoryErrors, so something to keep in mind as you play with this.

Below is the final product of running the GMapsFX plug-in within a NetBeans RCP application.

enter image description here

The GMapsFX project is open source with the project home at GitHub as mentioned above and can be accessed at:
http://rterp.github.io/GMapsFX/

twitter: @RobTerp

Adding Custom JavaFX Components to Scene Builder 2.0

With the release of Scene Builder 2.0 it is now much easier to add custom and 3rd party JavaFX controls to the component palette.

There are a couple of different strategies to add a custom component to Scene Builder, and this blog post will illustrate creating a custom component with FXML and importing the FXML file into Scene Builder so the custom component can be used within Scene Builder itself.

To start with, I’ll create a simple custom component in Scene Builder which will consist of a Button and a Label, which I have cleverly named a ButtonLabel. A screenshot of Scene Builder with the fancy new custom component is below.

enter image description here

Below is the FXML that SceneBuilder generates. As you can see I have an action defined on the button, but no controller defined for the component. This is because the button will end up pointing to whatever controller the Scene that this component gets bundled into. The other thing to note is that this FXML file contains no references to stylesheets or outside images.

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml" fx:controller="">
    <children>
        <Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
        <Label layoutX="126" layoutY="120" minHeight="16" minWidth="70" fx:id="label" />
    </children>
</AnchorPane>

Now to add the new component to Scene Builder so it can be added to an application, you must import the FXML file into Scene Builder by selecting the icon next to the search box at the top of the Library Pane.

enter image description here

Browse the the FXML file of the custom component.
enter image description here

The ButtonLabel now appears in the Custom section of the library and can be dragged onto the new Scene.
enter image description here

Below is the code that is generated by Scene Builder for the new Scene with the LabelButton. As can be viewed below, Scene Builder simply imports the custom FXML code into the new scene, and also adds the #handleButtonAction to the com.lynden.myapp.FXMLController class.

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="379.0" prefWidth="442.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="com.lynden.myapp.FXMLController">
    <children>
        <TableView layoutX="40.0" layoutY="28.0" prefHeight="200.0" prefWidth="356.0">
            <columns>
                <TableColumn prefWidth="75.0" text="C1" />
                <TableColumn prefWidth="75.0" text="C2" />
            </columns>
        </TableView>
        <AnchorPane id="AnchorPane" layoutX="108.0" layoutY="256.0" prefHeight="109.0" prefWidth="220.0">
            <children>
                <Button fx:id="button" layoutX="172.0" layoutY="43.0" onAction="#handleButtonAction" text="Click Me!" AnchorPane.rightAnchor="10.0" />
                <Label fx:id="label" layoutX="14.0" layoutY="31.0" minHeight="16" minWidth="69" prefHeight="47.0" prefWidth="100.0" text="Custom Text" />
            </children>
        </AnchorPane>
    </children>
</AnchorPane>

twitter: @RobTerp

GMapsFX Version 1.1.0

GMapsFX 1.1.0 has been released and supports the following new features and bug fixes.

We are at also looking at breaking the Java-to-JavaScript abstraction layer out of this project and creating its own project for it, as this layer could prove useful for interacting with other JavaScript applications within a JavaFX WebView.

We will have project artifacts published to Maven Central in the very near future.

The latest GMapsFX.jar file can be found here

Project documentation can be found here and with Javadocs located here

GMapsFX Version 1.0.0

GMapsFX 1.0.0 has been released and supports the following features.

  • Markers
  • Marker Animations
  • Info Windows
  • Polylines
  • Shapes
    • Circles
    • Polygons
    • Rectangles
  • Map State Events
    • Bounds Changed
    • Center Changed
    • Drag
    • Drag End
    • Drag Start
    • Heading Changed
    • Idle
    • MapTypeId Changed
    • Projection Changed
    • Resize
    • Tiles Loaded
    • Tilt Changed
    • Zoom Changed
  • Map UI Events
    • Click
    • Double Click
    • Mouse Move
    • Mouse Up
    • Mouse Down
    • Mouse Over
    • Mouse Out
    • Right Click

Below is a sample map displaying some of the new features including markers, a polyline, an info window, a rectangle, and a circle, as well as events which are monitoring the lat/long position of the center of the map as it is dragged around and updates the value at the top of the UI.

 

enter image description here

Special thanks to Geoff Capper who contributed the shape and event handling code for the latest version.

The GMapsFX.jar file can be found here
Project documentation can be found here and with Javadocs located here

Written with StackEdit.

GMapsFX :: Add Google Maps to your JavaFX application.

We have been considering adding a map component to our Freight Management application built on the NetBeans RCP and JavaFX, which would allow Lynden dispatchers to track its drivers throughout the city as well as highlight where trailers have been dropped off at customer locations and are ready for pickup.

Google Maps is a logical tool which could be utilized to accomplish this task. While there are examples out on the web for integrating Google Maps with JavaFX, these solutions require mingling JavaScript within the Java code in order to interact with a Google Map loaded within the application.

In an effort to remove the need to code JavaScript within JavaFX in order to use Google Maps, I have created a Java API ‘wrapper’ around the Google Maps JavaScript API and have dubbed this framework GMapsFX. This allows one to add a Google Map component to a JavaFX application and interact with it utilizing a pure Java API.

While at the present time only the most basic Google Map functionality has been ‘wrapped’ by the Java API, I am making this project open source in the hopes that if others find this library useful and require additional functionality, that it could be added and contributed back to the community.

The project can be found on GitHub at:
http://rterp.github.io/GMapsFX/

with the JavaDocs available at:
http://rterp.github.io/GMapsFX/apidocs/

Below is an example of using GMapsFX to add a map component to a Scene, setting the location to Seattle, and then adding a Marker to the map.

package com.lynden.gmapsexampleapp;

import com.lynden.gmapsfx.GoogleMapView;
import com.lynden.gmapsfx.MapComponentInitializedListener;
import com.lynden.gmapsfx.javascript.object.GoogleMap;
import com.lynden.gmapsfx.javascript.object.LatLong;
import com.lynden.gmapsfx.javascript.object.MapOptions;
import com.lynden.gmapsfx.javascript.object.MapType;
import com.lynden.gmapsfx.javascript.object.Marker;
import com.lynden.gmapsfx.javascript.object.MarkerOptions;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Scene;
import javafx.stage.Stage;


public class MainApp extends Application implements MapComponentInitializedListener {

GoogleMapView mapView;
GoogleMap map;

@Override
public void start(Stage stage) throws Exception {

    //Create the JavaFX component and set this as a listener so we know when 
    //the map has been initialized, at which point we can then begin manipulating it.
    mapView = new GoogleMapView();
    mapView.addMapInializedListener(this);

    Scene scene = new Scene(mapView);

    stage.setTitle("JavaFX and Google Maps");
    stage.setScene(scene);
    stage.show();
}


@Override
public void mapInitialized() {
    //Set the initial properties of the map.
    MapOptions mapOptions = new MapOptions();

    mapOptions.center(new LatLong(47.6097, -122.3331))
            .mapType(MapType.ROADMAP)
            .overviewMapControl(false)
            .panControl(false)
            .rotateControl(false)
            .scaleControl(false)
            .streetViewControl(false)
            .zoomControl(false)
            .zoom(12);

    map = mapView.createMap(mapOptions);

    //Add a marker to the map
    MarkerOptions markerOptions = new MarkerOptions();

    markerOptions.position( new LatLong(47.6, -122.3) )
                .visible(Boolean.TRUE)
                .title("My Marker");

    Marker marker = new Marker( markerOptions );

    map.addMarker(marker);

}

public static void main(String[] args) {
    launch(args);
}
}

 


 

The code above produces the following result

enter image description here

 

twitter: @RobTerp

Written with StackEdit.

My Interview with TechTarget.com on JavaFX, Swing, and the NetBeans Rich Client Platform

I sat down with Jan Stafford of Tech Target at JavaOne in September and discussed our experiences with beginning to migrate to JavaFX from Swing while continuing to use the NetBeans Rich Client Platform (RCP) as the foundation for our application.

I discuss some of the pros of using JavaFX and the NetBeans RCP, such as having the ability to develop very polished looking components using effects such as drop shadows, reflections, and gradients  which may have been do-able in Swing, but were extremely painful and time consuming.  These effects however are baked into the JavaFX framework.

The largest negative at this point has been the learning curve with getting up to speed on Cascading Style Sheets.  Since much of the look and feel of the UI can be applied via CSS, it took a bit of work to learn how to use the style sheets to properly style the UI components.  However, the benefit to using CSS is that the work can be given to a UX/UI developer rather than a Java developer, thus truly decoupling the UI development from the business logic development.

A link to the interview on Tech Target’s website is below.

Interview with Tech Target: JavaFX beats Swing: Changing RIA development platform

techTargetInterview

twitter: @RobTerp

Drag and Drop With Custom Components in JavaFX

In my last post I showed how to create custom JavaFX components with Scene Builder and FXML and how to add the custom components to a new Scene.  The screenshot below displays the custom TableView component where each row represents data about a shipping container, with a custom “Add Plan” component on the right side of the UI to add a specified container to a plan.

The functionality of this test application will now be extended so that the user can select a row from the table and drag it over to the “Add Plan” component where they can drop it. When the drop event occurs the application will show a dialog displaying the value of the “VFC #” field from the row that was dragged from the container table.

screen2Additionally, when the user selects the row and begins dragging the cursor, we would like the application to display a translucent image of a shipping container (pictured to the left).  Once the cursor is over the Add Plan component, the component will be set with a drop shadow effect, in addition to changing the cursor to indicate to the user that the Add Plan component will accept the drop

The Drop Source (TestTable class)

The drop source for this example is the TestTable class from the previous post.  I will show sections from this class below, and then will include the entire class at the end of this post for reference.

Below is the TestTable class which contains a TableView with the shipping container information.  The first modification to this class was to add a parentProperty listener so that we know when this component is added to its parent node.  We will need the parent node to register for drag events that will occur when the user drags outside of the bounds of this container.  Once we have the parent we can register the drag listeners.

public class TestTable extends AnchorPane {

    @FXML
    private TableView myTableView;
    private ImageView dragImageView;
    private Parent root;

    public TestTable() {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/com/lynden/planning/ui/TestTable.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();

            parentProperty().addListener(new ChangeListener() {
                @Override
                public void changed(ObservableValue<? extends Parent> ov, Parent oldP, Parent newP) {
                    root = newP;
                    registerDragEvent();
                }
            });
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }

The first step in the registerDragEvent() method is to read in the image of the shipping container that we want to display to the user and scale it down to 100×100.  Next, we can register the OnDragDetectedEvent for the TableView.  Once this listener detects a DragEvent it adds the ImageView to the Scene if it hasn’t already been added.  It then sets the opacity, sets MouseTransparent to true, which allows the mouse events to be passed to the components underneath it, and then displays the image.

The DragBoard is then fetched for the TableView, at which time we determine what row was selected, and put the value from the VFC# column as the content of the DragBoard.

 protected void registerDragEvent() {
        Image image = new Image(getClass().getResourceAsStream("/com/lynden/planning/ui/container2.png"));
        dragImageView = new ImageView(image);
        dragImageView.setFitHeight(100);
        dragImageView.setFitWidth(100);

        myTableView.setOnDragDetected(new EventHandler() {
            @Override
            public void handle(MouseEvent t) {

                AnchorPane anchorPane = (AnchorPane) myTableView.getScene().getRoot();

                if (!anchorPane.getChildren().contains(dragImageView)) {
                    anchorPane.getChildren().add(dragImageView);
                }

                dragImageView.setOpacity(0.5);
                dragImageView.toFront();
                dragImageView.setMouseTransparent(true);
                dragImageView.setVisible(true);
                dragImageView.relocate(
                        (int) (t.getSceneX() - dragImageView.getBoundsInLocal().getWidth() / 2),
                        (int) (t.getSceneY() - dragImageView.getBoundsInLocal().getHeight() / 2));

                Dragboard db = myTableView.startDragAndDrop(TransferMode.ANY);
                ClipboardContent content = new ClipboardContent();

                InboundBean inboundBean = (InboundBean) myTableView.getSelectionModel().getSelectedItem();
                content.putString(inboundBean.getVfcNumber());
                db.setContent(content);

                t.consume();
            }
        });

Next, we add the OnDragOver event handler to this component’s Parent.  This listener will re-position the shipping container image as the mouse is dragged around the UI.  If we just add the event handler to the TableView component, the position of the shipping container image will not be updated once the user drags the cursor outside of the TableView’s bounds.

//Add the drag over listener to this component's PARENT, so that the drag over events will be processed even
//after the cursor leaves the bounds of this component.
root.setOnDragOver(new EventHandler() {
    public void handle(DragEvent e) {
	Point2D localPoint = myTableView.getScene().getRoot().sceneToLocal(new Point2D(e.getSceneX(), e.getSceneY()));
	dragImageView.relocate(
		(int) (localPoint.getX() - dragImageView.getBoundsInLocal().getWidth() / 2),
		(int) (localPoint.getY() - dragImageView.getBoundsInLocal().getHeight() / 2));
	e.consume();
    }
});

The last step for the drop source is to add the OnDragDone event handler so that the shipping container image disappears when the user releases the mouse button.

myTableView.setOnDragDone(new EventHandler() {
    public void handle(DragEvent e) {
	dragImageView.setVisible(false);
	e.consume();
    }
});

The Drop Target (TestButton class)

The TestButton class is initialized in a similar fashion to the TestTable class.  The main difference is that the TestButton doesn’t care about its parent node, and we can just register for the drop events straight from the constructor without having to wait for the component’s parent to be set.

public class TestButton extends AnchorPane {

    @FXML
    private AnchorPane myTestButton;

    public TestButton() {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/com/lynden/planning/ui/TestButton.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }

        registerDropEvent();
    }

The first thing we would like to do is to register for the OnDragOver event so that we can give feedback to the user that this component is a valid drop target.  The acceptTransferModes(TransferMode.ANY) will change the cursor to indicate the component accepts a drop event as well as adding a drop shadow to the component to provide a bit of highlighting

  
private void registerDropEvent() {

myTestButton.setOnDragOver(new EventHandler() {
    @Override
    public void handle(DragEvent t) {
	t.acceptTransferModes(TransferMode.ANY);
	DropShadow dropShadow = new DropShadow();
	dropShadow.setRadius(5.0);
	dropShadow.setOffsetX(3.0);
	dropShadow.setOffsetY(3.0);
	dropShadow.setColor(Color.color(0.4, 0.5, 0.5));
	myTestButton.setEffect(dropShadow);
	//Don't consume the event.  Let the layers below process the DragOver event as well so that the
	//translucent container image will follow the cursor.
	//t.consume();
    }
});

If the user exits the drop target area without releasing the mouse button the drop shadow effect is cleared from the component by setting the test button’s effect to null.

 
myTestButton.setOnDragExited(new EventHandler() {
    @Override
    public void handle(DragEvent t) {
	myTestButton.setEffect(null);
	t.consume();
    }
});

Finally, if the user does release the mouse button over the target, we will read the content from the Dragboard and display the results in a JOptionPane (used for simplicity sake).

myTestButton.setOnDragDropped(new EventHandler() {
    @Override
    public void handle(DragEvent t) {
	Dragboard db = t.getDragboard();
	final String string = db.getString();
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		JOptionPane.showMessageDialog(null, "Creating a New Plan For Container #: " + string);
	    }
	});
	t.setDropCompleted(true);
    }
});

Results

Below are some screenshots of the drag and drop in action.
Selecting a row and begin dragging displays translucent the shipping container image.

Dragging cursor over the Add Plan component causes the component to be highlighted with a DropShadow effect.
screen4

Releasing the mouse button over the Add Plan component will display a dialog containing the VFC# of the container row that was selected from the TableView.
screen5

For reference I have included both the TestTable and TestButton classes below.

 

twitter: @RobTerp

TestTable class

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.lynden.fx.test;

import com.lynden.fx.InboundBean;
import java.io.IOException;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Point2D;
import javafx.scene.Parent;
import javafx.scene.control.TableView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane;

/**
 *
 * @author ROBT
 */
public class TestTable extends AnchorPane {

    @FXML
    private TableView myTableView;
    private ImageView dragImageView;
    private Parent root;

    public TestTable() {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/com/lynden/planning/ui/TestTable.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();

            parentProperty().addListener(new ChangeListener() {
                @Override
                public void changed(ObservableValue<? extends Parent> ov, Parent oldP, Parent newP) {
                    root = newP;
                    registerDragEvent();
                }
            });
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }

    protected void registerDragEvent() {
        Image image = new Image(getClass().getResourceAsStream("/com/lynden/planning/ui/container2.png"));
        dragImageView = new ImageView(image);
        dragImageView.setFitHeight(100);
        dragImageView.setFitWidth(100);

        myTableView.setOnDragDetected(new EventHandler() {
            @Override
            public void handle(MouseEvent t) {

                AnchorPane anchorPane = (AnchorPane) myTableView.getScene().getRoot();

                if (!anchorPane.getChildren().contains(dragImageView)) {
                    anchorPane.getChildren().add(dragImageView);
                }

                dragImageView.setOpacity(0.5);
                dragImageView.toFront();
                dragImageView.setMouseTransparent(true);
                dragImageView.setVisible(true);
                dragImageView.relocate(
                        (int) (t.getSceneX() - dragImageView.getBoundsInLocal().getWidth() / 2),
                        (int) (t.getSceneY() - dragImageView.getBoundsInLocal().getHeight() / 2));

                Dragboard db = myTableView.startDragAndDrop(TransferMode.ANY);
                ClipboardContent content = new ClipboardContent();

                InboundBean inboundBean = (InboundBean) myTableView.getSelectionModel().getSelectedItem();
                content.putString(inboundBean.getVfcNumber());
                db.setContent(content);

                t.consume();
            }
        });

        //Add the drag over listener to this component's PARENT, so that the drag over events will be processed even
        //after the cursor leaves the bounds of this component.
        root.setOnDragOver(new EventHandler() {
            public void handle(DragEvent e) {
                Point2D localPoint = myTableView.getScene().getRoot().sceneToLocal(new Point2D(e.getSceneX(), e.getSceneY()));
                dragImageView.relocate(
                        (int) (localPoint.getX() - dragImageView.getBoundsInLocal().getWidth() / 2),
                        (int) (localPoint.getY() - dragImageView.getBoundsInLocal().getHeight() / 2));
                e.consume();
            }
        });

        myTableView.setOnDragDone(new EventHandler() {
            public void handle(DragEvent e) {
                dragImageView.setVisible(false);
                e.consume();
            }
        });
    }
}

TestButton Class

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.lynden.fx.test;

import java.io.IOException;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

/**
 *
 * @author ROBT
 */
public class TestButton extends AnchorPane {

    @FXML
    private AnchorPane myTestButton;

    public TestButton() {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/com/lynden/planning/ui/TestButton.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }

        registerDropEvent();
    }

    private void registerDropEvent() {

        myTestButton.setOnDragOver(new EventHandler() {
            @Override
            public void handle(DragEvent t) {
                t.acceptTransferModes(TransferMode.ANY);
                DropShadow dropShadow = new DropShadow();
                dropShadow.setRadius(5.0);
                dropShadow.setOffsetX(3.0);
                dropShadow.setOffsetY(3.0);
                dropShadow.setColor(Color.color(0.4, 0.5, 0.5));
                myTestButton.setEffect(dropShadow);
                //Don't consume the event.  Let the layers below process the DragOver event as well so that the
                //translucent container image will follow the cursor.
                //t.consume();
            }
        });

        myTestButton.setOnDragExited(new EventHandler() {
            @Override
            public void handle(DragEvent t) {
                t.acceptTransferModes(TransferMode.ANY);
                myTestButton.setEffect(null);
                t.consume();
            }
        });

        myTestButton.setOnDragDropped(new EventHandler() {
            @Override
            public void handle(DragEvent t) {
                Dragboard db = t.getDragboard();
                final String string = db.getString();
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        JOptionPane.showMessageDialog(null, "Creating a New Plan For Container #: " + string);
                    }
                });
                t.setDropCompleted(true);
            }
        });

    }
}