skip to Main Content

I have this dynamic background (which is just a -fx-background-color gradient):

enter image description here

On this background, there’s a styled hbox:

enter image description here

And finally, my TextField node inside it:

enter image description here

I want to make the background of my textField transparent, although it is not possible due to the hbox background color. Is there anything I can do to bypass it?

AnchorPane gradient background css:

-fx-background-color: linear-gradient(to top right, red, green);

HBox css:

-fx-background-color: rgba(0, 0, 0, 0.5);

TextField css:

-fx-background-color: rgba(255, 255, 255, 0.1);
-fx-border-color: blue;
-fx-border-width: 3;
-fx-border-radius: 8;
-fx-text-fill: white;

2

Answers


  1. The transparency of the text field has nothing to do with the HBox background, it is only a property of the text field.

    What is actually required (as confirmed by the asker), is that, for some part of the TextField (e.g. the stuff inside the blue border), the underlying portion of the HBox should also be transparent, allowing the underlying gradient in the AnchorPane to become visible.

    Clip based Solution

    You can apply a clip to the HBox to mask out the area to be made transparent. If you have multiple text fields, then you can use shape subtraction to add additional fields to the clip.

    In the spirit of the question, I will not supply code for this.

    Login or Signup to reply.
  2. Inspired by what @jewelsea suggested in his answer, I thought to give a try to his solution :). And indeed it worked perfectly well !! For that, I would like to thank @jewelsea for sharing a concept (multiple shape subtraction) which I am not so familiar at.

    The general idea is to create a dedicated Pane that can drill holes for the provided list of nodes. These holes will be of same size of each node and at the location where they overlap.

    To say this technically, we take a big shape(Rectangle) and clip the regions (using shape subtraction) that overlay with the provided nodes, and place this computed shape between the background and our actual nodes layout.

    In the below demo, I added two columns of TextFields. Both are transparent text fields. I created a custom pane [named as PerforatedPane ;-)], to which I supplied only the first column TextFields. The layer clips all the regions that the Textfield(s) overlay. The pane also refills the holes when a node is removed from the list.

    enter image description here

    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.beans.InvalidationListener;
    import javafx.collections.FXCollections;
    import javafx.collections.ListChangeListener;
    import javafx.collections.ObservableList;
    import javafx.geometry.Bounds;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.TextField;
    import javafx.scene.layout.*;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.shape.Shape;
    import javafx.stage.Stage;
    
    import java.util.List;
    
    public class PerforatedPaneDemo extends Application {
        @Override
        public void start(final Stage primaryStage) {
            ObservableList<Node> nodesList = FXCollections.observableArrayList();
    
            final VBox layout = new VBox(10);
            layout.setPadding(new Insets(10));
    
            Button add = new Button("Add Row");
            add.setOnAction(e -> {
                TextField textField1 = new TextField();
                textField1.getStyleClass().add("transparent-field");
    
                TextField textField2 = new TextField();
                textField2.getStyleClass().add("transparent-field");
    
                HBox row = new HBox(10, textField1, textField2);
                layout.getChildren().add(row);
                nodesList.add(textField1);
            });
            Button remove = new Button("Remove");
            remove.setOnAction(e1 -> {
                int lastIndex = nodesList.size() - 1;
                nodesList.remove(lastIndex);
                layout.getChildren().remove(lastIndex);
            });
    
            PerforatedPane perforatedPane = new PerforatedPane(nodesList);
            StackPane container = new StackPane(perforatedPane, layout);
            VBox.setVgrow(container, Priority.ALWAYS);
    
            final VBox root = new VBox(new HBox(10, add, remove), container);
            root.setSpacing(10);
            root.setPadding(new Insets(25));
            root.setStyle("-fx-background-color:linear-gradient(to right,#B3C890,#DBDFAA,#F5F0BB,#F5F0BB,#DBDFAA,#B3C890)");
    
            final Scene scene = new Scene(root, 400, 300);
            scene.getStylesheets().add(getClass().getResource("PerforatedPaneDemo.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setTitle("PerforatedPane Demo");
            primaryStage.show();
        }
    
        public static void main(final String[] args) {
            launch(args);
        }
    
        /**
         * Pane that clips itself for the areas of the provided overlay nodes.
         */
        class PerforatedPane extends StackPane {
            /**
             * The sole purpose of this Rectangle is to provide the shape of this pane.
             * This is never used as clip. But used as a start/base for the shape subtraction.
             */
            private final Rectangle mainClip = new Rectangle();
            private final Rectangle subClip = new Rectangle();
            private InvalidationListener listener = p -> updateClip();
    
            private ObservableList<Node> nodes;
    
            public PerforatedPane(ObservableList<Node> nodes) {
                this.nodes = nodes;
                mainClip.widthProperty().bind(widthProperty());
                mainClip.heightProperty().bind(heightProperty());
                getStyleClass().add("perforated-pane");
                setAlignment(Pos.TOP_LEFT);
                addListenersToNode(nodes);
                nodes.addListener((ListChangeListener<? super Node>) p -> {
                    while (p.next()) {
                        if (p.getAddedSize() > 0) {
                            addListenersToNode((List<Node>) p.getAddedSubList());
                        }
                        if (p.getRemovedSize() > 0) {
                            removeListenersFromNode((List<Node>) p.getRemoved());
                        }
                    }
                });
                widthProperty().addListener(listener);
                heightProperty().addListener(listener);
            }
    
            private void addListenersToNode(List<Node> addedNodes) {
                addedNodes.forEach(node -> {
                    node.layoutBoundsProperty().addListener(listener);
                    node.layoutXProperty().addListener(listener);
                    node.layoutYProperty().addListener(listener);
                    node.translateXProperty().addListener(listener);
                    node.translateYProperty().addListener(listener);
                });
                Platform.runLater(this::updateClip);
            }
    
            private void removeListenersFromNode(List<Node> removedNodes) {
                removedNodes.forEach(node -> {
                    node.layoutBoundsProperty().removeListener(listener);
                    node.layoutXProperty().removeListener(listener);
                    node.layoutYProperty().removeListener(listener);
                    node.translateXProperty().removeListener(listener);
                    node.translateYProperty().removeListener(listener);
                });
                Platform.runLater(this::updateClip);
            }
    
            private void updateClip() {
                Bounds paneBounds = localToScene(getLayoutBounds());
                Shape finalClip = null;
                for (final Node node : nodes) {
                    Bounds nodeBounds = node.localToScene(node.getLayoutBounds());
                    if(nodeBounds.intersects(paneBounds)) {
                        subClip.setWidth(nodeBounds.getWidth());
                        subClip.setHeight(nodeBounds.getHeight());
                        subClip.setX(nodeBounds.getMinX() - paneBounds.getMinX());
                        subClip.setY(nodeBounds.getMinY() - paneBounds.getMinY());
                        if (finalClip == null) {
                            finalClip = Shape.subtract(mainClip, subClip);
                        } else {
                            finalClip = Shape.subtract(finalClip, subClip);
                        }
                    }
                }
                setClip(finalClip);
            }
        }
    }
    

    CSS file:

    .perforated-pane{
        -fx-background-color: #00000050;
    }
    
    .transparent-field,
    .transparent-field:focused{
        -fx-background-color:transparent;
        -fx-background-insets: 0;
        -fx-background-radius: 0;
        -fx-border-color: #777777;
        -fx-border-insets: 0;
        -fx-border-radius: 0;
        -fx-border-width: 1px;
     }
    
    .transparent-field:focused {
        -fx-border-color:-fx-focus-color;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search