最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - Can you change the clickable region around a label in JavaFX? - Stack Overflow

programmeradmin0浏览0评论

In JavaFX (version 21), there exists CSS properties for changing the margins around labels. This is usually used to increase the space between the text in the label and its border.

However, it is possible to do the reverse by assigning negative numbers to these properties, in which case the distance between the text and the border shrinks. It's not very obvious why this would be useful if you're working with a well-behaved font, but there are some fonts that, by default, create really bizarre margins. The worst example that I have found is the font "Harlow Solid Italic", demonstrated here with exaggerated colors on a black background:

.test-label {
    -fx-background-color: blue;
    -fx-border-color: red;
    -fx-font-family: "Harlow Solid Italic";
    -fx-font-size: 100;
}

It's pretty plain to see the problem here.

As mentioned before, this can be adjusted by using the -fx-padding property like so:

.test-label {
    -fx-background-color: blue;
    -fx-border-color: red;
    -fx-font-family: "Harlow Solid Italic";
    -fx-font-size: 100;
    -fx-padding: -40 20 0 20;
}

Much easier on the eyes.

At a cursory glance, it seems as though there is no issue here - the problem is solved. However, that is not the case. While, visually, everything seems to be in order, mouse event registration unfortunately is not. For example, consider making the label in the following way (forgive me for whatever slight mistakes may be in here, I'm cutting out a lot of the fat of manager classes for the sake of a minimally reproducible example):

public class MyApp extends Application {
    @Override
    public void start(Stage stage) {
        root = new Pane();
        
        root.setStyle("-fx-background-color: black;");
        root.getChildren().add(createTestLabel());
        scene = new Scene(root);

        scene.getStylesheets().add("/ExampleStylesheet.css");
        stage.setScene(scene);
        // stage.setMaximized(true); I do this for convenience
        stage.show();
    }

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

    public Label createTestLabel() {
        Label testLabel = new Label("Placeholder\nText");

        //testLabel.setLayoutX(xPos); for your convenience should you
        //testLabel.setLayoutY(yPos); want to test this yourself
        testLabel.getStyleClass().add("test-label");
        testLabel.setTextAlignment(TextAlignment.CENTER);
        //testLabel.setPickOnBounds(false); I include this because it does not make a difference for the issue I'm getting at.
        testLabel.setOnMouseEntered(event -> doSomething());
        testLabel.setOnMouseExited(event -> doSomethingElse());

        return testLabel;
    }

    public void doSomething() {
        //System.out.println("Inside label."); for example
    }

    public void doSomethingElse() {
        //System.out.println("Outside label."); for example
    }
}

If you do the above, then the result will be the screenshots from before, but with one slight issue - your doSomething() and doSomethingElse() will happen when the mouse goes slightly above the label's border up to where the border would have been without the padding. Visually, that looks like this:

The green area is the area that was cut off of the label by the padding property. Despite being gone, it counts as being inside the label for mouse events.

Here, having the mouse inside the green area still counts as having it inside the label. That is what I would like to change, because if you were to do something slightly more complicated than a print statement (along the lines of drag-and-drop, for example) with the label, then the fact that you can do so to the label without it looking like the mouse is actually inside the border of the label is really jarring.

This only applies to area that has been removed from the label in css. It does not apply to area that was added to the label. In that sense, the area on the left and right sides of the label are clickable, which makes sense and is desirable.

Finally, I should mention that I have tried other combinations of -fx-padding with -fx-border-insets and -fx-background-insets, as well as changing the pickOnBounds property for the label (as seen in the comment in the example code above). Neither of these helps to resolve this issue.

To me, this really just seems like a bug. I can't imagine this being intended behavior. That is why I would like to be able to directly manipulate the "clickable region" of the label, as a means around the issue, hence the question - can I do that and if so, how?

(Note: this is a repost of an old question I posted back in December. It was too confusing and not detailed enough to convey the problem properly, so I have since deleted that question, with this being my second, revised attempt, clarifying the points of confusion that others had with the original question.)

In JavaFX (version 21), there exists CSS properties for changing the margins around labels. This is usually used to increase the space between the text in the label and its border.

However, it is possible to do the reverse by assigning negative numbers to these properties, in which case the distance between the text and the border shrinks. It's not very obvious why this would be useful if you're working with a well-behaved font, but there are some fonts that, by default, create really bizarre margins. The worst example that I have found is the font "Harlow Solid Italic", demonstrated here with exaggerated colors on a black background:

.test-label {
    -fx-background-color: blue;
    -fx-border-color: red;
    -fx-font-family: "Harlow Solid Italic";
    -fx-font-size: 100;
}

It's pretty plain to see the problem here.

As mentioned before, this can be adjusted by using the -fx-padding property like so:

.test-label {
    -fx-background-color: blue;
    -fx-border-color: red;
    -fx-font-family: "Harlow Solid Italic";
    -fx-font-size: 100;
    -fx-padding: -40 20 0 20;
}

Much easier on the eyes.

At a cursory glance, it seems as though there is no issue here - the problem is solved. However, that is not the case. While, visually, everything seems to be in order, mouse event registration unfortunately is not. For example, consider making the label in the following way (forgive me for whatever slight mistakes may be in here, I'm cutting out a lot of the fat of manager classes for the sake of a minimally reproducible example):

public class MyApp extends Application {
    @Override
    public void start(Stage stage) {
        root = new Pane();
        
        root.setStyle("-fx-background-color: black;");
        root.getChildren().add(createTestLabel());
        scene = new Scene(root);

        scene.getStylesheets().add("/ExampleStylesheet.css");
        stage.setScene(scene);
        // stage.setMaximized(true); I do this for convenience
        stage.show();
    }

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

    public Label createTestLabel() {
        Label testLabel = new Label("Placeholder\nText");

        //testLabel.setLayoutX(xPos); for your convenience should you
        //testLabel.setLayoutY(yPos); want to test this yourself
        testLabel.getStyleClass().add("test-label");
        testLabel.setTextAlignment(TextAlignment.CENTER);
        //testLabel.setPickOnBounds(false); I include this because it does not make a difference for the issue I'm getting at.
        testLabel.setOnMouseEntered(event -> doSomething());
        testLabel.setOnMouseExited(event -> doSomethingElse());

        return testLabel;
    }

    public void doSomething() {
        //System.out.println("Inside label."); for example
    }

    public void doSomethingElse() {
        //System.out.println("Outside label."); for example
    }
}

If you do the above, then the result will be the screenshots from before, but with one slight issue - your doSomething() and doSomethingElse() will happen when the mouse goes slightly above the label's border up to where the border would have been without the padding. Visually, that looks like this:

The green area is the area that was cut off of the label by the padding property. Despite being gone, it counts as being inside the label for mouse events.

Here, having the mouse inside the green area still counts as having it inside the label. That is what I would like to change, because if you were to do something slightly more complicated than a print statement (along the lines of drag-and-drop, for example) with the label, then the fact that you can do so to the label without it looking like the mouse is actually inside the border of the label is really jarring.

This only applies to area that has been removed from the label in css. It does not apply to area that was added to the label. In that sense, the area on the left and right sides of the label are clickable, which makes sense and is desirable.

Finally, I should mention that I have tried other combinations of -fx-padding with -fx-border-insets and -fx-background-insets, as well as changing the pickOnBounds property for the label (as seen in the comment in the example code above). Neither of these helps to resolve this issue.

To me, this really just seems like a bug. I can't imagine this being intended behavior. That is why I would like to be able to directly manipulate the "clickable region" of the label, as a means around the issue, hence the question - can I do that and if so, how?

(Note: this is a repost of an old question I posted back in December. It was too confusing and not detailed enough to convey the problem properly, so I have since deleted that question, with this being my second, revised attempt, clarifying the points of confusion that others had with the original question.)

Share Improve this question asked Feb 5 at 19:51 George EconomouGeorge Economou 637 bronze badges 2
  • 2 A workaround may be to set a clip on the label. I didn't try it though. – jewelsea Commented Feb 5 at 21:41
  • 1 I just tested this. It works fantastically! This is exactly what I was looking for! Thank you! – George Economou Commented Feb 6 at 5:05
Add a comment  | 

1 Answer 1

Reset to default 2

One way I can think of, is to check if the mouse position is in correct layout bounds of the node, to evaluate and proceed with mouse event handlers. Something like

testLabel.getLayoutBounds().contains(event.getX(), event.getY());

Below is a refactored demo of your demo. You can notice the right label is default label and the left one is the one with padding. Once I add for the valid position check, I can only trigger my desired actions in the mouse event handlers (i.e to highlight the label with yellow background).

Note: I think this can be even simplified to do the check directly in the custom event dispatcher rather than doing in each handler.

import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;


public class ClickableLabelRegion_Demo extends Application {

    private final static String CSS = "data:text/css," +
            """
                                    .test-label1 {
                                        -fx-background-color: blue;
                                        -fx-border-color: red;
                                        -fx-font-family: "Harlow Solid Italic";
                                        -fx-font-size: 100;
                                        -fx-padding: -40 20 0 20;
                                    }
                    
                                    .test-label2 {
                                        -fx-background-color: blue;
                                        -fx-border-color: red;
                                        -fx-font-family: "Harlow Solid Italic";
                                        -fx-font-size: 100;
                                    }
                    
                                    .test-bg {
                                        -fx-background-color: yellow;
                                    }
                    """;

    @Override
    public void start(Stage stage) {
        HBox root = new HBox();
        root.setStyle("-fx-background-color: black;-fx-padding:40px;-fx-spacing:50px;");
        root.getChildren().addAll(createTestLabel(1), createTestLabel(2));
        Scene scene = new Scene(root, 1150, 450);
        scene.getStylesheets().add(CSS);
        stage.setScene(scene);
        stage.setTitle("Clickable Label Region Demo");
        stage.show();
    }

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

    private Label createTestLabel(int i) {
        Label testLabel = new Label("Placeholder\nText");
        testLabel.getStyleClass().add("test-label" + i);
        testLabel.setTextAlignment(TextAlignment.CENTER);
        testLabel.setOnMouseEntered(event -> mouseEntered(testLabel, event, i));
        testLabel.setOnMouseMoved(event -> mouseMoved(testLabel, event, i));
        testLabel.setOnMouseExited(event -> mouseExited(testLabel, event, i));
        testLabel.setOnMousePressed(event -> mousePressed(testLabel, event, i));
        testLabel.setOnMouseReleased(event -> mouseReleased(testLabel, event, i));
        testLabel.setOnMouseClicked(event -> mouseClicked(testLabel, event, i));
        return testLabel;
    }

    private boolean isValidRegion(Node n, MouseEvent e) {
        return n.getLayoutBounds().contains(e.getX(), e.getY());
    }

    private void mouseClicked(Node n, MouseEvent e, int i) {
        if (isValidRegion(n, e)) {
            System.out.println("Mouse clicked " + i);
        }
    }

    private void mousePressed(Node n, MouseEvent e, int i) {
        if (isValidRegion(n, e)) {
            System.out.println("Mouse pressed " + i);
            n.getStyleClass().add("test-bg");
        }
    }

    private void mouseReleased(Node n, MouseEvent e, int i) {
        if (isValidRegion(n, e)) {
            System.out.println("Mouse released " + i);
            n.getStyleClass().remove("test-bg");
        }
    }

    private void mouseEntered(Node n, MouseEvent e, int i) {
        if (isValidRegion(n, e)) {
            System.out.println("Mouse entered " + i);
        }
    }

    private void mouseMoved(Node n, MouseEvent e, int i) {
        if (isValidRegion(n, e)) {
            System.out.println("Mouse moved " + i);
        }
    }

    private void mouseExited(Node n, MouseEvent e, int i) {
        System.out.println("Mouse exited " + i);
    }
}

UPDATE (using clip):

As mentioned by @jewelsea, applying a clip on the Label resolves the issue.

class CustomLabel extends Label {
    public CustomLabel(String text) {
        super(text);
        Rectangle clip = new Rectangle();
        setClip(clip);
        layoutBoundsProperty().addListener(p -> {
            Bounds b = getLayoutBounds();
            clip.setWidth(b.getWidth());
            clip.setHeight(b.getHeight());
        });
    }
}
发布评论

评论列表(0)

  1. 暂无评论