Documentation



JavaFX: Handling Events

8 PaperDoll Drag-and-Drop Application

This chapter further illustrates the drag-and-drop feature using the PaperDoll application.

The basic principles explained in the Drag-and-Drop Operation are used here in a more advanced application that enables the user to drag the images of dresses and drop them on the image of a paper doll, and drag the images of dresses from the image of a paper doll.

Layout of the PaperDoll Application

The PaperDoll application displays four images that represent dresses (pieces of clothing) and a paper doll that participate in a drag-and-drop operation. The application window is shown in Figure 8-1.

Figure 8-1 Paper Doll Application

Description of Figure 8-1 follows
Description of "Figure 8-1 Paper Doll Application"

The graphical scene of the application consists of two parts:

  • A VBox object is displayed in the upper part of the window. It contains an image and the Paper Doll text, and is used for decoration only.

  • A GridPane object is displayed in the bottom part of the window.

    • The first column contains a FlowPane object with the images of clothing.

    • The second column contains a Pane object with an image of a paper doll.

The images of the clothing can be dragged and dropped on an image of a paper doll and back to their original locations. The PaperDoll application provides an example of a drag-and-drop operation in which the same object can be both the source and the target of the operation.

Organization of the PaperDoll Application

The PaperDoll application contains the following packages and classes:

  • PaperDoll.java is the main application class, which lays out the user interface (UI) elements and implements the application logic.

  • paperdoll.body contains classes that define a container for the body that accepts drops of the data.

  • paperdoll.clothes contains classes that define a draggable piece of clothing.

  • paperdoll.images contains the graphical resources for the application.

Note:

This chapter does not provide a step-by-step procedure to build the PaperDoll application.

You can download the PaperDoll.zip to see the completed NetBeans project.

The UI of the PaperDoll application is created as shown in Example 8-1.

Example 8-1

package paperdoll;

import paperdoll.clothes.Cloth;
import paperdoll.clothes.ClothListBuilder;
import paperdoll.body.Body;
import paperdoll.images.ImageManager;
import java.util.HashMap;
import java.util.List;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;


public class PaperDoll extends Application {
 
    public static void main(String[] args) {
        launch(args);
    }
    
    /**
     * All laying out goes here.
     * @param primaryStage 
     */
    
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Paper Doll");
        
        ImageView header = new ImageView(ImageManager.getImage("ui/flowers.jpg")); 
        VBox title = new VBox();
        title.getChildren().addAll(header);
        title.setPadding(new Insets(10.0));
        
        GridPane content = new GridPane();
        content.add(Body.getBody().getNode(), 1, 1);
        content.add(createItemPane(Body.getBody().getBodyPane()), 0, 1);
        ColumnConstraints c1 = new ColumnConstraints();
        c1.setHgrow(Priority.ALWAYS);
        ColumnConstraints c2 = new ColumnConstraints();
        c2.setHgrow(Priority.NEVER);
        c2.setPrefWidth(Body.getBody().getBodyPane().getMinWidth() + 20);
        content.getColumnConstraints().addAll(c1, c2);
        
        items = new HashMap<>();
        Body.getBody().setItemsInfo(itemPane, items);
        populateClothes();
        
        VBox root = new VBox();
        root.getChildren().addAll(title, content);
        primaryStage.setScene(new Scene(root, 800, 900));
        primaryStage.setMinWidth(800);
        primaryStage.setMinHeight(900);
        primaryStage.show();
    }
    
    private FlowPane itemPane = null;
    
    private HashMap<String, Cloth> items;
    
    /**
     * A container for unequipped items is created here.
     * @param bodyPane body container is needed so that the item is removed from
     * it when dropped here.
     * @return 
     */
    private FlowPane createItemPane(final Pane bodyPane) {
    // code for creating the itemPane container
    
    }

    private void populateClothes() {
    //code for adding items to the itemPane container
    }
}

The itemPane object represents separate pieces of clothing and the bodyPane object represents the body of the doll with pieces of clothing that can be put on.

Starting the Drag-And-Drop Operation

