How to avoid that multiline text is clipped in javafx? - java

I have a GridPane which I'm filling with various graphical/textual elements.
For the text, single line labels gets the right size. The same happens to e.g. images
of various sizes (the grid is stretched to give space for the image).
However, for multiline text elements (a.e. a label containing text with newlines in it), it clips the element at one line height... How can I force an UI element (like a label) to take up enough space to display its content?
Here's some code (scala):
val chatPanel = new GridPane {
setFitToWidth(true)
setFitToHeight(true)
setManaged(true)
setMaxWidth(10000)
setMaxHeight(10000)
}
def sendTextInfoBlock(title:String,message:String) {
val button = new Label(message) {
// setWrapText(true)
// setMinHeight(100) <- this works, but of course doesn't match the required height
}
// val button = new Button(message)
chatPanel.add(button,1,row)
the message is a text with newlines, like "this is a long\nand interresting\nmessage"

I gave up, stacked a VBox full of labels (one label per line of text), and just styled it
like a button. That worked well at least.

Related

How can I get the width and the height of a JavaFX Label?

There are obviously the two methods getWidth and getHeight but they return the old label size if we just changed the Label text value.
For example, in this code, the background is not correctly resized:
Label speedLabel = new Label();
Rectangle backgroundLabel = new Rectangle();
// Some initialization
// Change the label text
speedLabel.setText(connection.getSpeed_bps()); // Sets a bigger number
// Adjust the background
backgroundLabel.setWidth(speedLabel.getWidth());
backgroundLabel.setHeight(speedLabel.getHeight());
After the initialization, my Label is like that:
And after the text change and the background adjustment, my Label is like that:
I had a look at this post but it recommends a deprecated method:
How to get label.getWidth() in javafx
The reason for returning the "old" size that the label actually doesn't get updated on the GUI in the moment when you set the textProperty of it, therefore the size is not changed.
You can listen to the widthProperty and heightProperty of the Label and resize the Rectangle in the listener:
speedLabel.widthProperty().addListener((obs, oldVal, newVal) -> {
backgroundLabel.setWidth(newVal.doubleValue());
});
speedLabel.heightProperty().addListener((obs, oldVal, newVal) -> {
backgroundLabel.setHeight(newVal.doubleValue());
});
or simply use bindings between the properties:
backgroundLabel.heightProperty().bind(speedLabel.heightProperty());
backgroundLabel.widthProperty().bind(speedLabel.widthProperty());
But if you just want to achieve a Label with some background, you don't actually need a Rectangle, just some CSS - you can check this question: FXML StackPane doesn't align children correctly
You can do it in two ways.
Approach 1:
Give the background color of your label as that of rectangle. So that whatever is the Label's size, your background rectangle will take the width accordingly.
label.setStyle("-fx-background-color:grey; -fx-padding:5");
Approach 2:
Bind the Rectangle's size according to your label size
rectangle.prefWidthProperty(label.widthProperty());
rectangle.prefHeightProperty(label.heightProperty());

Change position of text inside AnchorPane?

Hello I have got the following code where I am adding a circle inside my anchorpane, and onto the circle I am adding a text.
Circle is an Circle, ArrayList, with multiple circles.
pane.getChildren().addAll(circle);
pane.getChildren().addAll(circle.stream().map(circ -> {
Label text = new Label(circ.getId());
text.setAlignment(Pos.CENTER);
AnchorPane.setLeftAnchor(text, circ.getCenterX());
AnchorPane.setTopAnchor(text, circ.getCenterY());
return text;
}).toArray(Label[]::new));
}
I can change the position from my circle with circle.get(i).setCenterY();
But how can I change the position of the text I added onto the circle?
Thank you in advance
To access the labels, you need to keep a reference to them, e.g.
pane.getChildren().addAll(circle);
List<Label> labels = circle.stream().map(circ -> {
Label text = new Label(circ.getId());
text.setAlignment(Pos.CENTER);
AnchorPane.setLeftAnchor(text, circ.getCenterX());
AnchorPane.setTopAnchor(text, circ.getCenterY());
return text;
}).collect(Collectors.toList());
pane.getChildren().addAll(labels);
And now of course you can just do
AnchorPane.setLeftAnchor(labels.get(i), newXValue);
AnchorPane.setTopAnchor(labels.get(i), newYValue);

