I am trying to create a grid that fills the width of a ContentPanel. The grid should have two columns of equal size that span the entire width of the grid. Resizing the browser window should update the grid size accordingly. I would expect the code below to accomplish this, but the grid does not grow on browser resize and there is a ~15px gap between the second column and the right edge of the grid.
Any ideas?
public class MyGrid extends ContentPanel {
#Override
protected void onRender(Element parent, int index) {
super.onRender(parent, index);
setLayout(new FillLayout());
ColumnConfig c1 = new ColumnConfig("value","value", 50);
ColumnConfig c2 = new ColumnConfig("value1","value1", 50);
ListStore<ModelData> store = new ListStore<ModelData>();
for (int i=0; i<10; i++) {
BaseModelData data = new BaseModelData();
data.set("value", "value");
data.set("value1", "value1");
store.add(data);
}
Grid<ModelData> grid = new Grid<ModelData>(store, new ColumnModel(Arrays.asList(new ColumnConfig[] {c1, c2})));
grid.setAutoHeight(true);
grid.setAutoWidth(true);
grid.getView().setAutoFill(true);
add(grid);
}
}
The cause of the extra space on the right of the table is grid.setAutoHeight(). Remove this and it will be gone. Getting the table to resize with the browser seems to require the use of a Viewport (I dont understand why the ContentPanel would grow without the grid in the code above). This code accomplishes what I was trying to do:
public class MyGrid extends Viewport {
#Override
protected void onRender(Element parent, int index) {
super.onRender(parent, index);
setLayout(new FitLayout());
ColumnConfig c1 = new ColumnConfig("value","value", 50);
ColumnConfig c2 = new ColumnConfig("value1","value1", 50);
ListStore<ModelData> store = new ListStore<ModelData>();
for (int i=0; i<10; i++) {
BaseModelData data = new BaseModelData();
data.set("value", "value");
data.set("value1", "value1");
store.add(data);
}
final Grid<ModelData> grid = new Grid<ModelData>(store, new ColumnModel(Arrays.asList(new ColumnConfig[] {c1, c2})));
grid.getView().setAutoFill(true);
ContentPanel panel = new ContentPanel();
panel.setLayout(new FitLayout());
panel.add(grid);
add(panel);
}
}
Have you tried this?
grid.getView().setForceFit(true);
Grid will fit its container. Also you really should use other layout for container to make inner elements fit container (FitLayout, BorderLayout, RowLayout are acceptable)
Related
I have a FlowPane with equally-sized rectangular children and I have this problem with the space at the right end of the flowpane, when the space is not enough to fit another column of children, I want it to be equally divided between the other columns.
An example of what I want to achieve is how the file explorer in windows behaves, see the gif bellow for reference
The default FlowPane behaviour doesn't look like this, it leaves the remaining width that can't fit a new child at the end of the region, as shown in the gif bellow
And I failed to find any API or documentation to help me achieve my goal, I thought of adding a listener on the width property and adjusting the hGap property accordingly, something like
[ flowPaneWidth - (sum of the widths of the children in one column) ] / (column count - 1)
But again, I have no idea how to figure out the column count, so any help would be appreciated.
Here is a MRE if anyone wants to try their ideas :
public class FPAutoSpace extends Application {
#Override
public void start(Stage ps) throws Exception {
FlowPane root = new FlowPane(10, 10);
root.setPadding(new Insets(10));
for(int i = 0; i < 20; i++) {
root.getChildren().add(new Rectangle(100, 100, Color.GRAY));
}
ps.setScene(new Scene(root, 600, 600));
ps.show();
}
}
After a bit of thinking, I tried to implement the idea mentioned in the question :
adding a listener on the width property and adjusting the hGap property accordingly
but I added the listener on the needsLayout property instead, as follows :
public class FPAutoSpace extends Application {
private double nodeWidth = 100;
#Override
public void start(Stage ps) throws Exception {
FlowPane root = new FlowPane();
root.setVgap(10);
for (int i = 0; i < 20; i++) {
root.getChildren().add(new Rectangle(nodeWidth, nodeWidth, Color.GRAY));
}
root.needsLayoutProperty().addListener((obs, ov, nv) -> {
int colCount = (int) (root.getWidth() / nodeWidth);
//added 4 pixels because it gets glitchy otherwise
double occupiedWidth = nodeWidth * colCount + 4;
double hGap = (root.getWidth() - occupiedWidth) / (colCount - 1);
root.setHgap(hGap);
});
StackPane preRoot = new StackPane(root);
preRoot.setPadding(new Insets(10));
preRoot.setAlignment(Pos.TOP_LEFT);
ps.setScene(new Scene(preRoot, 600, 600));
ps.show();
}
}
Not sure how this will hold up in a multi-hundred node FlowPane but it worked for the MRE
Let me know if you think there are better ways to do it.
I have a sample 3D application (built by taking reference from the Javafx sample 3DViewer) which has a table created by laying out Boxes and Panes:
The table is centered wrt (0,0,0) coordinates and camera is at -z position initially.
It has the zoom-in/out based on the camera z position from the object.
On zooming in/out the object's boundsInParent increases/decreases i.e. area of the face increases/decreases. So the idea is to put more text when we have more area (always confining within the face) and lesser text or no text when the face area is too less. I am able to to do that using this node hierarchy:
and resizing the Pane (and managing the vBox and number of texts in it) as per Box on each zoom-in/out.
Now the issue is that table boundsInParent is giving incorrect results (table image showing the boundingBox off at the top) whenever a text is added to the vBox for the first time only. On further zooming-in/out gives correct boundingBox and does not go off.
Below is the UIpane3D class:
public class UIPane3D extends Pane
{
VBox textPane;
ArrayList<String> infoTextKeys = new ArrayList<>();
ArrayList<Text> infoTextValues = new ArrayList<>();
Rectangle bgCanvasRect = null;
final double fontSize = 16.0;
public UIPane3D() {
setMouseTransparent(true);
textPane = new VBox(2.0)
}
public void updateContent() {
textPane.getChildren().clear();
getChildren().clear();
for (Text textNode : infoTextValues) {
textPane.getChildren().add(textNode);
textPane.autosize();
if (textPane.getHeight() > getHeight()) {
textPane.getChildren().remove(textNode);
textPane.autosize();
break;
}
}
textPane.setTranslateY(getHeight() / 2 - textPane.getHeight() / 2.0);
bgCanvasRect = new Rectangle(getWidth(), getHeight());
bgCanvasRect.setFill(Color.web(Color.BURLYWOOD.toString(), 0.10));
bgCanvasRect.setVisible(true);
getChildren().addAll(bgCanvasRect, textPane);
}
public void resetInfoTextMap()
{
if (infoTextKeys != null || infoTextValues != null)
{
try
{
infoTextKeys.clear();
infoTextValues.clear();
} catch (Exception e){e.printStackTrace();}
}
}
public void updateInfoTextMap(String pKey, String pValue)
{
int index = -1;
boolean objectFound = false;
for (String string : infoTextKeys)
{
index++;
if(string.equals(pKey))
{
objectFound = true;
break;
}
}
if(objectFound)
{
infoTextValues.get(index).setText(pValue.toUpperCase());
}
else
{
if (pValue != null)
{
Text textNode = new Text(pValue.toUpperCase());
textNode.setFont(Font.font("Consolas", FontWeight.BLACK, FontPosture.REGULAR, fontSize));
textNode.wrappingWidthProperty().bind(widthProperty());
textNode.setTextAlignment(TextAlignment.CENTER);
infoTextKeys.add(pKey);
infoTextValues.add(textNode);
}
}
}
}
The code which get called at the last after all the manipulations:
public void refreshBoundingBox()
{
if(boundingBox != null)
{
root3D.getChildren().remove(boundingBox);
}
PhongMaterial blueMaterial = new PhongMaterial();
blueMaterial.setDiffuseColor(Color.web(Color.CRIMSON.toString(), 0.25));
Bounds tableBounds = table.getBoundsInParent();
boundingBox = new Box(tableBounds.getWidth(), tableBounds.getHeight(), tableBounds.getDepth());
boundingBox.setMaterial(blueMaterial);
boundingBox.setTranslateX(tableBounds.getMinX() + tableBounds.getWidth()/2.0);
boundingBox.setTranslateY(tableBounds.getMinY() + tableBounds.getHeight()/2.0);
boundingBox.setTranslateZ(tableBounds.getMinZ() + tableBounds.getDepth()/2.0);
boundingBox.setMouseTransparent(true);
root3D.getChildren().add(boundingBox);
}
Two things:
The table3D's boundsInParent is not updated properly when texts are added for the first time.
What would be the right way of putting texts on 3D nodes? I am having to manipulate a whole lot to bring the texts as required.
Sharing code here.
For the first question, about the "jump" that can be noticed just when after scrolling a new text item is laid out:
After digging into the code, I noticed that the UIPane3D has a VBox textPane that contains the different Text nodes. Every time updateContent is called, it tries to add a text node, but it checks that the vbox's height is always lower than the pane's height, or else the node will be removed:
for (Text textNode : infoTextValues) {
textPane.getChildren().add(textNode);
textPane.autosize();
if (textPane.getHeight() > getHeight()) {
textPane.getChildren().remove(textNode);
textPane.autosize();
break;
}
}
While this is basically correct, when you add a node to the scene, you can't get textPane.getHeight() immediately, as it hasn't been laid out yet, and you have to wait until the next pulse. This is why the next time you scroll, the height is correct and the bounding box is well placed.
One way to force the layout and get the correct height of the textNode is by forcing css and a layout pass:
for (Text textNode : infoTextValues) {
textPane.getChildren().add(textNode);
// force css and layout
textPane.applyCss();
textPane.layout();
textPane.autosize();
if (textPane.getHeight() > getHeight()) {
textPane.getChildren().remove(textNode);
textPane.autosize();
break;
}
}
Note that:
This method [applyCss] does not normally need to be invoked directly but may be used in conjunction with Parent.layout() to size a Node before the next pulse, or if the Scene is not in a Stage.
For the second question, about a different solution to add Text to 3D Shape.
Indeed, placing a (2D) text on top of a 3D shape is quite difficult, and requires complex maths (that are done quite nicely in the project, by the way).
There is an alternative avoiding the use of 2D nodes directly.
Precisely in a previous question, I "wrote" into an image, that later on I used as the material diffuse map of a 3D shape.
The built-in 3D Box places the same image into every face, so that wouldn't work. We can implement a 3D prism, or we can make use of the CuboidMesh node from the FXyz3D library.
Replacing the Box in UIPaneBoxGroup:
final CuboidMesh contentShape;
UIPane3D displaypane = null;
PhongMaterial shader = new PhongMaterial();
final Color pColor;
public UIPaneBoxGroup(final double pWidth, final double pHeight, final double pDepth, final Color pColor) {
contentShape = new CuboidMesh(pWidth, pHeight, pDepth);
this.pColor = pColor;
contentShape.setMaterial(shader);
getChildren().add(contentShape);
addInfoUIPane();
}
and adding the generateNet method:
private Image generateNet(String string) {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
Label label5 = new Label(string);
label5.setFont(Font.font("Consolas", FontWeight.BLACK, FontPosture.REGULAR, 40));
GridPane.setHalignment(label5, HPos.CENTER);
grid.add(label5, 3, 1);
double w = contentShape.getWidth() * 10; // more resolution
double h = contentShape.getHeight() * 10;
double d = contentShape.getDepth() * 10;
final double W = 2 * d + 2 * w;
final double H = 2 * d + h;
ColumnConstraints col1 = new ColumnConstraints();
col1.setPercentWidth(d * 100 / W);
ColumnConstraints col2 = new ColumnConstraints();
col2.setPercentWidth(w * 100 / W);
ColumnConstraints col3 = new ColumnConstraints();
col3.setPercentWidth(d * 100 / W);
ColumnConstraints col4 = new ColumnConstraints();
col4.setPercentWidth(w * 100 / W);
grid.getColumnConstraints().addAll(col1, col2, col3, col4);
RowConstraints row1 = new RowConstraints();
row1.setPercentHeight(d * 100 / H);
RowConstraints row2 = new RowConstraints();
row2.setPercentHeight(h * 100 / H);
RowConstraints row3 = new RowConstraints();
row3.setPercentHeight(d * 100 / H);
grid.getRowConstraints().addAll(row1, row2, row3);
grid.setPrefSize(W, H);
grid.setBackground(new Background(new BackgroundFill(pColor, CornerRadii.EMPTY, Insets.EMPTY)));
new Scene(grid);
return grid.snapshot(null, null);
}
Now all the 2D related code can be removed (including displaypane), and after a scrolling event get the image:
public void refreshBomUIPane() {
Image net = generateNet(displaypane.getText());
shader.setDiffuseMap(net);
}
where in UIPane3D:
public String getText() {
return infoTextKeys.stream().collect(Collectors.joining("\n"));
}
I've also removed the bounding box to get this picture:
I haven't played around with the number of text nodes that can be added to the VBox, the font size nor with an strategy to avoid generating images on every scroll: only when the text changes this should be done. So with the current approach is quite slow, but it can be improved notably as there are only three possible images for each box.
I have a class which extends StackPane. Another one which makes a 20x20 2d Array of this class. I then translated their position somewhere on the scene. Right now, I can't obtain the position of a cell relative on the scene.
I tried Bounds, Point2D.zero and getMin..
cell.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
System.out.println("mouse click detected! " + mouseEvent.getSource());
// boundsInScene = cell.localToScene(cell.getLayoutBounds());
// System.out.println("Cell width: " + boundsInScene.getMinX());
// System.out.println("Cell height: " + boundsInScene.getMinY());
// cell.localToScene(Point2D.ZERO);
int x = (int) cell.getTranslateX();
int y = (int) cell.getTranslateY();
System.out.println("X: " + x + "y :" + y);
}
});
Part of my code:
private Parent createContent() {
root = new Pane();
root.setPrefSize(640, 480);
for (int col = 0; col < y_Tiles; col++) {
for (int row = 0; row < x_Tiles; row++) {
cell = new Cell(row, col);
grid_Array[row][col] = cell;
cell.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {});
root.getChildren().add(cell);
}
}
return root;
}
Bounds is resturning me 480 and 400. Another thing is that all cells return the same output for any of the methods I tried. Any idea how to cell the position of a cell on the scene?
PS:Sorry for bad english. Not native language
You shouldn't be using translateX/Y to set the location of your items. translateX/Y should just be used for temporary movements of an item, such as a bounce effect or a transition. layoutX/Y should be used to set the location of items. Normally layoutX/Y will be set automatically by a layout pane. If you aren't using a layout pane and are manually performing the layout in an unmanaged parent (such as a Pane or a Group), then you can manually set the layoutX/Y values as needed (relocate will do this).
Once you switch to setting the layout values rather than the translate values, then you determine the location of any node in a scene using the localToScene function.
Bounds boundsInScene = node.localToScene(node.getLayoutBounds());
I have a ListView with a custom ListCellFactory. In my CustomListCell implemenation I use setGraphic to set the list item graphic to a GridPane but I am not able not let the GridPane fill the whole width of the ListView. The following is a short version of my code:
class MyListCell extends ListCell<MyObject>
{
private final GridPane _myComponent;
public MyListCell()
{
...
setContentDisplay(ContentDisplay.GRAPHIC_ONLY); // does not help
//this.setPrefWidth(0); // does not help
myComponent.prefWidthProperty().bind(this.widthProperty()); // does not help
}
...
#Override
protected void updateItem(MyObject item, boolean empty)
{
...
setGraphic(_myComponent);
...
}
}
Even with the prefWidth bounding from another post my GridPane will not grow! No matter what combination of layout constraints I use it looks as follows:
Example View
The GridPane shall expand to the full width of the ListCell (I want to add a delete button at the buttom right corner). I'm getting crazy, please help!
With a little bit of css, you can check that the GridPane is actually using all the available space in the cell, but your controls are not:
public MyListCell(){
...
_myComponent.setStyle("-fx-border-color: black;");
}
So you need to select the column you want to take all the available space, and force it to use it.
Let's say you have one Label in the first cell, and one Button in the second, that should be aligned to the right. All you need to do is create a new ColumnConstraints for the second column of the grid, and set it to grow always and align the content to the right:
private final Label label = new Label();
private final Button button = new Button("Click");
public MyListCell(){
_myComponent.add(label, 0, 0);
ColumnConstraints c1 = new ColumnConstraints();
_myComponent.add(button, 1, 0);
ColumnConstraints c2 = new ColumnConstraints();
c2.setHgrow(Priority.ALWAYS);
GridPane.setHalignment(button, HPos.RIGHT);
_myComponent.getColumnConstraints().addAll(c1,c2);
}
And you will have your custom list cell:
I'm trying to test some custom scrolling behavior that changes the location of a view based on how far down the user has scrolled. I discovered that even though the view will scroll in Robolectric 2.2, the height of the ListView (and everything else) is 0.
I read about the shadow objects and that we're supposed to call visible() when starting up the Activity so that it'll be drawn, and that's done. The default layout for a ListActivity is just a ListView with match_parent for height and width.
What's missing? Why doesn't the list have height?
#Test
public void testListActivity() {
final ListActivity activity = Robolectric.buildActivity(ListActivity.class).create().start().resume().visible().get();
ListView listView = activity.getListView();
// Set up an adapter with items to scroll.
final ArrayList<String> messages = new ArrayList<>();
final int numberOfChildren = 100;
for (int i = 0; i < numberOfChildren; i++) {
messages.add("test");
}
final ArrayAdapter<String> listAdapter = new ArrayAdapter<>(activity, android.R.layout.simple_dropdown_item_1line, messages);
listView.setAdapter(listAdapter);
Assert.assertEquals(listView.getCount(), numberOfChildren);
Assert.assertTrue(listView.getHeight() > 0); // This fails.
}
Call populateItems() on the shadow of your listview after setting the adapter:
...
listView.setAdapter(listAdapter);
ShadowListView shadowListView = Robolectric.shadowOf(listView);
shadowListView.populateItems();
Assert.assertTrue(listView.getHeight() > 0);
...