JavaFX 8: Combining Language Features, Part 2
In my previous blog, I began the creation of a File Manager written in JavaFX 8, which is part of Java SE 8. In this installment, we'll add the ability to move files around via drag-and-drop (something JavaFX easily implements), and open files for viewing and editing using JavaFX controls. This is a work in progress, and we'll refine this over the next blog or two to make this functionality even more useful. Let's begin with moving files.
JavaFX Drag-and-Drop
Since many developers still think of Java for server-side duties only, you may not realize just how capable JavaFX is in terms of client functionality. This includes drag-and-drop, which is very easy with JavaFX. Every Node
object has the ability to be dragged from one container and dropped onto another. Although a lot of the mechanics are built-in to make it easy, you can still control what actually gets transferred.
For instance, in our File Manager application, I want to control what gets transferred depending on where a file is dropped: If it's dropped on a directory then the file itself gets transferred (moved) there via the filesystem. If the file is dropped on an editor (any editor configured on the running system), then I want the file contents to be transferred.
It turns out that implementing this differentiation is a cake walk with JavaFX, and Lambdas once again eliminate much of the code needed. Additionally, since drag-and-drop in JavaFX is built upon and analogous to mouse operations, the coding model is similar to what we've seen so far. Let's look at some of the changes in the code needed to support this (you can download the updated project here).
First, instead of adding Java String
s to each HeadList
— representing file and directory names — we need to change them to JavaFX Text
objects, which extend Node
. Here's the snippet of the method populateList()
with that change:
for (File file : files) { if ( ! file.isHidden() ) { if ( file.isFile() ) { Text source = new Text(file.getName()); results.add(source); } else if ( file.isDirectory() ) { Text target = new Text("<" + file.getName()+">"); results.add(target); } } }
Additionally, any place in the code that grabs items from the list and expects a String
needs to be changed to something like this:
Text item = (Text)list.getSelectionModel().getSelectedItem(); String filename = item.getText();
Overall, this is not a major change.
The drag-and-drop model in JavaFX involves handling a few steps each from the point of view of the source (the item being dragged) and the target (where the item is dragged to, or dropped on). Here are the steps to implement:
Source:
—Drag Detected: the first step when a Node
is dragged via the mouse
—Drag Done: the final step when the source node has been dropped on the target
Target:
—Drag Entered: occurs when the source first enters a drop target Node
—Drag Over: occurs repeatedly while the source is dragged over the target region
—Drag Exited: occurs when the source exits a drop target
—Drag Dropped: occurs when the source Node
is dropped via the mouse on the target Node
The methods to handle these steps, as DragEvent object, are implemented as EventHandlers
via Node
methods setOnDragDetected
, setOnDragDropped
, and so on. Here's the slight change required to the code snippet above to set the DragEvent
event handlers for the various Text Nodes created in our File Manager application:
if ( file.isFile() ) { Text source = new Text(file.getName()); source.setOnDragDetected(de -> { onDragDetected(de,source); }); source.setOnDragDone(de -> { onDragDone(de,source); }); results.add(source); } else if ( file.isDirectory() ) { Text target = new Text("<" + file.getName()+">"); target.setOnDragOver(de -> { onDragOver(de,target); }); target.setOnDragEntered(de -> { onDragEntered(de,target); }); target.setOnDragExited(de -> { onDragExited(de,target); }); target.setOnDragDropped(de -> { onDragDropped(de,target); }); results.add(target); }
The code to start the drag-and-drop process is in onDragDetected
, implemented for files (and not directories) in our File Manager application. Let's examine it now:
private void onDragDetected(MouseEvent me, Text source) { // start a drag-and-drop gesture HeaderList sourceList = getParentHeaderList(source); String sourcePath = constructPath( sourceList ) + stripDecorators( source.getText() ); Dragboard db = source.startDragAndDrop(TransferMode.ANY); // Put the file contents or filename into the clipboard ClipboardContent content = new ClipboardContent(); String fileContents = readFile( sourcePath ); if ( fileContents == null ) fileContents = source.getText(); content.putString(fileContents); db.setContent(content); me.consume(); }
First, we get a reference to the source Node
's list and construct the complete file path. Next, we get a reference to the JavaFX Dragboard object, used to manage the actual data that gets dragged. In addition to data, you can provide an image to be displayed with the mouse cursor as the drag-and-drop proceeds, and define how the data is transferred. For instance, is the data copied, moved, or is the source and target linked in some way as a result?