The source for the drag-and-drop operation is one of the ImageView objects that represent a Cloth item. At any moment, each currentImage is either a node in the itemPane or in the bodyPane. The setOnDragDetected method is implemented as shown in bold in Example 8-2.

Example 8-2

public class Cloth {
    
    private final Image previewImage;
    private final Image activeImage;
    private final Image equippedImage;
    
    private final ImageView currentImage;
    
    public void putOn() {
        currentImage.setImage(equippedImage);
    }
    
    public void takeOff() {
        currentImage.setImage(previewImage);
    }
    
    private void activate() {
        currentImage.setImage(activeImage);
    }
    
    public String getImageViewId() {
        return currentImage.getId();
    }
    
    public Node getNode() {
        return currentImage;
    }
    
    public Cloth(Image[] images) {
        this.previewImage = images[0];
        this.activeImage = images[1];
        this.equippedImage = images[2];
        
        currentImage = new ImageView();
        currentImage.setImage(previewImage);
        currentImage.setId(this.getClass().getSimpleName() + System.currentTimeMillis());
        
        currentImage.setOnDragDetected((MouseEvent event) -> {
            activate();
            Dragboard db = currentImage.startDragAndDrop(TransferMode.MOVE);
            ClipboardContent content = new ClipboardContent();
            // Store node ID in order to know what is dragged.
            content.putString(currentImage.getId());
            db.setContent(content);
            event.consume();
        });
    }
}

Note the usage of a lambda expression in this example. The setOnDragDetected method starts the drag-and-drop gesture that supports only the MOVE transfer mode by calling the startDragAndDrop(TransferMode.MOVE) method.

Handling the Drop of the Data

The target of the drag-and-drop gesture can be either the itemPane or the bodyPane object depending on where the drag-and-drop gesture was started, which means that the setOnDragOver and setOnDragDropped methods must be implemented for the both objects, itemPane and bodyPane.

As described earlier, the itemPane object is created in the PaperDoll.java class. Example 8-3 complements the code in Example 8-1, and provides the complete code to create the itemPane container.

Example 8-3

/**
     * A container for unequipped items is created here.
     * @param bodyPane body container is needed so that an item is removed from
     * the bodyPane when dropped on the itemPane.
     * @return 
     */
    private FlowPane createItemPane(final Pane bodyPane) {
        if (!(itemPane == null))
            return itemPane;
        
        itemPane = new FlowPane();
        itemPane.setPadding(new Insets(10.0));
        
        itemPane.setOnDragDropped((DragEvent event) -> {
            Dragboard db = event.getDragboard();
            // Get item id here, which was stored when the drag started.
            boolean success = false;
            // If this is a meaningful drop...
            if (db.hasString()) {
                String nodeId = db.getString();
                // ...search for the item on body. If it is there...
                ImageView cloth = (ImageView) bodyPane.lookup("#" + nodeId);
                if (cloth != null) {
                    // ... the item is removed from body
                    // and added to an unequipped container.
                    bodyPane.getChildren().remove(cloth);
                    itemPane.getChildren().add(cloth);
                    success = true;
                }
                // ...anyway, the item is not active or equipped anymore.
                items.get(nodeId).takeOff();
            }
            event.setDropCompleted(success);
            event.consume();
        });
        
        itemPane.setOnDragOver((DragEvent event) -> {
            if (event.getGestureSource() != itemPane &&
                    event.getDragboard().hasString()) {
                event.acceptTransferModes(TransferMode.MOVE);
            }
            event.consume();
        });        
        
        return itemPane;
    }
    
    /**
     * Here items are added to an unequipped items container.
     */
    private void populateClothes() {
        ClothListBuilder clothBuilder = new ClothListBuilder();
        if (itemPane == null)
            throw new IllegalStateException("Should call getItems() before populating!");
        List<Cloth> clothes = clothBuilder.getClothList();
        clothes.stream().map((c) -> {
            itemPane.getChildren().add(c.getNode());
            return c;
        }).forEach((c) -> {
            items.put(c.getImageViewId(), c);
        });
    }

