5

I am trying to force the child of a GridPane to fit the size of the cell it is in. In this case, I am creating a month view calendar and the columns and rows are a set size regardless of what is in it.

On this calendar, each cell contains a normal VBox and each VBox contains a label that displays the day of the month and each of the events for that day. Many of the days have no events; some of them have one event; and a few have more than one event.

The calendar size is dependent on the window size and will grow and shrink in accordance to the window. Right now, if the cell is too small to fit all of the events, then the height of that one VBox for that day in the cell becomes larger than the cell.

enter image description here

The header row has the following constraint:

HEADER_CONSTRAINT = new RowConstraints(10, -1, 500, 
        Priority.NEVER, VPos.BASELINE, true);

and the other rows have this constraint:

ROW_CONSTRAINT = new RowConstraints(30, 30, Integer.MAX_VALUE,
        Priority.ALWAYS, VPos.TOP, true);

What I think I need to do is:

grid.add(cell, c, r);
vbox.maxHeightProperty().bind(grid.getRow(r).heightProperty()); // <-- this line is not right, but something like this.
ToMakPo
  • 855
  • 7
  • 27
  • Are you sure you're using `HBox`es instead of `VBox`es. BTW: regardless of the `minHeight` and `maxHeight` properties in the end the `GridPane` does not force the `VBox` to resize below the value returned by the `minHeight` method. Since `VBox` determines the result of this method by adding up the min heights of it's managed children (plus some spacing) and return the maximum value of this an the value set to the `minHeight` property (special values for that property not considered). – fabian Jul 11 '18 at 00:40
  • Oh. you are right. VBox. – ToMakPo Jul 11 '18 at 00:55
  • 1
    You have already asked this question before. I have already said this - *don't try to make children of `GridPane` size itself to its parent `GridPane`, it is going to cause a cyclic relationship*. You need to create a [mcve]. I'm quite sure this problem is caused by something else in the scene graph, and not telling what happens elsewhere is not going to allow us to figure out what went wrong. – Jai Jul 11 '18 at 01:19
  • 1
    I saw your comment and it wasn't helpful so I thought I would try again. It seems odd to me that this is in no way an option. I don't think it would cause a cyclic relationship because I have a row constraint that I think gives it a fixed height; though I might be wrong. – ToMakPo Jul 11 '18 at 01:28
  • 1
    If your row has min/pref/max heights of a single fixed number, then there won't be any cyclic relationship. Trying again isn't going to help - you didn't manage to get a working solution from anyone because you didn't post something that other people can figure out what went wrong. If I really have to make a guess, then you probably used `Text` class (instead of `Label` class) inside that `VBox`. But, that is just a guess. – Jai Jul 11 '18 at 02:55

1 Answers1

0

As @fabian mentioned correctly, the size of a VBox is entirely dependent the sizes of its children. If you wish to have a container that does not resize depending on its children, you can use a Pane instead. Like this:

public void start(Stage primaryStage)
{
    GridPane root = new GridPane();
    Scene scene = new Scene(root, 300, 300);
    int c = 0, r = 0;

    Pane c0 = new Pane();
    c0.setPrefSize(200, 200);
    c0.setBorder(new Border(new BorderStroke(Color.RED, BorderStrokeStyle.SOLID, CornerRadii.EMPTY,
            new BorderWidths(1))));
    root.add(c0, c, r);

    primaryStage.setScene(scene);
    primaryStage.show();
}

ss1

Add a shape to test:

Pane c0 = new Pane();
c0.setPrefSize(200, 200);
c0.setBorder(new Border(new BorderStroke(Color.RED, BorderStrokeStyle.SOLID, CornerRadii.EMPTY,
        new BorderWidths(1))));
{
    Circle circle = new Circle(160, Color.BLUE);
    circle.setCenterX(160);
    circle.setCenterY(160);
    c0.getChildren().add(circle);
}
root.add(c0, c, r);

ss2

Note that although the shape protrudes outside of the bounds of c0, the borders of c0 stay in place, indicating that its size is unaffected. To prevent the content from protruding out, you need to add a clip:

c0.setClip(new Rectangle(c0.getPrefWidth(), c0.getPrefHeight()));

ss3

If you want a more fancy clip instead of a simple trim, such as fade-in, fade-out, and shadows, you can read this very good tutorial here.

Now just replace this Circle with your VBox, so that the VBox is a child of this Pane, and add the Pane to your GridPane and you're done. I will provide my final code here as a reference:

public void start(Stage primaryStage)
{
    GridPane root = new GridPane();
    Scene scene = new Scene(root, 300, 300);
    int c = 0, r = 0;

    Pane c0 = new Pane();
    c0.setPrefSize(200, 200);
    c0.setClip(new Rectangle(c0.getPrefWidth(), c0.getPrefHeight()));
    c0.setBorder(new Border(new BorderStroke(Color.RED, BorderStrokeStyle.SOLID, CornerRadii.EMPTY,
            new BorderWidths(1))));
    {
        Circle circle = new Circle(160, Color.BLUE);
        circle.setCenterX(160);
        circle.setCenterY(160);
        c0.getChildren().add(circle);
    }
    root.add(c0, c, r);

    primaryStage.setScene(scene);
    primaryStage.show();
}

Update:

@Jai pointed out that the size is static in this implementation. If you wish to dynamically adjust the size of the cells if the size of the GridPane changes, you can add a listener to its widthProperty and heightProperty like this:

int rowCount = 5, columnCount = 7; // You should know these values.
ChangeListener<Number> updater = (o, oV, nV) ->
{
    double newWidth = root.getWidth() / columnCount;
    double newHeight = root.getHeight() / rowCount;
    root.getChildren().forEach(n ->
    {
        // Assuming every child is a Pane.
        Pane p = (Pane) n;
        p.setPrefSize(newWidth, newHeight);
        p.setClip(new Rectangle(newWidth, newHeight));
    });
};
root.widthProperty().addListener(updater);
root.heightProperty().addListener(updater);

Alternatively...

If you wish to use ColumnConstraints and RowConstraints to determine the cell size you can set the Panes to expand, and only update their clips in the listener, which now listens to ColumnConstraints and RowConstraints:

ColumnConstraints cc = new ColumnConstraints(200);
RowConstraints rc = new RowConstraints(200);
c0.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);
c0.setClip(new Rectangle(cc.getPrefWidth(), rc.getPrefHeight()));

and...

ChangeListener<Number> updater = (o, oV, nV) ->
{
    root.getChildren().forEach(n ->
    {
        // Assuming every child is a Pane.
        Pane p = (Pane) n;
        p.setClip(new Rectangle(cc.getPrefWidth(), rc.getPrefHeight()));
    });
};
cc.prefWidthProperty().addListener(updater);
rc.prefHeightProperty().addListener(updater);
cyqsimon
  • 2,752
  • 2
  • 17
  • 38
  • I wouldn't recommend anyone to use `Pane` for this. All `Pane` does is to hold children - it does not do any kind size management. Doing this means that the OP should hard-code static sizes in the whole scene graph. – Jai Jul 11 '18 at 03:03
  • @Jai not necessarily. You can always add a custom listener to the container's width and height, in which you can update the size of the `Pane`s and their clips. It is a bit tedious, but for this specific use case (calender) in which you want every cell to be the same size, I think this solution is good enough. – cyqsimon Jul 11 '18 at 03:09