Layout of elements inside of HBox for JavaFX

I want to loop through each object of a list. For each entry I want to create a GUI object that looks like this:
A checkbox on the left
An image in the center
(later) A label on the left
My problem is, that each label has a different length and it looks rather strange if not all pictures are on the same line (as seen vertically). Is there a possibility by either java or css to align the ImageVew in center of the HBox?
imageView.setLayoutX(filterBox.getWidth()/2); didn't do the trick unfortunatly. And no -fx-align: right; or -fx-float: right; seems to be existing.
I included what I have so far.
VBox filtersBox = new VBox();
HBox filterBox;
for(Filter filter : filters.getFilters()){
if(!filter.isComplex()){
filterBox = new HBox();
filterBox.getStyleClass().add("filter");
ImageView imageView = new ImageView();
[image view stuff]
final CheckBox cbox = new CheckBox(filter.getName().toString());
filterBox.getChildren().addAll(cbox, imageView);
filtersBox.getChildren().addAll(filterBox);
}
}
As far as I'm aware, this is impossible.
I see two ways you can achieve this layout, though:
Have all the checkboxes have the same (constant) preferred width. This way your image views should line up.
Use a GridPane, and add rows instead of HBoxes

Description tooltips for Vaadin Grid header cells

I'd like to define descriptions for Grid header cells, similarly to how AbstractComponent.setDescription(String description) works (i.e. tooltip shown on mouse hover). As the Grid doesn't support this in itself, I tried adding a Label component into the header cell, and then use setDescription() on the label. I can get the info tooltip working like this, but the downside is that clicking on the label component doesn't trigger sorting. If I want to sort the column, I need to click the header cell on the really narrow area that's left between the right edge of the label component and the column border, where the sorting indicator will be shown. If you look at the screenshot below, the highlighted area is the label component, and in order to trigger sorting, the user needs to click on the space on the right side of the component.
Is there a better way to apply descriptions to header cells than the one I described? And if not, is there a way to make the sorting work properly when the header cell contains a Component?
Based on the answer from kukis, I managed to come up with a simpler solution that doesn't require any JavaScript. Instead of adding a Label component into the header cell, I'm adding a div element manually with StaticCell.setHtml(), and setting the title attribute on it:
#Override
protected void init(VaadinRequest request) {
Grid grid = new Grid();
grid.addColumn("to");
grid.addColumn("the");
grid.addColumn("moon");
Grid.HeaderRow headerRow = grid.getDefaultHeaderRow();
headerRow.getCell("to").setHtml("<div title='Hello world'>to</div>");
headerRow.getCell("the").setHtml("<div title='Hello world 2'>the</div>");
headerRow.getCell("moon").setHtml("<div title='Hello world 3'>moon</div>");
grid.addRow("1","2","3");
grid.addRow("d","v","w");
grid.addRow("g","s","h");
setContent(new VerticalLayout(grid));
}
Feature added to Vaadin 8.4.0
Feature added to Grid in Vaadin 8.4.0.
Ticket:
https://github.com/vaadin/framework/pull/10489
Release notes:
https://vaadin.com/download/release/8.4/8.4.0/release-notes.html
Grid headers and footers now support tooltips.
Well, since Grid doesn't support it by itself you can always use JavaScript to achieve desired behaviour. SSCCE:
private final String SCRIPT;
{
StringBuilder b = new StringBuilder();
b.append("var grid = document.getElementById('mygrid');\n");
b.append("var child = grid.getElementsByClassName('v-grid-tablewrapper')[0];\n");
b.append("child = child.firstChild.firstChild.firstChild;\n");
b.append("child.childNodes[0].title='Hello world';\n");
b.append("child.childNodes[1].title='Hello world 2';\n");
b.append("child.childNodes[2].title='Hello world 3';\n");
SCRIPT = b.toString();
}
#Override
protected void init(VaadinRequest request) {
final VerticalLayout layout = new VerticalLayout();
Grid grid = new Grid();
grid.addColumn("to");
grid.addColumn("the");
grid.addColumn("moon");
grid.addRow("1","2","3");
grid.addRow("d","v","w");
grid.addRow("g","s","h");
grid.setId("mygrid");
setContent(layout);
layout.addComponent(grid);
JavaScript.getCurrent().execute(SCRIPT);
}
Another possibility would be to develop your own Grid in GWT based on the Grid provided by Vaadin Team but it is a way higher cost approach.
Another solution would be to, as you have tried, put label in a column and propagate the label-clicked-event to the Grid.
I use my own utillity finction:
public static Grid setHeaderCellDescription(Grid grid, int rowIndex, String property, String description) {
Grid.HeaderCell cell;
String cellHeader = "<span title=\"%s\">%s</span>";
cell = grid.getHeaderRow(rowIndex).getCell(property);
cell.setHtml(String.format(cellHeader, description, cell.getText()));
return grid;
}
You may add some additional checks if need (existing of cell and row number).
Or other variant - instead setHtml use cetComponent.
Grid.HeaderCell cell = grid.getHeaderRow(rowIndex).getCell(property);
Label newLabel = new Label(cell.getText());
newLabel.setDescription(description);
cell.setComponent(newLabel);
Update for Vaadin 23: you can use your own Component as a column header with this method: com.vaadin.flow.component.grid.Grid.Column#setHeader(com.vaadin.flow.component.Component).
So you can use e.g. a Span with a title:
Span headerComponent = new Span();
headerComponent.setText("Your header text");
headerComponent.getElement().setProperty("title", "Your tooltip text");
column.setHeader(headerComponent);