Note that the itemPane.setOnDrageOver method must accept the transfer mode only if the source of the drag-and-drop gesture was not the itemPane object itself and the dragboard contains a string.

The itemPane.setOnDragDropped method is called when the mouse button is released over the itemPane object, which accepted the previous DRAG_OVER event. It is here that the draggable piece of clothing is added to the itemPane container and removed from the bodyPane object, and the drag-and-drop gesture is completed by calling the setDropCompleted (Boolean) method on the event.

Similarly, the setOnDragOver and setOnDragDropped methods for the bodyPane container are implemented as shown in Example 8-4.

Example 8-4

bodyPane.setOnDragDropped((DragEvent event) -> {
    Dragboard db = event.getDragboard();
    boolean success = false;
    // If this is a meaningful drop...
    if (db.hasString()) {
        // Get an item ID here, which was stored when the drag started.
        String nodeId = db.getString();
        // ...search for the item in unequipped items. If it is there...
        ImageView cloth = (ImageView) itemPane.lookup("#" + nodeId);
        if (cloth != null) {
            // ... the item is removed from the unequipped list
            // and attached to body.
            itemPane.getChildren().remove(cloth);
            bodyPane.getChildren().add(cloth);
            cloth.relocate(0, 0);
            success = true;
        }
        // ...anyway, the item is now equipped.
        items.get(nodeId).putOn();
    }
    event.setDropCompleted(success);
    event.consume();
});

bodyPane.setOnDragOver((DragEvent event) -> {
    if (event.getGestureSource() != bodyImage &&
            event.getDragboard().hasString()) {
        event.acceptTransferModes(TransferMode.MOVE);
    }
    event.consume();
});

Example 8-5 shows the complete code for the BodyElement class.

Example 8-5

package paperdoll.body;
 
import paperdoll.clothes.Cloth;
import paperdoll.images.ImageManager;
import java.util.Map;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.image.ImageView;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Pane;
 
/**
 * Container for body that accepts drops. Draggable details dropped here
 * are equipped.
 * 
 */
 
public class BodyElement {
    private final Pane bodyPane;
    private final ImageView bodyImage;
    private Pane itemPane;
    private Map<String, Cloth> items;
    
    public void setItemsInfo(Pane p, Map<String, Cloth> m) {
        itemPane = p;
        items = m;
    }
 
    public Pane getBodyPane() {
        return bodyPane;
    }
    
    public BodyElement() {
        bodyPane = new Pane();
        bodyImage = new ImageView(ImageManager.getResource("body.png"));
        
        bodyPane.setOnDragDropped((DragEvent event) -> {
            Dragboard db = event.getDragboard();
            boolean success = false;
            // If this is a meaningful drop...
            if (db.hasString()) {
                // Get an item ID here, which was stored when the drag started.
                String nodeId = db.getString();
                // ... search for the item in unequipped items. If it is there...
                ImageView cloth = (ImageView) itemPane.lookup("#" + nodeId);
                if (cloth != null) {
                    // ... the item is removed from the unequipped list
                    // and attached to body.
                    itemPane.getChildren().remove(cloth);
                    bodyPane.getChildren().add(cloth);
                    cloth.relocate(0, 0);
                    success = true;
                }
                // ...anyway, the item is now equipped.
                items.get(nodeId).putOn();
            }
            event.setDropCompleted(success);
            event.consume();
        });
        
        bodyPane.setOnDragOver((DragEvent event) -> {
            if (event.getGestureSource() != bodyImage &&
                    event.getDragboard().hasString()) {
                event.acceptTransferModes(TransferMode.MOVE);
            }
            event.consume();
        });
        
        bodyPane.getChildren().add(bodyImage);
        bodyPane.setMinWidth(bodyImage.getImage().getWidth());
        bodyPane.setPadding(new Insets(10.0));
    }
    
    public Node getNode() {
        return bodyPane;
    }
}

Application Files

Source Code 

Netbeans Projects 

Close Window

Table of Contents

JavaFX: Handling Events

Expand | Collapse