1
votes

I have recently started exploring Java FX and want to create a custom label which will have an ImageView inside it.

This is the code for my custom label.

Image image = new Image(getClass().getResourceAsStream("/img/remove.png"), 20, 20, true, true);
ImageView removeImageView = new ImageView(image);

Label customLabel = new Label(labelText, removeImageView);
customLabel.setFont(Font.font("Arial", FontWeight.BOLD, 20));

This is how my custom label looks.

enter image description here

Now I want to add a mouse click EventHandler to the ImageView. This is my code for handling the mouse clicks.

removeImageView.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
      @Override
      public void handle(MouseEvent event) {
        System.out.println("Imageview Clicked");
      }
    });

But when I am clicking on the cross image, the event is not getting captured.

I experimented a bit and tried to add an EventHandler to the customLabel. The label was able to capture the mouse click.

It seems to me that I am facing this issue because the ImageView is contained within the Label. What I want to know is, is this a limitation to JFX or is there any alternative way to achieve this functionality. Thanks.

3
I don't see why this would not work. Where do you set the event handler? - Sedrick
@Sedrick I have set it just below the line: ImageView removeImageView = new ImageView(image); - Rito

3 Answers

7
votes

This appears to be a consequence of fixing JDK-8117199. The fix added the following to LabeledSkinBase#updateChildren():

// RT-19851 Only setMouseTransparent(true) for an ImageView.  This allows the button
// to be picked regardless of the changing images on top of it.
if (graphic instanceof ImageView) {
    graphic.setMouseTransparent(true);
}

As you can see, when the graphic of a Labeled control is an ImageView it is set to be mouse transparent. A workaround would be to set the mouse transparency of the ImageView back to false.

ImageView view = new ImageView();
view.mouseTransparentProperty().addListener((observable, oldVal, newVal) -> {
    if (newVal) {
        view.setMouseTransparent(false);
    }
});

Since you aren't using CSS to change the image based on hover/armed state this shouldn't cause the problems related to the bug. I'd be cautious, though; just in case.


A better workaround, mentioned by @fabian, is to wrap the ImageView in some other Node (e.g. a Pane).

Label customLabel = new Label(labelText, new Pane(removeImageView));

This makes the graphic a Pane which means the special ImageView handling in updateChildren() doesn't take place.

2
votes

Another workaround to ImageView being Mouse Transparent (See Slaw answer ), is to handle the event on ImageView parent (Lable) and re fire it :

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class PropegateEvent extends Application {

    @Override
    public void start(Stage stage) {

        Image image = new Image("https://addons-media.operacdn.com/media/extensions/65/165965/1.1.0.0-rev1/icons/icon_64x64_6dd346d4c61d287ae74c612622d1353f.png");
        ImageView removeImageView = new ImageView(image);
        Label customLabel = new Label("rito", removeImageView);
        Pane root = new Pane(customLabel);
        stage.setScene(new Scene(root));

        //////////////////////////////////////////////////////////////
        //handle mouse event click on label -  refire it as image view event:
        customLabel.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> removeImageView.fireEvent(event));
        ///////////////////////////////////////////////////////////////

        removeImageView.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
              @Override
              public void handle(MouseEvent event) {
                System.out.println("Imageview Clicked");
                event.consume();
              }
            });
        stage.show();
    }

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

One way to handle this situation is to use HBox. Instead of passing ImageView to Label's constructor set ImageView and Label as child nodes of HBox

ImageView removeImageView = new ImageView(image); 
Label customLabel = new Label("rito");
customLabel.setFont(Font.font("Arial", FontWeight.BOLD, 20));


HBox hbox = new HBox(removeImageView,customLabel);
root.getChildren().add(hbox);

removeImageView.setOnMouseClicked(e->{
            System.out.println("ImageView clicked");
        });


customLabel.setOnMouseClicked(e->{
            System.out.println("Lable clicked");
        });