Grouping objects in JavaFX?

So I'm currently making a JavaFX GUI (with SceneBuilder) for a Connect4 game, and I was wondering if there's a way to 'group' objects together so that I can perform an action on all of them together?
Examples of what I mean:
I have 7 buttons for the columns (colButton1-7) and I want to disable all of them at once.
I use Ellipses for counters (counter1-40) and would like to change the color of all of them to white.
I searched around but couldn't find anything. I know how to do both for an individual object, but can't think of a way to easily apply the changes to all of them at the same time. Any help would be appreciated :)
There is no grouping mechanism to perform a single action on all of the members of the same group. On the contrary, you can have a single group/container to hold all your controls and apply the same action to each of its member.
For example, lets say I have a VBox containing Buttons and I want to disable them all.
for(Node node:vBox.getChildren()) {
node.setDisable(true);
}
or, to set Styling
for(Node node:vBox.getChildren()) {
node.setStyle("-fx-something");
}
For disabling, if you disable a node, then all it's child nodes will have disabled set to true. So you can do:
VBox buttonHolder = new VBox();
Button button = new Button(...);
buttonHolder.getChildren().add(button);
// repeat as necessary...
buttonHolder.setDisable(true); // all buttons in the VBox will now be disbaled
For styled properties, such as the fill of a shape, you should use an external style sheet. If you change the style class of the parent, then with an appropriate external style sheet you can change the style of all the children in one shot.
E.g.
Pane counterPane = new Pane();
for (int i=0; i<numCounters; i++) {
Ellipse counter = new Ellipse(...);
counter.getStyleClass().add("counter");
counterPane.getChildren().add(counter);
}
// ...
counterPane.getStyleClass().add("counter-pane"); // all counters white
// change to red:
counterPane.getStyleClass().remove("counter-pane");
counterPane.getStyleClass().add("warning");
External style sheet:
.counter-pane > .counter {
-fx-fill: white ;
}
.warning > .counter {
-fx-fill : red ;
}

Categories