griest
griest

Reputation: 410

Getting a MouseEvent to target ImageViews inside a TilePane

I have many ImageViews inside a TilePane, that is inside a StackPane and then a ScrollPane. There is no border, padding, or margin between the children of the TilePane so there is no chance that I'm not clicking on an ImageView. When I click on an image, I want the target of the MouseEvent to be the ImageViews, but instead it is the TilePane.

How can I get the event chain to end on an ImageView instead of ending early on the TilePane?

Otherwise, is there a way I can get the ImageView using other information? Perhaps using the coordinates of the event?

Upvotes: 2

Views: 3630

Answers (1)

James_D
James_D

Reputation: 209368

The usual way I do this is just to register the mouse listener with the node in which I am interested; in your case this means register a mouse listener with each ImageView. It's easy then to have each mouse listener have a reference to the particular image view with which it's registered, or to other data (e.g. a filename) if you need.

One thing that might be happening: if your images have transparent pixels, then mouse clicks on that part of the image will by default "drop through" to the node below. You can change this behavior by calling imageView.setPickOnBounds(true); on the image views.

Some test code. If you run this you'll see some numbered images with different colored backgrounds. About 1 in 4 have transparent backgrounds (they appear white). If you click on these (but not on the actual text of the number), you'll see the mouse handlers registered with the scroll pane and stack pane have the tile pane as the target, and the handler registered with the ImageView is not even invoked. For those without the transparent background, the target is always the ImageView. If you select the check box, so pickOnBounds is true for all the ImageViews, both transparent and opaque images behave as you want.

import java.util.Random;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class ImageViewClickTest extends Application {

    private static final Random RNG = new Random();

    @Override
    public void start(Stage primaryStage) {
        TilePane tilePane = new TilePane();

        CheckBox pickOnBounds = new CheckBox("Pick on bounds");
        pickOnBounds.setPadding(new Insets(16));

        for (int i=1; i<=200; i++) {
            ImageView imageView = createImageView(i);

            imageView.pickOnBoundsProperty().bind(pickOnBounds.selectedProperty());

            // mouse handler directly on image view:
            // can access image-view specific data...
            String message = "Clicked on Image "+i ;
            imageView.setOnMouseClicked(e -> 
                System.out.println("From handler on ImageView: "+message));


            tilePane.getChildren().add(imageView);
        }
        StackPane stack = new StackPane(tilePane);

        stack.setOnMouseClicked(e -> {
            // source will be the stack pane
            // target will be the top-most node 
            // (i.e. the ImageView, in most cases)
            System.out.println("From handler on stack pane: Source: "+e.getSource());
            System.out.println("From handler on stack pane: Target: "+e.getTarget());
        });


        ScrollPane scroller = new ScrollPane(stack);
        scroller.setFitToWidth(true);

        scroller.setOnMouseClicked(e -> {
            // source will be the scroll pane
            // target will be the top-most node 
            // (i.e. the ImageView, in most cases)
            System.out.println("From handler on scroller: Source: "+e.getSource());
            System.out.println("From handler on scroller: Target: "+e.getTarget());
        });

        BorderPane root = new BorderPane(scroller, pickOnBounds, null, null, null);

        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private ImageView createImageView(int index) {
        Label label = new Label(Integer.toString(index));
        label.setAlignment(Pos.CENTER);
        label.setMinSize(48, 48);
        label.setStyle(randomStyle());
        Image image = new Scene(label, Color.TRANSPARENT).snapshot(null);
        ImageView imageView = new ImageView(image);
        return imageView ;
    }

    private String randomStyle() {
        StringBuilder style = new StringBuilder();
        style.append("-fx-background-color: -fx-background;");
        style.append("-fx-background: ");
        if (RNG.nextDouble() < 0.25) {
            style.append( "transparent;");
            style.append(" -fx-text-fill: black;") ;
        } else {
            String bg = String.format("#%02x%02x%02x;", 
                    RNG.nextInt(256), RNG.nextInt(256), RNG.nextInt(256));
            style.append(bg);
        }
        return style.toString();
    }

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

Upvotes: 4

Related Questions