Question

I have a JavaFX 2 GUI which includes an HBox filling one of the cells of a GridPane. All of my components are dynamically sized. This displays fine, no problems.

First question: I'd like to clip the contents of the HBox at the edge of the GridPane cell they're in, so that none of the overflow is displayed, but I can't work out how. I've tried setting the clip for my HBox to a rectangle of the same size but this doesn't work. I guess because it's using the dimensions used for layout, rather than dimensions of what's displayed. Should I instead be clipping on the GridPane itself, or at least using its properties?

Second question (for a bonus point): how does clipping interact with translation and scaling of the Node?

Thanks for any help.

Was it helpful?

Solution

I tried a couple of short program with attempts to clip the Nodes within a GridPane. None of the clip attempt implementations I really liked all that well, though most of them worked in some fashion or other depending on requirements.

The clip attempt which most closely seemed to fit your description is the last which wraps the clipped node in a region with it's own size specifiers, so that layout of the clipped region matches the size of the clip node applied to the node.

class ClippedNode extends Region {
  private final Node content;
  private final double width;
  private final double height;

  ClippedNode(Node content, double width, double height) {
    this.content = content;
    this.width   = width;
    this.height  = height;

    content.setClip(new Rectangle(width, height));

    getChildren().setAll(content);
  }

  @Override protected double computeMinWidth(double d)   { return width; }
  @Override protected double computeMinHeight(double d)  { return height; }
  @Override protected double computePrefWidth(double d)  { return width; }
  @Override protected double computePrefHeight(double d) { return height; }
  @Override protected double computeMaxWidth(double d)   { return width; }
  @Override protected double computeMaxHeight(double d)  { return height; }
}

clippednode sample

A full executable sample demonstrating various clip approaches is provided below:

import java.util.Iterator;
import javafx.application.Application;
import javafx.beans.value.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class GridClipping extends Application {
  String buttonText[] = "The quick brown fox jumped over the lazy dog".split(" ");
  String[] colors = { "aqua", "coral", "cornsilk", "cornflowerblue" };

  @Override public void start(Stage stage) throws Exception {
    final GridPane grid = new GridPane();
    grid.addRow(0, createHBox("aqua"),     createHBox("coral"));
    grid.addRow(1, createHBox("cornsilk"), createHBox("cornflowerblue"));
    grid.setStyle("-fx-border-color: red;");

    final GridPane gridWithClippedBoxes = new GridPane();
    gridWithClippedBoxes.addRow(0, createClipWrappedHBox("aqua"),     createClipWrappedHBox("coral"));
    gridWithClippedBoxes.addRow(1, createClipWrappedHBox("cornsilk"), createClipWrappedHBox("cornflowerblue"));
    gridWithClippedBoxes.setStyle("-fx-border-color: red;");

    final RadioButton noClip            = new RadioButton("No Clip");
    final RadioButton restrictGridSize  = new RadioButton("Restrict Max Grid Size (doesn't work)");
    final RadioButton clipGrid          = new RadioButton("Clip Grid");
    final RadioButton clipHBoxes        = new RadioButton("Clip HBoxes");
    final RadioButton clipWrappedHBoxes = new RadioButton("Clip Wrapped HBoxes");

    final ToggleGroup clipRadios       = new ToggleGroup();
    clipRadios.getToggles().setAll(
     noClip, 
     restrictGridSize, 
     clipGrid, 
     clipHBoxes,
     clipWrappedHBoxes
    );

    final Rectangle gridClip = new Rectangle(0, 0, 100, 25);

    clipGrid.selectedProperty().addListener(new ChangeListener<Boolean>() {
      @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean wasClipped, Boolean clipped) {
        if (clipped != null) {
          if (clipped) {
            grid.setClip(gridClip);
          } else {
            grid.setClip(null);
          }
        } 
      }
    });

    restrictGridSize.selectedProperty().addListener(new ChangeListener<Boolean>() {
      @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean wasClipped, Boolean clipped) {
        if (clipped != null) {
          if (clipped) {
            // this does not work in our case.
            // the minimum size of the grid components > the max size of the grid.
            // so the grid expands in size to fit the minimum size of it's components anyway.
            grid.setMaxSize(100, 25); 
          } else {
            grid.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
          }
        } 
      }
    });

    clipHBoxes.selectedProperty().addListener(new ChangeListener<Boolean>() {
      @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean wasClipped, Boolean clipped) {
        if (clipped != null) {
          if (clipped) {
            for (Node gridCell: grid.getChildren()) {
              Rectangle cellClip = new Rectangle(100, 12);
              gridCell.setClip(cellClip);
            }
          } else {
            for (Node gridCell: grid.getChildren()) {
              gridCell.setClip(null);
            }
          }
        } 
      }
    });

    final VBox layout = new VBox(10);
    clipWrappedHBoxes.selectedProperty().addListener(new ChangeListener<Boolean>() {
      @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean wasClipped, Boolean clipped) {
        if (clipped != null) {
          if (clipped) {
            layout.getChildren().set(0, gridWithClippedBoxes);
          } else {
            layout.getChildren().set(0, grid);
          }
        } 
      }
    });

    noClip.fire();

    layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
    layout.getChildren().setAll(
      grid,
      noClip,
      restrictGridSize,
      clipGrid,
      clipHBoxes,
      clipWrappedHBoxes
    );

    stage.setScene(new Scene(layout));
    stage.show();
  }

  private Region createHBox(String webColor) {
    HBox box = new HBox(5);
    box.setStyle("-fx-background-color: " + webColor + ";");
    for (String text: buttonText) {
      box.getChildren().add(new Text(text));
    }
    box.setOpacity(0.5);

    return box;
  }    

  private Region createClipWrappedHBox(String webColor) {
    return new ClippedNode(createHBox(webColor), 100, 12);
  }

  class ClippedNode extends Region {
    private final Node content;
    private final double width;
    private final double height;

    ClippedNode(Node content, double width, double height) {
      this.content = content;
      this.width   = width;
      this.height  = height;

      content.setClip(new Rectangle(width, height));

      getChildren().setAll(content);
    }

    @Override protected double computeMinWidth(double d)   { return width; }
    @Override protected double computeMinHeight(double d)  { return height; }
    @Override protected double computePrefWidth(double d)  { return width; }
    @Override protected double computePrefHeight(double d) { return height; }
    @Override protected double computeMaxWidth(double d)   { return width; }
    @Override protected double computeMaxHeight(double d)  { return height; }
  }

  public static void main(String[] args) { Application.launch(args); }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top