Search code examples
javauser-interfacejavafxfxmljavafx-css

Why aren't my CSS borders showing up in JavaFX gui?


I'm trying to show walls on the GUI (fxml) with CSS borders using .getStyleClass().add() but they're not showing up.

I'm using a switch that adds the corresponding style classes to the cells.

The Java code:

private StackPane createCell(int row, int col) {
    var cell = new StackPane();

    cell.getStyleClass().add("cell");
    cell.getStyleClass().add((row + col) % 2 == 0 ? "light": "dark");

    for (var i = 0; i < 2; i++) {
        var pieceView = new ImageView(pieceImages[i]);
        pieceView.visibleProperty().bind(createBindingForPieceAtPosition(i, row, col));
        cell.getChildren().add(pieceView);
    }

    var wallsInDirections = state.checkAllWalls(new Position(row, col));
    for (var direction : wallsInDirections) {
        switch (direction) {
            case UP -> cell.getStyleClass().add("topwall");
            case RIGHT -> cell.getStyleClass().add("rightwall");
            case DOWN -> cell.getStyleClass().add("bottomwall");
            case LEFT -> cell.getStyleClass().add("leftwall");
        }
    }
    return cell;
}

The CSS code:

.cell.light {
    -fx-background-color: white;
}

.cell.dark {
    -fx-background-color: #F6F6F6;
}

.cell.light:hover, .cell.dark:hover {
    -fx-background-color: #FAFA33;
}

.topwall {
    border-top: solid black;
}

.rightwall {
    border-right: solid black;
}

.bottomwall {
    border-bottom: solid black;
}

.leftwall {
    border-left: solid black;
}

Pic of the GUI afterwards: result


Solution

  • The properties border-top, border-right, etc. are not valid JavaFX CSS properties.

    Referring to the documentation, you set the border using the properties

    selector {
        -fx-border-color: color ;
        -fx-border-width: width ;
    }
    

    where color and width are either a single value (of type paint and size, respectively) or four values separated by spaces, representing the top, right, bottom, and left borders.

    It should also be noted that borders are not typically used this way in JavaFX. The more usual approach is to use "nested backgrounds", where you would do something like this to define a border of width 5 around all four sides:

    .cell.light {
        -fx-background: white;
    }
    .cell.dark {
        -fx-background: #f6f6f6;
    }
    .cell {
        -fx-background-color: black, -fx-background;
        -fx-background-insets: 0, 5;
    }
    

    This works by painting two backgrounds, one over the other. The first is black, with no insets; the second is defined by the "looked-up color" -fx-background, with an inset of 5 pixels on all sides.

    My understanding (though I have no direct evidence of this) is that the nested background approach performs better than using explicit borders.

    Unfortunately, doing this entirely in an external CSS sheet is very verbose, as you have to handle all 16 possibilities individually (unless I am missing a clever trick):

    .cell.light {
        -fx-background: white;
    }
    .cell.dark {
        -fx-background: #f6f6f6;
    }
    .cell {
        -fx-background-color: black, -fx-background;
        -fx-background-insets: 0, 0;
    }
    
    
    .cell.topwall {
        -fx-background-insets: 0, 5 0 0 0;
    }
    .cell.rightwall {
        -fx-background-insets: 0, 0 5 0 0;
    }
    .cell.bottomwall {
        -fx-background-insets: 0, 0 0 5 0;
    }
    .cell.leftwall {
        -fx-background-insets: 0, 0 0 0 5;
    }
    .cell.topwall.rightwall {
        -fx-background-insets: 0, 5 5 0 0;
    }
    .cell.topwall.bottomwall {
        -fx-background-insets: 0, 5 0 5 0;
    }
    .cell.topwall.leftwall {
        -fx-background-insets: 0, 5 0 0 5;
    }
    .cell.rightwall.bottomwall {
        -fx-background-insets: 0, 0 5 5 0;
    }
    .cell.rightwall.leftwall {
        -fx-background-insets: 0, 0 5 0 5;
    }
    .cell.bottomwall.leftwall {
        -fx-background-insets: 0, 0 0 5 5;
    }
    .cell.topwall.rightwall.bottomwall {
        -fx-background-insets: 0, 5 5 5 0;
    }
    .cell.topwall.bottomwall.leftwall {
        -fx-background-insets: 0, 5 0 5 5;
    }
    .cell.topwall.rightwall.leftwall {
        -fx-background-insets: 0, 5 5 0 5;
    }
    .cell.rightwall.bottomwall.leftwall {
        -fx-background-insets: 0, 0 5 5 5;
    }
    .cell.topwall.rightwall.bottomwall.leftwall {
        -fx-background-insets: 0, 5;
    }
    

    (using -fx-border-* is no better).

    It may be easier to set the insets programmatically as inline styles:

    .cell.light {
        -fx-background: white;
    }
    .cell.dark {
        -fx-background: #f6f6f6;
    }
    .cell {
        -fx-background-color: black, -fx-background;
        -fx-background-insets: 0, 0;
    }
    

    and then

    int top, right, bottom, left ;
    top=right=bottom=left=0 ;
    var wallsInDirections = state.checkAllWalls(new Position(row, col));
    for (var direction : wallsInDirections) {
        switch (direction) {
            case UP -> top=5;
            case RIGHT -> right=5;
            case DOWN -> bottom=5;
            case LEFT -> left=5;
        }
    }
    
    cell.setStyle(String.format("-fx-background-insets: %d %d %d %d;", top, right, bottom, left));